# Step Triggers and NPC Script Entry

Lecture ID: lecture_w07_d02
Week 7, Day 2

## Objectives
- Identify the FireRed files, structs, functions, constants, data tables, and state variables that implement each major game system.
- Connect week 7 material to the overall course plan.

## Transcript
All right, let’s pick up exactly where yesterday’s architecture left us. Last time, we treated object events as data-backed actors: not “NPCs” in the abstract, but records loaded from a map, given local identities, assigned graphics, movement policies, elevations, coordinates, and script labels. Today we move one notch deeper into the question that usually separates a rough clone from a faithful engine: when the player takes a step, what code and data decide whether something should happen?

I want you to notice the word “step” here. Not button press. Not menu selection. Not battle turn. A step is a physical transition in the overworld state machine. In FireRed architecture, that matters because the player’s movement is not merely animation. It is a commit point. The engine says, in effect, “the avatar attempted movement, collision allowed it, the coordinates changed, now run the map’s consequences.” That is where coordinate events, warps, trainer sight checks, scripted floor tiles, and some link-room behaviors all become possible.

Now, before we go further, I’m going to put three phrases on the board: object event, coordinate event, background event. You already know the rough distinction from the prior lecture, but today we are going to make it operational. An object event is a placed actor, like the receptionist in `pokefirered/data/maps/BattleColosseum_2P/map.json`, specifically the chunk identified in our packet as `chunk_919152bd3bfceb95`. A coordinate event is a location-sensitive trigger, like the two player spot triggers in that same 2-player Colosseum map, `chunk_237e353ea279eb27`. A background event is usually a tile or map feature that responds to interaction, often through the A-button lookup path we already studied, rather than through the “I just completed a step” path. So these categories are not just content-authoring labels. They correspond to different entry points into event dispatch.

Let’s make that concrete. Imagine a player entering the 2-player Battle Colosseum. The receptionist is visually present. She is an object event. The map data says she exists, says which local ID identifies her, says where she stands, and says what script label should run when the interaction pathway reaches her. That is the object-event side of the map record. But the player spots in the room are not “people.” They are positions. When player one walks onto the correct coordinate, the engine is not asking, “Which NPC did you talk to?” It is asking, “Does this newly occupied coordinate match a coordinate event whose trigger variable and value allow it to fire?” That is a different question.

Check your understanding here. If I put the receptionist and a player spot on the same visible tile type, maybe both look like floor-adjacent interaction points, do they enter the script system the same way? No. They may both eventually produce scripts, and they may both use event variables or flags, but their lookup route differs. The receptionist is discovered through object-event interaction and local ID matching. The player spot trigger is discovered through coordinate-event dispatch after movement commits.

Now I want to connect this to our earlier weeks without reteaching them. In week 1 and week 2, we kept repeating the path “data table, runtime globals, callback or task, visible behavior.” That is still the spine. The runtime shell from `pokefirered/src/main.c`, including `AgbMain` in `chunk_b35676322b169f85` and `SetMainCallback2` in `chunk_6cfd90f6d0136799`, gives the game a place to run overworld logic. The task system from `pokefirered/src/task.c`, with `CreateTask` in `chunk_cb0bb91a147bfb59` and `RunTasks` in `chunk_3ffebc0341225936`, gives recurring behavior a per-frame dispatch mechanism. We are not reopening that machinery today. We are using it as the already-established runtime shell in which map events can be polled, animated, and advanced.

So, on the board, I’m drawing a thin pipeline. Start with map JSON. Then map header and event records. Then runtime event arrays or structs. Then player movement commit. Then event lookup. Then script entry. Then visible result. This pipeline is the architecture of today’s lecture.

The reason the Colosseum examples are valuable is that they are small but not toy examples. In `pokefirered/data/maps/BattleColosseum_2P/map.json`, the receptionist object event in `chunk_919152bd3bfceb95` demonstrates the actor path: a map-placed NPC with a script identity. In the same file, the two player spot triggers in `chunk_237e353ea279eb27` demonstrate coordinate triggers. Then `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`, gives us the four-player version, with four coordinate triggers. That lets us compare not only “object versus coordinate,” but also “same gameplay pattern scaled from two participants to four participants.”

That scaling is important. A weaker design might hard-code “if map equals BattleColosseum_2P and x equals this and y equals that, run this special thing.” FireRed’s architecture does not want every map behavior hard-coded into overworld C. It wants a generic event category: coordinate event. The map supplies the particular coordinates and script labels. The engine supplies the lookup and dispatch rule. That is one of the recurring lessons of this course: the engine is full of small, generic interpreters over data.

Let’s slow down on trigger variables, because this is where advanced learners often accidentally simplify too much. A coordinate event usually is not just “standing at x, y means script.” It often has a variable gate. Conceptually, the event says, “When the player steps onto this coordinate, check a variable; if it has the configured value, then run this script.” That means the coordinate event is both spatial and stateful. Space alone is not enough. State alone is not enough. You need both.

Why do that? Because the same tile can mean different things at different story stages. A spot in a link room can be inert until the room is ready. A route tile can trigger a rival only before the rival has been defeated. A hidden flow-control tile can act once, then never again, because a script sets a variable or flag after firing. This is what we mean when we say story progression is data-driven but not stateless. The data points into the script system, and the script system mutates state variables that later change what data can fire.

Now, we should distinguish variables and flags carefully. I am not going to re-lecture the whole event data system today, but I do want the distinction in your ears. A flag is typically a boolean fact: defeated, obtained, unlocked, hidden item taken. A variable is a small value: stage zero, stage one, stage two; selected mode; current room phase. Coordinate triggers like the ones we are studying are especially naturally paired with variables because they often represent phase-based progression. The trigger variable tells the map, “Only let this coordinate event be meaningful during this phase of the local scenario.”

The research packet points us to `pokefirered/src/battle_setup.c`, `chunk_494d132a59422c85`, as a useful cross-module signal because that file includes, among other things, `event_data.h`, `event_object_movement.h`, `metatile_behavior.h`, `fieldmap.h`, `field_control_avatar.h`, and `field_player_avatar.h`. I am not citing that because battle setup is our main topic today. I am citing it because it shows how FireRed’s systems are not isolated silos. Battle entry, overworld avatar control, metatile behavior, object movement, scripts, and event data are connected at real C include boundaries. When you trace a step trigger, you should expect to cross module boundaries.

Let’s do a professorly sanity check. If I ask, “What file proves that coordinate triggers exist in the 2-player Colosseum map?” you should answer with the data file, not a C file first: `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_237e353ea279eb27`. If I ask, “What file gives the receptionist as a data-backed actor?” same map, different chunk: `chunk_919152bd3bfceb95`. If I ask, “What file lets us compare the two-player pattern to a four-player pattern?” `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`. The point is not memorization of chunk IDs for its own sake. The point is that architecture claims should have source anchors.

Now let’s imagine the player pressing A facing the receptionist. The interaction route begins from field control and avatar facing. We covered that already, so I will not retread input polling or A-button event lookup in detail. The key detail for today is this: the object event has a local ID and script label in map data, so once the engine identifies the faced object event, the script entry has a concrete target. Local IDs matter because the map can have many object events, and scripts sometimes refer back to a specific actor. “Make the receptionist turn.” “Hide this NPC.” “Move that attendant.” Without local IDs, the script layer would have no stable local handle for actors on the map.

Now imagine the player simply walking onto a marked player spot. No A-button. No facing direction. No “which object am I touching?” Instead, after the movement completes, the event engine can inspect the coordinate events for the current map. The match key is coordinate plus elevation or trigger constraints, depending on the event category and engine path. Then it checks the trigger variable condition. If the condition is satisfied, script entry occurs. In a link room, that script may begin a readiness sequence, align participants, lock movement, or hand off toward link-related logic. We are focusing on the entry architecture, not the link protocol today, but the Colosseum makes a clean case study because the map’s purpose is very strongly tied to stepping onto designated positions.

Let me draw this on the board as two columns. Left column: NPC script entry. Data record: object event. Runtime identity: local ID and object event slot. Trigger action: interaction, usually A-button or forced script. Script target: object’s script label. Right column: step trigger entry. Data record: coordinate event. Runtime identity: coordinate and trigger variable condition. Trigger action: completed step onto tile. Script target: coordinate event’s script label. These two columns can both reach the same script VM, but they do not begin from the same evidence.

Here is a concrete clone-design example. Suppose you are implementing the lab system for this week. You have four event categories in your map schema: object, coordinate, background, warp. The tempting beginner move is to make a single function called `findEventAt(x, y)` and let it return “whatever event is there.” For this week’s architecture, that is too crude. The caller’s intent matters. If the player presses A, you search object and background events in the faced tile or current tile according to interaction rules. If the player completes a step, you search coordinate events and warps at the destination coordinate. If an NPC moves, you do not accidentally fire the player’s coordinate event just because an NPC crossed that coordinate, unless your engine explicitly supports NPC-triggered floor scripts. In FireRed terms, the player’s committed movement is a different dispatch moment than an object’s animation step.

So for the lab, I would expect four lookup functions or at least one dispatcher with four explicit modes. The point is clarity of entry path. Object lookup should preserve local IDs and script labels. Coordinate lookup should preserve trigger variable and trigger value checks. Background lookup should preserve facing and tile-interaction semantics. Warp lookup should preserve destination map, destination warp ID, and transition behavior. When you diff the 2-player and 4-player Colosseum records, you should be asking, “Which data varies, and which runtime rule stays stable?”

Now, pause for a check. In the 4-player Colosseum, if the map contains four coordinate triggers, does that imply four different C functions? No. It implies four data entries. The generic coordinate-event dispatch can iterate or search those entries. That is exactly the design payoff. The map author scales the scenario by adding records, while the engine’s step-trigger rule remains the same.

This is also where we connect week 7 to the full course plan. Weeks 6 through 8 are our Input, Events, and Story Progression module. Week 6 gave us the map and movement foundations. We used metatile data, tile behavior, collision, camera-relative drawing, field input, and avatar movement as prior machinery. This week, we are converting movement into meaning. Next, as we continue into the scripting and progression material, these step triggers become one of the cleanest examples of how physical space becomes story control. A creature-collector RPG is not just battles and party data. It is a world where walking to a coordinate can start a conversation, block a route, begin a rival encounter, open a link workflow, or transition maps.

One more source-grounded connection before we push deeper. The packet’s synthesis names `pokefirered/data` as a recurring course module around maps, elevation, JSON, and trainer data, with `pokefirered/data/maps/BattleColosseum_2P/map.json` and `pokefirered/data/maps/BattleColosseum_4P/map.json` as anchors. That is precisely why I am emphasizing the data-first reading. Then `pokefirered/src` is the C runtime side, where modules such as `event_object_movement.c` are high-signal for object-event movement behavior. We have already used that movement source conceptually in prior lectures, so today we only need the bridge: object events are not just records sitting in JSON; they become runtime actors that movement and script systems can address.

Let’s close this opening segment with a compact synthesis, not the end of the lecture, just a checkpoint. A FireRed map contains multiple kinds of actionable records. The receptionist in the 2-player Colosseum is an object event, anchored in `pokefirered/data/maps/BattleColosseum_2P/map.json`, `chunk_919152bd3bfceb95`, and she demonstrates NPC script entry through actor identity. The player spots in that same map, `chunk_237e353ea279eb27`, demonstrate coordinate-trigger entry through a completed step and a state gate. The 4-player map, `pokefirered/data/maps/BattleColosseum_4P/map.json`, `chunk_30cd3fd6e624b8c6`, demonstrates that the same rule scales by adding coordinate records, not by multiplying bespoke engine logic. And the larger shell we built in earlier weeks, from `AgbMain`, `SetMainCallback2`, `CreateTask`, and `RunTasks`, gives these event systems somewhere to live per frame.

Now keep that two-column model in mind, because next we are going to walk through the lifecycle of one step: before movement, during movement, after coordinate commit, trigger lookup, script launch, and field control lockout. That is where the architecture stops being a list of event categories and becomes an actual runtime sequence.

Good. So, step lifecycle. Let me slow this down, because a lot of advanced engine comprehension comes from refusing to hand-wave the phrase “the player steps on a trigger.” That phrase sounds atomic. It is not atomic. It is a compact description of several cooperating decisions.

On the board, I am going to write this as a pipeline. First, input produces intent. Second, the avatar movement system validates and begins movement. Third, the map position is committed after the movement completes. Fourth, the event layer asks whether the new position matches anything interesting. Fifth, if it does, field control is constrained so the script can own the next few frames. Sixth, the script system advances until it yields control back to the overworld.

Now, notice what I did not say. I did not say the trigger “runs while the player is walking across the tile.” For the class of coordinate triggers we are talking about today, the clean mental model is after-step dispatch. You finish a step, your coordinates now identify a cell in the map event tables, and then the engine gets a chance to test whether that cell contains a coordinate event whose conditions pass. That distinction matters when you implement this in the lab. If you test too early, before committing the destination coordinate, you will accidentally fire the trigger under the player’s old feet. If you test continuously while the walking animation is in progress, you may fire twice, or fire while field control still thinks normal movement is active.

Let us use the 2-player Battle Colosseum as the concrete case. In pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, the two player spots are coordinate trigger records. They are not object events. They do not have a sprite, they do not have a local actor identity, and they do not become visible NPCs in the object array. They are data saying, in effect, “when a player’s committed map coordinate equals this point, and the relevant trigger state allows it, enter this script.” Contrast that with the receptionist from pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. She is an object event. Her script entry is attached to an actor record. If I press A facing her, the event lookup path is asking, “is there an object event in front of me that can be interacted with?” If I walk onto a player spot, the lookup path is asking, “does my new coordinate match a coordinate event whose gate is open?”

Those two sentences are short, but they are the architecture. Actor entry versus coordinate entry. Facing-and-interacting versus arriving-and-matching.

Quick check for understanding. Suppose I delete the receptionist object event from the 2-player map but leave the coordinate triggers alone. Can I still step onto the player spots and have their coordinate scripts be eligible? Yes, because those records live in a different event category. Now reverse it. Suppose I leave the receptionist but delete the coordinate records. Can talking to the receptionist still work? Yes, assuming her script and local object record remain valid, but the floor spots no longer have automatic after-step entry. The player may stand there, but the map event table no longer says that standing there means anything.

This is why the week 7 homework is not asking you merely to identify files. By now, after homework_w06 and quiz_w06, you should be able to decompose the RPG into cooperating systems. For week 7, the bar moves upward. I expect you to describe the exact category of data, the runtime structure it feeds, and the dispatch condition that causes behavior. “It is in the map JSON” is no longer enough. We want: this map JSON record becomes part of the map’s event data; object events become data-backed actors with local IDs and script labels; coordinate events become positional gates evaluated after movement; background events are queried by tile position and interaction mode; warp events are transition triggers. That is the level of answer that tells me you are tracing architecture rather than memorizing file paths.

Let me add a board note here. I am writing “local ID is not script identity.” In object events, the local ID is the map-local actor handle. It lets scripts, movement commands, and engine routines refer to a particular object within this map’s object-event set. The script label or script pointer is the entry point for behavior. Those cooperate, but they are not the same idea. A receptionist can have a local ID, a graphics ID, movement type, coordinates, trainer or non-trainer behavior, and a script pointer. The local ID helps the engine identify that actor. The script pointer tells the script engine where to start when the actor is activated.

Coordinate triggers have a different problem to solve. There is no actor to identify. The coordinate itself is the selector, and the trigger variable is the state gate. This is one of the cleanest examples in FireRed of data-driven control flow: the same engine machinery can search coordinate event records, but the map author controls how many records exist and where they are placed. That is exactly what we see when comparing pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, with pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. The 4-player version adds four coordinate triggers. The concept does not require four custom C functions named “handle player one spot,” “handle player two spot,” and so on. The data table expands; the generic lookup remains the engine’s leverage point.

Now let us place this inside the larger runtime shell without redoing weeks 1 and 2. Earlier, we used pokefirered/src/main.c, chunk_b35676322b169f85, for AgbMain startup responsibilities, and pokefirered/src/main.c, chunk_6cfd90f6d0136799, for SetMainCallback2. We also used pokefirered/src/task.c, chunk_cb0bb91a147bfb59, for CreateTask, and pokefirered/src/task.c, chunk_3ffebc0341225936, for RunTasks. The important connection today is not startup itself. The important connection is that overworld event behavior needs a frame-by-frame home. Movement, field effects, scripts, message boxes, link-room waiting states, and control locks are all temporal. They unfold over multiple frames. The callback and task shell is what lets a step begin on one frame, animate across later frames, and then allow event dispatch when the movement result becomes stable.

So if you are building your clone engine, do not write your coordinate trigger system as a direct function call from raw button input. That is too early in the lifecycle. Button input says, “the player wants to move north.” Collision and movement say, “the player can move north, and the step is now in progress.” The coordinate trigger says, “the player has arrived at the northern tile, and now this final coordinate can be tested.” That sequencing is not decorative; it is what prevents the event system from racing the movement system.

Let me give you a concrete failure case. Imagine a lab implementation where the input handler sees the D-pad press, calculates the intended destination, and immediately runs the coordinate script for that destination before the avatar movement has finished. In a simple hallway, maybe nobody notices. But in a link-room style map like the Battle Colosseum, that can be disastrous. The script may lock field control, start text, or begin a synchronization state while the avatar is still visually between tiles. If the script then checks player position, object facing, or link-room participation state, it may observe a half-updated world. FireRed’s architecture avoids that kind of confusion by treating movement and event entry as staged responsibilities.

This is also why source focus on pokefirered/src/event_object_movement.c is useful today, even though we are not re-teaching object-event movement boundaries. That file is the high-signal object-event movement source in the packet, and it reminds us that object events are not just inert JSON rows. Once instantiated, they participate in a runtime movement system, with state, animations, and per-frame updates. The map record gives you the actor seed; the runtime object system gives that seed behavior over time. Coordinate events, by comparison, do not need continuous animation state. Their power is in lookup and script entry.

Now, watch the taxonomy carefully. Object events and coordinate events are both “events” in a designer’s everyday language, but the engine category matters. Object event: visible or at least actor-like entity, local ID, map object state, potential movement, interaction entry. Coordinate event: invisible position-sensitive record, trigger variable or state condition, script entry on arrival. Background event: usually tied to inspecting something at a tile or region, often with the A-button interaction path. Warp event: position-sensitive transition that loads a different map or coordinate. The lab asks you to implement lookup for object, coordinate, background, and warp categories precisely because a professional engine must not collapse them into a single vague “event” list unless that list still preserves dispatch mode, conditions, and result semantics.

Let us do a quick spoken exercise. I will describe an interaction, and you tell me which category owns first claim on it. The player presses A facing a receptionist sprite. Object event. The player walks onto a marked spot and a link-room script starts. Coordinate event. The player presses A facing a sign on the wall. Background event. The player steps on a door tile and changes maps. Warp event. Good. Now the advanced version: all four may ultimately enter the script system or map-transition system, but their entry predicates differ. That is the part you need to keep crisp.

I am going to underline “entry predicate” on the board. In architecture, an entry predicate is the condition that says, “this subsystem is allowed to take control now.” For an object NPC script, the predicate might include player interaction, facing direction, object presence, and script availability. For a coordinate event, it includes current coordinate and trigger state. For a warp, it includes current coordinate, elevation compatibility, and map transition data. For a background event, it includes the inspected tile, facing, and event type. When students blur these together, their clone engines become hard to debug because every event seems to be “almost the same.” They are not the same at the boundary. They may share downstream machinery, but they differ at entry.

That brings us to script launch and lockout. Once a coordinate event or NPC interaction chooses a script, the overworld cannot keep accepting ordinary movement as if nothing happened. The script may display text, move objects, set flags, change variables, wait for animation, start a battle, or transition to another scene. We saw this broader pattern in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where the battle setup source includes overworld, script, event data, event object movement, metatile behavior, field map, field control avatar, field player avatar, and battle transition headers. I am citing that chunk not because we are studying battles today, but because it demonstrates a recurring FireRed design truth: major visible behavior often sits at the intersection of overworld state, script state, event variables, and a specialized subsystem. Battle entry is one example. Step-trigger entry in a link room is another smaller example.

When field control is locked, that does not mean the CPU stops running overworld code. It means player agency is constrained while scripted behavior or transitional behavior owns the relevant state. The callback still runs. Tasks still run. Animations may continue. Text boxes may update. Link communication may continue. But the ordinary input-to-avatar-movement path is no longer allowed to freely initiate new steps. In other words, lockout is not global paralysis; it is controlled ownership.

That phrase, controlled ownership, is worth keeping. It will return in week 8 when we talk more about story progression and persistence. A script can temporarily own the field. A battle can temporarily own the main callback. A menu can own input interpretation. The save system can persist results after the script sets flags and variables. FireRed’s architecture is not one giant function that knows everything. It is a set of state machines that pass control carefully.

Now, let us connect this to trigger variables. In map data, coordinate events commonly include a variable and expected value, or some state gate with similar purpose. Why? Because a coordinate should not necessarily fire forever. In a link-room lobby, a player spot may mean one thing while waiting for participants, another thing once a session begins, and nothing after a setup script has advanced. The coordinate is stable; the meaning of the coordinate is state-dependent. That is why the trigger variable is part of the design. It lets the same map geometry participate in a progression state machine.

Here is a clone-engine example. Suppose your map has two pressure plates. In phase zero, stepping on either plate starts a tutorial prompt. In phase one, stepping on both plates opens a door. In phase two, the plates are inert. You could solve this by deleting and recreating trigger records at runtime, but that is clumsy. A cleaner FireRed-like model is to keep coordinate records in the map data and gate them through variables. The record says where the trigger lives; the variable says whether this script is currently eligible. That separation is simple and powerful.

Check yourself on this: what changes more often during play, the coordinate record or the trigger variable? The trigger variable. The coordinate record is map-authored data. The variable is runtime story or session state. If your implementation mutates map data every time the story advances, you are probably mixing design-time facts with runtime state. Sometimes engines do patch or hide map elements, yes, but as a default architecture, stable records plus runtime gates is easier to reason about and easier to save.

Now I want to briefly revisit the receptionist through that same lens. Her object-event record is stable map data. But her behavior can still branch based on flags, variables, party state, link state, or script-local logic. Object identity chooses the script entry point; script execution can then inspect progression state. For the coordinate trigger, the progression state can be part of the dispatch predicate before the script even starts. That is a subtle distinction. In one case, the actor is activated and the script decides what to say or do. In the other, the coordinate event may be skipped entirely unless the variable matches.

Let me say that again in a more operational way. NPC interaction often dispatches first and branches inside the script. Coordinate triggers often branch at lookup time through the trigger gate, then dispatch. There are exceptions and mixtures, but that is a useful default model.

This is the level of nuance I expect in your diff of the 2-player and 4-player Colosseum map records. Do not merely report, “the 4-player map has more triggers.” Ask what stayed invariant. The engine category stayed invariant. The record shape stayed conceptually invariant. The dispatch rule stayed invariant. What changed is cardinality and coordinates: two player spots in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, versus four coordinate triggers in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. That is data scaling. When architecture is healthy, increasing the number of designer-authored cases should mostly increase data, not multiply engine branches.

Slide cue here: imagine a small table with four columns: event category, lookup moment, identity key, and result. For object events, lookup moment is interaction or movement-related object processing; identity key is local object identity plus position and facing relationships; result is script entry or object behavior. For coordinate events, lookup moment is after coordinate commit; identity key is x, y, elevation or map context, plus trigger variable; result is script entry. For background events, lookup moment is inspection; identity key is target tile or region; result is text, script, hidden item, or other interaction. For warp events, lookup moment is stepping onto a warp coordinate; identity key is position and warp data; result is map transition. You do not need to draw the exact table in your notes, but you should be able to reconstruct it from first principles.

Now, because this is an advanced course, let us talk about the dangerous abstraction. Many of you, especially those with modern engine experience, will want to create a base Event class with subclasses for ObjectEvent, CoordEvent, BgEvent, and WarpEvent. That may be fine in your own clone, depending on language and runtime. But do not let the inheritance diagram hide the dispatch differences. A shared interface like tryActivate(player, world) can become too vague if every category needs different timing. The coordinate event should not be tested from the A-button path. The background sign should not fire just because you step next to it. The receptionist should not activate merely because your coordinate equals hers; she needs the interaction predicate unless she has a separate scripted movement or collision behavior. A shared type is only useful if it preserves the schedule of lookup.

In FireRed terms, this is why we keep returning to “data table to runtime globals to callback, task, or script to visible behavior.” The data table is not the whole explanation. The callback/task/script schedule is what decides when the data matters. Earlier source examples from pokefirered/src/daycare.c, chunk_119931105f546346, pokefirered/src/learn_move.c, chunk_af3598df6f092723, and pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, all show, in their own domains, the same broad dependency pattern: include the shared global contracts, use constants and event data where needed, and organize visible behavior through state machines or menu/script flows. We are not diving into daycare or move relearning today. I am pointing out the architectural recurrence. FireRed repeatedly turns authored data and persistent state into a runtime sequence mediated by specialized systems.

Let us bring it back to the one step. The player presses a direction. Prior weeks gave us the input polling and avatar movement attempt flow, so we will not re-teach that. The movement system decides whether a step can begin, using collision, tile behavior, object occupancy, and the other foundations you already built. The step animates. During that animation, object-event movement code and field update code keep the world coherent. At the end, the avatar’s logical coordinate becomes the destination coordinate. Now the coordinate event lookup is meaningful. The engine can search this map’s coordinate event records for matching x and y, check the relevant trigger state, and if a record qualifies, start the associated script. Once script launch begins, ordinary field input is locked or redirected until the script yields.

If you implement only one idea from today in your workshop, implement that separation: intent, movement, commit, lookup, script ownership. That separation will save you from a surprising number of bugs.

Here is one more check for understanding before we continue. Why is the 4-player Colosseum evidence for a data-driven design rather than just a bigger map? Because the four player spots are represented as additional coordinate trigger records in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. The engine does not need a new conceptual event category for “four-player spot.” It reuses the coordinate event category. The map data expresses the number and placement of spots. The runtime lookup consumes the records generically.

Good. Hold that in your head, because next we are going to take the same lifecycle and put the object receptionist back into it. We will compare what happens when the player faces an NPC and presses A against what happens when the player simply arrives on a coordinate, and the comparison will expose exactly where local IDs, script labels, facing direction, and field control lockout enter the sequence.

Now, let’s put the receptionist back into the model.

Imagine the player is standing one tile south of the receptionist in the two-player Colosseum. The receptionist is not a coordinate trigger. The receptionist is an object event record. That distinction matters because the player is not activating the script by stepping onto the receptionist’s tile. The player is activating the script by directing an interaction toward an actor. So, if I draw this on the board, I want two arrows. One arrow goes from the player’s current position to the tile in front of the player. The second arrow goes from that target tile to an object-event lookup. That is different from the coordinate trigger case, where the player’s own destination tile is the event key.

So, board cue here: left side, “step trigger.” Right side, “NPC interaction.” Under step trigger, write “player arrives at coordinate.” Under NPC interaction, write “player faces object and presses A.” These are both event entry paths, but they are not the same path.

The receptionist example from pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, is useful because it keeps the data boundary visible. The map data defines that this receptionist exists as an object event. That means the receptionist has a local identity on that map, a graphics identity, a position, movement properties, and, crucially for us today, a script label or script pointer that the runtime can enter when the player interacts with that object. The game does not need to ask, “Is this the Colosseum receptionist?” in some hardcoded global branch. It can ask the generic question: “Is there an object event in the target tile, and if so, what script is associated with it?”

That is the data-driven actor model from last time, but now we are emphasizing the entry point. The object event does not merely decorate the map. It is an addressable participant in the event system.

Compare that to the two player spot triggers in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. Those records are coordinate events. They do not need an actor to be facing the player. They do not need an A-button press. They are eligible when the avatar arrives at the matching coordinate, and then the coordinate event can inspect trigger variables or launch its script according to the record’s conditions. The four-player version, pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, scales this same idea by adding four coordinate trigger records. Again, more records, not a new engine subsystem.

Now, let me pause and sharpen a detail that advanced readers sometimes blur. “Script label” and “local ID” are not the same thing. Say that to yourself. Local ID is how this object is identified within the map’s object-event set. Script label is executable event content or an entry point into that content. The local ID lets the engine and scripts refer to “that object on this map.” The script label tells the event VM what to run when that object is invoked, or when a script wants to manipulate or query it. Those concepts often appear near each other in map data, and because they are both small tokens in the data, it is tempting to treat them as one vague thing called “NPC identity.” Don’t do that. In an engine architecture discussion, identity, placement, rendering, movement, and script entry are separate responsibilities that happen to be packed into one object-event record.

Here is a concrete mental trace. The player walks up to the receptionist. The overworld is already running inside the callback and task shell we have been building since week 1. That shell matters because the event system does not float in space. We saw that the runtime starts through AgbMain in pokefirered/src/main.c, chunk_b35676322b169f85, and that callback handoff is made explicit by SetMainCallback2 in pokefirered/src/main.c, chunk_6cfd90f6d0136799. Earlier, when we studied task allocation, CreateTask in pokefirered/src/task.c, chunk_cb0bb91a147bfb59, and RunTasks in pokefirered/src/task.c, chunk_3ffebc0341225936, gave us the frame-by-frame machinery. I am not reopening that whole lecture. I’m using it as the skeleton. The receptionist interaction is not magic. It is a data record being reached through the overworld’s current callback and task-controlled frame loop.

So what changes when the player presses A? The input layer has already told the overworld, effectively, “There was an interaction request.” We covered the polling and A-button lookup in the previous lectures, so I will not re-teach it. What matters now is the target calculation. The avatar’s facing direction determines which adjacent coordinate should be tested. If the player faces north, the engine tests the tile north of the player. If the player faces east, it tests east. That gives the interaction system a target coordinate, and now the system can ask: is an object event occupying that target coordinate?

If yes, we enter the object-event path. The runtime object that corresponds to the data-backed NPC is found. Its local ID and current map context keep the lookup scoped. Then the object’s script label becomes the bridge from “thing in the world” to “scripted behavior.” At that point, field control normally locks down. The player cannot continue walking away while the receptionist opens text, starts a menu, or initiates a link-room flow. That lockout is architectural, not cosmetic. It protects the script from running while the spatial assumptions underneath it are changing.

Let’s make that lockout concrete. Suppose the receptionist script displays a message and then expects the player to choose an option. If player movement were still accepted during the message, the player could step onto a coordinate trigger halfway through the receptionist conversation. Then two event entry paths would be competing for authority: the object script from the receptionist and the coordinate trigger from the floor. A robust overworld event system prevents that by giving field control to the active script or event process. This is the same broad pattern we have seen elsewhere in the course: one visible behavior is usually the product of data, a runtime state variable, and a state machine that decides who is allowed to act this frame.

Check for understanding: if I press A while facing the receptionist, what is the event key? It is not my current coordinate. The key is the target tile in front of me, plus the object event found there. If I step onto a player spot trigger, what is the event key? My arrived-at coordinate, plus the coordinate event records attached to that map. Good. And if both involve scripts, why are they not the same category? Because they are discovered by different runtime queries.

Now, let’s bring in trigger variables. Coordinate events frequently have a trigger variable and expected value. That gives the map designer a way to say, “This coordinate only does something when the story or room state says it should.” In a Colosseum room, those spots can represent readiness positions. A coordinate can be meaningful only while the room is in a setup phase. Later, the same physical coordinate might no longer dispatch the same behavior. So the coordinate event is not merely “x, y, script.” It can be “x, y, trigger condition, script.” That condition is the reason coordinate triggers are useful for story progression and room-state progression, not just stepping on switches.

Object events also interact with state, but in a different style. An NPC might be hidden or visible based on a flag. It might have movement type data. It might expose one script before a story flag and another script after, depending on how the script itself branches. The engine’s first lookup is still “object at target tile,” but once the script starts, it can read and write event variables and flags. That is why, in your homework this week, I do not want you to stop at “object events have scripts” or “coordinate events have scripts.” I want you to trace where the dispatch begins, what data category was searched, and which state variables can gate the result.

Let me write a small pseudo-table verbally. For object interaction, the record category is object event. The activation is facing plus A-button. The spatial query is the tile in front of the avatar. The identity tool is the object’s local ID within the map. The script entry comes from the object’s script reference. For coordinate trigger, the record category is coordinate event. The activation is movement arrival. The spatial query is the avatar’s new coordinate. The gating tool is commonly a trigger variable and expected value. The script entry comes from the coordinate event record.

That comparison is the core of today’s second half.

Now, because we are in week 7 and you have already done the week 6 decomposition work, I am going to ask you to connect this to the larger architecture. A creature RPG is full of systems that look unrelated from the player’s perspective. Talking to a receptionist, stepping onto a link-room spot, starting a battle, opening a party menu, using an item, saving the game. But architecturally, FireRed keeps reusing the same design habit: data tables describe possible behavior, runtime globals and structs capture current state, callbacks and tasks advance the world frame by frame, and scripts or specialized state machines handle modal sequences.

We can see that habit outside this exact map system. For example, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes event and overworld-facing dependencies such as script, event data, object movement, metatile behavior, field control, player avatar, battle transition, and battle communication headers. I’m citing that not because we are teaching battle setup today, but because it is evidence of the boundary between overworld events and battle entry. A script or overworld condition can lead toward battle, but the battle system is still a separate state machine with its own transition and setup responsibilities. The event system is a caller. It is not the entire game.

Likewise, pokefirered/src/daycare.c, chunk_119931105f546346, pulls together event_data, script, party menu, storage, pokedex, field transitions, trade-related code, and UI helpers. That kind of source file is a reminder that many visible features are hybrids. The daycare is not “just script,” not “just party data,” not “just menu UI.” It is a scripted service attached to overworld interaction that reaches into creature data and menu state. When you learn NPC script entry correctly, you are learning the front door into many of these hybrid systems.

And this is why I keep insisting on file names, structs, functions, constants, data tables, and state variables instead of just concepts. “Talk to NPC” is a player concept. “Object-event record with a script label reached through facing-direction A-button lookup” is an engine concept. “Step on spot” is a player concept. “Coordinate event record gated by trigger variable and dispatched on movement arrival” is an engine concept. Advanced architecture work means you can translate between those two layers without losing precision.

Let’s do a quick scenario. Suppose you are implementing a minimal clone for the workshop. You have four categories to support: object events, coordinate events, background events, and warp events. The lab asks you to implement trigger lookup for all four. You should not make one giant function called checkEverythingInteresting. That name is funny, but the design would be mushy. Instead, you want one clear dispatch moment per category. Object interaction uses a directed adjacency query. Coordinate events use arrival coordinate lookup. Background events use investigation or tile-based contextual lookup, often for signs, hidden items, or readable surfaces, depending on the engine’s category definitions. Warp events use movement into a warp coordinate and then transition to another map or location. The categories can share data structures and helper functions, but their activation semantics are distinct.

This is where many student clone engines accidentally become harder to extend than FireRed. They put all events in one array with fields like type, x, y, script, destination, message, npcId, flag, and then every event has half-empty fields. That can work for a prototype. But it erases useful invariants. An object event has actor-like properties. A warp has destination properties. A coordinate trigger has trigger-variable gating. A background event has its own interaction semantics. The production engine benefits from treating these as separate record categories because lookup is cheaper to reason about and behavior is easier to validate.

Now, check your understanding with a more subtle question. The receptionist is visible on the map. Does visibility alone make it interactable? No. Visibility is not enough. The runtime must have an active object event, the player must target it through facing and interaction, and the object must expose an appropriate script entry. An object could be visible but temporarily not eligible, or hidden by state, or physically present but using a different script branch. Conversely, a coordinate trigger may be invisible to the player but still active when the state variable matches. Visibility and dispatch eligibility are related design concerns, not the same field.

Let me put one more board cue up: “Eligibility is layered.” First layer, is the data record present on this map? Second layer, does the activation path find it? Third layer, do state variables or flags allow it? Fourth layer, once the script begins, does the script branch into the behavior the player expects? That layered model is a strong way to debug these systems.

Imagine a bug report: “Player one spot in the four-player Colosseum does nothing.” You do not immediately inspect the script text. You first ask whether the coordinate event record exists in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. Then you check whether its x and y match the coordinate the avatar actually occupies after movement. Then you check the trigger variable and expected value. Then you check whether the dispatch code is scanning coordinate events for that map. Then you inspect the script label. That order is not bureaucracy. It is how you avoid wasting an hour reading the script when the record coordinate is off by one.

Similarly, if the receptionist does nothing when pressed, you start with the object event record in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. Is the receptionist object in the map’s object-event list? Is its local ID sane within that map? Is it placed where the player expects? Is the facing-direction target tile actually occupied by that object? Is the script label attached? Is field control accepting A-button interactions at that moment, or is another script/task/modal state holding control? Only after those questions do you start blaming the content of the receptionist script.

This diagnostic ordering connects directly to our broader course objective: identify the files, structs, functions, constants, data tables, and state variables that implement each major game system. In week 3 and week 4, you were still learning to name the layers. By week 6, the homework and quiz asked you to decompose the full RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Now, in week 7, we are asking for a finer grain. Not “events exist,” but “which event category, which lookup path, which data record, which state gate, which script entry, and which runtime lockout?”

That is the sophistication jump.

Let’s bring in object movement briefly, but only as much as we need. The source focus for this week includes pokefirered/src/event_object_movement.c as the high-signal object-event movement source. We are not going to re-teach movement boundaries or collision. We already used that as prior foundation. What matters today is that object events are runtime actors. The receptionist’s data record is loaded into a form the overworld can update, draw, and query. Movement code gives these actors persistence and position. Script entry gives them behavior. Local IDs let scripts and systems address them. That is the actor-like nature of object events: they are not just tiles with text attached.

Coordinate triggers are intentionally less actor-like. They do not need animation state. They do not need a sprite. They do not turn to face the player. They are map-space conditions. That makes them perfect for room positions, scripted floor tiles, and progression zones. The Colosseum player spots are a clean example because the interesting thing is not a visible object. The interesting thing is the player occupying a designated coordinate while the room state is ready.

If you are building your own engine, this is the design lesson I want you to steal carefully. Do not ask, “Can I implement every interaction with one event interface?” Ask, “What are the activation semantics?” If the activation semantics differ, keep that difference visible in the architecture. You can still have a shared script VM. You can still have a shared event-data table format. But the path into that VM should preserve whether the event came from pressing A on an actor, stepping on a coordinate, reading a background tile, or entering a warp.

Now, one last comprehension check before we move into the next segment. I will describe an event, and you classify it. The player walks onto a marked tile and the game checks whether VAR_TEMP_0 equals a certain value before running a script. Coordinate event. The player faces a receptionist and presses A. Object event interaction. The player presses A while facing a sign on the wall. Background event or sign-style background interaction, depending on the exact map data category. The player steps onto a doorway and changes maps. Warp event. Good. The player-visible input may be similar, and the resulting script machinery may overlap, but the dispatch categories are different.

So, carry this forward: FireRed’s overworld does not treat “interesting things” as one undifferentiated blob. It has map data categories, and those categories align with different runtime questions. Who is in front of me? What coordinate did I arrive on? What background feature am I inspecting? Did I enter a warp? Once you see those questions, the Colosseum examples stop being isolated data snippets and start becoming a compact demonstration of the whole overworld event architecture.

Next, we are going to follow what happens after dispatch succeeds. We have talked about finding the event. Now we need to talk about entering the script cleanly: how the engine hands control from free movement into scripted execution, why that handoff must be modal, and how that pattern prepares us for week 8, where story progression, save-visible event state, and larger scripted sequences become the center of the course.

So now dispatch has succeeded. The engine has found a thing that should run. At that moment, the interesting question is no longer, “Which table did we search?” It becomes, “What kind of control transfer is this?” And I want you to hear that phrase in a very literal engine sense. We are transferring control from a player-driven overworld loop into a script-driven mode of play.

On the board, I’m going to draw three boxes in a row. First box: player input and movement. Second box: event lookup. Third box: script context. Most bugs in student clone engines happen in the gap between the second and third boxes. Students can usually find the event. They can usually print, “triggered script X.” But then their player can keep walking while the dialogue is open, or the same coordinate event fires every frame, or an NPC script starts without knowing which NPC was touched. FireRed avoids that by treating script entry as a modal handoff.

The data side gives us a pointer, or more precisely, a script label that becomes a script pointer in the compiled event data. We saw that in the 2-player Colosseum map: pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, has the receptionist object event using graphics_id OBJ_EVENT_GFX_UNION_ROOM_RECEPTIONIST, position x 9, y 3, elevation 3, movement_type MOVEMENT_TYPE_FACE_LEFT, and script BattleColosseum_2P_EventScript_Attendant. That object event is not itself the conversation. It is the record that lets the runtime actor exist and lets interaction resolve to a conversation entry point.

Now compare that with the coordinate triggers in the same room. The two player spots are not actors. They do not have graphics. They do not have movement. In pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, the two coordinate events sit at x 3, y 5 and x 10, y 5, elevation 3, both gated by VAR_TEMP_0 with var_value 0, and they point to BattleColosseum_2P_EventScript_PlayerSpot0 and BattleColosseum_2P_EventScript_PlayerSpot1. Same final concept, different lookup question. The object event asks, “Did the player interact with this actor?” The coordinate event asks, “Did the player arrive at this tile while this variable has this value?”

That distinction becomes sharper in the 4-player room. Bring up the 4-player slide. In pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, we have four coordinate triggers, not two: x 3, y 4 for PlayerSpot0, x 3, y 6 for PlayerSpot2, x 10, y 4 for PlayerSpot1, and x 10, y 6 for PlayerSpot3. Still elevation 3. Still VAR_TEMP_0 equals 0. The map changed by data expansion. The runtime question did not change. That is the architectural lesson: the engine’s event-dispatch machinery does not need a “two-player Colosseum mode” and a separate “four-player Colosseum mode” just to recognize pads. It needs a general coordinate-event array and scripts that define room-specific consequences.

Now, what does that array look like after generation? The relevant struct vocabulary is in pokefirered/include/global.fieldmap.h. The object template shape appears in chunk_39d2fac9c5c945d3 and continues into chunk_fdcd35627a051581: ObjectEventTemplate has localId, graphicsId, position, movement data, script, and flagId. Then in the same global.fieldmap.h chunk_fdcd35627a051581, CoordEvent has x, y, elevation, trigger, index, and script. MapEvents then groups objectEvents, warps, coordEvents, and bgEvents with their counts. That is the C-side contract behind the JSON categories.

Notice the naming shift. The JSON says var and var_value. The C struct says trigger and index. Those are not contradictory. They are two views of the same design. At authoring time, “var equals value” is the clearest way to describe the condition. At runtime, the coord event stores a variable id in trigger and a comparison value in index. Advanced engine design often has this kind of translation layer: data authoring names optimize for designers and map editors; runtime structs optimize for compact lookup and fixed memory layout.

Let’s walk the concrete step example carefully. Suppose the local player in the 2-player Colosseum steps onto x 3, y 5 at elevation 3. We are not going back through collision or avatar movement; we already covered that foundation. We begin after the move has completed and the field input record says a step happened. The field-control side checks step-based scripts. In pokefirered/src/field_control_avatar.c, chunk_3fbc9c684e3802d6, TryStartCoordEventScript asks GetCoordEventScriptAtPosition for a script at the current map position, offset back from the internal map grid coordinates. If the script is null, it returns false. If it gets a script pointer, it calls ScriptContext_SetupScript and returns true.

That call is the handoff. Not the lookup. The handoff. ScriptContext_SetupScript is defined in pokefirered/src/script.c, chunk_03258f0288d89251. It clears some message and quest-log input state, initializes the global script context with the script command table, sets up the bytecode script pointer, locks player field controls, and marks the global script context as running. That is modal entry in code form. Once that happens, the player is no longer simply in “free overworld movement with maybe some text.” The player is in a script-controlled state, and the script engine owns progression until it yields, waits, or finishes.

Check for understanding here. If your clone implementation finds the coordinate event and immediately calls the script function directly while leaving your normal movement input active, what invariant have you broken? Yes, exactly: you have broken the invariant that scripted sequences are exclusive with ordinary player control. And this matters even for small scripts. A tiny pad script may only set a variable or show one message, but the same handoff mechanism has to scale to attendants, trainer battles, warps, forced movement, menus, and eventually week 8 story sequences.

Now let’s inspect the coordinate gate, because there is a subtle point here that I want you to carry into the lab. In pokefirered/src/field_control_avatar.c, chunk_e4ddb50c2e908f23, GetCoordEventScriptAtPosition loops through coordEvents, compares x and y, checks elevation with the familiar “same elevation or zero” pattern, and then calls TryRunCoordEventScript. That helper has three behaviors. If the coord event has no script, it treats the trigger as a weather event. If trigger is zero, it runs the script immediately and returns null. Otherwise, it compares VarGet(coordEvent->trigger) to coordEvent->index, and only then returns the script pointer.

The Colosseum pads are in that third category. Their trigger is VAR_TEMP_0, and their index is 0. So, if VAR_TEMP_0 equals 0, the coordinate lookup returns BattleColosseum_2P_EventScript_PlayerSpot0 or PlayerSpot1. Then TryStartCoordEventScript calls ScriptContext_SetupScript. If VAR_TEMP_0 does not equal 0, the lookup does not return a script, and the step-based pipeline continues to other possibilities, such as warp handling or step counters. That is why trigger variables are not just story flags. They are dispatch filters. They decide whether this coordinate currently means anything.

Now, let’s contrast that with NPC script entry. When the player presses A toward the receptionist, the event system does not use the coordinate-event var gate. It resolves an object event. In pokefirered/src/field_control_avatar.c, chunk_9dd95d1c876cd421, GetInteractedObjectEventScript looks for an object event at the target position, handles the counter case if needed, rejects invalid object-event ids and the player object, and then sets gSelectedObjectEvent, gSpecialVar_LastTalked, and gSpecialVar_Facing before retrieving the script pointer. That is a lot of state to set for what looks like “just talk to an NPC.”

Why does it need that state? Because the script may need to know who “this NPC” is. The script label is shared code in some cases, and script commands may turn the selected object, move the selected object, branch based on the last talked local id, or use facing direction for presentation. The global gSpecialVar_LastTalked is declared in pokefirered/src/event_data.c, chunk_9b57e0026eafc232, alongside other special variables such as gSpecialVar_Result and gSpecialVar_Facing. That tells you these variables are part of the script-visible register file. They are not arbitrary globals sprinkled through the code. They are part of the boundary between native C event detection and bytecode script behavior.

And the object script pointer itself is still recovered from the object template. In pokefirered/src/event_object_movement.c, chunk_ba384b7485a4f0b4, GetObjectEventScriptPointerByObjectEventId maps the active runtime object event back through its local id, map number, and map group to the template script. That matters because active object events are runtime instances; templates are map data. The engine keeps both ideas distinct. A sprite moving on screen is not the same thing as the authored object record, even though they are linked by local id and map identity.

Let me pause and ask a second check question. If you are implementing the workshop lookup function, and you store only “sprite id” on your NPC, but you throw away local id and map identity, what do you lose? You lose the stable authored identity of the NPC. Sprite ids are runtime allocation details. Local ids and map identity let scripts, flags, rematches, hidden-state changes, and object lookup survive beyond one sprite allocation moment. That is the deeper reason we keep returning to local IDs this week.

Now, coordinate scripts do not normally set gSpecialVar_LastTalked, because there is no NPC being talked to. The player stepped on a tile. That sounds obvious, but architecturally it is important. The event categories do not merely change where lookup happens; they change what context gets prepared before script entry. Object interaction prepares selected-object context. Coordinate event entry prepares position-conditioned script execution. Background event entry prepares signpost or hidden-item behavior. Warp entry prepares map transition state. If your clone engine sends every category into a single “run script name” function without category-specific context, it may work for a demo and then collapse when scripts need the missing context.

Now bring your attention back to ScriptContext_RunScript. In pokefirered/src/script.c, chunk_42b85842caee059b, the comments and function shape show the global script context running until a wait call or the end of a script. Then chunk_03258f0288d89251 shows that when the script commands finish, the context status is set to shutdown and field controls are unlocked. So the mode is not “call script and forget.” It is “install script into a persistent context, run commands across frames as needed, and gate player control while that context owns the field.” This is one of the main reasons FireRed can express dialogue, movement, pauses, fades, menu waits, and battles from script without needing every sequence to be one enormous native C callback.

You can now see the connection back to weeks 1 through 3. When we studied AgbMain, SetMainCallback2, CreateTask, and RunTasks, we were studying the outer scheduler. When we studied overworld maps and field input, we were studying how that scheduler becomes field behavior. Today’s handoff is one layer deeper: within the overworld callback, within per-frame field control, the engine can install a script context that becomes the temporary authority for player-visible progression. Data table to runtime globals to callback or task to visible behavior; that course thesis is still the through-line.

And you can see the connection forward to week 8. Story progression is not a separate magical feature. It is built out of the same event-data variables, flags, script contexts, and category-specific entry points we are looking at now. The Colosseum example uses VAR_TEMP_0, a temporary variable, because these pads are local room-state coordination. Larger story sequences will use longer-lived flags and vars. But the dispatch idea is the same: a data record says “this event is available when this condition holds,” native code checks the condition, and script code performs the consequence.

Let’s do one more concrete comparison before we move on. In the 2-player Colosseum, the receptionist exists as an object event, and there are two dynamic warps plus two coordinate triggers. In the 4-player Colosseum, the object_events list is empty, while the room has four dynamic warps and four coordinate triggers; the four-trigger evidence is in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6, and the absence of object events with four dynamic warps is in the same map’s header chunk, chunk_f18829d873bc1246. So the room’s interaction model changes without changing the event taxonomy. The 4-player room does not need four attendants. It needs four pads. Data expresses that.

In your lab, when you diff these map records, I want you to annotate not only what changed, but what did not need to change. The struct MapEvents did not need a new category. The script context did not need a special Colosseum runner. The coordinate-event lookup loop did not need a hardcoded list of four player positions. The map supplies count plus array. The runtime supplies generic iteration, elevation matching, trigger-variable comparison, and modal script entry.

Now, one caution. Because TryStartStepBasedScript checks coordinate events before warp events, as shown in pokefirered/src/field_control_avatar.c, chunk_0f297957b0ba32b7 and continuing into chunk_3fbc9c684e3802d6, priority is part of the engine semantics. Even if the Colosseum data keeps pad coordinates and warp coordinates separate, your clone should still define category priority deliberately. Otherwise, the first time you place a warp and a coordinate trigger on overlapping coordinates, or you add forced movement into a trigger tile, your engine will behave accidentally. FireRed’s architecture gives you an ordering: coordinate event, then warp, then miscellaneous walking scripts, then step-count scripts such as poison or egg hatching checks. You do not have to copy every feature yet, but you should copy the discipline of having an explicit order.

This also gives us a more nuanced answer to “object events versus background events.” It is not merely visible versus invisible. It is runtime identity and prepared context. Object events are data-backed actors with local ids, graphics, movement, flags, and script pointers. Background events are coordinate-bound inspection targets, often signposts or hidden items, with no actor identity. Coordinate events are arrival triggers with variable gates. Warps are map-transition declarations. They can all eventually cause visible behavior, but they are not interchangeable in the engine because their entry context differs.

And this is why pokefirered/src/battle_setup.c, chunk_494d132a59422c, is relevant even though today’s source focus is maps and step triggers. That chunk shows battle setup including event_data, event_object_movement, metatile_behavior, field_control_avatar, script, event_scripts, and battle transition headers in one module neighborhood. A trainer battle or link battle is not architecturally isolated from overworld event entry. It is reached through the same world of event variables, object identities, field control, and scripts, then handed to the battle system. That is the pattern we will keep exploiting: overworld event dispatch is the front door to larger game systems.

So the recap for this stretch is: dispatch finds a candidate, but script entry makes it authoritative. Object scripts enter with selected-object and last-talked context. Coordinate scripts enter through position, elevation, and trigger-variable matching. ScriptContext_SetupScript installs the bytecode pointer and locks field controls, while ScriptContext_RunScript advances that installed context until it waits or finishes. The Colosseum maps are small, but they are not toy examples. They show the real architecture compressed into a room: authored data categories, runtime lookup, state-variable gates, and modal transfer into script execution.

Hold that model in your head as we continue, because the next layer is script responsibility after entry: what the pad scripts and attendant scripts are allowed to assume has already been prepared for them, and what they must set themselves so the room cannot retrigger incorrectly on the next step.

Now, when I say “what the script is allowed to assume,” I mean something fairly precise. A script that has just been entered from a coordinate trigger should not need to rediscover why it was entered. It should be able to assume, “the player is standing on the authored coordinate, the elevation matched, and the trigger variable was in the value that made this event eligible.” That assumption is part of the contract between the map-event dispatcher and the script engine. The script’s job is then to perform the authored consequence and, if necessary, advance the state variable so the same consequence does not fire every frame or every step forever.

Let me put that on the board as a small contract. On the left: lookup responsibility. On the right: script responsibility. Lookup responsibility is spatial and conditional. It answers, “Is there an event here, facing this way or standing here, and is it active under the current flag or variable state?” Script responsibility is narrative and mutational. It answers, “What should happen now, and what state should change so the world is coherent afterward?”

That division is easy to underestimate because, in a small room like the Battle Colosseum, the visible behavior is simple. Step on a pad. Talk to an attendant. Start a link-battle flow. But the architecture is the same one that supports a gym guide, a cutscene, a rival encounter, a hidden item, a sign, a warp, or a story gate. The dispatcher is not written for the Colosseum. The Colosseum is just supplying data to a general event system.

This is why the BattleColosseum_2P map record is such a good teaching example. In pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95 gives us the receptionist as an object event. That object event has placement, movement-facing behavior, a local identity, and a script label. Separately, chunk_237e353ea279eb27 gives us the two player-spot coordinate triggers. The design is telling us that the attendant and the player pads are not the same category of event, even if both can eventually lead into script execution. One is an actor you talk to; the other is an authored location that reacts to stepping.

Pause on that distinction. If we were designing this from scratch, a tempting but weaker architecture would be to say, “Anything interactive is just an object with a script.” Then the pads would be invisible objects. Or we might say, “Anything interactive is just a tile behavior.” Then the attendant would become an unusual tile interaction. FireRed does not collapse the categories that way. It keeps object events, coordinate events, background events, and warps separate because their lookup keys are different. An object event is found through object identity, position, facing, and interaction context. A coordinate event is found through the player’s current coordinates, elevation, and trigger state. A background event is found through position plus facing or examine context. A warp is found through position and movement into a transfer tile.

So, check for understanding here: if the player presses A while facing the receptionist, what category are we in? Object event. If the player walks onto the marked Colosseum standing position and a script starts without pressing A, what category are we in? Coordinate event. Same script engine downstream, different entry path upstream.

Now let’s talk about why the trigger variable matters. Coordinate events are dangerous without a gate. Imagine a coordinate script that begins the Colosseum match when the player steps onto a pad. If the player remains standing on that pad after a message box closes, or after a small script pause, what prevents the exact same script from starting again? The answer cannot be “hope the player moves.” It has to be state. The trigger variable records which phase of the room’s little state machine we are in.

This is where our week-seven theme becomes visible: object events are data-backed actors, and coordinate triggers are data-backed state transitions. The map data gives the runtime a table of possible event entries. The event variables tell the runtime which of those entries are live. The script mutates those variables as the player progresses. That is the loop.

Let me draw a simple timeline. First, map load installs event tables from the map header and associated map data. Second, the overworld loop runs through the main callback and task infrastructure that we studied earlier in the course. Recall, not re-teaching it, just placing it: pokefirered/src/main.c chunk_b35676322b169f85 was our anchor for AgbMain startup responsibilities, and chunk_6cfd90f6d0136799 showed SetMainCallback2 as the high-level mode switch. Per-frame task dispatch came from pokefirered/src/task.c chunk_cb0bb91a147bfb59 for CreateTask and chunk_3ffebc0341225936 for RunTasks. Those pieces form the runtime shell. Inside that shell, the field system samples input, updates movement, checks interactions and step events, and only then yields to scripts or transitions when appropriate. Third, when the player steps, the coordinate-event lookup consults the map’s coordinate event records. Fourth, if the record matches, ScriptContext_SetupScript transfers authority to the script. Fifth, the script changes variables or flags so the next lookup observes a different world state.

That last part is the part advanced learners should now demand from any event system. If an event system says, “I can fire scripts from data,” your next question should be, “Where is the retrigger prevention encoded?” Is it a one-shot flag? A quest variable? A temporary local variable? A global story progress variable? A movement lock? A task state? FireRed uses several of these patterns in different places, but for coordinate triggers, the trigger variable pattern is the key one we are focusing on today.

Let’s make this concrete with the two-player and four-player Colosseum comparison. In the two-player map, chunk_237e353ea279eb27 is small: two coordinate triggers, one for each player position. In the four-player map, pokefirered/data/maps/BattleColosseum_4P/map.json chunk_30cd3fd6e624b8c6 expands that to four coordinate triggers. Architecturally, that expansion is not a new engine feature. It is data scaling. The engine does not need a “four-player special case” to know that four authored spots exist. The table simply has more coordinate-event records. That is exactly the kind of data-driven extension we want you to be able to recognize in the codebase.

Now, I want to be careful with the word “data-driven,” because it gets used lazily. Data-driven does not mean “there is no code.” It means the code is general over authored records, and the records choose concrete behavior by naming scripts, coordinates, movement types, local IDs, flags, variables, and constants. FireRed’s map JSON is not the runtime format the GBA directly interprets as JSON, of course; from week two, you already know the toolchain transforms authored and generated assets into C-compatible data. But as a design object, the map JSON is still our clearest readable statement of intent. The map file says, “This room has these object events, these coordinate triggers, these warps, and these background events.” The C runtime then consumes compiled equivalents of those records.

Here is a useful mental test. If changing the number of pads from two to four requires editing the map data but not editing the coordinate dispatcher, that is data-driven. If adding a new receptionist with a different script requires an object-event record with a different script label but not a new field-control function, that is data-driven. If changing the entire rule for how coordinate triggers match elevation requires changing C code, that is engine behavior. You should distinguish those two levels in your homework this week.

Let’s do a short board example. Suppose we have a coordinate event record that says, in ordinary language, “At x equals 5, y equals 8, elevation equals 0, when trigger variable VAR_ROOM_STATE equals 1, run Script_PlayerReadySpot.” The player steps onto x 5, y 8. The dispatcher compares coordinates. Good. It compares elevation. Good. It checks VAR_ROOM_STATE. If the value is 0, nothing fires. If the value is 1, it installs Script_PlayerReadySpot. Now, inside Script_PlayerReadySpot, what must happen before the player regains normal control? At minimum, if this event should not immediately repeat, the script must set VAR_ROOM_STATE to some value other than 1, or move the player, or otherwise transition the room to a state in which that coordinate event is not eligible.

Notice that this is not merely a bug-avoidance trick. It is the story progression model. Variables and flags encode the player’s position in authored logic. The coordinate table is therefore not just spatial data; it is spatial data cross-indexed with story state.

Let me ask a quick check question. If a coordinate trigger works the first time but fires every time the player takes a step on and off the pad, where would you inspect first? You would inspect the script’s state mutation, especially the variable or flag that gates the coordinate event. If a coordinate trigger never fires, where would you inspect first? Coordinates, elevation, trigger variable value, and whether the current map’s coordinate-event table actually contains the record. That inspection order is the lab skill.

Now let’s connect this to object events. An NPC script can also mutate state, but the entry assumptions are different. When the player talks to the receptionist object in BattleColosseum_2P, the script can assume the object lookup identified the selected object. That means local object ID and selected-object context are meaningful. The receptionist is not just a coordinate. She is an actor record. Her local ID matters for commands that may turn her, move her, hide her, or refer to “the last talked” object. That is the reason chunk_919152bd3bfceb95 is not interchangeable with chunk_237e353ea279eb27. Both can name scripts, but they supply different context to those scripts.

This context becomes especially important in reusable script patterns. A generic NPC script may rely on the engine knowing which object initiated the interaction. For example, a script might face the player, display text, then return the object to its previous facing. Or it might start an applymovement command against the last-talked object. If you tried to launch that exact same script from a coordinate trigger, it might not have a meaningful selected NPC unless the script explicitly names one. So, script labels are not pure functions in the mathematical sense. They are bytecode entry points that may assume ambient engine context.

That should make you slightly cautious, in a good way. When you see a script label reused from more than one event category, ask what context it expects. Does it expect a last-talked object? Does it expect the player to be standing at a coordinate? Does it expect a variable to already have a certain value? Does it expect field controls to be locked? Does it expect a message box to own input? These are real contracts, even when they are not written in a formal type system.

And this is one of the deeper lessons of FireRed’s architecture: the type system is C, but many of the meaningful contracts are data-and-state contracts. The compiler can tell you whether a function exists. It cannot tell you that a coordinate trigger’s script forgot to advance the trigger variable. It cannot tell you that a script entered from a background event is trying to move the last-talked object. Those are architectural contracts enforced by conventions, tools, and testing.

Let’s bring in pokefirered/src/event_object_movement.c for a moment, because that file is the high-signal source for object-event movement in the packet. We are not going to re-teach movement boundaries today; we already used object movement, collision, and camera-relative drawing as foundations in the previous lectures. But you should remember what that source represents in the larger system. Object event records are not inert map decorations. At runtime, they become object-event state: facing, movement type, animation state, local identity, visibility, and position. When an NPC script begins, it is entering a world where those object states already exist and can be addressed. That is why NPC script entry is richer than “run this text.” It is script entry into a live actor system.

This is also why the Colosseum receptionist can be visually stationary and still architecturally important. A stationary attendant is still an object event. The movement system may not be doing dramatic pathfinding for her, but the object-event machinery still gives her a runtime slot, sprite identity, facing, interaction bounds, and script association. The code path does not become trivial just because the visible behavior is calm.

Now, compare that to the pads. The pads have no runtime actor identity. They do not face the player. They do not have a sprite callback. They do not become “last talked.” They are entries in a coordinate-event table. Their power comes from matching the player’s step, not from being entities in the object-event array. If you keep that distinction clean, you will avoid a lot of architectural confusion in your own clone projects.

Let me tie this into the broader course plan. Weeks six through eight are our Input, Events, and Story Progression module. Week six gave you the runtime field path: input polling, avatar movement attempts, collision and tile behavior, interaction checks. Week seven adds authored events: object records, local IDs, script labels, coordinate triggers, trigger variables. Week eight will push more directly into story progression and persistence, meaning how these flags and variables survive transitions, saves, and larger quest arcs. So today’s lecture sits exactly at the junction between physical movement and narrative state. The player takes a step. That step is physical. The room changes phase. That phase is narrative. The engine must connect them without hardcoding every room.

Let’s use the battle transition packet as a supporting clue. pokefirered/src/battle_setup.c chunk_494d132a59422c85 includes event_data, event_object_movement, metatile behavior, field control avatar, field player avatar, battle headers, battle transition, and related systems. That include cluster is not accidental. Battle entry in FireRed is not isolated from the field. It needs field state, event state, object movement state, and transition machinery. The Colosseum room is a particularly clear version of that crossing: field events prepare the conditions under which a battle-like or link-battle flow can begin. The lesson is not that every coordinate trigger starts a battle. The lesson is that major game systems meet at event boundaries.

So if I ask you, “Where is the Colosseum feature implemented?” the advanced answer is not one file. You would say: the map data defines object events and coordinate triggers; the event-object system realizes NPCs as runtime actors; the field-control and movement path determine when a step or A-button interaction is eligible; event variables gate coordinate dispatch; the script engine enters the chosen script; and downstream battle or link systems take over when the script requests them. That is the answer pattern I want in your homework. File, struct or table, function, global state, visible behavior.

Now, one subtle point: coordinate trigger dispatch must happen at the right time relative to player movement. If you check too early, you test the tile the player is leaving. If you check too late, after other state has changed or after control has already moved into another mode, you can miss or duplicate the event. FireRed’s field architecture is built around per-frame callbacks and tasks, but step events are not just “any frame where the player happens to be standing somewhere.” They are integrated with the movement completion flow. That matters because “standing on a coordinate” and “just stepped onto a coordinate” can be different semantic ideas.

In your lab implementation, I want you to be explicit about which one you are modeling. If your trigger lookup runs every frame while the avatar is idle, then your script gate must be very strict, because otherwise standing still can repeatedly dispatch. If your trigger lookup runs on step completion, then the movement system itself contributes to retrigger control. FireRed’s actual behavior is bound into its avatar movement and field-control flow, which we have been building toward since the input lectures. Do not flatten that into a naïve polling loop without understanding the difference.

Here is a concrete clone-design example. Suppose your minimal RPG engine has a map object with four arrays: objects, coordinateEvents, backgroundEvents, and warps. You implement tryInteractFacingTile for the A button. That function checks object events first, then background events, depending on your chosen priority rules. Separately, you implement onPlayerStepComplete. That function checks warps and coordinate events. A coordinate event record has x, y, elevation, variableName, requiredValue, and scriptName. When onPlayerStepComplete runs, it calls findCoordinateEventAt(player.x, player.y, player.elevation). If a matching record is found and variables[variableName] equals requiredValue, it calls scriptEngine.start(scriptName). Then the script must set variables[variableName] to the next value or move the player or otherwise consume the event.

That clone example is simplified, but the responsibility split mirrors the FireRed architecture. It also exposes the testing strategy. You can unit-test coordinate lookup without running the script VM. You can unit-test script state mutation without simulating collision. You can integration-test the full step-trigger path by placing the player one tile away, issuing a movement command, completing the step, and asserting that the correct script starts and the variable changes. That is exactly the kind of decomposition we have been practicing since week one, when we traced data table to callback to visible behavior.

Let me do another check for understanding. Why not make the script itself scan the coordinate-event table to decide whether it should run? Because by the time a script is running, the engine has already selected it. The script should not duplicate dispatcher logic. If scripts are responsible for finding themselves, you lose central priority rules, you create inconsistent behavior between event categories, and you make retrigger bugs harder to localize. Keep lookup in the dispatcher. Keep consequence in the script.

Now, there are exceptions in large games, of course. Scripts sometimes perform additional checks. A receptionist may ask whether the player has enough party members, whether link partners are connected, or whether the current mode permits entry. Those checks are not the same as rediscovering the event. They are domain checks after entry. The event system says, “The player talked to this attendant.” The script says, “Given that conversation, should we proceed, reject, wait, or branch?” This is a clean layering.

We can see similar cross-system layering in other source packets, even outside today’s map. For example, pokefirered/src/daycare.c chunk_119931105f546346 includes script, event_data, party menu, storage, Pokedex, naming screen, trade, and other modules. I am not dragging daycare into the Colosseum for content reasons. I am using it as architectural evidence: user-facing features routinely sit on top of script entry plus event data plus specialized subsystems. The daycare is not just a menu. The Colosseum is not just a map. These features are compositions.

Similarly, pokefirered/src/learn_move.c chunk_af3598df6f092723 explicitly presents a move relearner state machine and includes script, task, event_data, field fade transition, overworld, menus, constants, and summary-related systems. That is a different feature, but it reinforces the same course thesis. FireRed behavior is produced by cooperating state machines and data tables. A map event may be the doorway into a special UI state machine. A coordinate trigger may be the doorway into a link or battle flow. An NPC conversation may be the doorway into a menu, a party selection, a naming screen, or a trade.

So, when you map the architecture, resist the urge to draw “script” as a small box off to the side. Script is a central coordination layer. It does not replace C subsystems, and it does not own all logic, but it sequences them. It is the authored control plane that says, “show this message, set this variable, wait for this movement, start this special, fade the screen, restore control.” The dispatcher chooses entry. The script sequences consequence. The subsystem performs specialized work.

Let’s return to the Colosseum pads and ask what they must not assume. A pad script should not assume the other player is ready unless some other state says so. It should not assume link setup has completed unless the link subsystem or attendant script has established that. It should not assume every participant has stepped onto a pad merely because this player has. That is why the four-player room’s four coordinate triggers in chunk_30cd3fd6e624b8c6 are interesting. They likely participate in a broader state protocol. Four positions must be managed coherently. Each local coordinate event can detect one participant’s placement, but the room-level script logic has to decide what “ready” means for the group.

And that gives us a more advanced architectural phrase: coordinate triggers are local observations feeding a shared room state machine. In the two-player case, there are two observations. In the four-player case, four. The runtime does not need to philosophize about “a complete group.” It dispatches the authored script when a participant steps where the data says a participant matters. The scripts and variables encode the group-level progression.

If you are building the lab implementation, this is where many of you will be tempted to store readiness directly on the coordinate event records. That can be okay in a toy engine, but it is less flexible. FireRed’s style points you toward event variables and script-controlled state because the same machinery works for story cutscenes, gyms, puzzles, shops, and link rooms. The coordinate record should say when it is eligible. The variable system should say what phase the world is in. The script should move the phase forward.

Let me give a failure case. Suppose the two-player Colosseum has a trigger variable value of 0 before the attendant conversation, and the coordinate pads require value 1. The receptionist script is supposed to set the variable to 1 when the room is ready for players to step into position. If the receptionist script forgets to do that, the pads look broken. The player can step on them forever and nothing happens. The coordinate records are correct. The dispatcher is correct. The bug is in the prior script’s failure to prepare the state. Conversely, if the receptionist sets the variable to 1 too early, before link setup is valid, the pads can fire into a script that assumes readiness before the room has earned it. That is an ordering bug.

So, board note: trigger variables are not only retrigger prevention; they are phase ordering. They say which events are currently legal in the authored state machine.

Now, let’s make the priority question explicit. If a coordinate has a warp and a coordinate trigger, which one wins? If an object stands on a coordinate trigger, does the object matter? If the player presses A on the same frame movement completes, which event category owns control? These are not abstract concerns. Every event engine needs priority rules. FireRed’s categories help make those rules manageable because each category has a different dispatch moment and input condition. Warps are movement-entry events. Coordinate triggers are step-position events with variable gates. Object and background events usually depend on interaction checks from the A button. When we say “object events versus background events” this week, we are not just classifying map JSON. We are identifying dispatch timing and lookup priority.

For your lab, I do not need you to perfectly clone every FireRed priority rule. I do need you to write down your chosen priority rules and implement them consistently. If your implementation checks coordinate events before warps, say so. If it checks warps first, say so. If background events are only checked by A-button interaction, say so. The assessment is not merely whether a script starts. It is whether your event categories have coherent responsibilities.

Let’s tie one more knot to the previous lectures. The tile behavior and collision systems told us whether movement could happen and what kind of tile was underfoot. We are not re-teaching metatile behavior today, but remember the relationship: collision answers, “Can the avatar enter?” Coordinate events answer, “Now that the avatar entered, is there an authored consequence?” Background events answer, “When examining this location, is there something to say or do?” Object events answer, “When interacting with this actor, what script belongs to that actor?” Those are separate questions. A good engine does not make collision carry story logic unless the tile behavior itself is truly the story mechanic.

That separation is one reason the Colosseum map data is pedagogically strong. The pads are not special collision. The receptionist is not a special collision tile. The map remains a composition of layout, objects, and events. The engine can therefore reuse the same movement rules while changing the event tables to create different rooms.

Before we move to the next script-responsibility detail, I want you to do a quick mental trace. Player is in the two-player Colosseum. The receptionist object event from pokefirered/data/maps/BattleColosseum_2P/map.json chunk_919152bd3bfceb95 is present. The player presses A facing her. The interaction system identifies the object event and enters her script with object context. That script may set up link-room state and advance a trigger variable. The player walks to a pad. The coordinate-event records from chunk_237e353ea279eb27 are checked after movement. The player’s coordinate, elevation, and trigger-variable value match. The script context is loaded with the pad script. That script consumes or advances the trigger state and may wait for other participants or hand off to a link-battle flow. In the four-player room, chunk_30cd3fd6e624b8c6 gives the same pattern with four authored coordinate records.

That is the whole architecture in miniature: authored data, runtime actor state, event variables, dispatcher selection, script entry, script mutation, subsystem handoff.

And if that sentence sounds like the course thesis, it should. We are again tracing data table to runtime globals to callbacks, tasks, scripts, structs, and player-visible behavior. The difference is that by week seven you are expected to name the boundary precisely. “The script runs” is no longer enough. Which script? Entered from which event category? With which context variables prepared? Gated by which trigger variable? Mutating which state so the next dispatch changes? That is the level we need now.

Let’s hold there for a beat. If you are annotating your source, mark the Colosseum map chunks as a compact reference case: receptionist object event, two-player coordinate triggers, four-player coordinate triggers. Then beside pokefirered/src/event_object_movement.c, mark it as the runtime actor counterpart to the object-event records. Beside pokefirered/src/battle_setup.c chunk_494d132a59422c85, mark it as evidence that field event state and battle transition logic are part of the same larger feature pipeline. These annotations will pay off in the workshop, because you will be asked to diff data records first, then implement lookup behavior second.

Let me extend that annotation into a slightly more formal mental model, because this is where students often lose one layer of causality. We have data records that say, “there is an event here,” but the engine does not experience that as an English sentence. The engine experiences it as arrays, indices, local identifiers, trigger variables, coordinates, facing directions, and script pointers. So when you are debugging a step trigger or an NPC conversation, your first question should not be, “Why did this scene happen?” Your first question should be, “Which event table was searched, and what fields made this row match?”

On the board, I am going to draw four boxes in a row. The first box says map data. The second says runtime event tables. The third says script entry. The fourth says visible consequence. Keep those four boxes in your notes, because they are the backbone of the workshop. In week three and week four, we already practiced tracing from data into runtime shell behavior. We used the main callback system, for example, where pokefirered/src/main.c chunk_b35676322b169f85 and chunk_6cfd90f6d0136799 gave us the startup and SetMainCallback2 side of the story, and pokefirered/src/task.c chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936 gave us CreateTask and RunTasks as the per-frame dispatch side. Today the shape is the same, but the payload is different. Instead of a menu state machine or a top-level callback, the payload is a map event becoming a script entry.

Now, notice the asymmetry between object events and coordinate events. An object event is an actor-like record. It has identity. It can be visible or hidden. It can have movement type. It can face the player. It can be referred to by a local ID. It can be the receptionist in BattleColosseum_2P. The research packet points us specifically to pokefirered/data/maps/BattleColosseum_2P/map.json chunk_919152bd3bfceb95 as the receptionist object-event case. That chunk is valuable because it is not just “some NPC.” It is a clean example of a data-backed actor whose script entry depends on the player choosing to interact with an object that is already present on the map.

A coordinate event is different. A coordinate event does not need to be an actor. It does not need a sprite. It does not need a local ID in the same sense. It is a spatial condition, usually combined with a trigger variable or expected value, that says, “when the player steps here under this story state, enter this script.” The BattleColosseum examples are deliberately compact. In pokefirered/data/maps/BattleColosseum_2P/map.json chunk_237e353ea279eb27, we have the two player spot triggers. In pokefirered/data/maps/BattleColosseum_4P/map.json chunk_30cd3fd6e624b8c6, we have four coordinate triggers. Those chunks are a useful comparison because they show scaling without changing the concept. Two-player colosseum: two relevant positions. Four-player colosseum: four relevant positions. The engine’s job is not to “understand colosseum design.” The engine’s job is to match coordinate records against the avatar’s position and state.

Let me pause there and check understanding. If I ask, “Why is the receptionist an object event and the player spots coordinate events?” the answer is not, “because receptionists are people and player spots are tiles,” although that is true at the design level. The architectural answer is that the receptionist must participate in object-event machinery: local ID, graphics, movement, facing, script label, and object lifecycle. The player spot does not need actor lifecycle. It needs coordinate dispatch. Same map, same broad feature, different event category.

Now let us connect that to NPC script entry. When the player presses A facing the receptionist, the engine’s interaction path is looking for something interactable in front of the avatar. We covered that earlier with field input polling, avatar movement attempt flow, and interaction checks, so I am not going to rebuild the whole path here. Use that as prior foundation. What matters today is the handoff. The receptionist object event gives the engine enough information to say, “this object has this script.” That is a different entry path from “the avatar stepped onto coordinate x, y and the trigger variable matched.” In both cases we enter the script VM, but the reason we entered it is different, and the context available at entry may be different.

This is one of the reasons the course keeps insisting on exact source names and exact event categories. If you write in your homework that “the map calls a script,” that is not yet advanced analysis. Maps do not call scripts in the abstract. A map header points to event data. Event data includes categories. A runtime system checks one of those categories because a player action or movement update made it relevant. Then a matching record selects script entry. If you compress all of that into “the map calls a script,” you have lost the part where most bugs live.

Concrete example. Imagine you are building a minimal clone of this architecture. You implement the receptionist as a coordinate event because the player talks to her from one tile away and you figure, well, it happens at a location. What breaks? First, facing breaks or becomes awkward, because the receptionist no longer has a natural object identity to turn toward the player. Second, visibility and state changes become awkward, because hiding or replacing an NPC is not naturally represented by a coordinate-only trigger. Third, multiple approach directions become messy. The player might interact from below, left, or right, and now your coordinate model has to emulate an object-interaction model. You can make it work, but you are fighting the architecture.

Flip the error. Suppose you implement each colosseum player spot as an invisible object event. That may seem plausible at first, because object events have coordinates. But now you are treating passive tiles as actors. You have to worry about object slots, local IDs, maybe graphics placeholders, maybe collision interactions depending on how your clone is structured. Again, you can hack it, but your representation is lying. FireRed’s separation helps keep the system intelligible: actors are object events; spatial tripwires are coordinate events; inspectable map decorations may be background events; exits are warp events. That categorization is not cosmetic. It is a dispatch strategy.

Now, slide forward one click in your mind. We have object event versus coordinate event, but we also have trigger variables. Trigger variables are where map dispatch joins story progression. A coordinate record is rarely just “stand here forever and always run this script.” More commonly, it says, in effect, “if this variable has this value, and the avatar reaches this coordinate, run this script.” That turns a physical space into a state-dependent machine. The same tile can be inert before the right setup, active during a scene, and inert again after the script mutates the variable.

This is the pattern I want you to take into the lab. Data record, gate, script, mutation. Say that quietly to yourself when reading these maps. Data record: the coordinate event row. Gate: trigger variable and expected value. Script: the label or pointer selected by that row. Mutation: the script changes state so the next lookup behaves differently. Without mutation, triggers either repeat forever or require some other system to suppress them. With mutation, the map becomes a small state machine.

And this is also why pokefirered/src/battle_setup.c chunk_494d132a59422c85 belongs in today’s annotations, even though the file name sounds like battle rather than map events. The excerpt in the packet shows battle setup including event_data, event_object_movement, metatile behavior, field control, player avatar, screen effects, and battle transition headers. That is evidence for a recurring architectural point: features players perceive as single moments often cross subsystem boundaries. A trainer battle, or a colosseum entry, or a link-related transition is not just “battle code.” It begins in field state, uses map and event context, coordinates object behavior, and then hands control to a different runtime mode.

This should sound familiar from earlier weeks. In week one, we decomposed the game into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At the time, those sounded like neat boxes. By week seven, you should be suspicious of neat boxes. The boxes are useful for ownership and source navigation, but real gameplay crosses them. The receptionist is an overworld object. Her script may check event variables. The resulting action may prepare link or battle state. The transition may use callbacks and tasks. If the scene changes persistent progression, save-related state eventually matters. The architecture is modular, but the behavior is pipelined.

Let me put a small trace on the board. Player presses A. Field control finds an object event in front of the avatar. The object event has a local ID and script reference. The script engine begins at that script. The script may set variables, show text, move objects, or launch a transition. Now compare the step trace. Player completes movement onto a tile. Coordinate event lookup checks the map’s coordinate-event table. It compares x and y and trigger state. If matched, script entry occurs. That script may set the trigger variable to a new value. Same destination category, script execution, but different dispatch cause.

Here is the check for understanding I would give you if this were an oral exam. Suppose a bug report says, “In the four-player colosseum, only the first two player positions trigger the waiting script.” Where do you look first? You do not start in battle move logic. You do not start in the text printer. You start by diffing the two-player and four-player map records, especially pokefirered/data/maps/BattleColosseum_2P/map.json chunk_237e353ea279eb27 against pokefirered/data/maps/BattleColosseum_4P/map.json chunk_30cd3fd6e624b8c6. You ask whether the four coordinate records exist, whether their coordinates match the intended tiles, whether they share the right trigger variable, and whether their expected values are correct. Only after that do you inspect runtime lookup. Data first, dispatch second.

If the data is right, then you go to the implementation of lookup. In the workshop version of this task, when I say “implement trigger lookup for object, coordinate, background, and warp event categories,” I am not asking you to build the whole FireRed engine. I am asking you to preserve the category boundaries. Your clone can be minimal, but it must still ask the right question for each category. For object events, “which object is being interacted with, and what script does its local record provide?” For coordinate events, “does the player’s current coordinate and trigger state match a row?” For background events, “is the player inspecting a map position or facing a position that has a background script or hidden item style behavior?” For warp events, “does the player’s movement place them on an exit record that changes map context?” Different event types, different entry conditions.

Now, advanced point. There is a tempting abstraction that says, “All events are just triggers with predicates.” That is theoretically elegant, and if you were building a modern engine from scratch, you might indeed model it that way internally. But when studying FireRed, do not erase the concrete categories too early. The categories reflect tooling, data generation, runtime assumptions, and designers’ mental models. Remember our week two work on generated data and runtime JSON. The research synthesis emphasized pokefirered/data as a recurring course module, with maps and layouts as anchors, and the toolchain work around jsonproc and generated source. That means the shape of map.json is not just serialization trivia. It is the upstream source of how map event categories are authored and later consumed.

So if you build one generic event list too early, you may pass a toy test and fail the architecture lesson. The better minimal design is category-specific lookup functions behind a common result type. For example, your clone might have lookupObjectInteraction return a script entry with object context, lookupCoordinateTrigger return a script entry with coordinate context, lookupBackgroundEvent return a script entry with facing-position context, and lookupWarp return a map transfer result. You can unify the return channel, but do not pretend the inputs are identical.

Let us also name the relevant state variables conceptually, because this is part of the week seven objective. FireRed has event variables and flags in its event data system, object-event runtime state in its object movement system, map identity and layout state in overworld/map structures, avatar position and facing state in field player control, and battle transition state when a field event launches combat. The exact names vary by module, and we will keep reading them in source, but the architectural roles are stable. State answers three questions: where is the player, what is present on the map, and what phase of the story or scene are we in?

That third question is the one trigger variables answer. Coordinates alone are not enough. If coordinates alone controlled scenes, the player would retrigger the same scripted moment every time they stepped on the tile. Trigger variables turn a coordinate event from geometry into progression logic. And once a script mutates the variable, the map’s future dispatch changes without changing the map’s static coordinate table. This is an extremely common creature RPG pattern: static world data plus mutable event state.

A quick recap before we close this segment. Object events are data-backed actors. The receptionist in pokefirered/data/maps/BattleColosseum_2P/map.json chunk_919152bd3bfceb95 is our compact example. Coordinate events are position-and-state dispatch records. The two-player and four-player colosseum chunks, chunk_237e353ea279eb27 and chunk_30cd3fd6e624b8c6, let us compare that pattern at two sizes. pokefirered/src/event_object_movement.c remains the high-signal runtime counterpart for object-event movement and actor behavior. pokefirered/src/battle_setup.c chunk_494d132a59422c85 reminds us that field event state and battle transition logic cooperate in larger features. And the older runtime-shell sources, main.c and task.c, are still in the background as the frame-by-frame machinery that lets all of these systems run.

For the workshop, I want your diff to be almost boringly precise. Do not write, “the four-player map has more triggers.” Write, “the four-player coordinate-event table has four coordinate trigger records where the two-player table has two, and each record participates in the same dispatch pattern: coordinate match plus trigger-state gate plus script entry.” That is the difference between description and architecture.

And as a final synthesis for today’s lecture, step triggers and NPC script entry are not special cases floating outside the engine. They are the point where authored map data, runtime actor state, avatar control, event variables, and script execution meet. The player experiences that meeting as a receptionist speaking, a waiting position activating, a battle beginning, or a transition firing. But we, as engine readers, should see the pipeline underneath: data table to runtime lookup, lookup to script entry, script entry to state mutation, state mutation to changed future behavior. That pipeline is the through-line from week one’s decomposition to week seven’s map-event analysis, and it is exactly the level of precision I expect you to bring into the lab.

## Source References
- pokefirered/src/daycare.c
- pokefirered/src/battle_setup.c
- pokefirered/src/learn_move.c
- pokefirered/src/pokemon_summary_screen.c
- pokefirered/src/battle_util.c
- pokemon-showdown/data/text/moves.ts
