# Object Events as Data-Backed Actors

Lecture ID: lecture_w07_d01
Week 7, Day 1

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

## Transcript
Good afternoon, everyone. Let’s pick up exactly where last week’s field-control work left us. We already traced how input becomes an avatar movement attempt, how collision and tile behavior constrain that attempt, and how the A button becomes an interaction lookup. Today I want to shift the center of gravity from “the player moves through a map” to “the map is populated by actors whose behavior is mostly data-backed.” That phrase, data-backed actors, is the key idea for the day. FireRed does not treat every NPC as a bespoke little program hand-authored in C. It treats most map inhabitants as records first, then runtime structs, then visible sprites, then script entry points, then movement state machines.

So, if you are following on the board, write this chain down: map object record, local object identity, runtime object event, sprite and movement state, script label, script VM. That is the spine of today’s lecture. And I want you to notice how similar this is to the course-wide architecture pattern we have been using since week one: data table to runtime global state, to callback or task or script, to player-visible behavior. We saw that in startup and callbacks with `pokefirered/src/main.c`, especially the research packet’s `chunk_b35676322b169f85` for `AgbMain` and `chunk_6cfd90f6d0136799` for `SetMainCallback2`. We saw it in task scheduling with `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59` for `CreateTask` and `chunk_3ffebc0341225936` for `RunTasks`. Today we are applying that same architecture lens to object events.

Now, let me make a careful distinction before we go any further. In casual conversation, people often say “NPC” when they mean any character on the map. In FireRed architecture, that is too vague. An NPC, as a player-facing idea, is usually implemented as an object event. But the engine also has coordinate events, background events, and warp events, and these all live near one another in map data while behaving quite differently at runtime. That distinction matters this week because your lab is not just “make NPCs work.” Your lab is to diff the 2-player and 4-player Battle Colosseum map records, then implement trigger lookup for object, coordinate, background, and warp event categories. If you blur those categories, you will get a system that kind of works for standing people but fails the moment a script is entered by stepping on a tile or pressing A on a background object.

Let’s ground this in the source focus. The 2-player Battle Colosseum map has a receptionist object event in `pokefirered/data/maps/BattleColosseum_2P/map.json`, identified in the packet as `chunk_919152bd3bfceb95`. That is the clean example of an object event as a data record. The same map also has two player spot triggers in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_237e353ea279eb27`. The 4-player Battle Colosseum version has four coordinate triggers in `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`. So, already, before we talk about any C code, the map data is telling us something important: the receptionist and the spots are not the same kind of thing. One is an actor-like object with identity, position, facing, movement graphics, and a script. The others are coordinate-driven trigger entries, which are closer to “when the player is at this position under this condition, dispatch this script.”

This is where I want advanced students to slow down. In a small clone, you might be tempted to represent all of these as one generic “event” object with a type field. That can work if your dispatch layer is disciplined. But FireRed’s architecture gives us a stronger separation: object events are entities that become runtime object event structs and often sprites; coordinate events are spatial triggers; background events are inspected things, such as signs or hidden items; warps are map transitions. The categories exist because the lookup path is different. They are not just labels for the designer.

Now, imagine we are looking at the 2-player Colosseum map on the projector. I am pointing to the receptionist. The data record says, in effect, “place an object here, give it a local identity, assign it graphics and movement properties, and connect it to a script label.” I am saying “in effect” because the exact generated representation flows through the project’s data tooling, and we talked in week two about how generated data and runtime JSON are not the same thing. Remember the toolchain discussion: JSON map records are source data; generated C-facing arrays and constants are what the engine ultimately consumes. That point connects back to the packet’s recurring emphasis on `pokefirered/data` and the JSON processing pipeline. So do not picture the GBA opening a JSON parser at runtime. Picture source data being compiled into static tables.

Now let’s put the runtime path on the board. First, the map header points at an events collection. That events collection includes object-event records. When the overworld loads or changes maps, it uses those records to instantiate runtime state. The object event record is not itself the live moving NPC. It is more like a birth certificate and initial placement instruction. The live thing is a runtime object event, and the high-signal movement source for that layer is `pokefirered/src/event_object_movement.c`, which the packet explicitly calls out for this lecture. That file is where you look when you want to understand how the engine turns object-event definitions into movement, sprite state, direction, animation, and per-frame updates.

Let me pause for a check. If I ask, “Where is the receptionist’s script identity stored?” the wrong advanced answer is, “In the sprite.” The sprite may be the visual representation. The script association begins in the object-event data record. The runtime object event preserves the relevant identity needed for interaction dispatch. When the player presses A and the field-control logic determines that the player is facing an object, the engine can use the object event’s local identity and script pointer or script label linkage to enter the script system. That is the architectural through-line.

And notice how this builds on last lecture without redoing last lecture. We already studied field input polling and avatar movement attempt flow. We already studied interaction checks. Today, we care about what those interaction checks find. Last week, the question was, “Does pressing A find something in front of me?” Today, the question is, “If it finds an object event, what data-backed actor did it find, and how does that actor carry us into script execution?”

Let’s use a concrete example. Suppose the player in the 2-player Battle Colosseum walks up to the receptionist and presses A. At the player level, the experience is simple: face receptionist, press A, receive dialogue or link-battle setup flow. At the engine level, many layers are cooperating. The receptionist originated in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_919152bd3bfceb95`, as an object-event record. During map load, that record is converted into a runtime object event with current coordinates, facing direction, movement type, and active/inactive state. Its sprite is created or associated so that the actor can be drawn and animated. Per frame, the overworld callback runs its update path, and tasks may also be alive for adjacent field behavior. When the player interacts, the field-control layer resolves the target and enters the relevant event script. If that script transitions into a battle or link flow, now we are crossing into modules like battle setup and link management. The packet’s `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, is useful here because its includes show how battle setup sits at a junction of `task.h`, `overworld.h`, `script.h`, `event_data.h`, `event_object_movement.h`, `field_control_avatar.h`, and battle transition headers. That include neighborhood is not proof of one exact call path by itself, but it is strong evidence for the architecture we keep seeing: field objects, scripts, event data, and battle setup are wired together across module boundaries.

When I say “include neighborhood,” I want to model source-reading discipline. We do not claim, “This include list proves the receptionist starts a battle through this exact function.” That would be too strong. What we can say is that `battle_setup.c` in `chunk_494d132a59422c85` depends on the same field and event systems we are discussing, including `event_data.h`, `event_object_movement.h`, `script.h`, and `field_control_avatar.h`. That supports the broader claim that battle entry is not an isolated menu action. It is integrated with overworld control, scripts, event flags, and object state.

Now, let’s separate object events from coordinate events by using the 2-player and 4-player Colosseum records. In the 2-player map, `chunk_237e353ea279eb27` gives us two player spot triggers. In the 4-player map, `chunk_30cd3fd6e624b8c6` gives us four coordinate triggers. Architecturally, this is beautiful because the difference in map design is reflected as a difference in event data cardinality. Two-player room, two spots. Four-player room, four spots. You do not need four visible NPCs standing on those spots for the spots to matter. The spots are not actors; they are trigger coordinates. The player standing on or stepping onto those coordinates can satisfy dispatch conditions.

So if I were sketching this on the board, I would draw the receptionist as a circle with an identity label, maybe local ID 1, and then I would draw the player spots as small squares on the floor with coordinate labels. The receptionist circle has a script edge that says “talk.” The floor squares have script edges that say “step” or “coordinate condition.” This is not just a diagramming convenience. It matches how your engine implementation should think. Object-event lookup asks, “Is there an object actor at the relevant facing position?” Coordinate-event lookup asks, “Does the player’s current location match a coordinate event whose variable or condition allows dispatch?” Background-event lookup asks, “Is there an inspectable background event at the facing location?” Warp lookup asks, “Does the player’s movement or position intersect a warp event?” Same map, different dispatch semantics.

Here is the check for understanding. If the player stands on a Colosseum player spot and a script begins automatically, did the player “talk to an invisible NPC”? No. That is the wrong abstraction. The player matched a coordinate event. You could implement it with an invisible object in your own clone, but then you would be changing the architecture, and you would need to account for side effects: object occupancy, facing, sprite allocation, object-local identity, movement update cost, and interaction priority. FireRed’s separation avoids those unnecessary actor semantics.

Now let’s connect local IDs to script labels. Object events need map-local identity because the same map can have many object records and scripts often need to refer to a particular one. A local ID is not a global NPC database ID in the high-level RPG-design sense. It is a map-scoped handle that lets the runtime and scripts talk about “this object event on this map.” That is why local IDs are so important for movement scripts, visibility flags, and dialogue entry. If an NPC walks away after a cutscene, the script needs to identify which runtime actor should receive that movement command. The local ID is the bridge between static placement data and dynamic script command.

This is one of the reasons I keep pushing you not to flatten the architecture into “entities with components” too early when studying FireRed. Entity-component language can be useful, but if you import it before understanding the original engine, you may miss the actual contracts. FireRed has object-event data records, runtime object event slots, sprite slots, script VM state, event variables, and flags. Those are concrete contracts. A local ID is not just a component. A script label is not just a method. A task is not just an update function. They each have different lifetime rules.

Think back to our callback and task material. `SetMainCallback2` in `pokefirered/src/main.c`, `chunk_6cfd90f6d0136799`, gave us a way to swap the major per-frame mode. `RunTasks` in `pokefirered/src/task.c`, `chunk_3ffebc0341225936`, gave us a per-frame dispatcher over allocated task slots. Object events live under that broad runtime shell. They are not replacing callbacks or tasks. They are data-backed participants updated by the overworld while the field callback is active. That layering is the course plan in miniature: runtime shell first, overworld maps next, event scripting and progression on top of that, and then battle, inventory, UI, save, and link systems connected through explicit transition points.

Let’s make this more concrete with a short imagined trace. The player enters BattleColosseum_2P. The map header and map events are available from compiled data derived from `pokefirered/data/maps/BattleColosseum_2P/map.json`. The receptionist object event from `chunk_919152bd3bfceb95` is instantiated into a runtime object slot. Its initial position is loaded. Its movement type is loaded. Its script association is available. The player moves; previous lectures already gave us the movement and collision details, so we will not re-teach those. The player presses A while facing the receptionist. The field-control interaction lookup detects an object event. The object’s local identity leads to the script entry. The script VM runs dialogue or setup commands. If the script controls player placement, flags, variables, or later battle setup, it does so through the event data and script interfaces. If link battle setup is involved, the flow can eventually reach code in battle or link-facing modules, and `battle_setup.c`’s dependency cluster in `chunk_494d132a59422c85` is exactly the kind of source evidence we use to recognize these module boundaries.

Now, a subtle point: object events are “actors,” but they are not actors in the fully autonomous simulation sense. Some can wander. Some can face the player. Some can be locked by scripts. Some can run movement commands. But they are not generally independent threads of behavior. Their apparent agency is produced by data, per-frame movement update code, sprite animation, script commands, and task or callback scheduling. This is why “data-backed actor” is more precise than simply “object-oriented object.” The actor quality is assembled.

Let’s say that again because it is central. The receptionist is not interesting because there is a `Receptionist` class. The receptionist is interesting because a map record, local ID, graphics ID, movement setting, runtime object event struct, sprite, and script entry together create the player-facing illusion of a person. This is an old handheld RPG technique, but it is also a modern architecture lesson. You get content scale by making the common case data-driven. Hundreds of NPCs can share runtime machinery while differing in map placement and script linkage.

Here is another check for understanding. Suppose you are building your lab engine and you can already load object events and warps. You add coordinate triggers by checking only the tile behavior under the player’s feet. Is that sufficient? No. Tile behavior was last week’s foundation, and it tells you properties of the map tile or metatile behavior. A coordinate event is a map event record with its own script and conditions. A tile can be ordinary floor and still host a coordinate trigger. The Colosseum player spots are exactly the sort of example that keeps us honest: the gameplay trigger is not necessarily encoded in the tile’s collision or terrain behavior. It is encoded in the event records.

This also anticipates Wednesday’s lecture, Step Triggers and NPC Script Entry. Today we are establishing the categories and the data-backed actor model. Next time we will spend more time on entry mechanics: when a step dispatches a coordinate script, when an A-button press dispatches an object or background script, and how NPC script entry differs from floor-trigger script entry. So, for now, hold this distinction: object events are actor records that can become runtime object structs and sprites; coordinate events are spatial script dispatch records; background events are inspection dispatch records; warps are map transition records.

Let me pull the lens back to week seven as a whole. We are in the middle module of Input, Events, and Story Progression, weeks six through eight. Week six gave us overworld movement and interaction foundations. Week seven gives us object events, NPC movement, and map triggers. Week eight will make these ideas matter for broader progression: flags, variables, script outcomes, and how story state persists. The assessment expectations are rising because you have already had homework and quizzes asking you to decompose a creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At this point, I am no longer asking you to recognize that maps have NPCs. I am asking you to trace the exact architectural path from record to runtime state to callback, task, or script behavior.

One final board cue before we pause this first segment: draw four columns. Label them object, coordinate, background, warp. Under object, write: local ID, sprite, movement, script on interaction or script control. Under coordinate, write: x-y position, trigger variable or condition, script on step or position. Under background, write: facing-position lookup, sign or hidden item style interaction, script or item response. Under warp, write: destination map, destination warp, transition. Now circle only the object column and write “actor illusion.” Then circle all four columns together and write “map event system.” That is the balance. Object events are special because they behave like actors, but they are part of a broader map event dispatch system.

And as we continue, keep the receptionist in `BattleColosseum_2P` and the player spot triggers in the two Colosseum maps in your mind. They are small records, but they carry a surprisingly large amount of architecture. They show how FireRed lets designers place people, places, and transitions into maps without writing new C code for every case, while still relying on very concrete C modules like `event_object_movement.c`, the task system in `task.c`, the callback shell in `main.c`, and the script and event-data interfaces visible around `battle_setup.c`, `chunk_494d132a59422c85`. That is the pattern we will keep tracing through the rest of today’s lecture: data first, runtime representation second, dispatch path third, player-visible behavior last.

So now let’s make that pattern concrete by slowing down on one design distinction that becomes very important this week: an object event record is not itself the NPC. It is the recipe for making or maintaining an NPC-like runtime actor when the map is active. That sounds like a subtle distinction, but it is one of the reasons the architecture scales.

If I point to the receptionist record in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_919152bd3bfceb95`, I am pointing to authored map data. A designer, or a generated data pipeline standing in for the designer, says: on this map, there is an object event with a local identifier, a graphics identity, coordinates, facing or movement properties, and a script label. That record belongs to the map. It is stable in the sense that it exists before the player arrives. But the moving thing you see on screen is not the JSON record walking around. The runtime needs a struct in memory, sprite state, coordinates adjusted into the loaded map space, visibility rules, movement state, and script linkage. That runtime representation is what lets the engine ask questions like, “Is this actor loaded? Is it currently frozen? Is it facing the player? Is it running a movement script? Should it be hidden because an event flag says so?”

Put that on the board. Left side, write “authored object event.” Right side, write “runtime object actor.” Under the authored side, put local ID, position, graphics, movement type, script. Under the runtime side, put object event struct, sprite, movement state, map-local identity, current coordinates. The arrow between them is map loading and event object initialization. This is the part I want you to get comfortable tracing, because FireRed repeatedly uses this idea: data describes a possible thing, runtime code materializes it into a concrete thing, and then callbacks, tasks, and scripts give it behavior.

Now, because you are advanced students, I do not want you to merely say “data-driven” and stop. “Data-driven” is too vague by itself. Data-driven how? Through which function family? Into which runtime structure? Governed by which global state? Dispatched by which callback or script VM? In this course we keep using the longer chain: data record, generated or loaded representation, global or map runtime state, callback or task dispatch, script entry, source function, player-visible behavior. That is the discipline.

We have already seen the shell of that discipline earlier in the course. In `pokefirered/src/main.c`, `chunk_b35676322b169f85`, the startup responsibilities around `AgbMain` establish the GBA program’s central runtime context. In `pokefirered/src/main.c`, `chunk_6cfd90f6d0136799`, `SetMainCallback2` gives us one of the major control slots for deciding which high-level state machine owns the frame. In `pokefirered/src/task.c`, `chunk_cb0bb91a147bfb59`, `CreateTask` gives subsystems a way to allocate small per-frame state machines, and in `pokefirered/src/task.c`, `chunk_3ffebc0341225936`, `RunTasks` is the recurring dispatch point. I am reminding you of these not to re-teach callback architecture, but to connect today’s object events to that earlier shell. NPCs and triggers do not exist in a vacuum. They are serviced inside the same frame economy as menus, fades, transitions, scripts, and field control.

Let’s use the receptionist. Suppose the player walks up to the Battle Colosseum receptionist and presses A. At the authored-data level, the receptionist’s object event record in `BattleColosseum_2P` gives the map system enough information to know that there is an interactable object at a map coordinate, with a local ID and a script label. At the field-control level, the player’s A-button interaction path, which we covered last week, asks what is in front of the avatar. We are not going to re-teach that whole input-to-avatar path; use it as prior foundation. The important point today is what happens after the lookup says, “There is an object event here.” The engine does not need a special C function called something like “TalkToTwoPlayerColosseumReceptionist.” Instead, the object event routes into a script. The script can then manipulate event variables, display text, ask questions, set flags, launch link-related flows, or transfer control to another subsystem.

And that is where `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, is a useful architectural clue even though today’s main file is the map JSON and object movement code. Look at the include surface in that chunk. It includes `event_data.h`, `event_object_movement.h`, `script.h`, `event_scripts.h`, `field_control_avatar.h`, `field_player_avatar.h`, and battle transition headers. That tells us something about module boundaries. Battle setup is not simply “battle math.” It sits at a crossroads where field events, scripts, event data, avatar control, and battle transition machinery meet. So when a map script or object interaction eventually initiates a battle or a link battle flow, it is not violating the architecture. It is using the architecture’s intended crossing points.

Now, check for understanding. If I ask, “Where is the receptionist’s behavior?” the answer is not one file. The placement is in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_919152bd3bfceb95`. The per-frame ability for the game to run overworld logic is part of the main callback and task shell, including `main.c`, `chunk_6cfd90f6d0136799`, and `task.c`, `chunk_3ffebc0341225936`. The actor-like runtime movement and object maintenance are in the event object movement area, with the packet specifically flagging `pokefirered/src/event_object_movement.c` as the high-signal source for this week. The script and event-variable boundary is visible through modules like `script.h` and `event_data.h`, and `battle_setup.c`, `chunk_494d132a59422c85`, shows those interfaces being pulled together near battle and field transitions. So the correct answer is distributed. That is the point.

Let me say that another way, because this is where students often flatten the system too much. The NPC is not “implemented in JSON.” The NPC is also not “implemented in C” in the old-fashioned hard-coded sense. The NPC is implemented by a contract between authored data and engine code. The data says what actor exists and which script it points at. The C code knows how to instantiate actors, update them, test interaction, run movement, and enter scripts. The script language provides the designer-facing behavior layer. And event variables and flags provide memory, so the same object can behave differently after progression changes.

Now we transition from object events to coordinate events, because the Battle Colosseum examples are deliberately paired. In `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_237e353ea279eb27`, the packet points us to two player spot triggers. In `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`, it points us to four coordinate triggers. Do you see the symmetry? The two-player map has two spots because two players need to stand in positions. The four-player map has four coordinate triggers because the authored map must recognize four participant positions. The design is not “write a new map engine for four players.” The design is “change the data records, let the generic coordinate-event dispatch recognize them.”

Here is the conceptual distinction. An object event is actor-shaped. It has identity over time. It can face, move, animate, and be talked to. A coordinate event is location-shaped. It waits for the player to step on or satisfy a coordinate condition. It does not need to be a visible person. It is closer to a pressure plate in level design terms, though in FireRed it is usually connected to a script and often gated by a trigger variable. A background event is inspection-shaped. It is commonly attached to a tile or region where pressing A should produce text or behavior: a sign, a poster, a bookshelf, a hidden item-style lookup depending on the game’s event category. A warp event is transition-shaped. It moves the player from one map entrance to another. Same broad map-event system, different record categories, different dispatch moments.

Let’s put another board diagram next to the first one. Write four columns: object, coordinate, background, warp. Under object, write “A-button or runtime actor logic.” Under coordinate, write “step dispatch.” Under background, write “facing tile inspection.” Under warp, write “map transition.” Now circle the word “dispatch.” These are not merely different data formats. They are different questions asked by the overworld runtime. What am I facing? What did I step onto? Did I cross a warp tile? Which actors should exist and update this frame?

That is exactly why the week-seven lab asks you to diff the 2-player and 4-player Colosseum map records and then implement trigger lookup for object, coordinate, background, and warp event categories. The lab is not busywork. It forces you to separate category-specific dispatch from shared map-event storage. If your clone engine stores every event in one undifferentiated list and then brute-forces every possible behavior on every frame, you can make a tiny demo work, but you have not learned the architecture. FireRed’s structure encourages more disciplined questions. The player steps; now coordinate triggers and warps are relevant. The player presses A; now object events and background events are relevant. The map loads; now object events must be instantiated or hidden. The frame advances; now runtime object movement must update.

Let’s do a concrete walkthrough. Imagine the player in the two-player Colosseum map moves onto one of the designated player spots from `chunk_237e353ea279eb27`. The coordinate event is authored data. At runtime, after the movement attempt succeeds, the field system can test whether the player’s new coordinates match a coordinate event. If the trigger variable has the expected value, the engine dispatches the associated script. That script might record that the player is standing in the correct spot, coordinate with link state, or advance a waiting routine. The player-visible behavior may be subtle: perhaps a prompt appears, perhaps the link room proceeds, perhaps nothing obvious happens until both players are positioned. But the architecture is still active. Data record, current coordinates, trigger variable, script dispatch, visible state change.

Now imagine the four-player map. The same engine idea holds, but the authored data in `BattleColosseum_4P`, `chunk_30cd3fd6e624b8c6`, supplies four coordinate triggers. This is an excellent example of data-backed scaling. The map’s event table grows to represent more participants. The dispatch algorithm should not care whether there are two or four except as list length and matching conditions. If your implementation says, “if two-player map, check coordinate A and B; if four-player map, check coordinate A, B, C, and D,” you have encoded map content into engine logic. FireRed’s data-backed approach pushes you away from that. The map data should vary; the generic lookup should remain boring.

And boring is a compliment here. In engine architecture, boring dispatch code is often the sign that the data model is doing its job. The exciting parts belong in the content and in the scripts. The generic C code should say, in effect, “Given the current map and player position, find a matching coordinate event whose trigger condition permits activation, then start its script.” That is the kind of sentence you should be able to translate into code in your own projects.

Let me pause for a check. If I show you a map event with a local ID and a script label, and it is visible as a receptionist, which category is it? Object event. If I show you an invisible tile that fires when stepped on and is controlled by a trigger variable, which category is it? Coordinate event. If I show you a sign on the wall that produces text when the player faces it and presses A, which category is likely involved? Background event. If I show you a doorway that changes maps, which category is it? Warp event. Good. Now the advanced follow-up: do these categories require four totally unrelated engines? No. They require category-specific dispatch entry points over shared map event data.

This is where local IDs matter. For object events, local IDs give scripts and engine routines a stable way to refer to a map-local actor without needing to know its runtime slot. Runtime slots are volatile. If you load a different map, or if hidden objects are skipped, or if object event structs are allocated in a different order, the slot can change. But the local ID is authored identity. A script can say, conceptually, “turn this local object,” “hide that local object,” “apply movement to that local object,” or “start the script for the object I interacted with.” The engine resolves that local identity into whatever runtime object event instance currently represents it.

This is the same principle you have seen elsewhere in the course. Species IDs are stable data identities; party indices are runtime positions. Item IDs are stable data identities; menu cursor positions are runtime state. Map local IDs are stable within the map; object event slots are runtime state. Once you see that distinction, you start recognizing the architecture everywhere.

Now, let’s connect this to globals without pretending that globals are automatically bad. FireRed is a GBA game. It uses global state heavily because the platform, memory model, and engine style favor centralized runtime structures. The important question is not “are there globals?” The important question is “what contract does this global state serve?” For field maps, globals track the current map header, map layout, player avatar, object event arrays, script context, event variables, and callback ownership. Those globals let many small modules cooperate inside a strict frame loop. The risk, of course, is coupling. The benefit is speed, simplicity, and predictable memory.

When you read include surfaces like `battle_setup.c`, `chunk_494d132a59422c85`, you can see this style clearly. The file pulls in event data, script, field control, object movement, battle transition, and battle headers. That include list is not proof that every function is tangled with every other function. But it is evidence that high-level gameplay transitions cross module boundaries by design. The overworld asks for an event. The event script manipulates variables or begins a battle setup. The battle setup changes callbacks, transitions screens, and eventually yields control to another state machine. Later, the overworld resumes. This is the same callback handoff pattern from earlier weeks, now seen from the map-event side instead of the startup side.

So if I ask you to trace “talking to the receptionist,” I expect a layered answer. First, authored object event data in `BattleColosseum_2P` establishes that the receptionist exists and names the script entry. Second, map load and object event movement code materialize a runtime actor. Third, field input and interaction logic detect that the player is facing the actor when A is pressed. Fourth, script dispatch enters the receptionist’s script. Fifth, the script reads or writes event variables and may call into link, menu, message, or battle setup facilities. Sixth, the main callback and task loop continue advancing the active state machine each frame. Finally, the player sees text, menu choices, positioning behavior, or a transition.

Notice that none of those steps are optional if you want a real architecture trace. If you skip straight from JSON to “NPC talks,” you lose the engine. If you skip straight from C to “there is a function,” you lose the authored content model. If you ignore callbacks and tasks, you lose time. If you ignore scripts and event data, you lose progression.

Now let’s bring week seven back into the course plan. Weeks six through eight are our “Input, Events, and Story Progression” module. Last week, we established that walking and interacting are not vague verbs. They are composed of input polling, avatar movement attempts, collision and tile behavior checks, camera-relative map drawing, and A-button interaction lookup. We are using those as foundations now. This week, we are asking what those interactions find. They find object events, coordinate events, background events, and warps. Next, as we move into the second lecture of this week, “Step Triggers and NPC Script Entry,” we will lean harder on the dispatch moments: after movement, after interaction, after trigger-variable checks, and after script context selection.

That is also why the assessments are getting stricter. The week-six homework and quiz already checked whether you could decompose a complete creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. So today I am not satisfied with “NPCs are data-driven.” At this point in the course, you need to say which data, which runtime representation, which callback or task mechanism, which script boundary, and which visible behavior. The week-seven homework will expect that level of specificity.

Let me give you a small clone-engine design rule you can take into the lab. Your map event table can be content-authored, but your event lookup should be category-aware. That means you can store all event categories under the map, but your engine should expose operations like “find object event in front of player,” “find background event in front of player,” “find coordinate event at player position,” and “find warp at player position.” Those operations may share helpers for coordinate matching and script lookup, but they should not collapse into one confused mega-function that tries to do everything at once. The dispatch question should match the gameplay moment.

And now, before we move on, take thirty seconds and mentally trace the four-player Colosseum spots. Four coordinate records in `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`. Player steps. Runtime position updates. Coordinate trigger lookup runs. Trigger variable gates activation. Matching script enters. Script updates event or link-room state. The player sees the room react, or waits until other participants satisfy their spots. That is the whole chain. Small records, large architecture.

So let us now put a slightly sharper vocabulary around that phrase, “data-backed actor,” because it is easy to say and surprisingly easy to misunderstand. An object event is not merely an NPC, and it is not merely a sprite. It is a map-authored record that can become a runtime object with identity, position, graphics, movement behavior, facing, script entry, and sometimes trainer behavior. The authoring side says, in effect, “there is a thing on this map.” The runtime side says, “I have allocated a concrete object event slot, I know where it is, I know how to animate it, and I know what to do when the player interacts with it or when movement logic advances it.”

Now, notice the contrast. A coordinate trigger also has coordinates and script behavior, but it is not an actor. It does not walk around. It does not face the player. It does not have a sprite animation state. It does not need to be hidden or shown as a body in the world, although the script it starts may cause bodies in the world to change. A background event may be interactable, but again, it is not usually an actor. A signpost, a hidden item, a bookcase, a PC, a tile with an associated script: these are better modeled as map features. So the first discipline for this week is to stop calling every event “an NPC event.” That phrase blurs the architecture. FireRed separates these categories because the engine asks different questions at different gameplay moments.

Let me put that on the board. I’m writing four verbs here: talk, step, inspect, transition. Talk corresponds most naturally to object events. Step corresponds to coordinate triggers and warps. Inspect corresponds to background events. Transition corresponds especially to warps, though scripts can also perform transitions. This is not a perfect taxonomy, but it is a good mental model. When the player presses A facing a receptionist, the engine is in a talk or inspect mode. When the player walks onto a marked tile in the Colosseum, the engine is in a step mode. When the player crosses a door tile, the engine is in a transition mode. Same map, same avatar, same overworld callback shell, but different event lookup question.

This is where the week connects backward to our previous lectures without repeating them. In week 6 we traced field input polling into avatar movement attempts and A-button interaction checks. We are not going to re-teach that flow today. Instead, treat it as the upstream caller. The player input and avatar systems decide that an interaction moment has occurred, or that a step has completed. Once that moment exists, the map event system has to answer: what authored record, if any, should fire? That is why the data categories matter. The avatar system should not need to understand the semantic difference between a link-room ready spot and a receptionist script in deep detail. It should ask a focused question, and the event layer should answer from map data.

Now let us use the two-player Battle Colosseum as the clean example. In `pokefirered/data/maps/BattleColosseum_2P/map.json`, the packet identifies `chunk_919152bd3bfceb95` as the receptionist object event. That is the object-event side of the room. The receptionist is authored as a record in the map data. The player sees a person. But the engine sees a local object entry that can be instantiated into runtime object-event state and later resolved back to a script label. That local identity is crucial. We do not want to say, “run the script for whichever sprite happens to be drawn at these pixels.” We want to say, “the player is interacting with this object event, whose map-local ID and script field identify the intended behavior.”

Here is a concrete trace. The map is loaded. Its object-event records become candidates for runtime object events. The receptionist’s data record provides the basis for visible placement and script identity. The object movement source focus for this week is `pokefirered/src/event_object_movement.c`, which is the right place to look for the machinery that treats these authored object records as moving, animating runtime entities. Then the overworld frame loop continues under the callback and task shell we studied earlier. Remember the larger shell from `pokefirered/src/main.c`, especially `SetMainCallback2` in `chunk_6cfd90f6d0136799`, and the task dispatcher from `pokefirered/src/task.c`, especially `RunTasks` in `chunk_3ffebc0341225936`. Those pieces are not “object events” themselves, but they give object events a clock. Without that frame dispatch, the object record would just be inert data.

Now the player walks up to the receptionist and presses A. We already studied the interaction-check side, so I will only connect the endpoints. The engine determines the tile in front of the player, looks for an object event occupying the relevant map position and elevation, resolves that runtime object back to its map-authored identity, and enters the script named by the record. The visible behavior is simple: the receptionist talks. The architecture is not simple: data record, runtime object slot, input moment, lookup routine, script VM entry, message box or link-room script behavior, and then returning field control when the script allows it.

Check yourself here. If I ask, “Where does the receptionist’s dialogue come from?” the advanced answer is not “from the sprite.” The advanced answer is: from a script entry associated with a map object-event record, reached through runtime interaction lookup. The sprite is the visible body of the actor, not the source of the behavior. If you can keep that separation clean, you will avoid a whole class of engine design mistakes.

Now compare that with the two player spot triggers in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_237e353ea279eb27`. Those records are not receptionist-like actors. They are coordinate events. The player steps onto a tile, and the tile has meaning because the map says it has meaning. The player may see a floor marking, but the floor marking is not the event object in the same way the receptionist is. The dispatch happens after movement completes, and the lookup is by the avatar’s current coordinates and trigger conditions. The script may examine variables, set variables, wait for link state, or refuse to proceed until conditions match. But the trigger itself does not need movement state or facing logic.

Let me say that another way, because it matters for your lab. Object events answer, “What actor did I touch or talk to?” Coordinate triggers answer, “What script is attached to this step location under current variable conditions?” Background events answer, “What map feature did I inspect?” Warp events answer, “What transition should happen from this tile?” If your implementation flattens all of those into a generic “find event at x,y” and returns a loosely typed blob, it may pass a trivial test, but it will become unpleasant as soon as you add facing, elevation, hidden items, conditional triggers, and warps. The gameplay moments are distinct, so the query surface should be distinct.

The four-player Colosseum example makes that distinction more obvious. The packet points us to `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`, for four coordinate triggers. Four participants, four spots, four authored coordinate records. Nothing about that requires four NPC actors. You do not need four invisible people standing there. You need four step-sensitive positions whose scripts cooperate through variables or link-room state. In architecture terms, that is an excellent example of data-backed control flow without actor identity. The map data describes participation positions; the runtime trigger lookup turns a completed step into script execution; the script layer coordinates the multiplayer state.

Pause for a second and test the model. Suppose player one and player two stand on the correct spots, but player three is absent. Where should the waiting behavior live? Not in the coordinate-event lookup itself. The lookup should find the matching coordinate script. The script or link-room state machine should decide that the party is not complete and display or maintain the waiting state. That separation is important. Lookup should answer whether a matching authored event exists. It should not become the whole Colosseum protocol.

This is the same architectural habit we saw when studying battles, menus, and progression systems. Source modules include one another at the boundaries where systems must cooperate. For example, `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, includes headers such as `event_data.h`, `event_object_movement.h`, `metatile_behavior.h`, `field_control_avatar.h`, and `field_player_avatar.h`. That cluster is evidence of a real engine boundary: battle setup is not isolated from overworld state. It needs event variables, object movement, tile behavior, and avatar control to know when and how a battle transition can be started. But inclusion is not the same thing as conceptual collapse. The architecture can connect systems while still preserving their roles.

That point is worth underlining. Advanced architecture is not about making everything independent in a fantasy sense. FireRed is a game; systems must touch. The question is whether they touch through meaningful contracts. An object event’s map-local ID is a contract. A script label is a contract. A trigger variable is a contract. A task callback is a contract. A global event flag or variable is a contract. These contracts let data authored in JSON-like map records become behavior inside a C runtime without every module knowing every other module’s internals.

Let’s do a small board trace, and I want you to follow the arrows. I’m writing: map JSON record, generated C data, map header or map event table, runtime object or event lookup, script context, visible result. For the receptionist, the arrow from runtime lookup goes through object-event state. For a coordinate trigger, the arrow goes through coordinate matching and trigger condition evaluation. For a background event, the arrow goes through facing or inspection position. For a warp, the arrow goes through transition destination data. The beginning of the chain is similar: authored map data. The end of the chain is similar: visible behavior. The middle is category-specific.

That is why the lab asks you to diff the two-player and four-player Colosseum map records before implementing lookup. The diff is not busywork. The diff forces you to notice the authored shape of the room. In the two-player room, the relevant packet chunks separate the receptionist object event, `chunk_919152bd3bfceb95`, from the two player spot triggers, `chunk_237e353ea279eb27`. In the four-player room, `chunk_30cd3fd6e624b8c6` gives you four coordinate triggers. Once you see that structure, the engine design becomes less mysterious. The room behavior is not hardcoded as “BattleColosseum2PMode” or “BattleColosseum4PMode” at the first layer. The map records declare objects and triggers; scripts and link systems interpret what those records mean.

Now, of course, FireRed does contain specialized systems. Link rooms are not magic generated purely from coordinates. But specialization should enter at the appropriate layer. The coordinate record can launch a link-room script. That script can call special commands or interact with link state. The object receptionist can launch a receptionist script. That script can ask whether the player wants to participate, whether prerequisites are met, and so on. The map event system remains reusable because it does not need to know all of the domain logic of every room.

Here is a common bug in student engines at this stage. The student writes a map event loop that scans object events, background events, coordinate events, and warps all together, and the first matching coordinate wins. Then later the student puts a hidden item on a tile that is also a step trigger, or places an NPC near a warp, or creates a sign behind a counter, and suddenly pressing A fires the wrong event or stepping fires an inspection script. The issue is not just ordering. The issue is that the code lost the gameplay question. If the player pressed A, ask the A-button categories, with facing and inspection semantics. If the player completed a step, ask the step categories. If the step enters a warp tile, ask the transition category. The map can contain overlapping authored things because the player’s action disambiguates them.

You can see how this ties into the week 7 assessment. By now, the course has already checked whether you can decompose the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. The week 6 homework and quiz pushed that decomposition. This week raises the bar: it is not enough to list the systems. You need to trace an actual player-visible behavior across system boundaries. A receptionist conversation is not just “scripting.” It is map data plus object event runtime plus field input plus script context plus text UI and possibly link state. A Colosseum ready spot is not just “tile trigger.” It is map coordinate data plus avatar movement completion plus trigger variable gating plus script dispatch plus multiplayer coordination.

Let me give you a design heuristic for your own clone. Store the four categories separately, even if they share a common coordinate type. Your map can have an `objectEvents` array, a `coordEvents` array, a `bgEvents` array, and a `warpEvents` array. They may all use x and y. Fine. But their records should not be forced into one universal event class too early. Object events need actor fields. Coordinate events need trigger conditions. Background events need interaction kind or facing semantics. Warps need destination map and destination position. Shared helper functions are good. Shared confusion is not.

And when you implement object events, be careful with identity. A local ID is not the same as an array index forever. During loading, an authored object event may become a runtime slot. During gameplay, an object may be hidden, moved, or temporarily overridden. Scripts may refer back to local IDs because local IDs are stable in the map-authored sense. Array slots are implementation details. If you conflate them, your scripts become brittle. This is one reason old handheld RPG engines often look more indirect than a toy prototype. The indirection protects the authoring model from runtime churn.

Now, quick check for understanding. Imagine I describe a room with one clerk, two pressure plates, a poster on the wall, and a door. Which event categories do you expect? The clerk is an object event. The pressure plates are coordinate events. The poster is probably a background event. The door is probably a warp, perhaps with a coordinate or background script depending on whether it is entered by stepping or inspected by pressing A. If you answered “four events,” that is incomplete. If you answered with the four categories and the gameplay action that dispatches each one, you are thinking like an engine architect.

This also clarifies why `event_data.h` shows up across seemingly different gameplay modules. In `pokefirered/src/daycare.c`, `chunk_119931105f546346`, the include list contains `event_data.h`, `script.h`, and `overworld.h` among many others. In `pokefirered/src/learn_move.c`, `chunk_af3598df6f092723`, the move relearner code also includes `script.h`, `task.h`, `event_data.h`, and `overworld.h`. Those are not map-event lectures, but they reinforce the same course thesis: visible RPG features are usually not single-module islands. A daycare conversation, a move relearner menu, a Colosseum receptionist, and a coordinate trigger all rely on the engine’s ability to pass control among scripts, tasks, global state, and specialized UI or gameplay systems.

So when you read FireRed source, do not hunt for one giant “NPC system” file and expect the whole story to be there. `pokefirered/src/event_object_movement.c` is high-signal for object-event movement and actor state, yes. The map JSON chunks are high-signal for authored records, yes. But the full behavior emerges only when you connect those to the callback shell, task dispatch, script execution, event variables, and the field-control code that decides when the player is allowed to move again. That is the same stack we have been building since week 1: data table to runtime globals, callback or task, script or state machine, concrete visible behavior.

Let’s carry that forward into the next part of today’s lecture. We have separated object events from coordinate triggers conceptually. Now we need to talk about how scripts name and find actors without hardwiring sprite instances. That means local IDs, object-event templates, and script labels. It also means we need to be precise about what can change at runtime and what remains stable as authored data. So keep the Colosseum examples in mind: the receptionist gives us the actor case; the two-player and four-player spot triggers give us the coordinate-dispatch case. From here, we can ask the next architectural question: once a script starts, how does it refer back to the actor or map record that caused it?

So, once a script starts, the key thing is that it should not have to know, “I am talking to sprite slot 7 in OAM,” or “I am talking to the third currently allocated object.” That would be brittle. The visible sprite instance is runtime machinery. It is allowed to be created, destroyed, hidden, moved, or reloaded as the player changes maps. The script wants a more stable handle. In FireRed’s map-event model, that stable handle is the authored object event, and in practice the most important author-facing name is the local ID.

Let me put that on the board. On the left, I’ll write “authored map object.” Under that, local ID, graphics ID, x and y, elevation, movement type, range, trainer flag or other metadata, and script pointer or script label after generation. On the right, I’ll write “runtime object event.” Under that, active object slot, current coordinates, facing direction, movement state, animation state, invisibility, single-movement overrides, and script context. The bridge between those two columns is the object-event loader. It reads the map’s data-backed object records and produces runtime actors.

Now, notice the vocabulary shift. We are not saying the map JSON is the actor itself. We are saying it is an actor template or actor record. The runtime object-event struct is closer to the actual actor. But the game logic remains data-backed because the actor’s identity and default behavior come from the map record. That distinction matters for advanced work. If you build your own creature RPG, you do not want your script VM to chase rendering objects directly. You want it to ask, “Which authored object is local ID 3 on this map?” or “Which actor started this interaction?” and let the event-object layer resolve that to a live runtime instance.

Keep the Battle Colosseum receptionist in mind. The receptionist object event in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, is our clean actor case. It is not merely decoration. It is a data record that says, in effect, there is an NPC at this place, with this appearance and behavior, and with this script entry point. When the player presses A facing that NPC, the input and interaction code we studied last time does not magically know “receptionist.” It finds an event candidate in front of the player, resolves that candidate to an object event, and then starts the script associated with that object.

And here is the important part: once the script is running, the script can still talk about actors at the level of object identity. It can face the player, turn the receptionist, lock movement, release movement, apply a movement sequence, hide or show objects, or check event variables. Those operations are conceptually object operations, but they are not authored as “mutate whatever sprite tile happens to be drawn at screen coordinate 112, 64.” They are authored around stable map identities and current script context.

Quick check for understanding. If the receptionist walks one tile left during a script, should her local ID change? No. Her local ID is not a coordinate. It is an authored identity within the map’s object-event list. Her runtime x and y can change. Her facing can change. Her movement state can change. Her visibility can change. But the authored identity is what allows later script commands to still refer to “that same map actor.”

Now let’s compare that to the coordinate triggers, because this is where many engine designs accidentally blur categories. The two player spot triggers in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, and the four coordinate triggers in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, are not actors. They do not walk, face, animate, or have a visible sprite. They are authored event regions, or more narrowly coordinate events. They say, “When the player reaches this coordinate, under the right variable condition, dispatch this script.” That is an event lookup keyed by position and state, not by actor identity.

So if I draw this as two flows, the object interaction flow begins with a visible or interactable object and resolves to the object’s script. The coordinate trigger flow begins with the player’s position after movement and resolves to a coordinate event script. Both may enter the same script VM. Both may use variables. Both may ultimately manipulate actors. But their entry mechanisms are different. That is exactly why this week’s lab asks you to implement trigger lookup for object, coordinate, background, and warp event categories separately. The categories are not just labels in a file. They imply different dispatch rules.

Let’s do a concrete example. Suppose in a simplified clone map we author four records. Record one is an NPC with local ID 1, script “ReceptionistScript,” at x 5, y 3. Record two is a coordinate trigger at x 7, y 6, variable VAR_COLLOSSEUM_READY equals 0, script “EnterSpotScript.” Record three is a background event at x 4, y 2, script “ReadSignScript.” Record four is a warp at x 10, y 8, destination map lobby. If the player presses A facing x 5, y 3, object lookup should win because the thing being queried is an object interaction. If the player steps onto x 7, y 6, coordinate lookup should run after movement completes. If the player presses A facing x 4, y 2 and there is no object there, background lookup should run. If the player steps onto x 10, y 8, warp handling should transition maps. Similar coordinates, different event categories, different runtime moments.

This is why I keep insisting that “data-driven” does not mean “one giant generic event list.” A strong data-driven architecture still preserves semantic categories. FireRed’s organization is practical about this. Map data is authored in different event sections, and the runtime asks targeted questions: object at this interaction position, coordinate event at this movement destination, background event at this inspected tile, warp at this tile. If you flatten all of those too early, your dispatcher becomes a pile of priority exceptions. If you separate them cleanly, the rules remain understandable.

Now, let’s connect that to the source packet’s wider architecture evidence. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the include list itself is a useful architectural clue. That file includes script.h, event_data.h, event_object_movement.h, metatile_behavior.h, fieldmap.h, field_control_avatar.h, and field_player_avatar.h, alongside battle and transition headers. The claim is not that battle_setup.c implements today’s whole object-event pipeline. The claim is narrower and more important: FireRed’s visible gameplay features are coupled through explicit module contracts. Field control, avatar control, map behavior, event data, object movement, scripting, and battle setup are separate modules, but they meet at gameplay transitions. A trainer battle, a scripted receptionist exchange, or a link-room entry flow is not a monolithic function. It is a chain through data records, globals, callbacks, tasks, scripts, structs, and concrete source functions.

That phrase should sound familiar because it is the course spine. Since week 1, we have been tracing data table to runtime state to callback or task to visible behavior. Today we are applying that same spine to actors. An object event is not merely “an NPC.” It is a data-backed actor whose authored record gives the runtime enough information to instantiate, find, and operate on that actor. Coordinate triggers are not “invisible NPCs.” They are position-dispatched script entries. Background events are not “stationary object events without sprites.” They are inspection-dispatched map events. Warp events are not “scripts that happen to move you.” They are map-transition records with their own dispatch timing.

Let me pause here and ask a diagnostic question. Imagine you are debugging a bug where stepping onto the Colosseum player spot does nothing, but talking to the receptionist works. Where do you look first? You should not start in sprite animation. You should not start in OAM. You should first compare the coordinate-event record, the trigger variable condition, and the movement-completion dispatch path. The receptionist working tells you object interaction and script startup may be fine. The player spot failing points toward coordinate trigger lookup or variable gating. That is exactly the kind of narrowing you need as advanced engineers.

Now turn that around. Suppose stepping onto the spot works, but pressing A on the receptionist does nothing. Then the coordinate trigger machinery is not your leading suspect. You look at the receptionist object event in BattleColosseum_2P map data, especially the local ID and script association in chunk_919152bd3bfceb95, and then you inspect the object interaction path. Does the runtime object exist? Is it visible? Is the player facing it? Does the object have a script? Is some event flag hiding it? Has the map object been instantiated from the authored record? You are following the category-specific trail.

This is also why local IDs are scoped. A local ID is not a global character ID across the entire ROM. Local ID 1 on one map and local ID 1 on another map are different authored objects. The map context is part of the identity. That is a good design for a cartridge RPG because it keeps map records compact and authoring manageable. You do not need a global registry entry for every person standing in every town. You need stable identities within the currently loaded map, plus global event flags and variables for cross-map progression.

Think about that distinction carefully. Local identity solves “which object on this map?” Global event data solves “what has happened in the world?” If a story event causes an NPC to disappear permanently, the object’s local ID lets the map script or loader identify which object to hide. The global flag records that the story event happened. The next time you enter the map, the object loader can consult event data and decide whether that authored object should be instantiated or hidden. That is the architecture pattern: local authored records plus global progression state.

We saw the same pattern earlier in the course with tasks and callbacks, just at a different layer. A task ID is not a permanent gameplay identity. It is a runtime handle into a task array. A main callback slot is not the game mode itself. It is the current function pointer used by the shell. Likewise, a runtime object slot is not the authored NPC identity. It is the current loaded actor instance. The local ID and map record are closer to authored identity; the object slot is execution machinery. If you confuse those layers, you produce bugs that are hard to save, hard to reload, and hard to script.

Let me write a small pseudo-flow on the board, not as code to memorize, but as a mental trace. Player presses A. Field input has already been polled; we covered that in the previous lectures, so I will not reteach it. The avatar-facing tile is computed; we also covered movement and collision foundations already. The interaction system checks for an object event at that facing location. If it finds a live runtime object, it resolves the associated object-event record and script pointer. The script system starts the script. The script may lock player movement, turn the object toward the player, display text, set variables, or call into other systems. When finished, control returns to field movement.

Now here is the same style of trace for coordinate triggers. Player attempts movement. Collision and tile behavior have already allowed the step; those were week 6 foundations. The avatar finishes the step. The field system checks coordinate events at the new map position. It compares not only x and y, but also any trigger variable condition. If the condition matches, it starts the coordinate event’s script. That script may set the variable so the event does not repeat, or it may branch based on party, link state, or story state. In the Colosseum examples, the player spots are coordinate triggers because standing in the correct place is the event, not talking to an actor.

This brings us to script labels. In the JSON-era representation you are seeing generated or extracted map data, the human-facing script reference may look like a label rather than a raw address. That is healthy. Labels are source-level names. The toolchain can later convert those labels into symbols or compiled script data. We discussed in week 2 that generated data and runtime data are not the same thing, and this is another instance. A map JSON record may name a script label. The compiled GBA program uses linked data, pointers, constants, and binary script streams. For course work, you should be fluent in both views: the authored source view that is good for reading and diffing, and the runtime view that explains what the engine executes.

This is why the lab asks you to diff the two-player and four-player Colosseum map records. A diff forces you to notice what is actually different in data. In the two-player case, the player spot triggers in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, represent a smaller coordination problem. In the four-player case, the coordinate triggers in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, multiply the same pattern. The architecture does not need a new hardcoded “four player room” function just to detect four standing positions. It needs data records that express four trigger coordinates and scripts that interpret the resulting state.

Check yourself on this. If tomorrow’s homework asks you to add a third waiting spot to a custom link room, what should change? Ideally, the map event data and the script logic that counts or validates occupied positions. What should not change? The generic coordinate-event dispatcher. The dispatcher should already know how to scan coordinate events for the current map and test whether the player’s current coordinate and variable condition match. Adding one more coordinate record should not require adding a special case to the overworld loop.

Now, there is a subtle runtime issue here. Object events can move. Coordinate triggers do not move unless the map data or script changes the conditions around them. So if an NPC walks away from its original tile, the object interaction system must use current runtime coordinates, not just the original authored x and y. But if a coordinate trigger is at x 7, y 6, that trigger remains at x 7, y 6 as a map event. This is one reason object event templates must become runtime structs. Authored position is a starting value; current position is runtime state. For coordinate events, authored position usually is the dispatch position.

And because object actors have runtime state, they also need movement code. Today’s source focus includes pokefirered/src/event_object_movement.c as the high-signal object-event movement source from the packet. We are not going to re-open all of movement boundaries or collision, because those were already treated in the prior tile and camera lectures. But architecturally, you should place event_object_movement.c in the actor-runtime column. It is where the data-backed actor stops being just a row in a map record and starts behaving as a moving entity in the overworld simulation. When scripts ask an NPC to turn, step, or animate, they are relying on that actor-runtime layer.

The advanced point is that the script VM should remain relatively declarative from the content author’s perspective. A script writer wants to say, “apply this movement to local ID such-and-such,” or “turn the object toward the player,” not manually update sprite coordinates, animation frames, collision occupancy, and facing state. The engine owns those details. That is the same separation we saw in the move relearner interface. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the comment describes a move relearner state machine with callback and menu states. The player-visible workflow is a sequence of screens and choices, but the implementation is structured as states and tasks. For object events, the player-visible workflow is an NPC walking or speaking, but the implementation is structured as authored records, runtime object state, scripts, and movement functions.

Let me give a small failure-mode example from engine design. Suppose you implement “hide NPC” by deleting the sprite object only. It looks correct until you leave and re-enter the map, at which point the NPC comes back because the authored object record still loads. The correct architecture says hiding an NPC for story reasons must set progression state, usually through event data, and the map-loading or object-visibility logic must consult that state. Deleting a runtime sprite is only a visual operation unless it is tied back to persistent world state. This is exactly the difference between runtime object slots and data-backed actor identity.

Another failure mode: suppose you implement a cutscene movement by directly changing the NPC’s authored x and y in the map data structure. On original hardware, map data is not meant to be your mutable save-state object. Even in a modern clone, mutating source-authored map records during play is a bad habit. The runtime object should move. If that movement needs to persist, store persistence separately, usually as event variables or save data. The map record remains the default template.

That is a broader course principle. Static data gives defaults. Runtime structs hold the current simulation. Event variables and save data hold durable progression. Callbacks and tasks schedule execution. Scripts coordinate authored behavior. Concrete functions implement the steps. That is the sentence I want you to be able to apply to any system in FireRed by the end of this module.

Let’s now fold in the receptionist and player spots one more time. The receptionist object is visible, local, interactable, and actor-like. The player spot triggers are invisible, coordinate-based, and dispatch-like. The receptionist can face you. A coordinate trigger cannot face you. The receptionist can have a movement type. A coordinate trigger has a condition. The receptionist has a runtime object instance. A coordinate trigger is evaluated against the player’s current position. Both can start scripts. Both can set variables. Both can participate in the same room flow. But they enter that flow through different architectural doors.

Here is the comprehension check I would ask on an exam. “A script starts when the player steps onto a Colosseum tile. The script then turns the receptionist toward the player and displays text. Which event category caused the script entry, and which event category supplies the actor being manipulated?” The answer is: the coordinate event caused the script entry, using the spot trigger data, while the receptionist is an object event manipulated later by the script. One script can absolutely involve multiple event categories. Entry category and manipulated entity are not always the same thing.

That is a sophisticated point, so say it back to yourself in engine terms. Dispatch source is not identical to script subject. A coordinate can dispatch a script that manipulates an object. An object interaction can dispatch a script that sets a global variable affecting a warp. A background event can dispatch a script that hides an object. A warp can move the player to a map whose load script changes object visibility. The event categories define how scripts begin and what records are queried, not a prison around what the script may later do.

We are going to use that idea heavily in the next lecture, “Step Triggers and NPC Script Entry.” There we will sharpen the timing question: exactly when after movement do step triggers evaluate, and how does that differ from pressing A on an object or background tile? For now, I want you to keep the model clean. Object event: data-backed actor. Coordinate event: position-backed dispatcher. Background event: inspection-backed dispatcher. Warp event: transition-backed dispatcher. Script: shared control language that can coordinate all of them.

Before we move on, let me tie this back to the assessment trajectory. Homework week 6 and quiz week 6 asked you to decompose a creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Week 7 raises the bar. It is no longer enough to say “overworld maps contain NPCs.” You need to say, “The map contains object-event records; those records instantiate runtime object actors; local IDs give scripts stable map-scoped handles; coordinate events are separate records evaluated after movement; variables gate repeatability and progression; scripts then bridge into movement, UI, link, or battle systems.” That is the level of answer expected now.

And one last source-backed connection before we continue. The research packet repeatedly frames FireRed as a stack of cooperating state machines plus data tables. We have already cited main.c and task.c in earlier sessions for AgbMain, SetMainCallback2, CreateTask, and RunTasks. Today, our narrower evidence comes from the Colosseum map chunks and from cross-module includes such as pokefirered/src/battle_setup.c, chunk_494d132a59422c85. The architecture is consistent across scales. At the top level, callbacks switch modes. Per frame, tasks dispatch active work. In maps, event records define possible interactions. In scripts, commands sequence authored behavior. In object movement, runtime actors execute movement state. Same pattern, different layer.

So, as we go into the next segment, keep one question in front of you: when I see a visible behavior, which stable data record authorized it, which runtime struct carried it, and which dispatcher decided that now was the moment to run it? That question will keep you from treating the overworld as a blob. It will let you trace from the receptionist’s local ID to her script, from a Colosseum standing spot to a coordinate trigger, and from a script command back into object movement without mixing up the categories.

Now, let’s make that question do real work. Imagine you are debugging the Battle Colosseum, and a tester says, “When I stand on the player spot, the link battle setup behaves differently in two-player and four-player rooms.” That sounds like a gameplay report, but architecturally it is a data lookup report. Your first instinct should not be, “Where is the animation?” or “Where is the battle code?” Your first instinct should be, “Which event category is being entered, and which map record supplied it?”

So I’m going to draw a small stack on the board. At the bottom, I’ll write map JSON. Above that, event arrays. Above that, map loading into runtime state. Above that, field control and dispatch. Above that, script execution or movement execution. And at the top, player-visible behavior. Now, notice what I did not draw as the bottom layer. I did not draw C logic. The authored facts of the room are not born in C logic. The C logic is the machine that interprets them.

For our concrete case, the two-player Colosseum map gives us two very different authored facts. One is the receptionist object event, identified in the research packet as pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. Another is the pair of player spot coordinate triggers, identified as pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. Those are not merely two entries in the same file. They represent two different kinds of authority. The receptionist says, “There is an actor here, with a local identity, a position, facing and movement properties, and a script label that can be entered through interaction.” The coordinate trigger says, “There is a tile position here that matters when the avatar steps onto it, and it may be guarded by a trigger variable or script condition.”

That distinction is easy to say and surprisingly easy to violate in your own engine. I see students do it every semester. They make an NPC class, and then they make floor triggers also be invisible NPCs, because both can launch scripts. Or they make one giant event array and distinguish behavior with a type enum, and then two months later the movement system is checking for background signs, or the sign-reading system is checking NPC facing. FireRed’s architecture is not perfectly abstract in a modern sense, but it is very disciplined about categories. Object events, coordinate events, background events, and warps have different meanings because they are entered by different gameplay questions.

Let me say that again more slowly. The category is not cosmetic. The category tells you what question the overworld is asking.

When the player presses A, the question is something like, “Is there an interactable thing in front of the avatar?” That takes us back to the interaction material from last week, and I am deliberately not re-teaching the polling and facing logic. You already worked through that. The important part today is that the A-button question privileges object events and background events in a way that step triggers do not. An NPC can be talked to because the avatar is facing an object event. A sign can be read because the tile in front has a background event. A warp can fire because the avatar’s position enters a warp location. A coordinate event can fire because the avatar’s current position matches a coordinate trigger and its conditions. These are not four skins over one mechanism. They are four contracts between authored data and runtime dispatch.

Now, let’s put the receptionist under the microscope. In the two-player Colosseum map, the receptionist object event from pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, is not just a sprite placement. It is a small data-backed actor declaration. It gives the engine enough stable information to create or refer to a runtime object event. The word “actor” is useful here, but be careful with it. I do not mean an actor in the heavyweight object-oriented sense, where every NPC owns its own full behavior loop and message inbox. I mean actor in the game architecture sense: an entity with identity, spatial presence, visual state, and entry points into authored behavior.

The local ID is one of the key pieces. A local ID is map-local identity, not global identity. That matters. If you are in the Colosseum, local ID 1 might mean the receptionist. On another map, local ID 1 might mean a completely different NPC. The map header and map event data provide the namespace. So if a script says to face or move or hide a particular object event by local ID, the engine interprets that local ID in the context of the current map, not as a universe-wide object number.

Here is the board cue. I’m writing “local ID plus map equals stable reference.” Not “local ID equals stable reference.” That second version is the bug. If you build your clone with globally unique NPC IDs from the beginning, that can work, but it is a different architecture. FireRed-style map data expects locality. Locality keeps map records small, lets content authors reason about one room at a time, and allows many maps to reuse simple numbering without coordination. The cost is that runtime code must always know which map context it is resolving against.

Now connect that to script labels. The receptionist’s object record points, directly or indirectly through generated data, to a script entry. That script is not running all the time. It is not a component update method. It is dormant authored behavior until the dispatcher enters it. This is where the callback and task material from week 1 becomes more than trivia. We saw in pokefirered/src/main.c, chunk_b35676322b169f85, that startup builds the runtime shell, and in pokefirered/src/main.c, chunk_6cfd90f6d0136799, SetMainCallback2 switches the major mode of execution. We also saw in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, that CreateTask allocates per-frame work, and in pokefirered/src/task.c, chunk_3ffebc0341225936, that RunTasks is the recurring dispatcher. That callback-task shell is the outer rhythm. The receptionist script only becomes visible behavior because some frame-level control path decides that interaction has occurred, script execution should begin, and the relevant script pointer or label should be interpreted.

Check for understanding here. If I ask, “Where is the receptionist’s behavior?” there are three acceptable answers at three different levels. At the data level, it is attached to an object event record in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. At the script level, it is the script label that the object event points to. At the runtime level, it is wherever the current callback, tasks, script context, and object event structs are cooperating to execute that script. If you answer only one of those, you are not wrong, but you are incomplete. Advanced architecture work is often about knowing which level of answer a debugging question requires.

Now let’s contrast that with the standing spots. In the same two-player map, pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, gives the two player spot triggers. These are coordinate events. They do not need a visible sprite. They do not need to be faced. They are not actors in the same sense as the receptionist. Their important fields are the coordinates and the trigger condition. A coordinate trigger is closer to a map-authored tripwire. If the avatar steps onto this coordinate and the event system’s conditions match, dispatch script behavior.

The four-player room makes the comparison stronger. The four-player Colosseum map has four coordinate triggers, identified in the packet as pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. That difference is content-driven. The engine does not need a new “four-player standing spot subsystem” just because there are four spots instead of two. The data record count changes. The lookup category stays the same. This is one of the reasons we keep returning to data-backed architecture. A new room configuration should often be an edit to map records, not a new branch in the central overworld loop.

Now, that does not mean no C code participates. Of course C code participates. The map JSON is not executing itself. The point is that the C code should be category-general where possible. It should know how to search coordinate events for the current map. It should know how to test whether the avatar’s position matches. It should know how to check the trigger variable or condition. It should know how to begin the associated script. But it should not ideally know that “this exact room has two spots” or “this exact other room has four spots,” except through the data it is interpreting.

Let’s pause and do a tiny mental lab. Suppose your implementation of trigger lookup has a function called TryStartStepEvent. You step onto a tile. The function checks warps first, then coordinate events, then maybe other tile behaviors. Now suppose the two-player Colosseum works, but the four-player Colosseum only triggers the first two spots. What is the likely class of bug? It is probably not that link battle setup forgot about players three and four. Maybe it is, but that is not your first suspicion. Your first suspicion is that your map event loader, array count, or lookup loop is capped at two coordinate events. The data in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, is telling you, “There are four records.” If runtime sees two, your data-to-runtime bridge is broken.

That is why this week’s lab asks you to diff the two-player and four-player Colosseum map records before implementing trigger lookup. The diff is not busywork. It trains you to let data differences predict behavior differences. In the two-player room, you expect two coordinate records. In the four-player room, you expect four. If your implementation has separate lookup functions for object, coordinate, background, and warp categories, your tests can be direct. Load map, inspect counts, step or query at coordinates, assert the appropriate dispatch result. If every event is blurred into one bucket, your tests get mushier, because now you are constantly asking whether the type flag, spatial rules, interaction mode, and script dispatch all agree.

Now I want to connect object events to movement, because the lecture title is not just “Map Events as Records.” It is “Object Events as Data-Backed Actors.” The movement side is where the actor metaphor earns its keep. The packet flags pokefirered/src/event_object_movement.c as the high-signal source for object-event movement. You should think of that file as the machinery that turns the static object event declaration into something that can occupy the map over time: facing, stepping, animating, obeying movement type, being frozen by scripts, and responding to commands. Again, I am not going to re-teach collision checks or camera-relative drawing. We covered that in the tiles and collision lectures. Today the point is narrower. Movement code is downstream of the object event record. The record authorizes that an object exists. The runtime struct carries mutable state. The movement functions advance that mutable state frame by frame.

Here is a concrete example. The receptionist may have an object event record with a fixed position and a movement type that keeps her in place or gives her a limited facing behavior. When the map loads, that record is instantiated into runtime object state. The sprite system can draw her because there is runtime state to draw. The interaction system can find her because there is runtime state with map coordinates and local ID. A script can cause her to face the player because it can resolve her local ID to that runtime object state and issue a movement or facing change. The original map record does not turn to face you. The runtime object does. But the runtime object exists because the map record declared it.

That distinction matters for save and reload behavior too. Some object state is ephemeral. Facing direction during a single conversation may not be a permanent authored fact. Some visibility or progression state may be controlled by flags or variables. The map data provides the default declaration, and event data can modify whether the player sees or interacts with that declaration under current story conditions. You have already seen event_data included across systems. For example, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes event_data.h alongside overworld, script, field control, field player avatar, event object movement, and battle transition headers. That include cluster is a useful architectural clue. Battle entry from the overworld is not isolated battle code. It sits at a boundary where event variables, map control, avatar control, object movement, scripts, and battle setup all need to cooperate.

Let me be precise about what that evidence supports. The include list in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, does not by itself prove the entire call graph for every Colosseum script. An include list is not a trace. But it does support the claim that the battle setup module lives at the intersection of event data, scripts, overworld state, object movement, and field control. That is exactly the kind of boundary we care about in this course: authored map event leads to script, script can lead to mode transition, and mode transition can lead out of overworld behavior into battle or link-related systems.

Now, check yourself with this question. If a coordinate trigger starts a script that eventually initiates a battle mode transition, is the coordinate trigger “battle code”? No. It is map event data that can dispatch a script. Is the script “battle code”? Not exactly. It is authored control flow that may call commands or specials connected to battle setup. Is battle_setup.c “map event data”? No. It is C runtime logic that can be reached from event-driven overworld control. Keeping those labels clean is what lets you change one layer without accidentally baking assumptions into another.

This is also why I keep saying source tracing rather than source searching. Searching is typing “Colosseum” or “battle” into ripgrep and opening whatever comes back. Tracing is following the architectural handoff. Data record. Generated or compiled representation. Runtime globals and structs. Callback or task dispatch. Script entry. Concrete C function. Visible behavior. Search is useful, but tracing is how you avoid false conclusions.

Let’s use the player spot example as a trace. I step onto the left standing spot in the two-player Colosseum. The visible behavior might be that the room recognizes I am in position for link play. At the data level, I should be able to point to the coordinate trigger records in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. At the runtime level, the current map’s event data must have been loaded into a structure that preserves the coordinate event count and entries. At the dispatch level, the field control logic must be checking step events after avatar movement commits. At the script level, the trigger’s script label or pointer must become the active script context. At the C boundary, if the script enters battle or link setup, modules like pokefirered/src/battle_setup.c, chunk_494d132a59422c85, show us the kind of include neighborhood where script, event data, overworld, avatar control, and battle transition meet.

Now compare that with pressing A on the receptionist. Data level: pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. Runtime level: an object event struct and associated sprite state exist for the receptionist. Dispatch level: interaction checks identify an object event in front of the avatar, not a coordinate event under the avatar. Script level: the receptionist’s script becomes active. Movement level: the object may face the player or be frozen, and pokefirered/src/event_object_movement.c is the packet’s source focus for that movement machinery. C boundary: the script may eventually call into link, battle, message, or menu systems depending on authored behavior.

Those two traces are parallel, but they are not identical. That is the whole lesson.

I’ll add one more advanced wrinkle. In a data-backed actor system, not every actor decision is stored in the actor record. Some decisions are stored in global event variables or flags. This is where trigger variables become important. A coordinate event may have a variable condition, so the same coordinate can mean different things at different story moments, or nothing at all if the variable does not match. That gives designers a way to stage progression without editing code. But it also creates a debugging obligation. If a trigger does not fire, you inspect both geometry and event state. Is the coordinate right? Is the map loaded? Is the trigger variable correct? Is the script pointer valid? Has another system temporarily disabled script dispatch? Advanced debugging is mostly disciplined elimination.

Think back to homework week 6 and the quiz where you decomposed a creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Week 7 is where that decomposition stops being a diagram and becomes a set of tracing habits. Object events sit in overworld maps, yes, but they call into event scripting. Event scripting can call into UI, battle, link, inventory, or save-affecting state. The runtime shell decides when per-frame work happens. Tasks keep ongoing operations alive across frames. The map record is stable; the runtime state is mutable; the script is sequential; the callback/task shell is temporal. You need all four ideas in your head at once.

Let me give you the clone-engine version. If you are building the lab implementation, you might define separate data arrays for objectEvents, coordinateEvents, backgroundEvents, and warpEvents. Your object event entry might include localId, x, y, elevation, movementType, spriteId, and script. Your coordinate event entry might include x, y, elevation or trigger kind, variable, requiredValue, and script. Your background event might include x, y, kind, and script or hidden item data. Your warp might include x, y, destinationMap, destinationWarp, and maybe elevation. Then you write lookup functions that match the dispatch question. FindObjectEventFacingAvatar is not FindCoordinateEventAtAvatarFeet. FindBackgroundEventFacingAvatar is not FindWarpAtAvatarFeet. Same map, different query.

If you do that, the Colosseum diff becomes straightforward. The two-player map has a receptionist object event and two player spot coordinate triggers. The four-player map has four coordinate triggers. Your code does not need a special Colosseum if statement. It needs a correct loader, correct category counts, and correct dispatch queries.

Let me check the room. Suppose I ask, “Why can’t we just make coordinate triggers invisible object events?” Someone might answer, “Because coordinate triggers don’t have sprites.” That is true, but shallow. The deeper answer is that they are entered by different dispatch semantics. An invisible object event would normally participate in object lookup, local ID resolution, maybe movement state, maybe facing interaction, maybe collision or occupancy depending on flags. A coordinate trigger participates in step-position lookup and variable-gated script dispatch. You could engineer around all of that, but then you are fighting your categories. FireRed’s category separation is a design lesson: do not give an entity runtime responsibilities it does not need merely because it can launch a script.

Now, I want to briefly connect forward to tomorrow’s lecture, Step Triggers and NPC Script Entry. Today we are classifying the records and tying object events to runtime actors. Tomorrow we will spend more time on the entry moment: when the avatar step completes, when coordinate events are considered, how that differs from pressing A, and how NPC script entry participates in field control. We will use the same Colosseum records, especially pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, and pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, because they are compact enough to reason about without drowning in map content.

For now, the segment synthesis is this. An object event is not just a sprite, and it is not just a script pointer. It is a data-backed actor declaration that becomes mutable runtime object state. A coordinate event is not a hidden NPC. It is a position-gated script entry record. A background event is not a weaker coordinate event. It answers a different interaction question. A warp is not a script with a destination. It is a map-transition record with its own dispatch semantics. And all of them live inside the same broader FireRed pattern we have been building since week 1: stable data records feed runtime globals and structs; callbacks and tasks provide temporal control; scripts sequence authored behavior; concrete C modules implement the operations that scripts and dispatchers request.

So when you open the packet sources after class, do not read the Colosseum JSON as isolated content. Read pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, as the receptionist’s actor declaration. Read pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, as the two-player standing-position trigger set. Read pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, as the four-player version of the same coordinate-event idea. Then keep pokefirered/src/main.c, chunk_6cfd90f6d0136799, and pokefirered/src/task.c, chunk_3ffebc0341225936, in the back of your mind as the execution rhythm that eventually makes those records matter on a frame. That is the bridge from authored map data to player-visible behavior, and that bridge is the central object of study for the rest of this week.

Now, the subtle point here is that none of these records is, by itself, “the NPC.” The record is an authored promise. It says, here is a local object with a local identity, a sprite choice, a coordinate, a facing direction, movement behavior, visibility conditions, and a script entry point. The runtime object, the thing that actually advances through frames, collides, faces the player, or becomes the subject of a script, is produced later when the map is loaded and the object-event subsystem instantiates the record into working state.

Let me say that more plainly, because this is the distinction that tends to separate a shallow reading from an architectural reading. If you point at the receptionist entry in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, and say, “that is the receptionist,” you are half right. It is the receptionist as authored data. But it is not yet the receptionist as a moving, addressable actor in the overworld loop. The actor emerges when that data is copied, indexed, transformed, and connected to per-frame systems. That is why today’s lecture title uses “data-backed actors,” not simply “objects” and not simply “NPCs.”

On the board, I’m going to draw this as a chain. First box: map JSON object-event record. Second box: generated map data and map header connection. Third box: runtime object-event storage. Fourth box: movement and facing update. Fifth box: script entry. Sixth box: visible behavior. And I want you to notice that only the first box is purely authored content. Every later box is runtime machinery.

This also gives us a more precise vocabulary for local IDs. A local ID is not a global pointer to a person in the world. It is not a universal NPC identifier. It is a map-scoped handle that allows scripts and event code to refer to a particular object event in the currently relevant map context. In a small map like the Battle Colosseum, that sounds almost trivial, because there are only a few actors. But in the larger architecture, map-scoped identity is an important compression technique. The engine does not need a global registry entry for “the receptionist in this one room” in the same way that it needs stable global flags or variables for story progress. The object can be local because the room is local.

Check your understanding here. If I ask, “Why not just give every NPC in the game a permanent global ID?” the answer is not merely, “because they did not.” The answer is that the game’s map model already scopes many objects by current map. The receptionist only needs to be distinguishable among the object events currently loaded or relevant. Story state, by contrast, often must survive across maps and across sessions, so it lives in flags, variables, save data, and script-visible state. Those are different identity problems.

Now connect that to the trigger records. The two player standing spots in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, are not actors in the same sense as the receptionist. They do not have sprites. They do not face the player. They do not wander. They are coordinate-sensitive dispatch records. When the player steps onto or interacts with the relevant coordinate condition, the system can dispatch a script or event behavior based on map position and trigger state. So in the same map file, we have two very different kinds of authored event data. One is actor-like. One is coordinate-like. Both become player-visible only when the overworld runtime checks them at the right time.

This is why I keep using the word “dispatch.” Dispatch is the engine answering the question, “Given this frame, this player state, this map, this coordinate, and this input or movement event, which authored behavior, if any, should run?” For A-button interaction, last week, the dispatch was shaped by facing direction, tile, object presence, and event lookup. We are not going to re-teach that path now. Just carry it forward. For step triggers, the dispatch is shaped by the player’s coordinate and trigger variables. For object events, dispatch may be shaped by local object identity and script labels. These are different lookup paths, but they all serve the same high-level purpose: convert a player situation into a scripted or engine-defined response.

The four-player Colosseum version gives us a clean comparative example. In pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, the authored coordinate trigger set expands to four positions. That is not a new engine concept. It is the same coordinate-event pattern scaled to a four-player room. This is exactly the kind of difference I want you to practice seeing in the lab. Do not begin by asking, “What new system exists in the four-player room?” Begin by asking, “Which records changed, and which runtime interpretation stayed the same?” In data-driven architecture, that question is gold.

Let’s make this concrete. Suppose the two-player Colosseum has a receptionist object event near the entrance and two standing spots for players. The receptionist object record says, in effect, place this visible actor here and associate interaction with this script label. The coordinate records say, in effect, when the relevant player stands at this map coordinate under the proper trigger conditions, route control to the waiting or link setup behavior. The receptionist is discovered through object-event interaction. The standing spot is discovered through coordinate-event evaluation. To the player, both feel like “the room works.” To the engine, they are different categories of authored records going through different dispatch paths.

That distinction matters when you debug. If the receptionist is visible but pressing A does nothing, you inspect the object-event script binding, facing interaction path, local ID behavior, and script entry. If the receptionist is missing entirely, you inspect object-event instantiation, graphics, visibility flags, and map object loading. If the standing spot does nothing when walked onto, you inspect coordinate trigger records, trigger variables, step-event dispatch, and whether the player actually lands on the expected coordinate. Same room, same user-facing feature, different failure surfaces.

Pause there. Does everyone see why “NPC event bug” is too vague? I would want a bug report to say whether the failure is in object presence, object interaction, coordinate trigger dispatch, script execution, or progression state. That is not pedantry. That is how you cut a multi-system overworld bug into a solvable problem.

Now, let’s place this in the FireRed execution rhythm one more time. The course packet points us back to pokefirered/src/main.c, chunk_6cfd90f6d0136799, for SetMainCallback2, and pokefirered/src/task.c, chunk_3ffebc0341225936, for RunTasks. Those are not map-specific sources, but they are the rhythm sources. They remind us that the game is not executing map JSON in some magical continuous way. The game has a main callback and a task system. The overworld runs because callback-controlled frame logic and task dispatch repeatedly give subsystems a chance to update. Data records matter because runtime code keeps revisiting the world state and asking what should happen now.

This is the larger pattern you should carry into the homework. Data does not act. Runtime code acts using data. Scripts do not float above the engine. Scripts run because the engine enters script execution from specific dispatch points. Tasks are not just miscellaneous callbacks. They are an explicit way to schedule per-frame or multi-frame behavior in a fixed update rhythm. Once you see those relationships, FireRed becomes much easier to read.

Let me use a deliberately small pseudo-trace, spoken out loud. The player enters the Battle Colosseum map. The map header and generated map content identify the relevant layout, object events, coordinate events, warps, and background events. The object-event subsystem creates runtime object state for the receptionist from the authored object-event record in BattleColosseum_2P, chunk_919152bd3bfceb95. During overworld frames, object movement and animation systems maintain that actor’s visible state. The player walks to the receptionist, faces her, and presses A. The field interaction logic, which we studied in the prior lectures, resolves the facing target and finds an object event. That object’s script pointer or script label becomes the entry into script behavior. If instead the player walks onto a standing coordinate, the coordinate trigger records from chunk_237e353ea279eb27 are the relevant authored source, and the step-trigger path is the likely entry. Different record category, different dispatch moment, same architectural chain.

I am intentionally using “likely” in that sentence because a professional reader does not invent certainty beyond the cited packet. What the packet gives us clearly is the map data chunks and the general source focus for object-event movement. When we want to make a stronger line-by-line claim about an exact function, we go to the C source and verify it. That is part of the discipline of this course. You can form the architectural hypothesis from the data records. You confirm the exact execution path in the concrete source functions.

Now, someone may ask, “Where do globals fit into this?” Good. Globals are the connective tissue in many GBA-era C engines. They hold current map state, player avatar state, object event arrays, script context, event variables, and task lists. This is not a modern dependency-injection architecture. It is a compact runtime where many subsystems communicate through shared global structures and carefully sequenced callbacks. That is why tracing FireRed requires you to follow both static data and mutable global state. If you follow only the JSON, you see what can exist. If you follow only the C functions, you may miss why a particular map behaves differently from another. If you follow only the script labels, you miss how the script is reached. You need all three.

Let’s connect this to the research packet’s broader synthesis. The packet describes FireRed as a stack of cooperating state machines plus data tables. That is exactly what we are looking at today. The Colosseum map records are data tables. The overworld callback is part of the active runtime shell. The task system provides frame-level scheduling, as represented by RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936. The script engine sequences authored behavior after dispatch. The movement source, pokefirered/src/event_object_movement.c, is the kind of module where actor-like object state becomes concrete movement and presentation behavior. And when other modules need to cross from field state into battles or special interactions, you often see wide include surfaces. For example, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes event_data, event_object_movement, metatile_behavior, field_control_avatar, field_player_avatar, and script-related headers alongside battle setup headers. That include cluster is not proof of one specific Colosseum path, but it is evidence of the broader architectural style: field systems, event data, scripts, movement, and battle transitions are not isolated silos.

That should also sharpen your sense of why we are using Battle Colosseum this week. It is not because the Colosseum is the most narratively interesting room. It is because it is a compact architectural specimen. It has object events. It has coordinate triggers. It has a two-player and four-player comparison. It touches link-room behavior without forcing us yet to unpack every link subsystem detail. So it is ideal for practicing the habit of diffing data records and then asking what runtime categories those records feed.

For the lab, when you diff the two-player and four-player Colosseum map records, I do not want a purely textual answer like, “this file has two things and that file has four things.” That is a start, but it is not advanced work. I want you to classify the changed records by event category. Are these object events, coordinate events, background events, or warps? What identity fields do they use? Are they position-sensitive? Are they script-bound? Are they visible actors? Do they rely on trigger variables? Then, for each category, state what kind of runtime lookup would need to find it. That last sentence is the important one. Data diff first, runtime implication second.

Let’s do a quick comprehension check. Imagine I give you a map with a hidden item on a tile, a door warp, a wandering NPC, and a step tile that starts a cutscene. Which of those is most like the receptionist? The wandering NPC, because it is an object event with visible actor state. Which is most like the player standing spots? The step tile that starts a cutscene, because it is coordinate-sensitive dispatch. Which is neither an object actor nor a coordinate trigger in the same sense? The door warp belongs to the warp-event category, and the hidden item is typically closer to a background or hidden-object interaction category depending on how the map data represents it. The point is not memorizing labels. The point is choosing the right lookup model.

Now, one more layer of sophistication. In many engines, a beginner imagines that the map “contains” behavior. In a data-backed engine, the map contains references to behavior. That difference matters. The map record can point to a script label, but the script implementation lives elsewhere in the generated script data or event script sources. The map can say that an object uses a movement pattern, but movement execution lives in engine code and movement tables. The map can define a coordinate trigger, but whether that trigger fires depends on runtime variables and the player’s movement state. So the map is not behavior in full. It is a routing surface.

That phrase, routing surface, is worth writing down. A map is a routing surface for actors, coordinates, warps, backgrounds, scripts, layouts, and progression gates. It does not own all of those systems. It brings them together at a place.

And this links week 7 back to the overall course plan. In week 1, we decomposed the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. In week 2, we looked at generated data and the toolchain, including the idea that authored records become C-consumable structures. In week 6, we studied tiles, collision, camera, and avatar movement enough that we can now treat movement and interaction as prior foundations. Today, week 7, we are narrowing in on the event layer that sits on top of those foundations. The player can move through the map because of the movement and collision systems. The world can respond meaningfully because object events, coordinate triggers, background events, warps, scripts, variables, callbacks, tasks, and globals cooperate.

That is also why the course increases difficulty here. Earlier homework could ask you to identify systems. By homework_w07, I expect you to trace a feature through categories. “Where is the data?” “Where is the runtime state?” “What dispatch path reads it?” “What callback or task rhythm gives it time?” “What script runs?” “What concrete source module implements the requested operation?” Those questions are now the standard.

Let’s return to the receptionist one final time as a synthesis example. As authored content, she is a record in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. As runtime presence, she becomes object-event state during map setup. As visible behavior, she is maintained by the overworld frame rhythm, ultimately under the callback and task architecture represented by SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799, and RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936. As interaction, she is entered through an object-event lookup path rather than a pure coordinate trigger. As authored sequence, her response is delegated to script behavior. And as a piece of course architecture, she demonstrates how a creature RPG turns records into actors without giving every actor a bespoke class.

That last point is important. FireRed is not object-oriented in the classroom sense where every NPC is an instance of a C++ class with methods. But architecturally, the engine still produces actor-like behavior. The “object” is distributed across a data record, runtime struct storage, movement code, script pointers, event variables, and frame dispatch. That distributed actor model is common in game engines, especially on constrained hardware. The actor is not one file. The actor is a relationship.

So when you read event_object_movement.c after this, do not approach it as “the NPC file.” Approach it as one participant in the actor relationship. When you read map.json, do not approach it as “the room file.” Approach it as the authored routing surface. When you read task.c, do not approach it as unrelated infrastructure. Approach it as the timing mechanism that lets multi-frame gameplay exist. And when you read a script label, do not approach it as the whole event. Ask how execution got there.

We will continue next time by shifting attention from object-event entry to step triggers and NPC script entry. That means we will spend more time on the moment after movement resolves: the player arrives on a coordinate, trigger conditions are checked, and the game decides whether to start a script, block control, or continue normal overworld updates. The Colosseum standing spots are our bridge into that discussion. They are small records, but they force the exact question that matters for week 7: how does the engine know that a position in a map is not just a position, but an event boundary?

For today, the closing synthesis is this: FireRed’s overworld is not a pile of hard-coded rooms. It is a data-backed event machine. Map records declare actors and trigger surfaces. Runtime globals and structs hold the current instantiated world. Callbacks and tasks provide the frame rhythm. Dispatch functions connect player situations to event categories. Scripts sequence authored responses. Concrete C modules perform movement, control, transition, and system operations. If you can trace one receptionist and one standing spot through that chain, you are no longer just reading FireRed content. You are reading the architecture.

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