# Tasks as FireRed's Lightweight State Machines

Lecture ID: lecture_w01_d02
Week 1, Day 2

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

## Transcript
Last time, we treated FireRed’s callback shell as the spine of the program. Today I want to tighten the lens. We are not asking, “How does the whole game run?” in the broad sense. We are asking a more surgical question: when FireRed needs some piece of gameplay to unfold over several frames, without blocking the whole machine, what shape does that behavior take?

And the answer, most of the time, is: a task.

Now, I am going to assume you still have the callback picture in your head from the previous lecture. AgbMain initializes the environment and hands control to the main loop. The main callback slots define which large mode of the game owns the frame. SetMainCallback2 changes that active mode. We saw those in pokefirered/src/main.c, chunk_b35676322b169f85 for AgbMain and chunk_6cfd90f6d0136799 for SetMainCallback2. I am not going to re-teach that machinery today. Instead, keep it as the outer shell.

Inside that shell, tasks are the lightweight state machines that let FireRed say, “This screen is active, but within it, several smaller behaviors need to make progress one frame at a time.”

Let me say that again, because it is the conceptual center of today’s lecture. A callback owns the broad frame mode. A task owns a small ongoing behavior inside that mode.

So, if I draw this on the board, I would put a large rectangle labeled “main callback 2,” and inside it I would draw a row of little boxes labeled “task 0,” “task 1,” “task 2,” and so on. The callback says, “We are currently in the overworld,” or “We are currently in a menu,” or “We are currently initializing a transition.” The tasks say, “This NPC is walking,” “This menu cursor is waiting for input,” “This fade effect is halfway done,” “This helper is checking whether a script can continue.”

That division matters architecturally because FireRed is running on hardware where you do not get threads in the way a desktop programmer might casually expect them. You do not create an operating-system thread for the menu and another thread for the animation and another thread for the script engine. Instead, the game’s frame loop repeatedly calls small pieces of C code, and each piece remembers where it left off.

That is why I want you to hear the phrase “state machine” very literally. A task function is not just “a callback” in the vague modern UI sense. It is usually a compact state machine. It has an identity, it has a function pointer, it has some per-task data, and it advances based on its current state, frame timing, player input, flags, counters, or some global condition.

The course research packet points us directly to pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936 for RunTasks. Those two chunks are today’s anchor. CreateTask is the allocation side: find a free slot, attach a function, set priority, initialize data. RunTasks is the dispatch side: every frame, walk the active tasks and call their functions. That is the core rhythm.

Here is the advanced point: the task system is simple, but it is not simplistic. It is simple in data structure. It is an array of task records. It is not a scheduler with complex preemption. But it is sophisticated in design consequence, because it gives the engine a uniform pattern for hundreds of small behaviors.

If I were writing this on the board, I would write:

“persistent behavior equals function pointer plus small data array plus per-frame dispatch.”

That is what I want you to remember.

Now, let’s connect this to a concrete example. Suppose we are in a screen where the player selects a move to relearn. The research packet gives us pokefirered/src/learn_move.c, chunk_af3598df6f092723, and that excerpt is unusually friendly because the source comment literally calls out a “Move relearner state machine.” That file includes task.h, script.h, data.h, menu helpers, field fade transition code, pokemon summary screen code, graphics, strings, and move constants. Just from the include list, you can already see the architectural layering. This is not a standalone menu. It is a little gameplay workflow sitting at the intersection of scripts, party data, move data, UI, fades, and callback/task control.

The move relearner is a useful example because it is not just a static list. It has to enter from field context, probably after a script interaction. It has to fade or transition. It has to construct a list of eligible moves from species and level-up data. It has to display those moves. It has to respond to directional input. It has to confirm selection. It may need to open summary-like UI or ask which move to forget. Then it has to return a result to the script or field system.

That is too much to do in one blocking function.

So the engine breaks the workflow into states. One state might initialize resources. Another waits for a fade. Another handles menu input. Another opens a sub-menu. Another commits the selected move. Another cleans up and returns control. Each state is small enough to run during a frame and then yield back to the global frame loop.

Check your understanding here: why not just write a while loop that waits until the player chooses a move?

Because if you do that, the rest of the game stops advancing. You do not let VBlank-driven updates, animation timing, input processing, sound updates, and other engine obligations proceed normally. On this machine, “waiting” usually means “return this frame and come back next frame.” Tasks give the code a disciplined place to store that waiting state.

Now, I want to be careful with vocabulary. In many modern engines, you might call this a coroutine. In a JavaScript UI, you might call part of this an event loop pattern. In a gameplay engine, you might call it an update component. In FireRed’s C code, we should use the local language first: tasks. A task is the engine’s lightweight unit of scheduled per-frame behavior.

The phrase “lightweight” does not mean “unimportant.” It means a task is cheap enough and small enough to use all over the game. It does not own the whole program. It does not define an entire screen by itself unless the screen is very small. It usually collaborates with callbacks, global state, scripts, and data tables.

Let’s trace that collaboration more concretely.

Imagine a player talks to an NPC who opens a special menu. At the data layer, there is likely some event object or script reference in map data. We will spend future weeks on maps and the script VM, so I am not going deep there yet. For today, just note the path: map or event data points to a script; the script invokes C-side special behavior; the C-side behavior may change a callback or create one or more tasks; those tasks advance the visible UI over frames.

That is the pattern I want you to start seeing everywhere: data record, global or runtime structure, callback shell, task behavior, visible result.

The research synthesis says this course should repeatedly trace “data table to runtime globals to callback, task, or script to player-visible behavior.” That is exactly what we are doing. Week 1 is not an isolated tour of main.c and task.c. It is the foundation for reading every later system. When we reach overworld maps, the question will be, “Which callback owns the frame, and which tasks or scripts advance map behavior?” When we reach battle, the question will be, “Which state machine owns battle progression, and how do callbacks and tasks bridge the transition?” When we reach inventory and UI, the question will be, “Which menu state is held in task data, and which globals hold the selected item, cursor, or party index?”

This is why I do not want you to memorize CreateTask as a trivia fact. I want you to see CreateTask as a source-level contract.

In pokefirered/src/task.c, chunk_cb0bb91a147bfb59, CreateTask tells us what the engine believes a task is. A task has a function to run. It has a priority. It occupies a slot. It has local data. The precise field names are less important than the contract: if you can express your behavior as “call this function once per frame, with a small saved state,” it fits the task system.

RunTasks, in pokefirered/src/task.c, chunk_3ffebc0341225936, gives us the other side of the contract. The main loop does not need to know what every feature is doing. It only needs to say, “Run the active tasks.” That is a powerful inversion. The shell stays generic. Feature modules register behavior into the scheduler.

Now, if you are an advanced learner, you should immediately ask about ownership and lifetime. Who creates the task? Who destroys it? Who owns the data it points to? What if two tasks assume the same global variable? What if a task changes the main callback while other tasks exist?

Those are the right questions. FireRed’s architecture is disciplined, but it is also C code from a real commercial game. You will see shared globals. You will see implicit contracts. You will see cleanup functions that must happen in the right order. The task system gives structure, but it does not magically enforce safety. There is no borrow checker here. There is no scheduler-level type system proving that a task’s data is valid. The design relies on conventions, module boundaries, and careful sequencing.

Let’s use the board again. I will draw two columns. On the left: “explicit structure.” On the right: “implicit contract.”

Under explicit structure, I would put task slots, task IDs, function pointers, priority, data fields, active flags. Under implicit contract, I would put meanings of data indexes, when the task should self-destroy, which global variables it expects, which callback context it assumes, and what screen resources have already been loaded.

This distinction will save you a lot of confusion when reading the source. Some facts are encoded in structs and function signatures. Other facts live in the choreography.

A task ID is a good example. The task ID is explicit. It tells you which slot was allocated. But the meaning of data[0], data[1], data[2] inside that task is often implicit in the task function and nearby helpers. In one task, data[0] might be a state enum. In another, it might be a timer. In a third, data[0] might be a selected party index. The architecture gives you storage; the module gives that storage meaning.

This is one reason I want you to read tasks locally, not abstractly. Do not say, “data[0] is always state.” Instead say, “In this task, data[0] appears to be state because the function switches on it and increments it after this condition.” That is the level of rigor I expect in your call-flow maps.

Now, let’s bridge to scripts. FireRed’s scripts and tasks often cooperate because they solve related but different problems. A script expresses game logic in a data-driven event language: show text, set flag, give item, start battle, call special. A task expresses per-frame C behavior: wait for fade, animate, poll input, manage a menu, step through a transition. Scripts are excellent for content sequencing. Tasks are excellent for frame-by-frame engine work.

So when you see a script appear to “wait,” ask what is actually doing the waiting. Sometimes the script engine has its own wait mechanism. Sometimes a C special creates a task and returns. Sometimes a global flag or callback transition gates continuation. We will dig into that in later weeks, but today the conceptual hook is enough: tasks are one of the primary ways FireRed turns script-level intention into visible, timed behavior.

The battle transition area gives us another useful preview. The research packet includes pokefirered/src/battle_setup.c, chunk_494d132a59422c85. The excerpt is mostly includes, but that is still evidence. It includes task.h, overworld.h, script.h, field control modules, battle.h, battle_transition.h, event data, item, sound, safari zone, quest log, and more. That include cluster tells you battle setup is not merely “call battle.” It is an integration point. The engine must move from overworld state into battle state, preserving player context, honoring scripts, choosing transitions, and coordinating screen effects. Tasks are one of the mechanisms that make that kind of staged transition possible.

Do not overread the include list, of course. An include list does not prove a specific call path by itself. But it does tell us the module sits at the boundary of several systems. When paired with our task and callback model, it gives us a hypothesis to test in source: battle setup likely coordinates global state, transition effects, callback changes, and task-driven waiting.

This is how I want you to use the research packet throughout the course. Not as decoration. As evidence. A path plus a chunk ID gives us a source anchor. Then we inspect behavior, distinguish what the code proves from what we infer, and build an architectural model that can survive contact with the next file.

Let’s pause for a check. If I ask, “What is the difference between SetMainCallback2 and CreateTask?” I do not want the answer, “One changes callbacks and one creates tasks.” That is syntactically true but architecturally thin. A stronger answer is: SetMainCallback2 changes the broad owner of the frame, while CreateTask registers a smaller behavior to be advanced by the current frame owner’s task dispatch. The callback is the screen- or mode-level shell. The task is a scheduled state machine within that shell.

And if I ask, “Where does RunTasks fit?” the answer is: RunTasks is the per-frame dispatcher that makes task state machines progress. It is the loop that turns task records from stored intentions into actual behavior.

Now let’s make this even more concrete with a small imaginary scheduler, because this connects directly to your workshop. Suppose we implement a C-style task system for a tiny creature RPG. We might define a fixed array of sixteen tasks. Each task has an isActive flag, a function pointer, a priority, and sixteen signed shorts of data. When the player opens the party menu, the party menu callback initializes background graphics, creates a cursor task, creates an input task, maybe creates an animation task, and then every frame it calls RunTasks. The cursor task checks directional input and changes selected index. The animation task increments a timer and updates sprite position. The input task watches for A or B and advances a menu state. When the player exits, the tasks destroy themselves or the callback cleanup destroys them.

That is the same architectural pattern in miniature. The important thing is not the exact number of slots or fields. The important thing is the shape: fixed allocation, per-frame dispatch, tiny local state, cooperation with a larger callback.

In FireRed, fixed task slots are a design tradeoff. The upside is predictability. You do not dynamically allocate arbitrary scheduler objects from a heap every time a menu opens. On the Game Boy Advance, that predictability matters. The downside is that capacity and lifetime bugs become real engineering concerns. If you leak tasks, you run out of slots. If you assume a task exists and it has been destroyed, behavior breaks. If priorities are misunderstood, order-dependent bugs appear.

So the scheduler is simple, but using it well requires discipline.

Here is another board cue. I am going to write the lifecycle:

create, initialize data, dispatch each frame, mutate state, wait by returning, finish, destroy.

That is the task lifecycle. Notice the phrase “wait by returning.” That is counterintuitive if you come from normal blocking procedural code. In this architecture, returning from the task function does not mean the behavior is done. It often means, “I have done this frame’s work; call me again next frame.” Completion is explicit. Usually the task changes state, calls a cleanup helper, destroys itself, or requests a callback change.

Now, why call these “state machines” rather than just “update functions”? Because the meaningful logic is often organized around states. Sometimes that is a switch statement. Sometimes it is a task function pointer being changed to another function. Sometimes it is a numeric field in task data. Sometimes it is a combination. But the conceptual model is the same: the behavior has a current state, it observes inputs or conditions, it performs actions, and it transitions to another state.

The move relearner comment in pokefirered/src/learn_move.c, chunk_af3598df6f092723, makes this explicit. It names states such as fading, menu behavior, and input listeners for right-left or up-down movement. We will study that file more deeply later, but even now it gives you a clean example of a gameplay feature decomposed into stateful steps. The module is not just “a function that teaches a move.” It is an interactive workflow with persistent UI state.

Now, let me connect this forward to the whole course plan.

This week and next week are our “runtime shell and source contracts” module. That means we are learning the agreements that other systems depend on. Later, when we study overworld maps, you will see map headers, layouts, events, and scripts. Those data records do not become gameplay by themselves. They are interpreted by runtime systems inside the callback shell. When we study creature data, species tables and move tables do not become visible by themselves. They are pulled through party structs, battle structs, menu structs, and task-driven screens. When we study battle, the battle engine will look like a much larger state machine, with its own command phases and data records. When we study save and link systems, we will again ask: what is persistent data, what is runtime state, and what per-frame machinery moves the system forward?

So the task scheduler is not an isolated topic. It is a reading tool. Once you can identify a task and reconstruct its state machine, you can explain a large part of FireRed’s behavior from source.

Let me give you a comparison that will become useful later. The research packet mentions Pokemon Showdown as a secondary comparison, not as our primary authority. In pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, we see a modern battle simulator handling move damage through structured runtime logic: active moves, immunities, OHKO checks, damage callbacks, fixed damage, level-based damage. That is not FireRed’s architecture, and we should not pretend it is. But the comparison is useful because both systems must convert data into deterministic visible battle outcomes. Showdown tends to make battle logic legible through TypeScript objects and simulator methods. FireRed spreads comparable responsibilities through C structs, globals, callbacks, and state machines. Same domain pressure, different architectural expression.

That comparison should sharpen, not blur, our FireRed reading. FireRed is the authority for this course. Showdown is a foil we use when we want to ask, “How else could this data-driven behavior be represented?”

Now, before we go further, I want you to do a mental exercise. Picture the player pressing A on a menu item. In a naive model, you might say, “The menu code handles A and does the thing.” In the architecture model we are building, I want a longer trace. The button state is sampled. The active callback runs. That callback or one of its helpers calls RunTasks. A menu task sees the input in its frame update. The task checks its current state and selected index. It may play a sound, update a global, call a helper, create another task, start a fade, or request a callback transition. The visible effect occurs over one or many frames.

That is the kind of trace I expect in your homework. Not just “input goes to menu.” I want source-grounded steps.

And when you cannot prove a step yet, label it as an inference. Advanced engineering reading is not pretending you know everything. It is keeping proven facts and hypotheses separate so you can revise cleanly.

Let me stop here with a check for understanding. If a task needs to wait thirty frames before continuing, what are two likely implementation strategies?

One answer: store a counter in the task’s data, decrement or increment it every time the task function runs, and transition when it reaches a threshold. Another answer: set the task’s state to a waiting state and poll some external condition each frame, such as whether a fade has completed or whether another subsystem has cleared a flag. Both are state-machine patterns. Both avoid blocking the main loop.

Good. Now in the next stretch, we are going to look more closely at how to read task data and task functions without getting fooled by local naming, magic indexes, or helper calls that hide transitions.

So, let’s pick up exactly there. When you read one of these task functions in FireRed, do not begin by asking, “What does this function do?” That question is a little too broad. Begin by asking, “What state does this function represent, and what can cause it to stop representing that state?” That phrasing matters because many task functions are not complete features. They are one slice of a feature, running once per frame, with enough memory in the task data array to know where they left off.

I’m going to put a small reading pattern on the board. First, find the task creator. Second, record the priority. Third, record the initial task function. Fourth, map every write to the task’s data fields. Fifth, map every place where the task function changes, the state field changes, or the task is destroyed. That is the practical workflow. It sounds mechanical, but it protects you from a very common mistake: treating the first function you find as the whole behavior.

Now, recall our prior foundation from `pokefirered/src/task.c`, especially `chunk_cb0bb91a147bfb59` for `CreateTask` and `chunk_3ffebc0341225936` for `RunTasks`. I am not going to re-teach those in full, but keep the contract in your head. A task is allocated into a global task table. The caller receives a task ID. Then, each frame, `RunTasks` walks the active tasks and calls their current function. That means any gameplay system using tasks is really joining a shared per-frame dispatch economy. It does not own the machine. It owns a slot in the machine.

Let’s make that concrete. Suppose you find a function named something like “open menu,” or “start fade,” or “handle selection.” At the advanced level, you should be suspicious of the name in a productive way. Not cynical, just careful. A name may describe the first visible thing the function does, but the actual lifetime of the task may extend past that initial action. It may start a fade, wait for the fade, build windows, wait for input, update sprites, then return control to a script. So the name is a hint, not a proof.

This is why task data fields are so important. FireRed’s task data is compact. You will often see `data[0]`, `data[1]`, `data[2]`, and so on carrying meanings that are not locally obvious. One field might be a state number. Another might be a timer. Another might be a selected menu row. Another might be an object event ID, a sprite ID, or a pointer split across multiple halfwords. If you read those indexes as anonymous integers, the code feels messy. If you rename them mentally as state, timer, cursor, sprite, or result, the code often becomes quite disciplined.

Here is the board-work cue. I’m drawing a two-column table. On the left, task data index. On the right, inferred meaning. We do not fill this table from wishful thinking. We fill it from writes first. Initialization writes are strongest evidence. If `data[0]` is set to zero immediately after creation, and then a switch statement uses `data[0]`, that is probably a state field. If `data[1]` is set to thirty and then decremented each frame until zero, that is probably a timer. If `data[2]` is assigned from a menu creation function and later passed into a menu helper, then it is probably a menu task ID or menu cursor handle. You see the pattern. Writes give identity. Reads confirm identity.

And notice the word “probably.” In this course, I want you to separate proven facts from useful hypotheses. “This field is read by the switch” is a fact. “This field is the state” is a strong hypothesis until you see the whole pattern. “This value stores a menu row” may be a good hypothesis if it is passed to a list menu helper, but you confirm that by tracing the helper’s contract. This discipline is what lets you work in a decompiled codebase without fooling yourself.

Now, a source-grounded example. In `pokefirered/src/learn_move.c`, the research packet points us to `chunk_af3598df6f092723`, and the excerpt is unusually generous because the file contains a comment explicitly calling out a “Move relearner state machine.” That is not just a comment for beginners. It is an architectural confession. The move relearner is a gameplay feature, yes, but its implementation is a series of callback and task states: initialize, fade, show menus, respond to list navigation, branch based on player choice, possibly open a summary screen, and eventually return to the surrounding field or script context. The include list itself is a clue too: `script.h`, `task.h`, `field_fadetransition.h`, `overworld.h`, `list_menu.h`, `pokemon_summary_screen.h`, and constants for moves and songs. That one file sits at a junction between data, UI, field control, and script return behavior.

So let me ask you this. If you are tracing the move relearner, where is the actual “feature”? Is it in the move table? Is it in the menu code? Is it in the task function? Is it in the script that invoked the relearner? The answer is yes, distributed across all of those. The feature is not located in one function. The feature is the path through records, globals, callbacks, tasks, scripts, and structs. That is one of the main course theses.

Let’s slow that down. The creature’s learnable moves are data. The party Pokémon is structured runtime state. The NPC script or special function begins the interaction. The callback shell controls which screen mode is active. The task scheduler gives the interaction a per-frame body. The menu subsystem gives reusable UI behavior. Then the resulting selected move mutates the Pokémon data, or declines to mutate it, and control eventually returns to the overworld script. So when we say “tasks are lightweight state machines,” we are not saying tasks replace everything else. We are saying tasks are one of the main glue mechanisms that let these other systems cooperate without blocking the frame loop.

Now, compare that with `pokefirered/src/pokemon_summary_screen.c`, `chunk_e8e17a42bbf17d46`. The excerpt lists includes such as `pokemon.h`, `pokemon_summary_screen.h`, `task.h`, `menu_helpers.h`, `link.h`, `overworld.h`, `battle_main.h`, `region_map.h`, and `party_menu.h`. Again, do not treat an include list as full proof of behavior. But at the architecture-reading level, it tells you what neighborhoods the module touches. The summary screen is not “just drawing text.” It is a callback-driven screen with Pokémon data, task-driven UI transitions, possible link restrictions, item and move display, region met data, and return paths back to party, battle, or overworld contexts. That is a lot of state. Tasks make that state progress frame by frame.

A common advanced bug when reading this kind of code is to assume the visible screen equals the current task. That is often false. You may have one main callback running a screen loop, and within it multiple tasks: one for input, one for animation, one for palette fade, one for a cursor, one for a submenu, one for delayed cleanup. The screen is an orchestration. A task is a participant.

Check for understanding here. If I tell you that a UI screen has a main callback and three active tasks, which one “owns” the frame? The best answer is that the main loop owns the frame, the active main callback owns the broad screen mode, and the tasks own smaller per-frame behaviors inside that mode. Ownership is layered. If you collapse those layers, you will misread the control flow.

Let’s talk about magic indexes more concretely. Imagine you see a task function that does this in spirit: it checks `gTasks[taskId].data[0]`, switches on it, and in case zero begins a fade. In case one it waits until the fade is done. In case two it creates a window. In case three it waits for A or B input. In case four it calls a cleanup helper and destroys itself. The beginner says, “This is ugly because it uses numbers.” The advanced reader says, “This is a compact state enum encoded in task-local storage, probably because the engine has a fixed task struct and the codebase is optimized for GBA constraints.” Both statements may have some truth, but the advanced reader gets more work done.

Now, would we write brand-new C code this way today? Usually no. We might use named enums, explicit structs, or a small object with fields. But for this codebase, the pattern is coherent. FireRed’s task system provides a uniform shape: function pointer plus small data array plus active flag and priority. The price is that local meaning must be reconstructed at each task site. The benefit is that many unrelated systems can share the same scheduler without heap allocation or heavyweight object machinery.

This is where `SetMainCallback2` from `pokefirered/src/main.c`, `chunk_6cfd90f6d0136799`, stays relevant. We already covered the callback spine, so just carry forward the key implication: changing the main callback changes the broad mode of execution. Creating or updating tasks changes the fine-grained behaviors within that mode. If `SetMainCallback2` is the theater stage being changed, tasks are actors crossing the stage at frame speed. That analogy is imperfect, but it helps separate granularity. A callback transition might take you from overworld to battle setup, from title screen to menu, or from one screen framework to another. A task transition might take you from fade-in to wait-for-input inside that screen.

Let’s bring in `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, because it shows the architectural density we will be dealing with later. The excerpt includes `task.h`, `overworld.h`, `script.h`, `event_data.h`, `field_player_avatar.h`, `battle.h`, `battle_transition.h`, and many other systems. Battle entry is not a single call that says “start battle” and everything magically happens. It is a negotiated transition from field state into battle state. Some data comes from maps and encounters. Some comes from trainer or script state. Some comes from party state. Some is stored in globals used by the battle engine. Some visible behavior is transition animation. Tasks are a natural fit for this boundary because the game must animate, wait, prepare, and then hand off.

So, if you are tracing “I stepped into grass and a wild battle began,” you should resist a linear story that looks like a normal desktop program call stack. The call stack is only part of the story. The real story has a temporal dimension. Frame N checks movement and tile behavior. A script or field control path determines an encounter should happen. Global battle setup state is prepared. A transition may be launched. A task may wait for that transition. The main callback may eventually change. Then the battle engine’s own runtime begins. That is not one uninterrupted function call. It is a chain of state changes across frames.

Let me make this more precise with a reading tactic. When you see a helper call inside a task function, classify it immediately. Is it a pure calculation? Is it a one-frame draw/update? Is it launching another asynchronous process? Is it polling completion? Is it mutating global state? Those categories matter. A helper named like “BeginFade” is not equivalent to a helper named like “UpdateFade.” One starts a process; the other advances or checks it. If you do not distinguish launch helpers from polling helpers, you will think the code has already completed something that it has only requested.

This is one of the places where advanced students get tripped up, because you are used to abstraction hiding complexity. Here, helper calls may hide state transitions, and they may hide ownership transfer. A helper might create another task. A helper might change `gMain.callback2`. A helper might set a script context to stop or resume. A helper might allocate sprites. A helper might start audio. So every helper call inside a task function deserves a quick question: after this call, who continues the work?

Now I want to connect this to scripts, because otherwise tasks can look too self-contained. In FireRed, event scripts are another form of state machine. They are bytecode-like progressions through commands, with waits, calls, flags, variables, message boxes, and special functions. A script can invoke C code, and that C code may create a task to perform work that cannot complete instantly. The script then may wait until the task reports completion. This means the script VM and the task scheduler cooperate. One is not “above” the other in a simple hierarchy. They handshake.

When we get deeper into the overworld in the next part of the course, you will see this pattern repeatedly: script starts something, task performs per-frame work, global or local flag signals completion, script resumes. For now, keep the handshake model. It is a source contract. If the task forgets to signal completion, the script hangs. If the script resumes too early, visible behavior breaks or data mutates before the UI catches up. If the task destroys itself before cleanup, you leak visible state such as sprites, windows, palettes, or input modes.

Let’s do a quick oral exercise. Suppose a task opens a list menu, lets the player choose an item, then returns the selected item to a script variable. What pieces of state do you expect? Say them out loud mentally. You expect a task state field, because opening and input are different phases. You expect a menu cursor or list task ID, because the menu subsystem needs its own handle. You expect a result field or a write to a global script variable. You expect maybe a scroll offset if the list can move. You expect a cancellation path for B button. And you expect cleanup: remove windows, destroy menu tasks, restore input or callback context, and signal the script. That is the checklist.

Now, this checklist is not arbitrary. It follows from the architecture we have already seen. `RunTasks` calls task functions each frame, as grounded in `pokefirered/src/task.c`, `chunk_3ffebc0341225936`. `CreateTask` allocates the slot and returns the task ID, as grounded in `chunk_cb0bb91a147bfb59`. Main callbacks set the broad active loop, with `SetMainCallback2` in `pokefirered/src/main.c`, `chunk_6cfd90f6d0136799`. Feature files like `learn_move.c`, `chunk_af3598df6f092723`, then use these contracts to build concrete player-facing interactions. The architecture is not vague. It is visible in the source.

Let me give you another caution. Do not overfit one task pattern to all tasks. Some tasks use `data[0]` as a state. Some use function-pointer replacement instead of a numeric state. Some do both. Some tasks are tiny and exist only to wait for an animation or delayed sound. Some are long-lived managers. Some task data fields are signed counters. Some are unsigned IDs. Some store bitfields. Some store halves of a pointer, depending on the codebase conventions and platform constraints. The right method is not “assume data zero means state.” The right method is “trace writes, reads, transitions, and lifetime.”

This is also why source comments are useful but not sovereign. The move relearner comment in `pokefirered/src/learn_move.c`, `chunk_af3598df6f092723`, is valuable because it gives us intended structure. But you still verify it against code. Comments can drift. Decompilation names can improve or mislead. Local variable names may be inferred. Helper names may describe outcome rather than timing. Your job is to reconstruct the runtime behavior.

Now, a brief bridge to our broader course plan. Week one is about the runtime shell: startup, callbacks, tasks, and the habit of tracing from data to visible behavior. Week two will still live near this layer, but we will start leaning more heavily into source contracts between runtime systems. After that, when we study overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems, we will keep using the same tracing method. We are not learning tasks because tasks are cute. We are learning tasks because they are one of the repeating structural solutions that make a creature-collector RPG possible on this hardware and in this code style.

And when we later compare FireRed to Pokémon Showdown, the contrast will be useful precisely because the runtime shells are so different. The research packet flags Showdown files like `pokemon-showdown/data/learnsets.ts` and battle scripts as high-signal secondary comparisons, but FireRed remains our primary authority. In Showdown, battle data and deterministic simulation patterns are organized for a server-side or testable JavaScript/TypeScript environment. In FireRed, gameplay has to cooperate with a frame loop, GBA memory constraints, rendering hardware, input polling, and global mode transitions. Same domain, very different runtime pressures.

Let me return to tasks and give you a debugging frame. If a task-driven feature appears stuck, ask four questions. What is the current task function? What is the state field value, if there is one? What condition is it waiting on? Who is responsible for changing that condition? That fourth question is the one people skip. If a task is waiting for a palette fade, then the palette fade system must be running. If it is waiting for a script flag, then some script or helper must set it. If it is waiting for a submenu task to die, then that submenu must actually be receiving frames. A stuck task is often not broken locally. It may be waiting on a contract that another subsystem failed to fulfill.

Here is the check for understanding. A task function has a case that says, effectively, “if fade is not active, move to the next state.” The task never advances. Where do you look? First, confirm the task is still active and running. Second, confirm the fade was actually started. Third, confirm the fade update system is being called under the current main callback. Fourth, confirm no later code restarts the fade or leaves the fade-active flag set. That is how you debug across the shell instead of staring at one switch statement.

Now, I want to make the “data table to callback to visible behavior” path feel less abstract. Think about a move name shown in a move relearner menu. The move name is data. The Pokémon species and level determine eligibility through data and party state. The NPC script or menu entry launches the interaction. The callback context makes a menu-capable screen active. A task owns the menu progression. The list menu subsystem renders choices and handles cursor movement. The player presses A. The task interprets that input. Another state asks, “Can this Pokémon learn this move now, and what should be forgotten?” More UI appears. Eventually a Pokémon struct is mutated. Then the script or field context resumes. The player-visible behavior is “my Pokémon relearned a move,” but the architectural path is long and layered.

This is why advanced architecture study requires patience with boring-looking glue code. The glue is the architecture. The glamorous systems, battle and creatures and maps, all depend on these contracts. If callbacks fail, screens do not own the machine correctly. If tasks fail, time does not advance cleanly. If scripts fail, events do not sequence. If structs are misunderstood, data changes land in the wrong place. If globals are misread, one subsystem clobbers another. Week one is about giving you the reading muscles for all of that.

Let’s pause on one last nuance for this stretch: task priority. We mentioned it before, so I will not expand it into a new lecture, but keep it alive. `CreateTask` takes a priority, and `RunTasks` dispatches active tasks according to the scheduler’s ordering contract in `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59` and `chunk_3ffebc0341225936`. Priority is part of behavior. If one task updates input-derived state before another task reads it, order matters. If one task destroys or creates visual objects before another task animates them, order matters. You usually should not assume priority is decorative. It can be part of the state-machine design.

So, when reading a feature, put priority into your notes beside the initial function. You may not need it. Often it will turn out to be ordinary. But when behavior is subtle, especially around fades, menus, sprite animation, link states, or transitions, that one number may explain why one piece of code reliably sees the world before another piece changes it.

All right, let me recap this segment before we keep moving. A FireRed task is not just a callback with a smaller name. It is a compact, scheduled, per-frame state machine. To read it well, find its creator, task ID, priority, initial function, data writes, function changes, state changes, wait conditions, and destruction path. Treat names and comments as helpful evidence, not final proof. Treat helper calls as possible hidden transitions. And always ask who continues the work on the next frame. That question is the bridge from static source reading to runtime architecture. Next, we are going to apply this reading method to a fuller call-flow trace, where a player action passes through an event or menu entry, enters a task-driven interaction, and returns control without ever blocking the main loop.

Now, let’s take that bridge and walk across it slowly. Imagine the player is standing in front of a Move Relearner-style NPC. The visible experience is ordinary: press A, read text, choose a Pokémon, choose a move, maybe confirm, maybe cancel, and then return to the overworld. But architecturally, that interaction is not one long blocking function. The game does not say, “pause everything, run a modal program, and resume when the player is done.” Instead, FireRed slices that interaction across callbacks, scripts, tasks, menus, and shared state. That is the important move in your mental model.

On the board, I’m going to write the trace shape, not the exact code yet: player input, field control, script entry, special function or menu callback, screen callback, task or tasks, state variable, wait condition, cleanup, return callback. Notice that only some of those are source functions. Some are data records. Some are globals. Some are numeric states stored inside a struct or task data array. Some are conventions. FireRed architecture is not located in one place. It is distributed across small pieces that agree on who owns the next frame.

A good concrete anchor here is pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723. The research packet points out that this file includes script.h, task.h, data.h, field_fadetransition.h, overworld.h, menu.h, list_menu.h, event_data.h, pokemon_summary_screen.h, graphics.h, strings.h, and constants for songs and moves. That include list alone is not the behavior, but it tells you what kinds of systems must cooperate: scripting, task scheduling, data lookup, screen fades, overworld return, menu rendering, list interaction, event flags or variables, summary screen integration, graphics, text, and move constants. Then the excerpt gives us an even stronger clue, because the source comment literally names a “Move relearner state machine” and names CB2_MoveRelearner_Init and MoveRelearnerStateMachine. So this is not us imposing a pattern from outside. The source itself is advertising that this feature is organized as callback initialization plus a state machine.

But be careful. When you see “state machine” in a comment, do not stop reading. The comment is a map legend, not the territory. It says CB2_MoveRelearner_Init creates listMenuScrollPos to listen to right and left buttons, and creates listMenuScrollRow to listen to up and down buttons. What does that tell us? It tells us the feature has at least one central state machine and additional supporting tasks or menu listeners. That means, already, a single visible screen is not necessarily a single task. It can be a tiny ecosystem of scheduled functions that divide responsibility: one task may represent the overall flow, another may represent scroll position, another may represent row movement, and menu library code may maintain its own internal cursor or window state.

Let me pause there, because this is a common source-reading mistake. Students often ask, “Where is the function for the Move Relearner?” That question is too singular. A better question is, “Where is the entry point, and what scheduled units keep the interaction alive after entry?” In week one, that is the whole point. FireRed does not merely call features. It installs feature continuations into a runtime shell.

Now, let’s walk the example as if we were debugging it. We are not going to pretend we have every line of the file projected in front of us. We are going to use the source packet as evidence and practice the trace discipline. The player activates an NPC. In a field map, that visible object is backed by map data and event data, and the interaction enters the script system. We will study maps and scripts in detail later, especially when we get to the overworld and event VM, but for today we only need the boundary: a script can initiate a special interaction that leaves ordinary field input and enters a screen-specific flow.

At that boundary, ask: does the interaction stay inside the script interpreter, or does it hand off to a callback and tasks? For a complex menu like relearning a move, it cannot just run synchronously inside a script command, because the player needs to render frames, move a cursor, wait through fades, open list menus, maybe inspect a Pokémon summary, and return. So the script side has to yield control to a system that can keep advancing frame by frame. That is why the presence of script.h and task.h together in pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, matters. It is a boundary file. It participates in the scripted world, but it also uses the per-frame task architecture we are studying today.

Here is the board-work version. I’ll write “script asks for feature,” then an arrow to “SetMainCallback2 or callback install,” then an arrow to “init callback builds screen resources,” then an arrow to “CreateTask for feature state,” then an arrow to “RunTasks calls state per frame,” and then an arrow to “cleanup returns to previous shell.” I’m deliberately using general labels there, because the exact function names differ from feature to feature. Your job as architecture readers is to recognize the handoff pattern before you memorize local names.

Now, compare that with pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46. The packet shows that file including global.h, gflib.h, pokemon.h, pokemon_summary_screen.h, help_system.h, task.h, menu_helpers.h, link.h, overworld.h, constants for songs and items, strings, menu helpers, item, data, trade, battle_main, scanline effects, moves, placeholder text, region map sections, field specials, party menu, and battle constants. Again, that include list is architectural evidence. The summary screen is not “just a UI.” It is a UI that may be entered from the party menu, from trade, from battle-adjacent contexts, from field contexts, and from move-related flows. It has to know where it came from, what it is allowed to do, and where control should return.

So, if the Move Relearner opens the summary screen, the deeper question is not “what draws the summary screen?” It is “what continuation does the summary screen preserve?” If the player backs out, does control return to the Move Relearner menu? If the player confirms, does it return to a relearn move list? If the player came from a battle context, would the return path differ? This is why FireRed code so often carries mode values, callback pointers, selected party indexes, menu types, and task IDs. Those little fields are the glue between screens. They are how a callback-driven program remembers the sentence it was in the middle of speaking.

Check for understanding: if a function opens a menu and then immediately returns, has the menu finished? No. In this architecture, returning from the C function often means, “the setup for future frames is complete.” The real work happens later through the current main callback and through tasks. So when you are tracing behavior, a return statement is not necessarily the end of the feature. It may be the point where the feature becomes alive.

Let’s add a second example, a battle transition, because it broadens the pattern. In pokefirered/src/battle_setup.c, chunk chunk_494d132a59422c85, the packet shows a dense include list: task.h, overworld.h, item.h, sound.h, pokemon.h, load_save.h, safari_zone.h, quest_log.h, script.h, script_pokemon_util.h, event_data.h, event_object_movement.h, metatile_behavior.h, fieldmap.h, field_control_avatar.h, field_player_avatar.h, field_screen_effect.h, field_message_box.h, vs_seeker.h, battle.h, battle_transition.h, and more. This file sits at another boundary. It translates field conditions, player state, scripts, Pokémon data, and transition visuals into the battle system. A wild encounter or trainer battle is player-visible as one event: screen flashes, battle starts. Architecturally, it is a staged handoff from overworld control to battle control.

That is the same class of problem as the Move Relearner, only with higher stakes. The game must stop ordinary field movement, preserve enough world state to return later, set battle flags and participants, run transition effects, install battle callbacks, and eventually return to the field or a post-battle script. If this were written as one blocking function, the GBA would have no chance to animate, poll input, stream sound updates, update OAM, or progress DMA timing in the normal way. Instead, FireRed uses callbacks and tasks to make time explicit.

Let’s underline that phrase: make time explicit. That is what these lightweight state machines buy you. A regular function hides time on the call stack. It starts, calls subroutines, and returns. A task state machine externalizes time into data. It says, “state 0 this frame, state 1 after fade completes, state 2 while input is pending, state 3 after the player chooses, state 4 during cleanup.” That is more awkward than linear code, yes. But it matches a game loop. It survives across frames. It cooperates with rendering. It lets unrelated systems take their turn.

And this is where we connect back to the course thesis without replaying Monday’s lecture. FireRed is a creature-collector RPG built out of cooperating state machines plus data tables. The data table says what a move is, what a Pokémon species can learn, what an NPC script does, what a map contains, what a battle transition type means. The runtime shell provides the per-frame spine. Callbacks decide the major mode of the program. Tasks express smaller ongoing processes within that mode. Scripts express authored event logic. Structs and globals hold the current facts. Concrete source functions mutate those facts one frame at a time.

Now, because you are advanced learners, I want to push beyond “tasks are state machines” into “tasks are contracts.” A task contract answers several questions. What data slots does this task own? What external globals does it rely on? Who is allowed to change its function pointer? What condition means it is waiting? What condition means it is done? What cleanup must happen before destruction? And what callback or script is expected to continue after it dies? If you cannot answer those questions, you have not finished reading the task.

In the Move Relearner case, the named comment in pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, gives us the first contract clues. There is an initialization callback, CB2_MoveRelearner_Init. There is a MoveRelearnerStateMachine. There are menu scroll listener tasks for horizontal and vertical input. There is a state called MENU_STATE_FADE_TO_BLACK named in the excerpt. That is enough to infer a staged contract: the feature begins with a fade transition, prepares menu state, listens for directional input, and advances through named menu states rather than completing in one call. I am saying “infer” here deliberately. The packet supports the pattern, but a full audit would require reading each state case and each helper. In source work, mark the difference between evidence and inference. That habit will save you from confident but wrong diagrams.

Let’s do a miniature trace. Suppose the player chooses a Pokémon that has eligible relearn moves. Somewhere, the feature needs to construct a list. That list is data-driven: species, level-up learnset, current moves, maybe move constants and text. Then the visible list must be displayed. But the player may press up, down, left, right, A, B, maybe Start in some menus. The list state cannot live on the local stack of a function that already returned. So the scroll position, selected row, and menu state must live somewhere durable: task data, a feature struct, globals, or menu library state. On each frame, RunTasks reaches the relevant task, and the task reads input, updates selection, and perhaps changes the central state. When the player confirms, another state handles move replacement or cancellation. If a fade or text print is pending, the state machine waits.

Now look at that through the lens of visible behavior. The player experiences a responsive menu. The programmer experiences a chain of small continuations. The architect sees a handoff between systems with carefully preserved state. Same event, three perspectives.

Here is a check for understanding. If you find a state machine with a state called something like “fade to black,” what should you search for next? You should search for the condition that ends the fade wait. Does it call a palette fade active check? Does it test a screen effect? Does it wait for a callback to finish? Does it advance by incrementing a state field? And after it advances, what resources are assumed to exist? That last question matters. A fade state often hides setup ordering. The screen may be black not just for polish, but because graphics, tilemaps, windows, sprites, or palettes are being rebuilt underneath.

Let me contrast two kinds of state that you will see repeatedly. One is feature state: “we are choosing a move,” “we are confirming deletion,” “we are returning to the field.” The other is mechanical wait state: “a fade is active,” “text is still printing,” “DMA has not copied yet,” “a menu input task has not resolved yet.” Clean FireRed reading separates those. If you mix them together, your diagram becomes too noisy. On your diagrams, I recommend writing feature states as named boxes and wait conditions as small annotations on arrows. So the box says “choose move,” and the arrow says “A pressed and selection valid.” The next box says “confirm replacement,” and an arrow says “text printer done.” That is usually more readable than making every low-level wait a full box.

Now, we should also talk about source boundaries. The research packet’s synthesis says the course should repeatedly trace “data table to runtime globals to callback, task, or script to player-visible behavior,” using FireRed first, with Pokémon Showdown only as a secondary comparison for battle data modeling and deterministic battle tests. That matters for how you study. FireRed is our architectural authority because it shows the original constraints: GBA main loop, memory layout, task arrays, callback slots, script engine, fixed assets. Showdown is useful later because it makes battle data and deterministic simulation easier to inspect in a modern TypeScript codebase, but it is not the runtime model for FireRed. Do not project Showdown’s server-style battle abstractions backward onto the GBA engine.

We can still use a comparison carefully. The packet includes pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk chunk_3480e574205d5d0f, where damage logic checks immunities, OHKO moves, damage callbacks, level-based damage, and fixed damage before continuing. That is a clean example of battle logic organized around data and callbacks in a modern simulator. Later in the course, when we reach battle calculation, we will compare that style to FireRed’s battle engine. But today, the comparison is narrower: both systems avoid treating data as inert. Data selects behavior. A move can have a damage callback in Showdown; a FireRed script or menu record can lead into a callback/task flow. Different platforms, same architectural lesson: records and callbacks cooperate.

Back to FireRed. When you inspect a file like pokefirered/src/daycare.c, chunk chunk_119931105f546346, the include list again shows the breadth of cooperation: data.h, battle.h, event_data.h, random.h, moves constants, menu systems, script.h, strings.h, party_menu.h, list_menu.h, overworld.h, pokedex.h, naming_screen.h, help_system.h, field transitions, trade, and more. Daycare is a feature the player experiences as a set of NPC conversations and menu choices, but internally it touches party data, species data, move inheritance, random generation, event variables, UI, and scripts. If you carry today’s task-reading discipline forward, daycare will not look like a pile of unrelated functions. It will look like a cluster of state transitions around a persistent data model.

That is the bridge to the rest of week one and week two. The runtime shell tells us how time advances. Tasks tell us how small processes survive across frames. Scripts tell us how authored game events express progression. Overworld code tells us how maps, objects, collisions, and field effects produce navigable space. Creature data tells us what exists in the world. Battle code tells us how those records become competitive state changes. Inventory and UI code tell us how the player inspects and mutates state. Save code tells us what must persist after power-off. Link code tells us what must synchronize with another machine. The deeper course is not eight disconnected modules. It is one repeated question: where is the state, who owns the next frame, and what data selected this behavior?

Let’s do one more board cue. I’m drawing three columns. Left column: “record.” Middle column: “runtime holder.” Right column: “scheduled behavior.” For a move list, the record side might be move constants and learnset-like data. The runtime holder is the selected Pokémon, current move list, menu selection, and task or feature struct. The scheduled behavior is the MoveRelearnerStateMachine and supporting menu tasks. For a battle start, the record side might be trainer data, encounter data, battle transition data, and party Pokémon. The runtime holder is battle setup globals and overworld state. The scheduled behavior is transition tasks, main callback changes, and battle callbacks. For a summary screen, the record side is Pokémon species, moves, items, region map sections, strings, and UI assets. The runtime holder is the selected party index and summary screen mode. The scheduled behavior is summary screen tasks and callbacks.

When you read FireRed source, train yourself to fill in those columns. If a column is empty, your understanding is incomplete. If you only know the records, you have a database, not a game. If you only know the scheduled behavior, you have animation without meaning. If you only know globals, you have a memory dump. Architecture is the relation among the three.

Now, I want to address a subtle point about task IDs. Earlier we treated task IDs as handles into the task array, and we practiced looking for creators and destroyers. In a larger trace, task IDs also become ownership markers. If a parent task creates a child task, does it store the child ID? If a screen callback creates several tasks, who destroys them? If a helper function returns a task ID, who is responsible for cleaning it up? Memory is limited, but so is conceptual ownership. A leaked task may not look like a heap leak; it looks like a function that continues to run every frame after its screen is gone. That can corrupt input, overwrite graphics, or transition the program from an impossible state.

So when you draw the trace for the workshop, I want you to include destruction paths. Not just “CreateTask here.” Include “DestroyTask here,” or “task changes function here and self-destructs there,” or “callback cleanup destroys all tasks of this feature.” If you cannot find a destroyer, write a question mark. That question mark is not a failure. It is an honest research artifact.

Let’s check understanding with a scenario. Suppose the player opens a menu, then backs out before the fade-in completes. Where should the cancellation logic live? There is not one universal answer. It may be impossible to cancel during that fade because input is ignored until the next state. It may be handled by a task that checks B only after the menu is active. Or a parent state machine may see a child result code and start cleanup. The point is not to guess. The point is to look for the wait condition and ask whether input is even being read during that state. In frame-based code, “can the player cancel?” is a scheduling question as much as a UI question.

That is why source comments like the one in pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, are valuable but not sufficient. The comment can say “this listens to buttons,” but you still need to know when the listener task exists, whether it is active during fades, whether it writes shared variables, and whether the central state machine consumes those writes immediately or one frame later. Timing details are behavior.

Let me bring us back to the phrase “lightweight state machines.” Lightweight does not mean trivial. It means FireRed can allocate many of these little machines cheaply, run them in priority order, and remove them when they are done. The tasks are small, but their composition is rich. A menu feature may be central task plus scroll task plus text-print wait plus fade wait plus callback return. A battle transition may be field task plus palette effect plus sound cue plus callback swap. A summary screen may be input task plus sprite animation plus page state plus return callback. Once you see that, the codebase becomes less mysterious. It becomes a set of cooperating continuations.

And there is a discipline to modifying this architecture. If, in the lab, you implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch, do not treat it as toy code. It is a compression of the real pattern. Give each task a clear owner. Store state explicitly. Make waits explicit. Do not block inside the task. Do not spin until input appears. Return control every frame. Make destruction visible. If you add a simulated fade, make it a countdown or condition checked across frames. If you add a menu, store cursor position outside the local function stack. That is how you will feel the design pressure that produced FireRed’s architecture.

All right, let’s collect this segment. We used the Move Relearner as a richer example because pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, names a state machine and shows the interaction between scripts, tasks, menus, data, transitions, and summary screens. We used pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46, to see that screens are reusable callback-driven contexts with multiple possible callers. We used pokefirered/src/battle_setup.c, chunk chunk_494d132a59422c85, to generalize the same handoff pattern to battle entry. And we glanced at pokefirered/src/daycare.c, chunk chunk_119931105f546346, as a reminder that many apparently simple NPC features are actually cross-system interactions.

The big idea is this: when FireRed moves from one visible activity to another, it usually does not travel by a single long function call. It travels by installing future work into the runtime shell. That future work may be a main callback, a task, a script continuation, a menu callback, or some combination of them. So the architectural reader’s question remains: who owns the next frame? In the next segment, we will take that question and turn it into a repeatable annotation method you can use directly on source files, so that when you open an unfamiliar feature, you can mark entry, state, wait, handoff, and return without getting lost in every helper call.

So, let’s make that annotation method concrete. Imagine I hand you an unfamiliar FireRed feature and say, “Tell me how this becomes visible behavior.” The first temptation is to start at the top of the file and read downward. That is usually the wrong unit of reading. The file is not the story. The frame sequence is the story.

Put five words on the board: entry, state, wait, handoff, return. Those are not official engine terms. They are our reading terms. They are the marks we place on source so that the architecture becomes legible.

Entry means, “How did execution get here?” Did a main callback call this directly? Did a script command invoke it? Did a menu option install it? Did a task function reach this state after several frames? In FireRed, the entry point is often less obvious than the visible feature. That is why we keep tying this back to the callback shell from pokefirered/src/main.c, chunk chunk_b35676322b169f85 for AgbMain, and chunk chunk_6cfd90f6d0136799 for SetMainCallback2. We are not redoing that material now; we are using it as the north star. If a feature owns the whole screen, look for a main callback transition. If it shares time with other little behaviors, look for task creation. If it waits for dialogue, movement, or a script-controlled event, expect a script continuation or field task.

State means, “Where is the progress stored between frames?” This is the key difference between reading ordinary application C and reading a GBA game architecture. In an ordinary command-line program, progress may live on the call stack. In FireRed’s runtime shell, progress frequently lives in globals, task data arrays, menu structs, save blocks, script contexts, or screen-specific structures. So when you annotate state, you do not just circle local variables. You ask, “What survives after this function returns?” That is the real state.

Wait means, “What condition prevents the next step from happening immediately?” Waiting is everywhere: waiting for fade completion, waiting for text printing, waiting for player input, waiting for animation, waiting for DMA-safe rendering periods, waiting for another task to finish. A state machine becomes visible when you find the waits. If there is no wait, the code can just call the next function. If there is a wait, the feature has to externalize its continuation.

Handoff means, “Who owns the next frame?” We have been repeating that question because it is the most reliable way to avoid getting lost. Handoff might mean SetMainCallback2 installs a new callback. It might mean CreateTask puts a function into the task array, as shown in pokefirered/src/task.c, chunk chunk_cb0bb91a147bfb59. It might mean a task changes its own function pointer or increments a small state field. It might mean battle setup prepares globals and transfers control toward the battle engine, which is why pokefirered/src/battle_setup.c, chunk chunk_494d132a59422c85, is such a useful bridge file for us.

Return means, “How does this feature give control back?” Not every return is a C return statement. In this architecture, return may mean restoring the overworld callback, resuming a script, closing a menu, destroying a task, or re-enabling player input. When you see a visible screen vanish and the player regain control, some return path happened. Your job is to identify the architectural return, not merely the lexical end of a function.

Now let’s do a small example out loud. Suppose we are looking at the move relearner. The source packet gives us pokefirered/src/learn_move.c, chunk chunk_af3598df6f092723, and even the excerpt names a “Move relearner state machine.” That is a gift. But notice what makes it educationally valuable: it sits at the intersection of script, menu, task, creature data, and UI. A player talks to an NPC. That is not just a function call to “teach move.” The NPC interaction is script-mediated. The selected party Pokémon is creature state. The list of possible moves is data-derived. The visible move list is menu state. The input loop is task or callback-driven. The final decision mutates the Pokémon’s move set.

So if I annotate this source, I do not write, “This file teaches a move.” That is too coarse. I write, “Entry: script or field interaction opens the move relearner flow.” Then I look for the installed screen callback or task initialization. The excerpt mentions CB2_MoveRelearner_Init and MoveRelearnerStateMachine. That naming tells us there is whole-screen ownership at least during initialization, and then a state machine takes over the staged behavior. For state, I look for the selected Pokémon, the list menu scroll position, the selected move, the current menu state, and any task data that remembers where the player is in the interaction. For wait, I mark fade-to-black, menu input, yes/no confirmation, text completion, and possibly summary screen handoff. For handoff, I watch transitions to list menu code, summary screen code, naming or message helpers if present, and the script return. For return, I mark the path that closes the UI and resumes the field script.

Check for understanding here: if the move relearner displays a list and waits for the player to press A, where is the continuation? It is not on the C stack. The function that drew the list has returned. The continuation is encoded in some persistent state: a callback, a task function, a menu process, a state variable, or a combination. That is the conceptual shift. FireRed does not pause a normal function until the player decides. It rebuilds the next moment from stored state on the next frame.

Let me put a tiny pseudo-trace on the board, not as exact source, but as an annotation shape. Script says, “start special.” Special prepares feature state. Feature installs callback or task. Per frame, callback runs. Task checks menu input. If no input, return to scheduler. If A, update state and maybe print text. If text not done, return to scheduler. If done, apply mutation. Then destroy task or change callback. Then resume script or field. That is the rhythm.

Now compare that to the Pokémon summary screen, using pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46. The include list alone is architecturally revealing. We see pokemon.h, pokemon_summary_screen.h, task.h, menu helpers, link, overworld, item constants, data.h, battle_main.h, moves, region map, party menu, and more. I am not asking you to memorize include lists. I am asking you to learn what they imply. A summary screen is not “just UI.” It is a read-only or mostly read-only view over party data, move data, item data, region metadata, and mode-specific constraints. In battle, the same screen may have different exit rules than in the overworld. In link contexts, it may have different restrictions. So the annotation method gives you a sane path through what would otherwise feel like a dense feature file.

Entry: how is the summary opened? From party menu? From battle? From trade? From storage? State: which Pokémon is selected, which page is visible, what cursor mode is active, what sprites and windows are allocated, what background assets are loaded. Wait: input, page slide animation, cry playback, maybe palette fades. Handoff: to tasks that animate, to callbacks that redraw, to helper menus. Return: back to whichever caller opened it. The advanced point is that “return” is parameterized by entry. A summary screen does not have one universal exit meaning. It has an exit contract with its opener.

This is why I keep using the phrase source contracts. Week one is not only about “what calls what.” It is about who promises what to whom. AgbMain promises a stabilized runtime shell. SetMainCallback2 promises that a new owner can be installed for the main loop. CreateTask promises a small, indexed, per-frame continuation slot. RunTasks, from pokefirered/src/task.c, chunk chunk_3ffebc0341225936, promises that active task slots get dispatched every frame in priority order according to the task system’s rules. Feature code relies on those promises. In return, feature code must clean up its tasks, avoid assuming stack persistence, and leave global state coherent for the next owner.

Let’s connect this to data records, because otherwise “state machine” can sound too abstract. Take the summary screen again. The visible move names, types, power, accuracy, and descriptions are not invented by the summary screen. They come from data tables and constants included through the data and move headers. The party Pokémon stores move IDs and PP state. The screen code turns those IDs into visible text and numbers. So the trace is: data record defines a move, party struct stores a move reference, summary state selects a Pokémon and page, callback/task/menu code renders the selected page, player input changes page or exits. That is the full chain: data table to struct to globals or feature state to callback/task to visible behavior.

Now, another check: when you see a function named something like Init, does that mean the feature is done? No. Usually it means the feature has made future execution possible. Initialization is not completion; initialization is the construction of the next state machine. This matters because many bugs in student reimplementations come from doing everything inside the init function. They load data, draw once, wait in a loop for input, and wonder why their scheduler freezes. FireRed’s style says: initialize quickly, install future work, return to the shell.

Let’s bring in battle setup for a moment, but only as a bridge, because full battle architecture comes later. The packet identifies pokefirered/src/battle_setup.c, chunk chunk_494d132a59422c85, with includes for overworld, script, event data, field movement, battle, battle transition, and battle communication modules. That file is valuable because battle entry is one of the clearest examples of handoff across subsystems. A trainer sees you, or grass produces an encounter, or a script starts a battle. The player-visible event begins in the field. But the actual battle is not a subroutine of walking around. The field prepares battle parameters, sets flags and transition state, and hands the next frames to battle transition and battle callbacks. Eventually battle returns results that the field and scripts can interpret.

So annotate battle entry this way. Entry: field trigger, trainer event, wild encounter, or script command. State: battle type flags, opponent identifiers, party references, map/script context, transition state. Wait: trainer approach movement, exclamation icon, text boxes, fade, transition animation. Handoff: battle transition and battle main callback. Return: post-battle script branch, overworld restoration, party updates, money and item consequences, trainer defeated flags. We will study those in detail later, but the reading pattern is already available to you now.

Here is the important sophistication for an advanced learner: FireRed often composes multiple state machines at different scales. A main callback can own the broad mode. Within that mode, tasks can manage smaller timed behaviors. Within a task, a state field can implement a mini-state-machine. A script interpreter can be suspended while a special feature runs. A menu can have its own cursor and scroll state. These are not competing explanations. They are nested cooperating continuations.

Let me say that another way. Do not ask, “Is this feature callback-based or task-based?” Ask, “At what level is it callback-based, and at what level is it task-based?” A screen may be callback-owned, task-animated, script-entered, and data-driven all at the same time. That is not messy once you have the right categories. It is FireRed’s lightweight architecture doing exactly what a small handheld game needs it to do.

Now, if we connect this forward to the course plan, week one gives us the runtime shell. Week two will deepen source contracts and map out how persistent state and runtime state interact. Then the overworld unit will take this same annotation method into maps, objects, warps, collision, metatile behavior, and field scripts. The creature data unit will ask how species, moves, growth, party structures, and storage structures feed screens and battle. The battle unit will expand the callback and task story into a much larger deterministic engine. The inventory and UI unit will show how menus reuse the same waiting and handoff patterns. Save and link systems will raise the stakes because state must persist across power cycles or synchronize across devices.

Pokemon Showdown will be useful later as a contrast, not as our primary authority. For example, the packet includes pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk chunk_3480e574205d5d0f, where damage logic is expressed in a much more modern, data-and-callback-heavy simulation style. That comparison will help us talk about deterministic battle rules and testable battle logic. But for FireRed architecture, the source of truth is the GBA code path: globals, callbacks, tasks, scripts, structs, and frame dispatch.

Let’s do one more concrete mental exercise. Imagine the player opens a party menu, selects a Pokémon, opens summary, checks moves, exits, talks to the move relearner, selects a move, confirms, and returns to the overworld. Architecturally, that is not one long interaction. It is a chain of ownership transfers. Field script or field input opens a menu. Menu state selects a party slot. Summary screen receives party context and owns frames for a while. Summary returns to party or field context. Move relearner receives selected Pokémon context, builds a candidate move list from data, owns frames through fade and menu interaction, mutates party data, then returns to the script or field. At every step, the player feels continuity. Internally, continuity is maintained by explicit state and disciplined handoff.

That is the trick FireRed performs constantly. It gives the player the illusion of a continuous world, while the code is a sequence of small frame owners taking turns.

For your own source notes, I want you to start using a two-column habit. On the left, write the visible behavior: “screen fades,” “list appears,” “cursor moves,” “text waits,” “battle starts,” “player regains control.” On the right, write the owner: “main callback,” “task,” “script context,” “menu struct,” “global flag,” “save block field.” If you cannot fill in the right column, you do not yet understand the behavior architecturally. You may understand the user experience, but not the engine path.

And notice how this prepares you for the lab. When you implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch, the goal is not to imitate every FireRed detail. The goal is to experience the pressure that produced this architecture. You will discover that as soon as you need a fade, a menu, and a delayed transition, ordinary straight-line code stops being enough. You need a shell. You need frame ownership. You need persistent state. You need a way to say, “Not yet; check again next frame.” That is what tasks are.

Quick recap before we move on. Today’s deeper claim is that a FireRed task is not merely a callback in an array. It is a compact continuation object: function pointer, activity flag, priority, and small data storage, interpreted once per frame by the scheduler. But tasks are only one member of a larger family. Main callbacks own broad modes. Scripts own event progression. Menus own local input state. Feature structs own screen-specific memory. Globals connect subsystems. The architectural reader traces how those pieces pass control.

So when you open the next unfamiliar source file, do not begin by asking, “What does every helper do?” Begin with five annotations: entry, state, wait, handoff, return. Then, once you know the frame story, the helper calls will have somewhere to live in your mind.

Now, let’s take that method and apply it to a slightly messier case, because real FireRed features rarely announce themselves as cleanly as the move relearner does. Imagine a field NPC whose interaction can lead to dialogue, a party check, an item check, a menu, a screen transition, and then a return to the map. This is where weak source-reading habits break down. You see event_data.h, script.h, party_menu.h, item.h, overworld.h, and task.h included in the same neighborhood, and it feels like a tangle. But with our method, the tangle becomes a sequence of contracts. The script owns the story logic. The party or item module answers questions about player state. The menu or special screen owns input for a while. The task scheduler handles per-frame waiting. The overworld regains control only after the interaction has resolved.

Pause on that phrase: “answers questions about player state.” Not every subsystem that is included becomes a frame owner. Some modules provide data queries. Some mutate persistent state. Some install callbacks. Some allocate tasks. Some only draw. An advanced reading of FireRed depends on telling those roles apart. If every function call looks equally important, you will drown in detail. But if you classify calls by role, you can read very large files selectively and accurately.

That is where we will continue next: distinguishing query calls, mutation calls, rendering calls, scheduling calls, and ownership-transfer calls inside a single feature path.

Good. So let’s take that classification and make it operational. Imagine we are reading a feature path, not a whole file. That is the first discipline I want you to practice. You do not open a FireRed source file and say, “I will understand all of this top to bottom.” Sometimes you can, but that is rarely the fastest route. Instead, you choose a player-visible behavior and you ask, “Who owns time? Who owns state? Who owns the visible result? Who hands control away?”

Let me put those five verbs on the board. Query. Mutate. Render. Schedule. Transfer.

Now, when I say “query,” I mean a function whose main job is to ask the current game state a question. Does the player have this item? Is this Pokémon fainted? Is the current tile a warp tile? Which move is selected? What species is in this party slot? A query may still touch globals, because this is C code from a GBA game, not a modern pure-functional system. But conceptually, it is answering.

When I say “mutate,” I mean the function changes game state. It gives an item, removes money, changes a flag, writes to party data, changes the player’s map position, changes a task’s internal step, or advances a script pointer. Mutation is where persistence and progression begin to matter. Later in the course, when we study save blocks and event flags, this category becomes absolutely central.

When I say “render,” I mean the function’s job is to make some state visible. It copies graphics, creates windows, prints text, updates sprites, starts a palette fade, or arranges backgrounds. Rendering code may look very busy, and it often is, but it is not always game logic. Advanced readers must resist the temptation to treat drawing code as the source of truth for behavior.

When I say “schedule,” I mean the function creates work to be resumed later. That can mean installing a main callback, creating a task, changing a task function pointer, setting a script to wait, or preparing a transition effect. This is the center of today’s lecture. FireRed is constantly splitting behavior across frames, and scheduling is the mechanism that makes that split survivable.

And when I say “transfer,” I mean the function changes who owns the frame. This is stronger than scheduling. A task can be created while the same screen remains in charge. But when a function sets the main callback, or moves from overworld to battle, or from field to menu, we have an ownership transfer. You should hear that phrase as architectural, not merely mechanical.

Now, let’s test this against a concrete path. Suppose the player steps into a trainer’s line of sight and a battle begins. We are not going to fully analyze trainer detection today; that belongs more naturally with the overworld and event system. But we can classify the downstream shape. The overworld has been running as the frame owner. Some field logic has detected a condition. A script or special routine participates. Battle setup begins preparing state. Eventually, ownership transfers into battle callbacks and transition code.

If you look at pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the include list alone is a useful architectural signal. It pulls in overworld, item, sound, pokemon, load_save, safari_zone, quest_log, script, event data, event object movement, metatile behavior, field control, field player avatar, field screen effect, field message box, battle, battle transition, and more. I am not citing that include list because include lists are beautiful. They usually are not. I am citing it because it tells us battle setup is not a tiny isolated algorithm. It is a boundary module. It sits at the edge between field state, persistent player state, event progression, and the battle engine.

So, if you open that file and try to read it as “battle code,” you will misread it. The better label is “battle-entry orchestration.” Some functions in that region will query map or trainer state. Some will mutate global battle setup variables. Some will manipulate scripts or event flags. Some will schedule fades or transitions. Some will transfer ownership toward battle. That is a very different reading strategy.

Pause there. Check for understanding. If I point to a function that starts a screen fade before a wild battle, should you immediately classify it as battle logic? No. You should ask whether it is rendering, scheduling, ownership transfer, or actual mutation of battle state. The same feature path can contain all five roles, and confusion begins when we flatten them into one category.

Let’s do another example, one closer to tasks. The move relearner is a lovely example because the source itself announces its structure. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the excerpt contains a comment calling out a “Move relearner state machine,” and it names CB2_MoveRelearner_Init and MoveRelearnerStateMachine states such as a fade-to-black menu state. That is exactly the vocabulary we have been building. A callback initializes a special screen. Tasks listen to directional input. A state machine advances through menu states. Data comes from Pokémon and move tables. The visible behavior is a player selecting a move to relearn.

Now, do not make the beginner’s mistake of saying, “Ah, this file has a state machine, therefore the whole file is just one state machine.” Better: there are several cooperating state machines at different scales. The main callback owns the frame while the move relearner screen is active. One or more tasks may own small pieces of input or animation. The list menu helper may have its own cursor state. The text window system may have its own print progress. The party data is not a state machine in the same sense; it is a structured store being queried and mutated. And the script system may be waiting for this special screen to finish so it can resume the NPC interaction that launched it.

I want you to notice how this starts to answer a question that comes up whenever people first study decomp projects: “Where is the game?” Is the game in main.c? Is it in overworld.c? Is it in battle_main.c? Is it in scripts? Is it in data tables? The honest answer is: the game is in the contracts between them. main.c gives us the runtime shell. The task scheduler gives us per-frame resumability. Scripts express progression and event logic. Structs hold durable and temporary state. Data tables define species, moves, items, maps, trainers. Concrete source functions interpret those records and push behavior into callbacks and tasks.

That is why the course thesis keeps repeating the same trace: data table, runtime globals, callback or task or script, visible behavior. That is not a slogan. It is a debugging method.

Let me draw a small board example. On the left, I write “species and move data.” In the middle, I write “party Pokémon struct.” To the right of that, “move relearner screen state.” Then “task data.” Then “player sees selectable moves.” Now ask: where does the move list come from? Not from the task scheduler. The scheduler does not know what a move is. It only knows that a task function should run. Where does the current cursor live? Possibly in task data or a menu helper struct. Where does the permanent change live after the player confirms? In the Pokémon’s move slots, meaning a mutation of party state. Where does the script resume? At the boundary after the special screen reports completion.

This is the kind of decomposition I expect in your workshop call-flow maps. I do not want a spaghetti diagram that says “function calls function calls function calls function.” That is not architecture; that is transcription. I want annotated edges. This call queries party data. This call schedules a fade. This callback owns the frame. This task waits for input. This function commits the selected move. This callback returns control.

Now, let’s relate that to the summary screen, because it shows a slightly different version of the same idea. pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes pokemon, task, menu helpers, link, overworld, item constants, data, item, trade, battle_main, region map, field specials, party menu, and battle constants. Again, the include list is evidence of boundary behavior. A summary screen is not only a renderer. It is a temporary frame owner that displays structured creature data and may be entered from several contexts: party menu, battle-adjacent flows, trade-adjacent flows, field interactions, and so on.

This is where advanced architecture becomes less tidy than textbook architecture. In a textbook, you might have a clean Model-View-Controller separation. In FireRed, you have C modules, globals, callback slots, task arrays, and many specialized helpers. But the conceptual separation still exists if you look for roles. The Pokémon struct and associated accessors are model-like. The windows, backgrounds, and sprites are view-like. The task and callback functions are controller-like, but not in a pure framework sense. The script system may act like a flow controller outside the screen. The distinction is useful as long as you do not force the code to pretend it was written in a modern web framework.

Let me say that more sharply. We are not modernizing FireRed in our heads. We are learning to read it on its own terms. Its own terms are callbacks, global state, task slots, packed structs, data tables, and explicit per-frame functions.

Here is a check for understanding. If the summary screen shows a Pokémon’s held item, and then the player returns to the party menu, what kinds of components were involved? Take five seconds mentally. You need a data source, which is the party Pokémon state and item data. You need rendering, which is the summary screen printing and drawing. You need input handling, probably through tasks or the screen’s callback-owned logic. You need ownership transfer into the summary screen and then back to the caller. And if the item is merely displayed, there may be no persistent mutation at all. Displaying a value is not the same as changing it.

That last sentence matters. It is one of the easiest mistakes in code review. A function that prints money does not spend money. A function that shows a move does not teach a move. A function that opens a menu does not necessarily commit a menu choice. In FireRed, these often sit close together, so you need to read for the commit point.

Let’s connect this to daycare briefly. pokefirered/src/daycare.c, chunk_119931105f546346, includes party menu, pokemon storage, event data, random, moves constants, menu, list menu, script, overworld, Pokédex, naming screen, field fade transition, trade, and other modules. That tells us daycare is another boundary feature. It touches creature data, NPC script flow, menu presentation, persistent event variables, and sometimes generated outcomes like eggs or inherited moves. Later in the course, when we study creature data and progression systems, daycare will be a good case study for how a feature is not simply “one system.” It is a negotiated path through several systems.

For today, the point is that tasks make such negotiated paths possible without blocking the frame. A daycare menu can wait for input. A naming screen can take over. A fade can run. A script can pause. A party selection can happen. Then the original interaction can continue. None of this requires a thread. None of it requires a heap-allocated object graph. It requires disciplined state: which callback owns the frame, which task owns the local waiting behavior, which globals or structs hold the results, and which script or caller resumes afterward.

Now, let me bring in the comparison source for just a moment, because it helps sharpen what is distinctive about FireRed. In the research packet, Pokémon Showdown’s data and scripts appear heavily, and one high-value excerpt is pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f. That excerpt shows damage logic taking a move, normalizing it, checking immunity, handling OHKO moves, damage callbacks, level-based damage, fixed damage, and then continuing through calculation. That is a battle simulator style of architecture. It is still data-driven, and it still interprets move records, but it lives in a very different runtime world: TypeScript objects, battle methods, explicit active move objects, and testable deterministic calculation paths.

FireRed also has data-driven battle behavior, but our week-one lens is lower level. Before we can responsibly analyze damage formulas, we need to understand how the game gets to the battle engine, how the engine owns frames, how screens enter and exit, and how per-frame state advances. Showdown is useful later as a contrast for data modeling and deterministic tests. It is not the primary runtime model for the GBA program. Keep that distinction clean.

On the board, I’m going to write two phrases: “interpreter of records” and “owner of frames.” Many modules are interpreters of records. A move calculation interprets move data. A map loader interprets map data. A menu builder interprets item data. But only some modules are owners of frames. The main callback owns the broad frame loop. A special screen callback may own the current mode. A task owns a repeated per-frame behavior inside that mode. Confusing those two phrases leads to bad architecture diagrams.

Let’s return to FireRed’s task design, but at a higher level than before. We already studied allocation and dispatch, so I am not going to re-teach CreateTask or RunTasks line by line. Instead, ask what design pressure this solves. On the GBA, the game must return to the main loop every frame. You cannot sit in a blocking while loop waiting for the player to choose a menu option, because the screen still has to update, input has to be sampled, audio has to continue, sprites may need to animate, and DMA or VBlank-related work has to be coordinated. So a “wait” becomes a state. A “sequence” becomes a function pointer plus data fields. A “mode” becomes a callback.

That is why task data arrays are so small but so important. They are not glamorous. They are not self-describing. But they are where time is stored. A variable like data[0] might mean “current step.” data[1] might mean “timer.” data[2] might mean “selected slot.” You should not love that style blindly; it has obvious maintainability costs. But you should understand its economy. It is compact, predictable, and cheap. In a constrained runtime, that matters.

Here is a concrete pseudo-trace, spoken rather than written as code. An NPC script starts. It asks whether the player has an eligible Pokémon. That is query territory. It opens a message box. That is rendering and scheduling, because text printing completes across frames. It calls a special function to open a selection screen. That transfers ownership, or at least installs a new callback-driven mode. The selection screen creates tasks for cursor movement and input handling. That is scheduling. The player chooses a Pokémon. The screen writes the chosen slot somewhere the script or caller can read. That is mutation, though perhaps mutation of temporary global state rather than save state. The screen fades out and restores the overworld callback. That is rendering plus ownership transfer. The script resumes and checks the result. That is query again. If confirmed, it changes the party or event flags. That is persistent mutation.

Notice how little of that story is “the task runs.” The task is a crucial mechanism, but the feature path is larger. This is one of the reasons I warned you against turning the task scheduler into the whole architecture. Tasks are lightweight state machines, yes, but they are embedded inside a callback shell, script system, data model, and rendering pipeline.

Now, I want to make this practical for your homework. When you draw the boot-to-screen or interaction-to-screen flow, annotate the moment where a function stops being an ordinary helper and becomes a frame-ownership boundary. The classic example from this week is SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799. We already discussed it, so I will only recall the role: it changes the callback that the main loop will run as the primary mode. That makes it architecturally heavier than a helper that merely computes a value. In your diagram, it deserves a boundary marker.

Similarly, when you see task creation, recall pokefirered/src/task.c, chunk_cb0bb91a147bfb59, and RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936. You do not need to redraw the whole scheduler every time. But you should mark that a task ID now exists, that per-frame dispatch will revisit this behavior, and that local state may be stored in the task’s data fields. That is the right abstraction level for week-one diagrams.

Let me check the room. Suppose I ask: “Where would you look for the permanent result of a move being relearned?” Would you look first in RunTasks? No. RunTasks can tell you that task functions are being dispatched, but it does not know the semantic meaning of “Thunderbolt was placed into slot two.” You would follow the move relearner state machine in pokefirered/src/learn_move.c, chunk_af3598df6f092723, until you find the commit to Pokémon data or the function that performs that commit. The scheduler explains when code runs. It does not by itself explain what the code means.

And suppose I ask: “Where would you look for why the move list contains these moves rather than those moves?” Again, not in the scheduler. You would look at species data, move learn data, and helper functions that construct the candidate list. The task may move the cursor over that list; it does not author the list’s contents.

That separation is the heart of advanced source reading. Ask one question at a time, and choose the source region that can actually answer it.

Before we close this portion, I want to connect week one to the larger course map. In weeks one and two, we are building the runtime shell and source-contract vocabulary. That means you are learning how FireRed divides time and authority. Later, when we study overworld maps, you will see map headers, layouts, object events, scripts, and movement systems feeding into the same callback-and-task runtime. When we study event scripting, you will see scripts pause on message boxes, fades, menus, and specials. When we study creature data, you will see party structs and species tables become visible through summary screens, battle setup, storage, daycare, and menus. When we study battle, you will see a larger and more specialized state machine, with its own command buffers and turn phases. When we study inventory and UI, you will see list menus and tasks everywhere. When we study saving, you will see why it matters to distinguish temporary task state from persistent save state. And when we study link systems, you will see frame ownership complicated by communication timing.

So week one is not “just main.c” and it is not “just task.c.” It is the grammar for reading the rest of the game.

Here is the recap I want in your notes, in words, not as a memorized diagram. FireRed behavior emerges when data records are interpreted by concrete functions, placed into global or struct state, exposed through callbacks and tasks, sometimes coordinated by scripts, and finally rendered as visible player experience. The task scheduler is the per-frame dispatch mechanism for many small state machines, but it is not the semantic owner of every feature. Main callbacks define broad modes. Scripts often define event progression. Structs and globals hold state. Rendering code presents state. Mutation code changes state. Query code asks about state. Ownership-transfer code changes who gets the next frame.

We will continue from exactly there next time in this lecture sequence: taking a single feature path and marking those roles directly against source functions, so that your call-flow maps become architectural maps rather than call stacks with arrows.

So let’s make that promise concrete before we let this lecture close. I want one feature path on the board, not because we are going to exhaust the battle system today, but because battle entry is a very good stress test for the vocabulary we have built. On the left side of the board, write “field event.” In the middle, write “battle setup.” On the right, write “battle mode.” Now underneath those, leave room for scripts, globals, callbacks, tasks, and structs.

Suppose the player steps into a trainer’s line of sight, or talks to an NPC that begins a battle. At the player level, this feels like one thing: dialogue, screen transition, battle. Architecturally, it is not one thing. It is a handoff chain. The map and event system identify that something should happen. Script logic may decide progression: which text to show, whether the trainer has already been defeated, what flags to set, what battle type to start. Battle setup code then prepares the battle context. The callback shell then moves the program from overworld-style frame ownership into battle-style frame ownership. Tasks may animate transition effects or manage short-lived per-frame steps along the way.

Now, check that against the source evidence. In the research packet, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is especially useful because even the include list tells an architectural story. That file pulls in task.h, overworld.h, script.h, event_data.h, event_object_movement.h, field_control_avatar.h, field_player_avatar.h, field_screen_effect.h, field_message_box.h, battle.h, and battle_transition.h. That is not random. It tells us battle setup sits at a junction. It knows about field state, event state, scripts, message boxes, player avatar control, transitions, and battle state. So if you are drawing the call-flow map from “trainer sees player” to “battle starts,” battle_setup.c should not be treated as merely a battle file. It is boundary code.

And boundary code is one of the big themes of this course. FireRed is not organized as a clean modern service graph where each subsystem owns one isolated namespace and communicates through typed interfaces. It is a C program for a constrained platform, so the boundaries are often made from convention, globals, callback replacement, task IDs, and shared structs. That does not mean the architecture is sloppy. It means you have to read ownership differently. Ownership is often temporal. Who owns the next frame? Who is allowed to mutate the player’s control state right now? Which callback is active? Which task is still alive? Which script is waiting? Which global has been populated for the next subsystem?

Let me give you a more UI-shaped example as well, because it will keep us honest. The move relearner is not the battle engine, but it is a beautiful miniature of this lecture’s main idea. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the packet notes a source comment naming “Move relearner state machine,” with CB2_MoveRelearner_Init and MoveRelearnerStateMachine moving through menu states such as fading to black. That is almost too perfect for today. The source is telling you directly: this feature is a state machine. But notice the distribution of responsibility. The callback initializes or owns the broad screen mode. The task system can listen to input and update menu behavior. Data tables provide moves and species rules elsewhere. Menu helpers render choices. Script code can be the reason the move relearner interaction began in the first place.

So if I ask, “Where is the move relearner?” the advanced answer is not “in learn_move.c,” even though that is the right source anchor. The better answer is, “The move relearner behavior is coordinated through learn_move.c, with state-machine logic in that module, but its visible behavior depends on task dispatch, menu systems, Pokemon data, move constants, text windows, callbacks, and sometimes script entry.” That is the kind of answer I want you to practice.

Quick check for understanding. If a screen fades out, draws a list, lets the player move the cursor, accepts A or B, and returns to the overworld, which one object owns that whole behavior? Pause for a second. The answer is: probably no single object, not in the modern object-oriented sense. The broad screen mode is owned by the active main callback. The per-frame cursor or menu work may be tasks. The selected creature and move information lives in structs or globals. The legal move options come from data and query functions. The return path may restore another callback or script context. That is exactly why this week matters before we study maps, scripts, battle, inventory, and save.

Let’s connect that forward to the course plan. When we get to overworld maps, you will see data records for layouts, metatiles, event objects, warps, and scripts. Your instinct should now be to ask, “Which callback is currently interpreting this world, which tasks are alive, and which globals represent the current map state?” When we get to event scripting, you should ask, “Is this script truly doing the work, or is it queuing work that a callback or task will complete over later frames?” When we get to creature data, ask, “Is this table passive until queried, or is it copied into party state, battle state, summary-screen state, or daycare state?” That distinction matters.

The daycare example from pokefirered/src/daycare.c, chunk_119931105f546346, supports that broader view. The packet shows it includes data.h, battle.h, pokemon_storage_system.h, event_data.h, script.h, party_menu.h, list_menu.h, overworld.h, pokedex.h, naming_screen.h, help_system.h, field_fadetransition.h, and trade.h. Again, this is not just “daycare logic.” It touches creature data, storage, script flags, menus, overworld return paths, naming screens, and UI transitions. Later in the course, when we study systems like breeding, storage, or summary screens, we will keep asking the same architectural question: what is the data, what is the runtime state, what is the per-frame owner, and what player-visible behavior emerges?

Now look at pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. The packet identifies includes like pokemon.h, pokemon_summary_screen.h, task.h, menu_helpers.h, link.h, overworld.h, strings.h, new_menu_helpers.h, menu.h, constants/items.h, data.h, item.h, trade.h, battle_main.h, scanline_effect.h, constants/moves.h, region_map.h, field_specials.h, and party_menu.h. A summary screen sounds like a read-only display. But architecturally, it sits near party data, item data, move data, link contexts, trade contexts, battle contexts, and menu navigation. So even a “screen” is often a negotiated runtime mode with data access, callback ownership, and tasks handling small state changes.

Let me write one sentence on the board, and I want you to copy it because it is a useful debugging sentence: “A feature is not where its name appears; a feature is the path from stored facts to frame-by-frame control.” That sentence is how you avoid getting lost in a large decompilation project. Search results are useful, but search results are not architecture. Architecture is the pattern of control and state over time.

So, as a final concrete pass, imagine your homework map. You start at a static data record: a trainer, a map event, a species, an item, a move list. Then you identify what runtime state receives that information: party structs, map object structs, script variables, event flags, battle globals, menu state. Then you identify who receives frames: the main callback established through the callback shell we studied with pokefirered/src/main.c, chunk_6cfd90f6d0136799, and the task scheduler whose creation and dispatch behavior we studied in pokefirered/src/task.c, chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. Then you identify the visible behavior: a menu opens, an avatar stops, a text box waits, a transition plays, a battle begins, a move is learned, a summary page flips.

If you can draw that path, you are no longer just reading C files. You are reading a runtime.

And that is where week one lands. We began with AgbMain and the callback spine, not because startup code is glamorous, but because it tells us how the game becomes a repeating frame machine. We then looked at tasks as lightweight state machines, not because tasks explain everything, but because they explain a great deal of the small per-frame coordination that makes a handheld RPG feel alive. The larger point is that FireRed’s architecture is distributed. Data records do not act by themselves. Globals and structs preserve state across frames. Callbacks define broad modes. Tasks advance local state machines. Scripts coordinate progression and events. Source functions perform concrete mutations and queries. Rendering systems turn all of that into player experience.

Next time, we will start using this model as a working tool rather than a lecture diagram. We will take a feature path, mark each role directly against source functions, and turn a call-flow map into an architectural map. That is the transition from “I found the function” to “I understand the system.”

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