# AgbMain and the Callback Spine

Lecture ID: lecture_w01_d01
Week 1, Day 1

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

## Transcript
All right, let’s begin at the outermost edge of the program, because for this course that edge matters. Before we talk about maps, trainers, battles, party data, evolution, save blocks, inventory pockets, link cable states, or scripted NPCs, we need to understand the shell that lets any of those systems get time on the machine. In FireRed, the first architectural lesson is not “how does a Pokémon learn a move?” or “how does damage work?” The first lesson is “who is allowed to run this frame?” That is the question AgbMain and the callback spine answer.

So, today is week 1, day 1, and we are in the module I am calling Runtime Shell and Source Contracts. For the next two weeks, we are not yet trying to master every subsystem. We are building the mental skeleton that will let the rest of the course make sense. If you can look at FireRed and trace from startup, to a main callback, to a task, to a visible screen behavior, then battle, inventory, summary screens, overworld events, and save transitions stop looking like separate mysteries. They start looking like variations on a shared runtime pattern.

Let me put the big map on the board first. I’m drawing nine boxes. Runtime shell. Overworld maps. Event scripting. Creature and party data. Battle. Inventory. UI. Save. Link systems. Now I’m drawing arrows from runtime shell to every other box, because that is the point of today. The shell does not know the details of every map tile, every move, every item, or every wireless exchange, but it owns the rhythm. It owns the frame. It owns the mechanism by which some piece of code becomes the currently active experience.

When we say “creature-collector RPG architecture,” we are not saying there is one giant function called RunPokemonGame. That would be pleasant in a diagram and disastrous in a real game. FireRed is closer to a federation of state machines. Some are large, like the overworld. Some are specialized, like the move relearner. Some are data-driven, like trainer tables or species tables. Some are transient, like a fade task or a menu cursor. But all of them need to participate in a very constrained machine: the Game Boy Advance, one frame at a time, with graphics memory, input, audio, DMA, interrupts, and save hardware all competing for careful attention.

Now, because this is an advanced course, I want us to be precise about what “callback” means here. A callback is not magic. It is a function pointer selected by some controller code and called later by the main loop. In a desktop engine, you might call this a scene, a mode, a screen controller, an update delegate, or a state function. In FireRed’s codebase, the naming convention often gives us CB2-style functions, and the source focus for today points us directly to pokefirered/src/main.c, chunk_b35676322b169f85, for AgbMain, and pokefirered/src/main.c, chunk_6cfd90f6d0136799, for SetMainCallback2. Those are not incidental details. They are the entry point for understanding why the rest of the codebase is shaped the way it is.

Let’s slow that down. AgbMain is the startup and main-loop anchor. It is responsible for getting the program from “the cartridge code has begun executing” into a stable runtime posture. Think initialization of global systems, hardware-facing setup, default callback selection, and then the recurring loop where the game repeatedly processes one unit of time. I am not asking you to memorize every startup responsibility as a flat list. I want you to see that AgbMain defines the contract of the rest of the engine. If a subsystem wants to animate, poll input, fade, move a cursor, process an NPC script, or advance a battle animation, it eventually has to fit into the rhythm that AgbMain sustains.

Now I’m going to write a simplified structure on the board, not as exact source code, but as an architectural sketch. At the top: boot. Under that: initialize hardware and memory. Under that: choose initial main callback. Under that: loop forever. Inside the loop: wait or sync to frame, handle input, run callback, update graphics and sound. This is the mental model. The exact FireRed source has the real names and sequencing, and again our source anchor is pokefirered/src/main.c, chunk_b35676322b169f85. But the important architectural move is this: the game is not progressing because a script says “now do everything.” The game progresses because the runtime shell calls the current callback every frame, and that callback may delegate to tasks, scripts, object events, menus, or battle controllers.

Here is the first check for understanding. If I ask, “Where does the item menu live?” the wrong advanced answer is “in the item menu files.” That is true at the file organization level, but incomplete architecturally. The better answer is: the item menu is a mode of execution installed into the callback spine, with its own supporting tasks and state, entered from some previous mode and eventually returning control to another mode. That way of answering forces you to explain how the player-visible screen gets time each frame.

Now, SetMainCallback2 is one of the key doors in that system. In pokefirered/src/main.c, chunk_6cfd90f6d0136799, the source focus identifies SetMainCallback2 as the function we care about for swapping the main callback slot. Again, the exact body is not the lecture’s whole point. The architectural point is that there is a named operation for changing what the main loop calls. That means screen transitions are not just visual transitions. They are control-flow transitions. When the game moves from field to battle, or from field to party menu, or from a menu back to overworld, it is not merely drawing different tiles. It is changing which subsystem is the frame owner.

Let me give you a concrete example. Imagine the player presses Start in the overworld and opens the menu. The visible behavior is simple: a menu appears. But architecturally, what has to happen? First, the overworld callback is running as the current major mode. It is reading input and updating map objects. When the Start input is recognized, the overworld system does not become a menu by wishful thinking. It either creates menu-related tasks within the current callback context, or it transitions to another callback that owns the menu flow, depending on the screen and implementation pattern. That menu code then needs to draw windows, place text, process cursor input, maybe launch a party screen or bag screen, and eventually return. Every one of those steps must fit the per-frame dispatch model.

And this is why we are pairing main callbacks with tasks this week. Today is the callback spine. The next lecture, “Tasks as FireRed’s Lightweight State Machines,” will zoom into the finer-grained scheduler. But we cannot separate them completely, because the callback gives you the coarse mode and tasks give you a flexible set of per-frame actors inside that mode. The research packet points us to pokefirered/src/task.c, chunk_cb0bb91a147bfb59, for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936, for RunTasks. Those two chunks are the task-side equivalent of today’s callback story. CreateTask allocates a task slot and returns a task ID. RunTasks dispatches active task functions per frame. That is a major source contract for FireRed.

Let’s pause on “task ID,” because this is easy to underestimate. In a modern engine, you might allocate heap objects, attach components, register event listeners, and let the engine manage lifetimes. FireRed’s task system is more explicit and constrained. Tasks live in an array of task slots. A task ID is an index-like handle into that array. The task function gets called repeatedly, and task-local data fields let it remember where it is. That means a task can behave like a small state machine without needing a heap allocation or a separate object instance in the C++ sense. It is C code organizing time.

I’m writing that phrase on the board: “C code organizing time.” Keep that phrase close. A creature RPG is full of data, but data does not become gameplay until something organizes time around it. A species table tells you what Bulbasaur is. A map JSON or layout table tells you where the player can stand. An item table tells you what a Potion means. A move table tells you what Tackle does. But none of those tables are visible unless a callback or task or script reads them at the right moment and uses them to update memory, graphics, text, or control flow.

This is also why the course thesis in the research packet is phrased as “data table to runtime globals to callback, task, or script to player-visible behavior.” That phrase is going to recur all semester. When we later inspect battle setup, for example, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is useful because it imports and connects overworld, item, sound, Pokémon, load-save, Safari Zone, quest log, script, field effects, field map, player avatar, message boxes, battle, and battle transition systems. That include list alone tells a story. Battle is not isolated. Battle starts as an event in the world, consults party and trainer data, changes music and screen state, interacts with scripts and flags, and then hands control to a battle runtime. The callback spine is one reason those handoffs are possible without one monolithic program flow.

Similarly, when we eventually study the Pokémon summary screen, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, gives us a source anchor for a UI-heavy subsystem that imports Pokémon data, help system, tasks, menu helpers, link, overworld, items, party menu constants, trade, battle main, moves, placeholder text, region map, and more. That is a lot of dependency surface for what the player experiences as “I looked at my Charmeleon’s stats.” The runtime shell lets that screen become temporarily central without becoming the entire game.

Now, I want you to notice an important design implication. The callback system is not just an implementation detail. It shapes how features are built. If your architecture says, “There is one active main callback,” then major gameplay modes tend to be written as things that can own the frame. If your architecture says, “Inside that callback, there is a task array,” then smaller ongoing behaviors tend to be written as task functions with state in task data. If your architecture says, “Scripts can suspend, resume, and invoke specials,” then event progression becomes another layer of stateful control. The source code style follows the runtime contract.

Let me give you another concrete example, this time from a specialized subsystem. The research packet highlights pokefirered/src/learn_move.c, chunk_af3598df6f092723, and the excerpt explicitly comments on the move relearner state machine. It names CB2_MoveRelearner_Init, list menu scroll tasks, and MoveRelearnerStateMachine states such as fade transitions. That is a wonderful example because it is not the whole overworld and not the whole battle engine. It is a small, feature-specific flow. The player sees a list of moves and chooses one. Underneath, there is callback initialization, task creation for menu scrolling, state machine progression, fade handling, and return behavior. That is the course in miniature.

Check yourself here. If you are tracing a feature and you find a data table, what is the next question? Not “what are all the values?” That matters, but it is not first. The next question is “who reads this table, in which runtime mode, and how often?” Is it read once during initialization? Is it consulted every frame? Is it read when a script command fires? Is it read when a battle command resolves? Is it used to build menu text and then forgotten? That question turns static source reading into architectural reading.

Now, let’s connect this to the lab you will do this week. You are going to draw a call-flow map for booting into a callback-driven screen, and then implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch. The point of that lab is not to produce a cute toy engine. The point is to make you feel the constraints that FireRed’s architecture is solving. Once you implement even a small version, you will understand why CreateTask in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, must care about free slots, priorities or ordering, active flags, and task-local data. You will understand why RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936, is a per-frame dispatch mechanism rather than a one-time function call. You will also understand why forgetting to destroy or advance a task can leave invisible logic still consuming frames.

Let’s do a small thought experiment together. Suppose we make a title screen. We install TitleScreenCallback as main callback 2. That callback does three things each frame: it reads input, it runs tasks, and it draws or updates screen elements. On the first frame, it creates a blinking “Press Start” task. That task has a data field called timer and a data field called visible. Every frame, RunTasks calls the task function. The timer increments. Every thirty frames, visible toggles. The main callback or the task updates the OAM or tilemap state so the prompt appears and disappears. When Start is pressed, the title callback calls SetMainCallback2 to install the overworld setup callback or next boot flow. That is the whole spine in miniature: a main mode, a task slot, persistent task data, per-frame dispatch, and callback replacement.

Now apply the same logic to FireRed, but scale up. The overworld is not just a screen with a blinking prompt. It has map loading, camera movement, object events, player input, collision, scripts, wild encounters, trainer sight lines, field effects, message boxes, and transitions into battle or menu screens. The battle engine has its own command sequencing, animations, text, HP bars, party state mutation, and eventual return. The inventory has pocket data, item effects, menus, quantities, registered key items, and interactions with overworld or battle contexts. The save system has persistent blocks, checksums, flash memory behavior, and restore paths. The link system has remote state, timing, protocol constraints, and screens that must keep local UI responsive while waiting for other machines. These systems differ in domain, but they all rely on the runtime contract of “what code runs this frame?”

I want to bring in Pokémon Showdown briefly, because the research packet tells us to treat it as a secondary comparison, not as our primary authority. For example, pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, shows a very different environment for move damage logic: TypeScript, a battle object, callbacks like damageCallback, immunity checks, move fields such as basePower and category. That source is useful later when we compare data modeling and deterministic battle tests. But today, FireRed is our architecture authority. Showdown can help us ask, “How might this rule be expressed in a modern simulator?” FireRed asks the more constrained question: “How does this cartridge runtime move from boot to visible behavior frame by frame?”

Look back at the board. Runtime shell is the leftmost box, and I have arrows into everything. I’m now adding a vertical line through the diagram and labeling it “control transfer.” Whenever FireRed changes from one player-visible experience to another, there is a control transfer. Sometimes it is explicit through SetMainCallback2. Sometimes it is delegated to tasks or scripts. Sometimes it is a layered interaction: a script command starts a fade, a task waits for the fade, a callback initializes a new screen, that screen creates more tasks, and eventually another callback returns to the field. Your job in this course is to stop seeing those as disconnected tricks and start seeing them as one family of runtime patterns.

Here is a quick comprehension check. I’ll phrase it as a debugging scenario. The player opens a menu, the screen appears, but input no longer moves the cursor. At an architectural level, where do you look? You do not start by changing the cursor graphic. You ask: Is the correct main callback installed? Is the per-frame task dispatcher being called in that callback? Was the cursor task created successfully? Is its task ID valid? Is its state data initialized? Is input being read before the task expects it? Did another task lock input or own the menu state? That checklist comes directly from understanding the callback spine and task scheduler.

One more scenario. A fade to battle completes visually, but the battle never starts. Again, the architectural questions come first. Which callback owns the fade? Which task waits on it? Who calls SetMainCallback2 for the battle setup or battle main callback? Has the battle setup state been populated from overworld encounter data, trainer data, or script variables? When we later inspect battle_setup.c, chunk_494d132a59422c85, we will see why the battle boundary touches so many systems. But the method starts today: trace the running code, not just the domain noun.

Now, because this is the first lecture, I want to name the skill you are practicing. You are learning source-grounded architecture reading. That means you do not merely say “FireRed has tasks.” You cite pokefirered/src/task.c, chunk_cb0bb91a147bfb59, when discussing task creation, and pokefirered/src/task.c, chunk_3ffebc0341225936, when discussing per-frame task dispatch. You do not merely say “FireRed uses callbacks.” You cite pokefirered/src/main.c, chunk_6cfd90f6d0136799, when discussing SetMainCallback2, and pokefirered/src/main.c, chunk_b35676322b169f85, when discussing AgbMain’s startup and loop responsibilities. The citation is not academic decoration. It is how we keep our architectural claims tied to real code.

Let’s recap this first movement before we go deeper. A complete creature-collector RPG is too large to understand as one procedural story. FireRed becomes understandable when we decompose it into runtime shell, overworld, scripts, creature data, battle, inventory, UI, save, and link systems. The runtime shell is first because it governs frame ownership. AgbMain establishes the program’s main rhythm. SetMainCallback2 changes the coarse active mode. CreateTask and RunTasks provide finer per-frame state machines inside those modes. And the recurring trace we will practice all semester is data table, to runtime state, to callback or task or script, to player-visible behavior.

Keep that model in your head, because next I want to zoom in on AgbMain itself, not line by line as trivia, but responsibility by responsibility: what must be true before any callback-driven screen can safely run, and why those startup responsibilities shape the architecture of everything that follows.

So, let’s put AgbMain on the board as a contract, not as a function name. I’m going to write three words: initialize, select, loop. Those are not the only things happening, but they are the architectural responsibilities that matter to us today. AgbMain has to bring the machine into a known state, it has to arrange which high-level callback owns the next phase of execution, and then it has to keep yielding frames to that owner. When I cite this for you, I mean the FireRed runtime shell source focus in pokefirered/src/main.c, chunk_b35676322b169f85 for AgbMain, and pokefirered/src/main.c, chunk_6cfd90f6d0136799 for SetMainCallback2. Those two chunks are our first evidence that this program is not best understood as “the game starts, then calls the title screen, then calls the overworld, then calls battle” in a simple nested sequence. It is better understood as a loop whose owner changes.

Now, pause there, because that distinction is easy to say and very easy to underuse. In a beginner mental model, you imagine a title screen function that runs until the title screen is done, then returns to something that calls the next screen. In FireRed’s style, the more useful mental model is: each frame, the shell asks, “Who owns this frame?” The answer might be a title-screen callback, an overworld callback, a battle callback, a menu callback, or a transition callback. SetMainCallback2 is the handoff point for that coarse ownership. It does not mean every detail of the screen is inside that one function. It means the large mode of the program has changed.

Here is the board sketch. I’ll draw a vertical box on the left labeled AgbMain. From it I draw an arrow into “main loop.” Inside that loop I draw a smaller box labeled main callback 2. Then, inside that, I draw “tasks,” “scripts,” “sprites,” “input,” “DMA or graphics updates,” depending on the current mode. The point of the drawing is that the callback is not just a function pointer trick. It is a frame ownership boundary. When you see SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, read it as, “the architecture is changing who gets to interpret time.”

That phrase, interpret time, is important. The machine gives you frames. The RPG gives those frames meaning. In the overworld, a frame might mean move the player one walking substep, poll an NPC script trigger, animate water tiles, advance a text box, and check whether a menu button was pressed. In battle, a frame might mean advance the battle script cursor, animate a move effect, wait for HP bar interpolation, or resolve input from the fight menu. In a summary screen, a frame might mean listen for directional input, redraw a page, handle a cancel command, and restore the previous callback when finished. Same hardware rhythm, different semantic owner.

That is why I want you to resist the temptation to treat AgbMain as “startup boilerplate.” Startup code in this architecture is not incidental. It establishes the stable environment in which callback ownership is possible. Before a callback-driven screen can run safely, global memory conventions must be reliable, hardware-facing systems must be in a known condition, the interrupt and VBlank rhythm must be coherent, and the game’s shared registries have to be ready enough that later modes can assume them. I am not asking you to memorize a list of every initialization call today. I am asking you to name the responsibility: AgbMain makes the rest of the program allowed to be modular.

Let me make that concrete. Suppose we are entering a screen that displays a Pokémon’s summary. The source packet points us to pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, and the include list alone is revealing: it pulls in pokemon data, task infrastructure, menu helpers, link, overworld, battle main, move constants, region map information, party menu, and more. A summary screen looks like “just UI” to a player. Architecturally, it is a meeting point. It reads creature data, draws UI, handles input, may be reached from party or battle-adjacent contexts, and must return control cleanly. That only works if the runtime shell has already established a predictable callback and task environment. Otherwise, every feature screen would need to defend itself against the whole machine being half-initialized.

Now, check for understanding. If I say, “The summary screen is callback-owned,” does that mean the summary screen performs every sub-action directly inside one giant function? No. It means a high-level callback owns the frame, and inside that ownership it may use tasks, menus, sprite callbacks, helper state, and data tables. The callback is the boundary of responsibility, not necessarily the only unit of behavior.

Good. Now let’s move one layer inward, from callback ownership to task dispatch. The research packet names pokefirered/src/task.c, chunk_cb0bb91a147bfb59 for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936 for RunTasks. These are the pieces that keep FireRed from having to create an entire separate bespoke loop for every little UI, animation, or waiting state. A task is a lightweight state machine slot. It has a function, an ID, some small data storage, usually an active flag and priority-like ordering in the implementation, and it gets called once per frame by RunTasks while it remains active.

Notice the difference in scale. SetMainCallback2 changes the coarse mode. CreateTask creates a fine-grained per-frame participant inside a mode. RunTasks is not “the game loop” by itself; it is a dispatch pass used by modes that choose to run tasks. This is a common point of confusion. Students see RunTasks and think, “Ah, that’s the engine.” It is part of the engine, but it is not the whole shell. AgbMain owns the outer rhythm. The main callback owns the current mode. RunTasks advances the active task machines that the current mode depends on.

Let’s do a simple imagined trace. I’ll keep it C-shaped, because your lab this week asks you to implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch. Imagine the current callback is an overworld callback. The player presses Start. The overworld callback notices the input and decides to open a menu. It might set up windows, load graphics, freeze some overworld behavior, and create one or more tasks to run the menu state. The task’s data array stores things like cursor position, current submenu, delay counters, or return mode. Each frame, RunTasks calls that task. The task looks at input, updates its internal state, maybe changes the selected row, maybe plays a sound, maybe starts a fade. When the player cancels, the task destroys itself or requests a callback transition back to the previous owner.

Now, in that trace, where is the “object”? There may not be a heap object in the modern sense. There is a task slot. The task ID is a handle into a fixed scheduler. Advanced learners should be very precise here. If you mentally translate every task into a class instance, you may overfit object-oriented assumptions that the original architecture does not have. FireRed’s task system is closer to a cooperative array of small state machines. CreateTask, in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, is important because allocation is constrained and explicit. RunTasks, in chunk_3ffebc0341225936, is important because dispatch is per-frame and cooperative. No task preempts another. No task gets its own thread. If a task blocks, the frame owner suffers.

And that leads us to a design law for this course: never hide long-running behavior inside a single blocking call when the rest of the game expects per-frame cooperation. In FireRed, waiting is usually a state, not a sleep. Fading is a state. Text printing is a state. A menu selection is a state. Battle animation is a state. Link communication is especially stateful because it depends on synchronization across devices. This is not just old hardware austerity. It is an architecture of responsiveness under constraints.

Let’s connect that to the full creature-collector decomposition we named earlier. The overworld maps system is one major frame owner at times. Event scripting is another layer of state machinery inside and alongside the overworld. Creature data is largely model data, but it becomes visible only when some callback or task reads it and renders or mutates it. Battle is a large mode with its own internal script-like sequencing. Inventory is data plus UI plus scripts plus save effects. Save is persistence, but save operations must be coordinated with runtime state and hardware realities. Link systems are communication protocols, but in a game they also become UI states, battle states, trade states, and failure states. The callback spine is how all of those modules take turns without becoming one enormous procedural tangle.

The research packet gives a nice example of this cross-module density in pokefirered/src/battle_setup.c, chunk_494d132a59422c85. That source chunk is not our main focus today, but it supports a claim we will use repeatedly: a battle does not belong only to “battle code.” The setup layer touches overworld, items, sound, Pokémon data, save loading, Safari Zone logic, quest log, scripts, event data, field effects, player avatar control, battle transition, and battle configuration. So when a trainer battle starts, the visible behavior is the outcome of a chain. It may begin with map or event data, go through script conditions, prepare battle setup state, launch a transition, change callback ownership, and only then become the battle screen the player recognizes.

Let me say that again in the semester formula: data table, to runtime state, to callback or task or script, to visible behavior. For a trainer battle, the data table might define a trainer, map event, or script reference. Runtime state records where the player is, which flags are set, which trainer has been defeated, what party the player has. A script or overworld task detects the encounter condition. Battle setup prepares the battle. A callback transition hands frame ownership to the battle mode. The visible behavior is the swirl, the trainer intro, and then the battle interface. If you jump directly from “trainer data exists” to “battle happens,” you miss the architecture.

Here is a second concrete example, this time from a smaller UI system. The packet points us to pokefirered/src/learn_move.c, chunk_af3598df6f092723, and the excerpt explicitly labels a move relearner state machine. That is gold for us. The move relearner is conceptually simple: show a list of possible moves, let the player choose, maybe replace a move, then return to the script or menu context. But the source organization tells us the implementation needs script integration, task infrastructure, data tables, menu and list-menu helpers, text windows, graphics, strings, and move constants. In player terms, “I selected a move.” In architecture terms, “a callback-owned UI created tasks and list-menu state, read move data, coordinated with script continuation, and eventually changed persistent creature state.”

Quick check. If the move relearner uses a state machine, is that because the feature is unusually complex? Not really. It is because even ordinary RPG interactions unfold across frames. You wait for fades. You wait for text. You wait for list input. You wait for confirmation. You may branch based on whether the Pokémon already knows four moves. Each of those waits has to leave the game alive for the next frame. That is what state machines buy you.

Now, I want to sharpen the distinction between three words we will use all semester: callback, task, and script. A callback is a C function pointer used by the runtime to delegate some recurring responsibility, often per frame. A task is a scheduled lightweight state machine, usually managed in fixed slots and dispatched by RunTasks. A script is a data-driven or bytecode-like progression mechanism for authored game events, especially in the overworld and story logic. These three can call into each other’s worlds, but they are not interchangeable. If you blur them, the architecture becomes soup.

For example, an NPC conversation in the overworld may be authored as a script. But the text box that appears is not “just script.” Rendering and input are frame-based UI behavior. The script may request text, then wait until the message system reports completion. Somewhere beneath that, callbacks and tasks are advancing the visible state. Conversely, a task may wait for a script condition or set a script variable. These systems cooperate through shared state and conventions rather than through one central omniscient function.

I’ll put a small table on the board verbally. Callback: who owns the frame? Task: what small machines run this frame? Script: what authored event sequence is currently being interpreted? Data table: what static facts or content are being consulted? Runtime globals: what is true right now? The skill you are building is the ability to place a line of code into one of those roles quickly.

Now let’s bring AgbMain back into focus. Why start the course here instead of starting with Pokémon species data, moves, or battle formulas? Because the callback spine explains how those more glamorous systems become playable. A species table sitting in data is inert. A move definition is inert. A map layout is inert. Even a script is inert until some runtime owner interprets it. AgbMain and the callback shell define the program’s “heartbeat,” and the rest of the course attaches organs to that heartbeat. I’m using that metaphor carefully: we are not saying AgbMain knows how everything works. We are saying it establishes the conditions under which specialized systems can run in turns.

This also explains the week sequence. Today is AgbMain and the Callback Spine. The next lecture is Tasks as FireRed’s Lightweight State Machines. The lab asks you to draw a call-flow map for booting into a callback-driven screen, then implement a tiny scheduler. That lab is not a toy aside. It is the smallest runnable version of the architecture. If you can build a main callback slot, a SetMainCallback2-like setter, a fixed task array, CreateTask-like allocation, task IDs, task data, and a RunTasks-like per-frame dispatch, then you have felt the pressure that shaped the original code. You will understand why FireRed code often decomposes behavior into “initialize once, then advance one frame at a time.”

Let’s test that with a design question. Suppose you are implementing a bag pocket screen. The player can scroll items, select an item, choose Use, Toss, Register, or Cancel, and maybe return to the overworld or to battle depending on context. Would you implement that as one function that loops until the player is done? On a desktop command-line toy, maybe. In this architecture, no. You would initialize the screen, set the appropriate main callback or enter from an existing menu callback, create tasks for input and menu behavior, store cursor and pocket state in task data or associated globals, and return every frame. When the player makes a selection, you transition the task state. When the feature completes, you restore or set the next callback. That is the architectural answer.

And if someone says, “But isn’t that more complicated?” the honest answer is yes, locally. It is more complicated than writing a blocking while loop. But globally it is simpler because animations, sound, input polling, VBlank updates, link checks, and transitions remain alive in the same frame rhythm. The system pays a small complexity cost in state machines to avoid a catastrophic complexity cost in uncoordinated blocking flows.

Now, there is one more advanced point I want to introduce before we continue: source tracing should move in both directions. Sometimes you start with visible behavior and trace backward. The player sees a battle transition; you ask, “Which callback owns this? Which task advances it? Which script requested it? Which data selected the trainer or wild species?” Other times you start with data and trace forward. You find a move constant or map JSON or trainer entry; you ask, “Who reads this? Into what runtime structure? Under which callback? What player-visible behavior changes?” This bidirectional tracing is how you avoid becoming either a code tourist or a data-table tourist.

The packet’s broader synthesis tells us to use FireRed as the main authority and Pokémon Showdown only as a secondary comparison, especially later for battle data modeling and deterministic battle tests. That matters because Showdown often presents a cleaner modern data-and-simulation view, while FireRed presents a hardware-era shipped game architecture. For this week, stay with FireRed. When we eventually compare battle logic to sources like pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, we will do it to sharpen our understanding of data-driven battle behavior, not to replace FireRed’s callback architecture with a server simulator’s architecture.

So, if you are taking notes, the next sentence is worth starring. FireRed’s architecture is not merely data-driven; it is data-driven through scheduled ownership. Data alone does not move the game. Callback ownership, task dispatch, script interpretation, and shared runtime state move the data into behavior. That is the central idea of week one.

Let me do one more check for understanding before we move further into AgbMain’s startup responsibilities. Imagine a fade-to-black transition before opening a menu. Where might the fade live? It could be managed by a task or a callback-specific state machine. What should it not do? It should not freeze the entire program in a busy loop that prevents the frame rhythm from updating other expected systems. What tells us that this is the right instinct? The presence of the callback spine in pokefirered/src/main.c, chunks chunk_b35676322b169f85 and chunk_6cfd90f6d0136799, and the per-frame task dispatch model in pokefirered/src/task.c, chunks chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. The source structure supports the architectural reading.

Now let’s return to the phrase “what must be true before any callback-driven screen can safely run.” First, the callback slot itself has to have meaningful contents. A null or stale frame owner is not an architectural abstraction; it is a crash or undefined behavior waiting to happen. Second, shared systems must agree on their initial state. A screen that assumes graphics memory is prepared, tasks are initialized, input is readable, and save-related globals have known values is relying on startup discipline. Third, the main loop has to call things in a stable order. In a frame-based game, order is a hidden API. Input before update is different from input after update. Tasks before rendering is different from tasks after rendering. VBlank work before visible updates is different from doing it whenever you remember.

I am deliberately not turning that into a universal engine law, because different engines choose different orders. What matters is that FireRed’s shell establishes an order and the rest of the codebase grows around it. Once enough modules rely on that order, changing it is not a refactor; it is an architectural migration. That is why startup and frame dispatch are source contracts.

When we say “source contract” in this course, we mean a rule that may not be written as a formal interface but is enforced by the shape of the code. A task function expects to be called repeatedly and quickly. A callback expects to own a frame until it changes ownership. A script command may expect some UI state to continue across frames. A save routine expects data to be coherent at the moment persistence is requested. These contracts are often stronger than comments, because hundreds of call sites depend on them.

In a moment, we’ll start walking responsibility by responsibility through AgbMain with that contract lens. Not “what line comes next,” but “what assumption is being made available to the rest of the RPG?” As we do that, keep the entire course map in sight: overworld maps, event scripting, creature data, battle, inventory, UI, save, and link are all downstream consumers of the runtime shell. Week one gives us the spine. Weeks after this attach the systems. And each time we attach one, we will ask the same disciplined question: who owns the frame, what state machine advances, what data is being interpreted, and what does the player see?

All right, so let’s put AgbMain on the board as a set of promises, not as a mystery ritual. The first promise is: when the game begins executing its own logic, the hardware-facing baseline has been made predictable enough that higher systems can behave as if they are running inside a game engine, not inside raw hardware chaos. That distinction matters. FireRed is not a desktop program with an operating system giving it windows, event queues, memory protection, and process scheduling. Its runtime shell has to manufacture the illusion of a stable application environment.

So, board cue here: I’m writing “AgbMain equals environment constructor plus frame pump.” That is the mental model I want you to keep. In pokefirered/src/main.c, chunk_b35676322b169f85, AgbMain is our anchor for this startup responsibility. We are not using it merely because it is the first interesting function. We are using it because everything else in the RPG quietly depends on it. Battle setup, overworld loading, menu screens, link communication, audio, save access, and scripted events all assume that some root loop exists and that control will return to them frame after frame in a disciplined way.

Now, if you come from modern game engines, you might be tempted to ask, “Where is the scene manager?” Or, “Where is the application class?” In this architecture, the equivalent is split across global runtime state, callback slots, task arrays, and system initialization. That is why I keep using the phrase callback spine. It is not a single class. It is a control convention. The game says: at any moment, there is a currently active main behavior; that behavior may create tasks; those tasks advance small pieces of state each frame; and visible game behavior is the result of data being interpreted by those callbacks and tasks.

Let’s slow that down with a concrete example. Imagine the player presses Start in the overworld and opens the menu. The visible behavior is simple: the field darkens or pauses appropriately, a menu appears, the cursor moves, sound effects play, the player selects Bag or Pokemon or Save. But architecturally, that is not one big permanent “game object” doing everything. The overworld has been running under a particular callback regime. Opening the menu changes which logic owns the frame, or creates tasks under the current owner, depending on the screen and transition. The menu itself interprets data tables for text, window layouts, items, party state, or options. When the menu exits, control returns to the prior runtime context. So the player sees “I opened a menu.” We see a handoff among callback ownership, task dispatch, and domain data.

Check for understanding: if I ask, “What owns the frame while the menu is animating in?” the answer cannot just be “the menu.” That is too vague. A better answer is, “Some active callback, usually selected through the callback slot mechanism, is being invoked once per frame, and within that callback tasks or screen-specific state machines may be advanced.” That phrasing is closer to the way the codebase is built.

Now let’s connect this to SetMainCallback2, because this is the first place where the architecture becomes visible rather than implied. In pokefirered/src/main.c, chunk_6cfd90f6d0136799, SetMainCallback2 is the source focus for changing the main callback slot. The name is plain, but the design consequence is large. A function like this is not just a setter. It is an architectural gate. It says, “The next phase of frame ownership is here.” It permits one subsystem to yield to another without requiring a giant central switch statement that knows every screen in the game.

On the board, I’ll draw three boxes in a row: boot, title screen, overworld. Between those boxes I’ll draw arrows labeled SetMainCallback2. That drawing is intentionally oversimplified, but useful. Each box is a regime. Inside a regime, the code may allocate tasks, poll input, decompress graphics, load maps, run scripts, or set up battles. The arrows are not just “go to function.” They are “change the interpretation of future frames.”

This is why callback-driven architecture is so effective on constrained hardware. You avoid a heavy stack of nested loops. You avoid keeping every system active all the time. You let one top-level behavior own the frame, and you let that behavior install the next owner when the transition is ready. In practice, that means the title screen does not need to know how to walk a trainer around a map. The battle intro does not need to keep the entire overworld loop active as if nothing changed. The Pokemon summary screen, which we will later study through pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes battle, party, menu, link, region map, and move-related dependencies precisely because UI screens often sit at intersections of many systems. But they still need a frame owner. They still need to live inside the callback shell.

Now, let’s bring in tasks. The second major promise of the runtime shell is that small, temporary state machines can be allocated, identified, run, and destroyed without each feature inventing its own scheduler. In pokefirered/src/task.c, chunk_cb0bb91a147bfb59, CreateTask is the important source focus. Again, don’t reduce it to “it creates a task.” That is true, but not architectural enough. It allocates a slot in a fixed task system and returns a task ID that other code can use to refer to that small state machine.

What is a task, conceptually? A task is not a thread. That is the first misconception to kill. A task does not run in parallel. It does not preempt another task. It is cooperative. The main loop reaches a point where tasks are dispatched, and each active task gets a chance to run its function for that frame. If a task needs to wait for an animation, it does not sleep the CPU in the desktop sense. It records progress in its data fields and returns. Next frame, RunTasks calls it again.

The source focus for that per-frame dispatch is pokefirered/src/task.c, chunk_3ffebc0341225936, RunTasks. That function is one of the cleanest architectural ideas in the project. The game has many features that need to unfold over multiple frames: fades, messages, menu cursor movement, NPC movement, field effects, link negotiation, battle transitions. If every feature wrote a private while loop that waited until completion, the game would freeze input, audio, rendering, and communication. RunTasks gives those features a way to move one step at a time.

Let me give you a concrete example. Suppose we are transitioning from the overworld into a trainer battle. The player steps into a trainer’s line of sight. The event system detects that condition. A script or field-control path triggers battle setup. Now, the code does not simply call “battle” and disappear into a monolithic battle function forever. There are visible transitions: attention mark, trainer approach, text box, screen effect, battle transition, battle initialization. The research packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, whose include list alone is revealing: task, help system, overworld, item, sound, pokemon, load_save, script, event object movement, metatile behavior, field effects, battle, battle transition, and more. That file sits at a boundary. It proves that “starting a battle” is a cross-system orchestration, not a single isolated calculation.

So, if I trace player-visible behavior back to architecture, I might say: data in the map and trainer definitions indicates a trainer exists and can see the player; overworld and event logic interpret that data; battle setup coordinates the handoff; task-based transitions manage multi-frame presentation; then a battle callback regime takes ownership. That is the course thesis in miniature: data table to runtime globals to callback or task to visible behavior.

Pause there. Does everyone see why we are not starting this course with battle formulas? Battle formulas are interesting, and we will get there. But if you do not understand the runtime shell, then the battle engine appears to be a floating island. You will read battle code and wonder, “Who called this? Why does this global already exist? Why does this function return instead of finishing everything?” The answer is usually: because the callback spine and task scheduler are shaping the flow.

Now I want to sharpen the distinction between main callbacks and tasks, because advanced learners often blur them. A main callback is closer to frame ownership. It defines the broad mode of the program. A task is a scheduled unit of work inside that mode or across a transition managed by that mode. If callbacks are rooms in a building, tasks are the people moving around inside the currently unlocked room. That analogy is imperfect, but it captures one useful point: changing the main callback changes the context in which future per-frame work is interpreted.

Let’s put a tiny C-style sketch on the board, not as exact FireRed code, but as a model for the lab. I’ll write: mainCallback2 points to current screen update. Then below it: tasks array contains active task function pointers and data. The frame loop says: poll input, run callback, run tasks, render or wait for VBlank, repeat. The exact order and hardware details matter in real source, and we will be precise when we read the code, but for architectural thinking, this model is enough to explain why a menu cursor, a fade, and a message box can each persist across frames without blocking the entire program.

Now, check for understanding. Suppose a fade-to-black takes sixteen frames. Where should its progress live? Not in a local variable inside a function that returns, because that variable disappears. Not in a blocking loop, because then other per-frame obligations may be starved. In this architecture, the progress belongs in persistent state: task data, screen state, global transition state, or some combination chosen by the subsystem. The important idea is that the runtime shell must be able to return to the same state machine next frame and advance it.

This is where we begin decomposing the full RPG. The runtime shell is one layer. Overworld maps are another. The maps give us spatial data, warps, events, metatiles, object events, scripts, and encounters. Event scripting is another layer, and it turns authored scenario data into operations: show text, set flags, give items, start battles, move NPCs, change maps. Creature data is another layer: species, moves, stats, growth, party structures, storage, ownership, and evolution rules. Battle is a large state machine that consumes creature data, move data, items, random numbers, AI choices, and player input. Inventory and UI form another cluster, because items are data but item use is behavior, and menus are the bridge. Save persists selected runtime state. Link systems coordinate communication with another machine and therefore impose stricter timing and state coherence rules.

But here is the key: none of these layers floats independently. Each one needs to be entered, advanced, paused, resumed, or exited under the callback spine. Later, when we study the move relearner, for example, pokefirered/src/learn_move.c, chunk_af3598df6f092723 is useful because it explicitly frames the move relearner as a state machine and references callbacks, list menu scrolling, field transitions, overworld, menu systems, event data, summary screen, graphics, and move constants. That is a specialized UI feature, but its shape will look familiar after today: initialize, allocate supporting state, respond to input across frames, invoke domain rules, then return to the caller’s context.

Let’s take a more advanced angle now. What are the failure modes of this architecture? One failure mode is stale ownership. If a subsystem sets the main callback too early, then the next frame may run code before required data has been initialized. Another failure mode is orphaned tasks. If a task remains active after the screen it depends on has been torn down, it may read invalid assumptions or mutate state that no longer belongs to it. A third failure mode is hidden blocking. If a task or callback performs too much work in a single frame, the game may miss timing expectations, especially around rendering and link communication. A fourth failure mode is incoherent persistence: saving while a subsystem has half-applied changes can produce a file that reloads into nonsense.

These are not abstract software engineering worries. They are exactly the kind of bugs that callback architectures invite if contracts are informal. So when we read FireRed source, I want you to ask, “What must be true before this callback is installed? What tasks does it assume exist? What tasks does it create? Who destroys them? What globals does it treat as initialized? What visible state is being promised to the player by the next frame?”

Now I’ll connect that to the lab. In the workshop, you will draw a call-flow map for booting into a callback-driven screen, then implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch. The point is not to imitate every FireRed macro or hardware detail. The point is to feel the design pressure. You will discover that once callbacks return every frame, you need somewhere to put state. Once tasks are fixed slots, you need allocation rules. Once tasks have IDs, you need to think about identity and lifetime. Once transitions span multiple frames, you need to prevent the next screen from observing half-built state.

Let’s do a miniature example verbally. Imagine a title screen callback. On its first state, it loads graphics. On its second state, it waits for the load or fade. On its third state, it polls for Start. When Start is pressed, it creates a task that fades out the title music and screen. That task increments a counter each frame. When the counter reaches the target, it calls SetMainCallback2 to install the overworld initialization callback, and then it destroys itself. The overworld initialization callback loads map data, places the player, initializes object events, and eventually installs the overworld main callback. The player experiences this as “press Start, fade, appear in game.” The architecture sees callback handoffs and task-managed transitions.

Now, a subtle question: should the fade task itself set the overworld callback? Sometimes yes, sometimes no. That depends on ownership. If the task was created specifically as a transition controller, it may be appropriate. If the current screen owns transition policy, then the task should merely report completion through task data or a flag, and the screen callback should install the next callback. This is not a universal rule. FireRed’s codebase uses established local patterns, and part of advanced source reading is noticing where control decisions live.

Let’s bring in the inventory and UI briefly, because they are excellent stress tests. An item in a bag is just data until the UI interprets it. But item use may touch party data, field state, battle state, scripts, sound, animations, and save-relevant flags. The bag interface therefore cannot be understood as “draw list of items.” It is a callback/task participant that exposes commands into multiple domains. When we later read inventory paths, we will trace item data to menu display to item effect callback to visible change. That is the same pattern as today, just with more domain-specific tables attached.

Creature data has the same shape. A Pokemon in the party is persistent structured data. But the player never sees “structured data” directly. They see a summary screen, a battle sprite, HP bars, moves, level-up text, evolution screens, storage boxes. Each view is a callback-driven or task-assisted interpretation of the same underlying creature model. The summary screen source focus I mentioned earlier, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, is useful because its dependencies show how a supposedly simple inspection UI touches Pokemon data, menu helpers, link, overworld, items, moves, region maps, party menus, and battle constants. That is not accidental coupling for its own sake. It reflects the fact that creature data is central, and many screens are projections of it.

Now, quick comprehension check. If I say, “The player selected a move to forget in the move relearner,” what layers are involved? Think it through. There is UI input. There is a list menu state. There is creature move data. There are move constants. There may be script context, because an NPC or service opened this screen. There is a callback or state machine controlling the screen. There may be tasks for fades or scrolling. And after the decision, control must return to the overworld or script continuation. If your answer only says “change the Pokemon’s move,” you are describing the domain mutation but missing the runtime architecture around it.

This is why, in this course, we will repeatedly move from the specific to the spine and back. We will not just say, “Here is battle.” We will ask how battle is entered. We will not just say, “Here is the map format.” We will ask what callback interprets it. We will not just say, “Here is a script command.” We will ask when the script VM advances and what else is allowed to run in the same frame. We will not just say, “Here is save data.” We will ask what runtime state is stable enough to serialize.

Let’s return to AgbMain one more time before we shift into a closer read of frame dispatch. The crucial thing AgbMain gives the rest of the codebase is confidence. It establishes the initial world in which callbacks mean something. It prepares the program to enter a loop where frame-based behavior is normal. After that, SetMainCallback2 becomes the mechanism for broad behavioral transitions, CreateTask becomes the mechanism for allocating lightweight state machines, and RunTasks becomes the per-frame heartbeat for those state machines.

So if I were to compress today’s architecture into one sentence at this point, it would be this: FireRed is a data-driven creature RPG whose major systems become player-visible through a callback-owned frame loop and a cooperative task scheduler. That sentence is dense, so let’s unpack it one last time. Data-driven means maps, creatures, moves, items, scripts, and UI tables often define what can happen. Creature RPG means the central progression loop is about collecting, training, battling, storing, and using creatures across maps and events. Callback-owned frame loop means the program’s current broad mode determines what logic runs each frame. Cooperative task scheduler means smaller multi-frame behaviors voluntarily advance and return, rather than preempting each other.

Now, as we move into the next segment, I want you to keep a tracing discipline. Whenever you see a visible behavior, ask where the data came from. Whenever you see a table, ask who interprets it. Whenever you see a callback, ask who installed it. Whenever you see a task ID, ask who owns its lifetime. And whenever you see a system boundary, such as overworld into battle or script into menu, ask what state must be coherent before the handoff. That habit is going to make the source feel much less like a pile of C files and much more like a deliberately constrained runtime architecture.

So let’s now apply that tracing discipline to a boundary that every player understands: standing in the overworld, triggering a battle, and ending up on a battle screen. I am not asking you yet to understand the entire battle engine. That is a later module. What I want you to see today is the architectural shape of the handoff.

On the board, I’m going to write four words in a row: overworld, event, transition, battle. Those are not necessarily four files. They are four runtime responsibilities. The overworld owns field movement and map presentation. The event layer decides that something meaningful has happened, maybe a trainer spotted you, maybe a wild encounter fired, maybe a script command requested a battle. The transition layer manages the visual and stateful crossing from one screen mode to another. Then the battle runtime takes over as the broad per-frame owner.

Now connect that back to our callback spine. The machine is not running “all of FireRed” every frame in some vague sense. It is running the current main callback, and that callback may run tasks, update sprites, animate backgrounds, accept input, and eventually install another callback. That is why SetMainCallback2, in pokefirered/src/main.c chunk_6cfd90f6d0136799, matters so much. It is not just a helper. It is one of the places where the program says, “The meaning of the next frame has changed.”

And this is also why AgbMain, in pokefirered/src/main.c chunk_b35676322b169f85, is more than startup ceremony. Startup establishes the loop in which these broad modes can take turns owning the machine. If you have ever built a game loop in a modern engine, you may be tempted to imagine there is one eternal update function with a large switch statement over game states. FireRed’s organization is more distributed than that. The callback pointer is the switch. The current callback is the active state. The tasks beneath it are the smaller cooperative state machines.

Let’s make that concrete. Imagine the player presses A in front of an NPC. The visible behavior is simple: text appears, the NPC speaks, maybe the conversation offers a choice, maybe an item is given, maybe a battle starts. If we trace backward, the text did not originate in the input handler. The input handler only recognized an interaction. The behavior likely comes through event data and script interpretation. And the script system does not need to know how the entire hardware frame loop works. It only needs a way to suspend, resume, request UI, and eventually yield control back to the overworld or move into another mode.

The research packet is useful here because it shows these systems touching at the source level. For example, pokefirered/src/battle_setup.c chunk_494d132a59422c85 includes headers for overworld, script, event data, field control, player avatar, field message boxes, battle, and battle transition. That include cluster is not proof of every control-flow detail by itself, but it is strong architectural evidence about the boundary this file sits on. It is not pure battle math. It is not pure map rendering. It is the staging area where field state, script state, transition effects, and battle entry agree on what happens next.

Pause there and check your understanding. If I ask, “Why wouldn’t battle setup belong entirely inside the battle engine?” the answer is that battle setup must know about the world the battle came from. It may need to know whether this was a wild battle, trainer battle, safari encounter, scripted battle, link battle, or some other variation. It has to preserve or transform overworld state before the battle callback owns the frame. Once the battle runtime is in charge, it should not have to rediscover all of that from scratch.

Now, on the board, I’ll add a second row underneath: data, globals, callback, tasks, visible behavior. That row is our tracing template. In FireRed, many features begin with static data or save-backed data, move through globals or runtime structs, enter a callback-owned frame loop, and express themselves through tasks and draw/update routines. If you only read the final display function, you will miss the source of truth. If you only read the table, you will miss the interpreter.

Take a menu example. The Pokémon summary screen is a good one to keep in mind because it is neither as broad as the overworld nor as mathematically dense as battle. The packet points us to pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46, and the included dependencies tell a story: pokemon data, summary screen declarations, task support, menu helpers, link, overworld, item constants, battle main, moves, region map sections, party menu, and battle constants. That screen is not “just UI.” It is a UI surface over creature data, party context, move data, item data, possible link constraints, and return paths back to the mode that opened it.

So, suppose you open the summary screen from the party menu. The screen needs to display species, level, stats, moves, held item, maybe ribbon or location-related information depending on the page. The visible text and icons come from data tables and Pokémon structs. But the act of paging left and right, animating a cursor, waiting for input, and returning to the prior screen is exactly the kind of thing FireRed likes to express through callback plus tasks. The broad callback says, “We are in the summary screen now.” The task or tasks say, “Within this screen, the current interaction state is page navigation, move selection, exit fade, or return.”

That distinction becomes important when you debug. If the wrong stat appears, your first suspicion should not be the main callback. You should trace data extraction and formatting. If the screen never exits, your suspicion moves toward state progression: a task that did not advance, a callback that was not restored, or a fade transition that never reached its completion condition. If the entire game appears to freeze but audio or VBlank behavior continues, you ask whether the active callback is still running but waiting on a state flag. If nothing frame-level is being dispatched, you ask whether the callback spine itself has been disrupted.

Let me tie this to the task scheduler directly. CreateTask, in pokefirered/src/task.c chunk_cb0bb91a147bfb59, gives you allocation into a fixed task array and returns a task ID. RunTasks, in pokefirered/src/task.c chunk_3ffebc0341225936, is the per-frame dispatch mechanism. That means a task ID is not just a convenient handle. It is a lifetime contract. Whoever creates a task must have a theory of when it stops being needed. Whoever stores a task ID must know whether that ID remains valid. And because tasks are cooperative, a long-running task must be written to do a little work and return, not to monopolize the frame.

Here is the advanced point: this architecture turns temporal behavior into data stored across frames. A local variable inside a normal blocking function disappears when the function returns. A task’s data fields survive until the next frame. So if a fade takes thirty frames, or a menu waits indefinitely for input, or an NPC movement script progresses tile by tile, the task data is where “where we are in the behavior” can live. The task function is not merely code. It is code plus a small persistent record, called once per frame, until some termination condition or function swap changes what happens next.

You can now see why tomorrow’s lecture, “Tasks as FireRed’s Lightweight State Machines,” is not a side topic. It is the other half of today. Today’s callback spine explains who owns the broad frame. Tomorrow’s task scheduler explains how many small behaviors coexist inside that ownership. And the lab asks you to implement a tiny C-style scheduler precisely so that this becomes muscle memory. Once you write your own main callback slot, task array, task IDs, task data, and per-frame dispatch, FireRed’s source will feel much less mysterious.

Let’s do a small mental exercise. Imagine a fade-to-black before opening a menu. Student in the third row, what owns the overall frame during the fade? The answer depends on where the fade is occurring. If we are still in the overworld, the overworld callback may still be broad owner while a field transition task advances the fade. If we have already switched into a menu initialization callback, then the menu setup callback may be the broad owner. The important thing is not memorizing one universal answer. The important thing is asking, “Which callback is installed right now, and which tasks are active beneath it?”

Now imagine the fade completes and the menu appears. What should happen to the fade task? It should either destroy itself, become inactive, or hand off to a new task state depending on the local pattern. What should not happen is an orphan fade task continuing to mutate blend registers forever after the menu has taken over. That is a lifetime bug. In a callback-driven architecture, lifetime bugs often look like two systems believing they own the same visual resource, the same input interpretation, or the same return path.

This is why I keep using the word “ownership.” C will not enforce it for us. There is no borrow checker here, no scene graph ownership model, no modern engine inspector telling you which subsystem owns input this frame. The ownership is encoded by convention: callback slots, task IDs, global flags, save blocks, and carefully sequenced handoffs. The source is readable when you respect those conventions. It is chaotic when you pretend everything can call everything at any time.

Let’s bring in another source example, the move relearner. The packet highlights pokefirered/src/learn_move.c chunk_af3598df6f092723, and the excerpt even labels a “Move relearner state machine.” That is a gift for us pedagogically. The move relearner is not simply “print a list of moves and let the player choose.” It crosses script, Pokémon data, move tables, menus, list menus, fades, summary screen access, graphics, strings, and sound. The comments name states such as fading to black and menu behavior. This is exactly the callback-and-task philosophy in a more localized feature. A user-facing service, “teach an old move,” decomposes into staged UI states, each advancing over frames.

Notice the course-wide pattern. Creature data alone does not make the relearner. Menu code alone does not make the relearner. Script code alone does not make the relearner. The feature exists because a script reaches a service, that service reads creature and move data, the UI presents valid choices, and the callback/task machinery keeps the interaction alive across frames. This is the architecture of a creature RPG: not one giant mechanic, but many thin boundaries that preserve enough state to let a player experience a continuous world.

Now, check for understanding again. If a player reports, “The move relearner opens, but pressing up and down does nothing,” where would you start? Not in AgbMain. AgbMain is too far down the spine for that symptom. You would ask which callback currently owns the move relearner screen, which task reads directional input, whether the list menu helper has been initialized, and whether the task’s state data points to the correct list. If the move list is empty, you go toward creature data and learnset eligibility. If the cursor exists but does not move, you go toward input dispatch and list-menu state. If the screen never appears, you go earlier, toward script entry, callback installation, or fade transition.

That diagnostic separation is one of the main reasons we study architecture before features. Without it, every bug is “somewhere in the game.” With it, the bug has a neighborhood.

Let’s zoom back out to AgbMain for a moment. In a desktop application, startup might allocate services, create windows, initialize logging, and then enter a message pump. On the Game Boy Advance, the hardware context is more direct and more constrained. AgbMain’s job is to put the program into a known starting condition and then sustain the main loop that makes callbacks meaningful. You should think of it as establishing the runtime shell, not as implementing gameplay. Gameplay emerges only after the shell repeatedly gives control to the installed callback and the callback drives its active tasks and subsystems.

That phrasing matters: “repeatedly gives control.” Nothing magical happens between frames unless some installed function is called again and observes updated state. Input is sampled. Timers advance. Animation counters change. Tasks run. A callback may install a new callback. State persists in globals, save blocks, task data, sprite data, and subsystem-owned structures. This is why reading FireRed requires temporal imagination. You are rarely looking at a single function that completes a whole player action from beginning to end. You are looking at one slice of a multi-frame behavior.

This also explains why data-driven design in this course is not limited to obvious tables like species stats or moves. Data-driven architecture includes callback tables, task arrays, script command tables, map object data, item effect tables, and battle command sequences. Some tables hold game content. Some tables hold behavior entry points. Some tables bridge content to behavior. Our course thesis, from the research synthesis, is to teach FireRed as cooperating state machines plus data tables, always tracing data table to runtime globals to callback, task, or script, and then to player-visible behavior. Today is the first real spine of that thesis.

I’ll give you one more concrete trace, this time through inventory, because inventory sits in the middle of the course map. Suppose the player uses an item from the bag. The item itself is data: an ID, pocket, name, description, effect rules, and context restrictions. The bag UI is a screen mode that can scroll, select, open submenus, and exit. The effect might alter party data, trigger a field move-like behavior, heal a Pokémon, start an evolution-adjacent check, or fail with a message. The bag screen cannot be a dumb list, because selecting an item can cross into Pokémon selection, script messaging, sound effects, field context, and save-relevant mutation. But it also should not own the whole game permanently. It borrows the frame through a callback, performs its interaction, and returns.

We have a cited clue for this style in pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46, where UI includes item and party-related dependencies, and in pokefirered/src/daycare.c chunk_119931105f546346, where daycare logic touches data, battle-related constants, items, Pokémon storage, event data, menus, scripts, strings, party menu, list menu, overworld, Pokédex, graphics, naming screen, help system, field fade transition, and trade. Again, the point is not that includes are control flow. The point is architectural adjacency. These gameplay services are built as intersections of data, UI, script, and runtime state.

For an advanced learner, the temptation is to jump immediately to “how would I refactor this?” Hold that thought, but do not start there. Before you refactor a decompiled commercial game architecture in your head, understand the constraints it solved. Fixed memory. Hardware-timed rendering. No threads in the way you are used to. A need for deterministic frame behavior. A large number of screens with similar but not identical lifetimes. A content-heavy RPG where many systems must pause and resume each other without a heap-heavy object model. Under those constraints, callback slots and cooperative tasks are a very practical architecture.

Let’s mark the course map now. Weeks one and two are the runtime shell and source contracts. That means we are learning how to read the program as a running machine. After that, overworld maps will make sense because you will know who owns the frame when walking around. Event scripting will make sense because you will know how scripts wait, resume, and request other systems. Creature data will make sense because you will know how tables become runtime party structures and UI output. Battle will make sense because you will recognize a more specialized state machine rather than a mysterious monolith. Inventory, UI, save, and link systems will all reuse this same discipline.

One more check before we move on. If I show you SetMainCallback2 and ask, “Is this a high-level gameplay function or a low-level runtime function?” the best answer is: it is a low-level runtime mechanism with high-level consequences. It does not know the story meaning of entering battle, opening the Pokédex, or returning to the overworld. But without it, those story-visible transitions do not become frame ownership. That is the kind of distinction I want you to practice all semester.

So, to synthesize this segment, AgbMain gives us the shell, SetMainCallback2 gives us broad mode replacement, CreateTask gives subsystems a way to allocate small persistent behaviors, and RunTasks gives those behaviors a chance to advance each frame. Source files like battle_setup.c, pokemon_summary_screen.c, learn_move.c, and daycare.c show us that actual creature-RPG features live at the intersections: data plus script, UI plus party state, field context plus transition code, menu state plus persistent save effects. The callback spine is not the whole game, but it is the reason the whole game can be decomposed into readable, swappable, frame-by-frame modes.

Now, in the next stretch, I want to tighten the lens further. We are going to look at what it means for a task to be “lightweight,” why a fixed task array is enough for so many features, and how task data turns a function that returns every frame into something that still remembers a long-running interaction.

So, let’s pick up exactly there. When I call FireRed’s task system “lightweight,” I do not mean “unimportant.” I mean something more precise: the task is small enough that the engine can afford to scan it every frame, predictable enough that allocation is just choosing a slot, and persistent enough that it can remember a workflow without becoming the whole program.

Look at the board for a second. I’m going to write three words: function, slot, memory. That is the task model in miniature. A task has a function to call. It occupies a slot in a fixed task array. And it has a small amount of memory, usually task-local data, that lets it say, “Last frame I was waiting for fade-out; this frame the fade is done, so now I can load the next state.” That is the difference between a callback and a task. A main callback says, “This is the current major mode of the program.” A task says, “Within this mode, here is one small behavior that needs to keep advancing.”

And this distinction is not cosmetic. In `pokefirered/src/main.c`, `chunk_6cfd90f6d0136799`, `SetMainCallback2` gives the game a way to replace the current second-stage main callback. That is a large-scale steering operation. In `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59`, `CreateTask` is doing something smaller and more numerous: allocating one behavior into the task system. Then `RunTasks`, in `pokefirered/src/task.c`, `chunk_3ffebc0341225936`, provides the per-frame dispatch pass over those active behaviors. So the architecture gives us two levels of motion. Mode motion through callback replacement, and local procedural motion through tasks.

Now, why not just make everything a callback? That is a good advanced question, because in a tiny program you might. You might say, “Well, I have a title screen callback, a menu callback, a battle callback, a naming callback. Done.” But a creature RPG is not tiny. It has fades, text boxes, scripted NPC interactions, field effects, encounter checks, menu cursors, sprite animations, party updates, item effects, link communication, save prompts, and battle transitions. If every one of those were a top-level mode, the global callback would thrash constantly, and you would lose the ability to compose behaviors.

Here is a concrete example. Imagine the player steps on a grass tile, the game decides a wild encounter should begin, and the screen starts a transition into battle. That visible moment is not just “call battle.” It involves overworld context, player state, encounter data, transition graphics, audio, and eventual replacement of the active mode with battle setup or battle execution. The research packet points us to `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, and even the include list is revealing. That file touches `overworld.h`, `script.h`, `event_data.h`, `fieldmap.h`, `field_player_avatar.h`, `battle.h`, and `battle_transition.h`. That is exactly the intersection we care about. A battle is not isolated from the field. It is entered through the runtime shell.

Pause there and check yourself. If I ask, “Where does the battle transition live architecturally?” the best answer is not “in battle code” and not “in overworld code.” The better answer is: it lives at a boundary. The field discovers or initiates the condition, setup code stages the transition, the callback spine eventually gives control to the battle-facing mode, and tasks can carry the multi-frame transition through time. That is the architectural move I want you to see.

Now let’s turn “task data” into something concrete. Suppose we write a small C-style task for a menu that fades in, waits for input, plays a sound, fades out, and then returns to the overworld. Since `RunTasks` calls the task function again and again, the function itself cannot rely on the C call stack to remember where it was. It returns every frame. So where does it store its position? In task-local data. Maybe `data[0]` means the current state. Maybe `data[1]` means selected row. Maybe `data[2]` is a timer. This is why many GBA-era systems look like compact state machines: not because the programmers loved ceremony, but because the hardware and runtime model reward resumable, frame-by-frame procedures.

Let me say that another way, because it is central. A normal blocking function might say, “Fade out, wait until done, then load the new screen.” But a game loop cannot usually block like that. The hardware still needs VBlank handling, input polling, audio, DMA timing, and rendering updates. So instead of blocking, the task says, “I am in fade-out state. If fade-out is not done, return. If it is done, change my state variable and return.” On the next frame, it resumes logically, not through the call stack, but through the task data.

You can see this style named very explicitly in `pokefirered/src/learn_move.c`, `chunk_af3598df6f092723`. The excerpt identifies a “Move relearner state machine,” with `CB2_MoveRelearner_Init` and a `MoveRelearnerStateMachine` progressing through menu states such as fading to black. That file also includes `task.h`, `script.h`, `field_fadetransition.h`, `overworld.h`, menu helpers, list menu code, Pokémon summary screen code, graphics, strings, and move constants. That is a wonderful source example for this week because it shows a feature that sounds like game design, “remember an old move,” becoming architecture: script entry, menu UI, party data, move data, fade transitions, callbacks, and tasks.

Now, quick check for understanding. If the move relearner opens a list of possible moves, and the player scrolls down, are we probably replacing the entire main callback every time the cursor moves? No. That would be the wrong granularity. Cursor movement is local behavior. It belongs in a task, or in a helper called by a task, or in some menu subsystem that is itself advanced per frame. The callback establishes that we are in the move relearner screen or related mode. The task-level machinery handles the interactive details.

This is also why a fixed task array can be enough. At first, fixed arrays sound restrictive. We are used to modern heaps, dynamic containers, promises, coroutines, and event buses. But on the GBA, fixed capacity is not just acceptable; it is a design tool. It gives bounded memory use. It makes iteration predictable. It avoids allocation fragmentation. It makes task IDs small. And for a frame-based game, you often do not need thousands of independent behaviors. You need a controlled set of active behaviors that cooperate.

On the board, I’m going to draw a row of boxes: zero, one, two, three, up through however many task slots the engine provides. Each active box has a function pointer, a priority or ordering concept, a small data array, and a flag saying whether the slot is active. Now, `CreateTask`, again from `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59`, is the allocation moment. It finds room and returns an ID. That ID is important because the task may need to update its own data, destroy itself later, or be referenced by another system. Then `RunTasks`, `chunk_3ffebc0341225936`, is the dispatch moment. Every frame, active tasks get their opportunity to run.

Notice the philosophical consequence. The game is not “calling a feature” once. It is continuously asking all active small machines, “Do you have work this frame?” That is why the task model maps so well onto UI and transitions. A text printer may have one more character to reveal. A fade may have one more blend step. A menu may have one more input edge to consume. A link operation may have one more communication state to poll. A field effect may have one more animation frame. Each one can advance a little, return, and allow the rest of the frame to proceed.

Let’s connect this to the broader course, because week 1 is not an isolated systems-programming curiosity. The whole course thesis, from the research synthesis, is that FireRed is a stack of cooperating state machines plus data tables. The runtime shell is just the first layer where that becomes obvious. In later weeks, when we study overworld maps, we will trace map data from `pokefirered/data`, such as layouts and map JSON, into runtime map behavior. When we study event scripting, we will see script commands behaving like another kind of resumable program. When we study creature data, we will see species, moves, party state, and derived stats feeding UI and battle. When we study inventory, we will see item tables and bag screens. When we study save, we will see persistence boundaries. When we study link systems, we will see synchronization constraints that make frame-by-frame state even more important.

So if you are thinking, “This callback and task material feels low-level,” yes, it is low-level. But it is not merely low-level. It is the shape that the rest of the game must fit through.

Consider the Pokémon summary screen. The packet points us to `pokefirered/src/pokemon_summary_screen.c`, `chunk_e8e17a42bbf17d46`. The include list brings together `pokemon.h`, `pokemon_summary_screen.h`, `task.h`, menu helpers, link, overworld, item constants, party menu, battle main, moves, region map sections, and more. That tells us the summary screen is not just a display template. It is a mode of interaction sitting on top of creature data, party context, move data, item context, possible link restrictions, and UI machinery. The task system is what lets that screen respond over many frames without blocking the engine. The callback system is what lets the game enter and leave that screen as a major mode.

Let me give you a slightly more detailed hypothetical trace. The player opens the party menu from the overworld and chooses a Pokémon. The game enters a summary screen. Somewhere, a main callback has been installed for that screen’s active mode. Initialization sets up backgrounds, windows, sprites, and text. One or more tasks are created to process input, update page selection, animate sprites, or manage transitions. Each frame, the main loop invokes the current callback. That callback does screen-level work and calls into task dispatch. `RunTasks` gives each active task a slice. The cursor task sees the player pressed right. It updates its state. The display task, or the same task in another state, refreshes visible move or stat information. When the player exits, the state machine begins a fade or cleanup phase. Only when cleanup is ready does the engine hand control back to the previous callback or next callback.

Now, I’m using “hypothetical” for the exact task breakdown because we are not walking line-by-line through that file today. But the architectural claim is grounded in the source pattern: `pokemon_summary_screen.c`, `chunk_e8e17a42bbf17d46`, includes task and menu systems alongside creature, move, item, link, and overworld dependencies. The feature is a composition point.

And here is the advanced lesson: composition points are where architecture becomes visible. If you only inspect isolated data tables, you learn content structure. If you only inspect the main loop, you learn dispatch structure. But if you trace a feature from data table to callback to task to visible behavior, you learn how the game is actually built.

Let’s do another check. Suppose I show you an item-use feature later in the course. Maybe the player chooses an item from the bag and uses it on a party member. What questions should you ask first? You should ask: what data table defines the item? What runtime state says the player owns it? What menu or UI mode allows selection? What callback owns that screen? What task handles the multi-frame interaction? What script or effect function applies the outcome? What save data changes afterward? That sequence is our recurring method.

This method also protects you from a common reverse-engineering mistake, which is to overvalue file names. File names matter, but a creature RPG feature rarely lives in one file. `pokefirered/src/daycare.c`, `chunk_119931105f546346`, is a good example. The include list reaches into data, battle, items, mail data, Pokémon storage, event data, random, moves, menu, script, party menu, list menu, overworld, Pokédex, decompression, songs, text windows, graphics, naming screen, help system, field fade transition, and trade. That is not accidental messiness. Daycare is inherently cross-cutting. It touches party and storage rules, progression flags, random or deterministic generation, UI, script entry points, naming, and field transitions. So when you see `task.h` and menu systems appear in these feature files, you should recognize the pattern: long-running player interactions are decomposed into small frame-advanced states.

Now, let me add a nuance. Lightweight tasks are not the same as threads. This is important because if you come from modern engine programming, you may be tempted to say, “Oh, tasks, so concurrency.” Not exactly. These are cooperative per-frame state machines. They do not run simultaneously on separate cores. They run because the main loop dispatches them. If one task takes too long, the frame suffers. If one task waits in a blocking loop, the game stalls. So the discipline is cooperative brevity: inspect state, do a small piece of work, update task data, return.

That discipline affects how you read source. When you encounter a task function, do not read it like a normal top-to-bottom procedure. Read it like a state transition table written in C. Ask what values in task data mean. Ask which state increments to which next state. Ask when the task self-destructs. Ask which callback installed it. Ask which visible screen or effect it owns. Ask which globals or save fields it touches. That is the reading strategy for this architecture.

Let’s put a tiny example on the board. I’ll write:

state zero: initialize windows.
state one: wait for fade-in.
state two: process input.
state three: wait for confirmation text.
state four: fade out.
state five: destroy task and set next callback.

That is not FireRed code verbatim. It is the shape. And once you see that shape, files like `learn_move.c`, with its explicitly described move relearner state machine in `chunk_af3598df6f092723`, become much less mysterious. The code may be full of constants, helper functions, and hardware-specific setup, but the controlling idea is straightforward: spread one user-facing interaction across many frames.

Now, I want to connect this back to `AgbMain`, because the danger in this part of the lecture is that tasks become so interesting that we forget the spine they hang from. `AgbMain`, in `pokefirered/src/main.c`, `chunk_b35676322b169f85`, is part of the startup and runtime shell story. It is where the program’s life begins in the GBA environment. It establishes the conditions under which callbacks and tasks can do their work. Then `SetMainCallback2`, in `chunk_6cfd90f6d0136799`, gives the program a controlled way to replace the active major mode. The task system does not replace that. It depends on it. A task is only meaningful because some current callback, directly or indirectly, allows task dispatch to happen as part of the frame.

So, if I ask you to draw the call-flow map in the workshop, I do not want a vague blob labeled “game loop.” I want you to draw the responsibility boundaries. Startup enters the runtime shell. The main callback slot points at the current major behavior. That callback performs per-frame mode work and participates in dispatch. `RunTasks` advances active task slots. A task may update UI, watch input, wait for a transition, or eventually call for a callback change. And visible behavior emerges from that chain.

Let’s make that concrete with the lab you are going to do. You will implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch. The point is not to clone every detail of FireRed. The point is to force your intuition into the same shape. You will have a global main callback pointer. You will have something like `SetMainCallback2`, because even in a toy scheduler, replacing the current mode should be explicit. You will have a fixed array of task records. `CreateTask` will search for an inactive slot, initialize it, and return an ID. `RunTasks` will iterate through active slots and call their functions. Each task will receive or recover its task ID so it can access task-local data.

Then you will build one tiny visible scenario. For example, boot into a title callback. On input, switch to a menu callback. The menu callback creates a task that moves through states: draw menu, wait for input, show confirmation, fade out, return. Again, the goal is not polish. The goal is to feel the architecture. Once you have written it, FireRed’s choices will look less arbitrary.

Check for understanding again. Why do we give tasks task-local data instead of just local variables inside the task function? Because local variables disappear when the function returns, and a task function returns every frame. The task needs memory outside the call stack. Why use a fixed array instead of allocating arbitrary task objects? Because fixed arrays give predictable memory and iteration behavior, which fits the GBA constraints and the frame loop. Why separate main callbacks from tasks? Because major mode ownership and local interactive behaviors are different levels of control.

Now, there is a subtle design tradeoff here. A lightweight task system can become hard to read if state numbers are undocumented, if too many unrelated meanings are packed into small data fields, or if tasks silently depend on global variables set by distant initialization code. That is why we will keep returning to source tracing. When you see a task, you should not merely identify that it exists. You should trace its birth, its state data, its per-frame advancement, its destruction, and its visible effect. In a decompiled or reconstructed codebase, names may help, but the lifecycle is the proof.

This also sets up our next lecture, “Tasks as FireRed’s Lightweight State Machines.” Today we are seeing tasks as part of the callback spine. Next, we will zoom into task allocation, task IDs, priority, task-local data, and the patterns by which tasks represent long-running UI and field behaviors. We will ask when a feature deserves a task, when it deserves a callback, and when it is just a helper called inside an existing task.

Before we move on, let me give a short synthesis of where we are in the course map. A full creature-collector RPG decomposes into runtime shell, overworld maps, event scripting, creature and party data, battle, inventory, UI, save, and link systems. Week 1 starts with the runtime shell because every one of those systems must eventually become frame-by-frame behavior. `AgbMain` gives us startup responsibility. `SetMainCallback2` gives us mode replacement. `CreateTask` gives us lightweight behavior allocation. `RunTasks` gives us per-frame task dispatch. And the feature files in the packet, including `battle_setup.c`, `pokemon_summary_screen.c`, `learn_move.c`, and `daycare.c`, show that real gameplay emerges where these mechanisms meet data and player-facing rules.

So keep this model in your head as we turn the lens one notch tighter: callbacks tell us what kind of screen or mode owns the frame; tasks tell us which small remembered behaviors are alive inside that frame. The rest of the architecture, from maps to battles to saves, becomes much easier to reason about once that distinction is stable.

So, let’s put one more layer on the board. I’m going to draw a vertical stack, not a call stack exactly, but an ownership stack. At the top, I’m writing “hardware frame.” Under that, “main callback.” Under that, “tasks.” Under that, “feature data and globals.” And then at the bottom, “visible behavior.” This is the mental model I want you to carry into the lab. The screen does not become the Pokémon summary screen because there is a magical summary-screen universe somewhere. It becomes the summary screen because the main callback slot is changed to code that knows how to initialize and maintain that mode, and then a set of smaller task or state-machine behaviors take over the details.

Now, advanced students sometimes make a different mistake here. Beginners think everything is one loop. Advanced students sometimes think everything is a clean object hierarchy. FireRed is neither. It is closer to a disciplined C runtime shell with global state, callback replacement, table-driven data, and small procedural state machines that cooperate once per frame. That sounds less elegant than an object model, but it is very appropriate for the Game Boy Advance. It lets the program control memory, timing, and ownership with very little abstraction overhead.

So, if I ask, “Where does gameplay live?” the best answer is not “in battle.c” or “in overworld.c” or “in the UI files.” Gameplay lives in the transitions among these pieces. The research packet gives us good examples of this. In `pokefirered/src/battle_setup.c`, chunk `chunk_494d132a59422c85`, the include surface alone tells us that battle setup is not isolated. It pulls in overworld, item, sound, Pokémon data, load-save, scripts, event data, field effects, avatar control, messages, trainer systems, and battle transition code. That source boundary is a clue. Starting a battle is not merely calling a damage calculator. It is a mode transition from field control into battle ownership, with script and map context still mattering.

Let’s make that concrete. Imagine the player steps into tall grass. The visible behavior is simple: the player moves, the screen flashes, a wild encounter begins. Architecturally, many systems have to agree. The overworld map system has to know the player’s tile and movement state. Event or field control code has to decide whether an encounter check should happen. Creature data has to provide a legal species, level, and possibly encounter table result. Battle setup has to prepare the battle context. The display has to transition away from the overworld. At some point, a main callback must stop treating the frame as overworld exploration and start treating it as battle transition, and then as battle. That is the callback spine doing real work.

Pause there and check your understanding. If a wild encounter begins, do we say the task scheduler alone “caused” the battle? No. The scheduler may dispatch small behaviors that participate in the transition, but the battle begins because data, global state, callback ownership, and feature code converge. If a student says, “The callback is the battle,” I would also push back. The callback is the frame owner. The battle system is a larger collection of state, tables, commands, animations, UI, and rules that the callback gives time to.

Now, on the board, I’m going to write the phrase “data table to callback to visible behavior.” We will use this phrase all semester. It is not just a slogan. It is an investigative method. When you see something on screen, you ask: what data table made this possible, what state selected it, what callback or task consumed it, and what visible artifact did the player experience?

Take the Pokémon summary screen. The packet points us to `pokefirered/src/pokemon_summary_screen.c`, chunk `chunk_e8e17a42bbf17d46`. Its include list connects Pokémon data, task scheduling, menu helpers, link code, overworld, item constants, party menu code, battle main, moves, region map sections, and more. That tells us the summary screen is not “just UI.” It is a UI mode over creature data, with enough awareness of party state, items, moves, and sometimes link or battle context to behave correctly. When the player presses a button and moves from one summary page to another, the program is not recreating the entire game. It is allowing a screen-owned callback and its supporting tasks to interpret input, update state, and redraw the appropriate pieces.

Here is the example I want you to hold. Suppose Bulbasaur is in slot one, and the player opens the party menu, chooses summary, and flips to the moves page. The species data and individual Pokémon struct answer questions like current level, current moves, stats, markings, held item, and so on. The summary screen code decides which panels and strings to display. The input handler, likely through a task or screen-local state machine, remembers whether the player is on the info page, skills page, or moves page. The visible behavior is just a page flip. But the architecture underneath is a collaboration between persistent creature data and temporary UI state.

And notice the design value here. The creature data does not need to know how to draw the summary screen. The summary screen does not need to know how the whole overworld works. The main callback does not need to know the details of move descriptions. Each part has a narrower job, but the runtime shell lets one part own the frame long enough for the player to experience a coherent mode.

Now let’s compare that with a smaller, very instructive state machine: the move relearner. The packet points us to `pokefirered/src/learn_move.c`, chunk `chunk_af3598df6f092723`, and the excerpt explicitly names a “Move relearner state machine.” That is a gift for our purposes. The comment describes a callback initialization path and then menu states such as fading to black, creating scroll listeners, and handling directional input. This is exactly the pattern we have been building toward. A feature begins with a callback-level entry point, allocates or configures smaller stateful pieces, and then advances through states over multiple frames.

Why multiple frames? Because on the GBA, and honestly in most game engines, many operations are temporal. Fades take time. Menus animate. Input happens one frame at a time. DMA, decompression, tilemap updates, palette changes, and sound calls have timing constraints. So instead of writing one huge blocking function that says, “fade out, load menu, wait for input, choose move, return,” FireRed spreads that behavior across callbacks and tasks. One frame begins the fade. Later frames observe whether it is done. Another state creates the list. Another state reacts to input. The result feels continuous to the player, but the code is advancing a state machine.

Let me ask a quick check question. If the move relearner needs to wait for a fade to complete, why not use a while loop that spins until the fade is done? Right, because a spin loop would starve the rest of the frame. You would stop normal dispatch, risk missing VBlank-sensitive work, and collapse the cooperative structure of the engine. The correct shape is to store “I am waiting for fade completion” as state, return to the main loop, and let the next frame continue. That is the callback-and-task style in one sentence: remember just enough state to continue later.

Now, the daycare gives us another useful example because it sits between creature data, progression rules, menus, scripts, and storage-like behavior. The packet points to `pokefirered/src/daycare.c`, chunk `chunk_119931105f546346`, where the include surface connects Pokémon data, battle constants, items, mail data, storage, event data, random, moves, menus, scripts, Pokédex, decompression, naming screen, help system, transitions, and trade. Again, not isolated. Daycare gameplay seems gentle from the outside. You leave a Pokémon, walk around, come back, maybe receive an egg or level gains. But architecturally, it touches persistent save data, map/event progression, creature rules, UI flows, and scripts.

So, when we later study save systems, remember this point. Save data is not a separate “save chapter” sealed away from the game. Save data is constantly being shaped by gameplay systems. Daycare state must persist. Party state must persist. Inventory changes must persist. Event flags must persist. The save system is downstream of many feature decisions, and upstream of the next boot. That is why week one begins with `AgbMain`. Boot is not merely technical ceremony. Boot is how persisted world state becomes running world state again.

Let’s bring `AgbMain` back in, but only briefly, because we have already established its role. In `pokefirered/src/main.c`, chunk `chunk_b35676322b169f85`, `AgbMain` is our startup anchor. It is responsible for getting the program into a valid runtime condition before ordinary gameplay mode ownership can proceed. Then `SetMainCallback2`, in `pokefirered/src/main.c`, chunk `chunk_6cfd90f6d0136799`, gives the program the mechanism for replacing the second main callback. That replacement is one of the most important architectural moves in this codebase. It means the engine can say, “The next frame belongs to this mode now.”

Underneath that, `CreateTask` in `pokefirered/src/task.c`, chunk `chunk_cb0bb91a147bfb59`, gives us allocation of lightweight task entries. And `RunTasks` in `pokefirered/src/task.c`, chunk `chunk_3ffebc0341225936`, gives us the per-frame dispatch mechanism. Put those four source anchors together and you get the week’s spine: start the machine, choose the frame owner, allocate small behaviors, run them every frame.

Now I want to widen the course map, because this is where week one connects to everything else. When we study overworld maps, we will not treat maps as static backgrounds. We will ask how map headers, layouts, metatiles, object events, warps, scripts, and player movement become per-frame exploration. When we study event scripting, we will ask how a script command changes flags, opens a message box, gives an item, starts a battle, or moves an NPC without taking over the entire engine irresponsibly. When we study creature data, we will ask how species tables, moves, experience, stats, and party structs become both long-term identity and immediate screen behavior. When we study battle, we will ask how turn commands, battle scripts, move effects, animation states, and UI tasks cooperate within battle’s frame ownership. When we study inventory and UI, we will ask how list menus and item effects bridge data tables and callbacks. When we study save, we will ask how runtime globals are serialized and restored. When we study link systems, we will ask how two or more machines maintain synchronized behavior under stricter timing and communication constraints.

That is the whole course in miniature. Runtime shell first, because every subsystem eventually has to answer the same question: who owns the frame, what small state machines are alive, and what data are they interpreting?

Let me give you a more advanced debugging scenario, because this is the kind of reasoning I expect you to practice in the workshop. Suppose a menu opens correctly, but after you exit it, the overworld no longer accepts player movement. A shallow diagnosis might say, “Input is broken.” But with today’s model, you should ask sharper questions. Did the mode restore the correct main callback? Did a task that blocks movement remain active? Did a script context fail to release control? Did a fade or message-box state machine remain in a waiting state? Did global flags indicate that the player avatar is still locked? You are tracing ownership and liveness, not just looking for the word “input.”

Here is another one. Suppose a battle transition starts, the music changes, but the screen never reaches the battle interface. You ask: did battle setup finish preparing the battle state? Did the transition task advance? Did the main callback change at the right moment? Did some required resource loading fail? Did a script or field effect keep control longer than expected? Again, this is why the include surface in `pokefirered/src/battle_setup.c`, chunk `chunk_494d132a59422c85`, matters. Battle setup is a crossing point. Bugs at crossing points often look like visual glitches, freezes, or control loss, but they are really ownership bugs.

Now, I want to make a distinction that will help with tomorrow’s lecture. A callback is usually coarse-grained ownership. A task is usually fine-grained remembered behavior. That is not a law of physics, but it is a useful reading rule. If you see `SetMainCallback2`, think, “We are changing the kind of frame we are running.” If you see `CreateTask`, think, “We are adding a participant to future frames.” And when you see `RunTasks`, think, “Every active participant now gets a chance to advance.” Tomorrow, when we focus on tasks as FireRed’s lightweight state machines, this distinction becomes more concrete. We will look more closely at task IDs, task data fields, priority, destruction, and the discipline required to avoid leaking behavior across modes.

Let’s do one last board trace. I’m writing: “Open summary screen.” Step one, some prior mode, likely a party menu or another UI context, receives player input. Step two, the program prepares summary state: which Pokémon, which page, which return location. Step three, it changes or enters the summary screen callback path. Step four, the summary mode creates or uses tasks to process input, animate, and update display. Step five, the player exits. Step six, the summary mode returns control to the appropriate previous context. If any one of those steps is wrong, the player sees a bug. If the selected party slot is wrong, data are wrong. If callback ownership is wrong, the screen may not run. If tasks are wrong, input may not respond. If return context is wrong, the player may exit to the wrong place.

That little trace is the same shape as many other features. Open bag. Start battle. Use item. Talk to NPC. Enter trade. Hatch egg. Learn move. Save game. The specific data and feature rules change, but the architectural grammar repeats.

And that is what I want you to notice when reading this codebase. Do not get hypnotized by file size or by the number of globals. Ask what role a piece of code plays in the frame economy. Is it bootstrapping the machine? Is it choosing the current mode? Is it allocating behavior? Is it dispatching behavior? Is it interpreting a data table? Is it bridging persistent state to visible output? Once you can answer those questions, even a large C game becomes navigable.

For the lab, you are going to draw a call-flow map for booting into a callback-driven screen, and then you are going to implement a tiny C-style scheduler with main callbacks, task slots, task data, and per-frame dispatch. Keep it small. The goal is not to rebuild FireRed. The goal is to feel why `AgbMain`, `SetMainCallback2`, `CreateTask`, and `RunTasks` form a reusable runtime pattern. Your homework this week will ask you to explain that pattern in source-grounded terms, and by the quiz in week two I will expect you to distinguish callback ownership from task-local state without hand-waving.

So let’s close today’s lecture with the synthesis. FireRed is a creature-collector RPG, but architecturally it is also a set of cooperating state machines arranged around a frame loop. `AgbMain` establishes the runtime. `SetMainCallback2` changes the major mode. `CreateTask` gives small behaviors a place to live. `RunTasks` advances those behaviors frame by frame. Around that spine, maps, scripts, creatures, battles, inventory, UI, saves, and link play all become understandable as data and state moving through scheduled behavior. If you can trace that path from table to callback to task to visible effect, you are no longer just reading code. You are reading the game as a running system.

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