# Anatomy of a FireRed Map Record

Lecture ID: lecture_w03_d01
Week 3, Day 1

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

## Transcript
Good, let’s begin where week two left us, not by reopening the whole startup path, but by using it. By now, when I say “FireRed is data-driven,” I expect you to hear something fairly specific. I do not mean that the Game Boy Advance is parsing friendly JSON while you walk around Pallet Town. We already established that distinction in week two. The JSON is part of the build-side representation in this decompilation workflow. The runtime sees compiled C data, binary assets, global state, callbacks, tasks, and script pointers. So today, when we look at a map record, I want you to keep both worlds in view: the authoring-side record that is readable to us, and the compiled runtime structures that the engine consumes after the toolchain has done its work.

Now, let me put the title on the board: Anatomy of a FireRed Map Record. Under that I am going to write three phrases: identity, geometry, and behavior. Those are the three large pieces I want you to see in every map record for the rest of the course. Identity means “what map is this, and where is it in the global map registry?” Geometry means “what are the dimensions, tiles, border blocks, movement permissions, and visual layout?” Behavior means “what happens here when the player enters, steps somewhere, presses A somewhere, collides with a warp, or interacts with an object event?”

That separation sounds clean, but FireRed’s architecture is interesting because it does not store those categories in only one place. The map header points at layout data. The layout data points at tileset and block information. The event data is adjacent to the map header but is conceptually gameplay behavior, not floor geometry. And then runtime systems such as the overworld loader, object event movement, script engine, field control, and warp handling interpret those records. So if you are tracing a player-visible event, say, “the player walks onto a door tile and gets transported to another map,” you are not allowed to stop at the warp record. You have to keep tracing into the system that consumes the warp record.

This is where week three begins to matter. Weeks one and two gave us the architectural skeleton. Week one gave us the runtime shell: callbacks, tasks, the idea that visible behavior emerges from a per-frame engine. Week two gave us headers and generated data as architectural maps. Now week three starts filling in the overworld map runtime. In the larger course plan, weeks three through five form a single module: maps, scripts, and progression. This is the part of the game that makes the creature RPG feel like a place instead of just a battle simulator or a menu system.

Let’s use a deliberately small example today: the link Battle Colosseum maps. The source packet points us to `pokefirered/data/layouts/layouts.json`, specifically `chunk_76c0c20446e9e415` for the `BattleColosseum_2P` layout and `chunk_4a91aa64203b6d9a` for the `BattleColosseum_4P` layout. It also points us to `pokefirered/data/maps/BattleColosseum_2P/map.json`, with `chunk_919152bd3bfceb95` containing the map header, object events, and warps, and `chunk_237e353ea279eb27` containing coordinate triggers. Finally, `pokefirered/data/maps/map_groups.json`, `chunk_f22eb0b9c9a5d2a4`, anchors these link-oriented maps in the map group registry.

Why choose a Battle Colosseum map rather than a route or town? Because it is small enough to reason about without drowning in NPCs, wild encounters, shops, signposts, hidden items, and story flags. But it still has enough map anatomy to teach the architecture: a header, a layout reference, object events, warps, and coordinate triggers. It also reminds us that the overworld runtime is not only for the ordinary single-player world. Link rooms are still maps. They still participate in map grouping. They still need layouts. They still need event records. So the map architecture is not “town architecture” or “route architecture.” It is a general spatial runtime used by many parts of the game.

Let me draw a box on the board. Inside the box I’ll write `BattleColosseum_2P/map.json`. Now I’ll draw an arrow from that box to a second box labeled `BattleColosseum_2P layout`. That arrow is the first relationship I want you to internalize. The map record is not the layout itself. The map record names or references a layout. The layout is the geometric and graphical foundation. The map is the gameplay-facing record that says, in effect, “use this geometry, but also put these objects here, these warp exits here, these coordinate triggers here, and these scripts around the edges of the player’s experience.”

This is one of the most important design choices in map-driven RPG engines. If geometry and gameplay are separated, designers can reuse or compare layouts, and the engine can load collision, dimensions, block data, and tile assets through a more stable layout registry. The source focus makes that visible: the layout registry lives in `pokefirered/data/layouts/layouts.json`, while the concrete Battle Colosseum map record lives in `pokefirered/data/maps/BattleColosseum_2P/map.json`. The cited chunks are not incidental. `pokefirered/data/layouts/layouts.json` `chunk_76c0c20446e9e415` is evidence for the layout-side record, and `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_919152bd3bfceb95` is evidence for the map-side record. Different files, different architectural responsibilities.

Now, pause for a check. If I ask, “Where do I look to find the dimensions and base layout identity of the BattleColosseum_2P space?” you should be thinking of the layout record. If I ask, “Where do I look to find what events are placed on top of that space?” you should be thinking of the map record. In practice, you often open both. But architecturally, those are different questions.

Let’s make that distinction more concrete. Imagine I have a four-by-four room, just a plain room. The layout tells the engine, “This room is this wide, this tall, uses these tilesets, has this blockmap, and has these movement permissions.” That is not enough to make it a playable RPG space. The player can stand there, maybe walk around, but nothing semantically meaningful happens yet. Now I add a warp event at coordinate, say, x equals 3, y equals 7. Now a tile is no longer merely passable floor or a door-looking tile. It becomes an exit. Then I add an object event at x equals 5, y equals 4. Now a visual sprite can stand there, face a direction, maybe execute a script when talked to. Then I add a coordinate trigger. Now stepping across a particular tile can run a script automatically. That is the difference between geometry and gameplay data.

The Battle Colosseum maps are especially good for seeing this because link rooms have a strong behavioral purpose. They are not just decorations. Their job is to stage multiplayer interactions, and the map group registry must make them addressable as maps. That is why `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4` matters. Map groups are one of the global indexing mechanisms that let the engine treat maps as members of larger collections. You can think of a map group as part of the address system for the world. A warp does not merely say, “go to a vibe.” It needs a destination that the engine can resolve. The destination must be named or indexed within the world’s map registry.

Now I want to introduce a useful mental model. A FireRed map record is a contract between content and engine subsystems. Not a header file contract in the C sense, which we talked about last week, but a data contract. The content promises: “I will provide a layout reference, event lists, connections if relevant, scripts if relevant, music and map type metadata if relevant, and enough indexing information for the runtime to load me.” The engine promises: “If the data follows this shape, I can render it, collide against it, place events, run scripts, process warps, and transition to other maps.”

Let me say that another way because it is a common source of bugs in student engine projects. A map record is not simply a serialization format. It is an execution recipe. Every field exists because some loader, dispatcher, movement routine, script routine, or transition routine expects to find it. Advanced learners sometimes over-focus on schema neatness: “Can I model this record elegantly?” That is useful, but the more important architecture question is, “Which runtime system consumes this field, and when?” If you cannot answer that, your schema is decorative.

This is exactly the tracing discipline your week two homework and quiz were preparing you for. You practiced tracing from data records through globals, callbacks, tasks, scripts, structs, and functions to player-visible behavior. Today we apply that trace to maps. The source packet’s broader synthesis says the course should repeatedly trace “data table to runtime globals to callback or task or script to player-visible behavior,” with FireRed as the primary authority. That is the pattern we are using now. A warp in `map.json` is the data table side. The overworld runtime and script systems are the consumers. The visible behavior is: the player steps onto a tile, the game fades or transitions, then the avatar appears in another map.

Now, I’ll write four event categories on the board: object events, warp events, coordinate triggers, background events. These are the gameplay layer that sits on top of the layout. The exact balance varies by map. Some maps are dense with NPC object events. Some maps are mostly terrain and wild encounters. Some maps have signposts, hidden items, scripted cutscenes, or automatic step triggers. For today’s first pass, we are focusing on the record anatomy: where these concepts live and what role they play. Later this week, we will separate layout data versus gameplay data more aggressively, and in the lab you will build a minimal map schema loader that reads layout ID, map dimensions, object events, warps, coordinate triggers, and movement permissions from a FireRed-like JSON record.

That lab is intentionally narrower than the real game. You are not being asked to implement all of FireRed. You are being asked to prove that you can read the map as an architecture. If your loader can identify the layout reference, dimensions, object event list, warp list, coordinate trigger list, and movement permissions, then you have captured the spine of the map record. You are building a small version of the same content-to-runtime handoff that FireRed performs at a much larger scale.

Let’s take the object event first. An object event is a placed entity in the map. Usually you should imagine an NPC, but do not reduce it to “NPC” too quickly. An object event is a runtime object seed. It says something like: create an object with this local id, this graphics id, this position, this movement type, this facing direction, this script, and maybe visibility conditions or trainer properties, depending on the map and engine conventions. The important point is that the map record does not itself perform the movement. It provides the initialization data. The runtime object system interprets the movement type and updates the object over frames.

Here is the advanced-level distinction. The object event record is static placement data, while the object event runtime instance is mutable state. The static record may say, “This attendant starts here and uses this movement behavior.” But after loading, the runtime must track the current coordinates, animation state, facing, movement progress, maybe whether the object is frozen during scripts, and whether it is active in the current camera range. The content record is the seed; the runtime object is the living instance. If you conflate those, you will build a map loader that can display the initial room but cannot support actual play.

Now compare that to a warp event. A warp event is usually less like a living entity and more like a spatial trigger. It says, “At this coordinate, there is a transition to another map and another warp index or coordinate.” Again, there is a static record and a runtime interpretation. The static record sits in the map JSON; in our source focus, the relevant map-side material is in `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_919152bd3bfceb95`, which includes the map header, object events, and warps. But the visible behavior only happens when the player’s movement routine reaches the coordinate and the field control or warp processing logic decides that the player has activated that warp.

Let’s do a concrete example. Suppose the BattleColosseum_2P map has a warp leading back out to a link room or lobby. I am not asking you to memorize the destination right now. I am asking you to trace the dependency chain. First, the map belongs to a group, and that group membership is represented in `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4`. Second, the map header in `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_919152bd3bfceb95` identifies the map’s layout and event lists. Third, the warp event in that map record gives a coordinate and a destination reference. Fourth, the runtime detects that the player has stepped onto or activated that coordinate. Fifth, the engine resolves the destination map through the map grouping and map header data. Sixth, the overworld transition machinery loads the new map’s layout and event records, places the player, and resumes field control.

Notice what that example does not require us to re-teach. We do not need to reopen `AgbMain` from scratch. We do not need to re-explain the callback slots from week one. We do need to remember that the overworld is running inside that callback-driven runtime shell. The map engine is not magical. It is work scheduled through the same broad callback and task architecture you already studied. That is the forward connection: week one gave us the engine’s heartbeat; week three asks what data that heartbeat animates.

Now, coordinate triggers. These are easy to misunderstand because students lump them together with warps. A warp is a kind of coordinate-based behavior, yes, but in FireRed map anatomy we should distinguish warp events from coordinate triggers. A coordinate trigger is a script activation tied to stepping on a coordinate under specified conditions. It is not necessarily a map transition. It might begin a cutscene, make an NPC walk, display text, set a flag, or gate progress. In our source focus, `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_237e353ea279eb27` is specifically called out for coordinate triggers, so when we discuss automatic tile-based behavior in this lecture, that is the local evidence.

Here is the check for understanding. If the player steps on tile x,y and the game immediately transfers them to another map, which record category are you expecting first? Warp event. If the player steps on tile x,y and an attendant turns, walks over, and starts a script, which record category are you expecting first? Coordinate trigger, likely backed by script data and object movement commands. If the player presses A facing a sign, which category might be involved? That leans toward a background event or signpost-style interaction. If the player presses A facing an NPC, which category? Object event, with an attached script. Those distinctions matter because they correspond to different runtime checks.

Let me put a small table verbally on the board. Across the top: player action. Down the side: event source. Step onto tile: warps and coordinate triggers. Press A toward tile: background events. Press A toward object: object events. Enter map: map scripts or header-associated scripts. This is not a perfect complete taxonomy for every edge case, but it is a useful operational model. When you debug a map, ask what action the player performed. That narrows which event list the runtime is probably consulting.

Now let’s talk about map header fields. A map header is the central map descriptor. In the JSON representation, it is the place where the map announces its layout, event collections, and metadata. For the BattleColosseum_2P case, the source packet identifies `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_919152bd3bfceb95` as containing the map header, objects, and warps. Architecturally, the header is the handshake point. It ties the map’s identity and metadata to the layout and event data.

If you are building the lab loader, your first pass should not try to render beautiful tiles. Your first pass should load the header, resolve the layout id into a layout record, read the dimensions, then list the event counts and coordinates. That is enough to prove the data model. After that, movement permissions become the next meaningful layer because they let you answer a gameplay question: where can the avatar stand or move? Without movement permissions, you can draw a map but cannot correctly walk it.

This is also why layout records matter. The layout is not just an image. It is the gameplay geometry. In `pokefirered/data/layouts/layouts.json`, `chunk_76c0c20446e9e415` for the two-player Battle Colosseum layout and `chunk_4a91aa64203b6d9a` for the four-player Battle Colosseum layout give us a nice comparison point. Two related maps can have related purposes but different layouts. A two-player battle space and a four-player battle space serve similar link-system functions, but the spatial requirements differ. More participants mean different standing positions, different staging, possibly different coordinate assumptions. That distinction shows why the layout registry is its own table rather than being buried casually inside each map’s event list.

Now, I want you to listen carefully to this design principle: layout data answers “what kind of space is this?” Map event data answers “what happens in this space?” The two-player and four-player Battle Colosseum layouts are evidence for the first question. The BattleColosseum_2P map record’s header, object events, warps, and coordinate triggers are evidence for the second question. If you keep that distinction clean, the rest of the week becomes much easier.

Let’s connect this to constants and runtime state variables, because advanced architecture is never just file names. A map record eventually becomes runtime state: current map group, current map number, current warp destination, current object event array, current script context, current player coordinates, and current layout dimensions. The exact names vary across decompilation conventions, and we are not going to overclaim a symbol that is not in today’s packet. But the architectural categories are unavoidable. Somewhere, the engine must store “where am I?” Somewhere, it must store “what map data is loaded?” Somewhere, it must store “what event objects are active?” Somewhere, it must store “what script is currently executing or ready to execute?” The map JSON is inert until those runtime state variables are filled and consumed by functions.

That is the same lesson you saw in `pokefirered/src/battle_setup.c` `chunk_494d132a59422c85`, though in a different domain. The packet notes that this source includes dependencies such as `overworld.h`, `script.h`, `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 chunk to teach battle setup today. I am citing it to show the cross-module reality: map behavior, player control, metatile behavior, scripts, and battle transitions are not sealed compartments. FireRed’s systems meet at field control. A tile, an object, or a script can become a battle, a transition, a message, or a state change.

So when we say “major game system,” maps are not isolated. The overworld map system is the staging ground for script progression, trainer encounters, item pickup, wild encounters, link rooms, UI entry points, and save positioning. This is why the course spends three weeks here. If you understand maps, you understand how a data-driven RPG turns records into places. And if you understand how records become places, then later systems like battles and inventory have somewhere meaningful to attach.

Let me give you one more concrete debugging scenario before we pause this first segment. Suppose a student adds a new door tile to a FireRed-like map. The tile appears visually. The player can walk up to it. But stepping on it does nothing. The student says, “The door is broken.” My first question is: did you add geometry only, or did you add a warp event? If they added only a door-looking metatile in the layout, they changed visual geometry. They did not necessarily add behavior. My second question is: is the warp coordinate correct relative to the map’s coordinate system and border offset conventions? My third question is: does the destination map exist in the map group registry? My fourth question is: does the destination warp index or target coordinate resolve correctly? My fifth question is: are any scripts or conditions blocking the transition?

That debugging sequence is the architecture in miniature. Visual tile, movement permission, event record, map registry, destination resolution, runtime state. Each step belongs to a different layer. If you skip layers, you guess. If you trace layers, you debug.

Quick check: if a map draws correctly but the player cannot walk where expected, which data layer do we inspect first? Movement permissions and metatile behavior, not object scripts. If the player can walk correctly but an NPC is missing, what do we inspect? Object events and their visibility conditions, then the runtime object loading path. If the NPC appears but pressing A does nothing, what do we inspect? The object event’s script association and the interaction path. If stepping onto a tile fails to run a cutscene, what do we inspect? Coordinate triggers, like the material called out in `pokefirered/data/maps/BattleColosseum_2P/map.json` `chunk_237e353ea279eb27`.

That is the level of precision I want this week. Not “the map has events,” but “this player action consults this event category, which was loaded from this map record, which references this layout, which is registered in this global map organization.”

Now, keep that board model in view: identity, geometry, behavior. In the next portion, we will open the map header more carefully and treat its fields as a set of engine promises: what the loader can assume, what the event system can assume, and what your own schema loader in lab must preserve if it is going to behave like a real FireRed-inspired map runtime.

So, let’s slow down over the phrase “engine promises,” because that is doing real architectural work here. A map header is not just a bag of settings. It is a contract between data authors, generated C data, and runtime systems that do not want to ask philosophical questions every frame. When the player enters BattleColosseum_2P, the map loader should not be asking, “Does this place have dimensions? Does it have a layout? Are there warps? Are objects encoded in a compatible way?” Those questions have already been answered by the map record and by the build pipeline.

On the board, I’m going to draw three stacked rectangles. The bottom one is layout. That is the physical substrate: width, height, border, block data, tileset references, movement permissions as derived from metatile behavior. The middle one is map header. That is identity and runtime classification: which map this is, which layout it points at, what type of map it is, which scripts and event arrays belong to it. The top rectangle is live runtime state: player coordinates, object event instances, script context, warp destination state, flags and vars, and whatever task or callback is currently driving the visible screen.

Now notice something important. We have already studied callbacks and tasks. I am not going to re-teach that spine today. But I do want you to reuse it. The map record is not itself the running game. It becomes running game only when a callback path loads it, allocates or initializes state, and then per-frame systems interpret that state. In other words, this week’s material plugs into the runtime shell from week 1 and the data-generation discussion from week 2.

Let’s make that concrete with the Battle Colosseum examples. The research packet points us to pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, for the BattleColosseum_2P layout, and pokefirered/data/layouts/layouts.json, chunk_4a91aa64203b6d9a, for the BattleColosseum_4P layout. Those are layout registry records. They are not the same thing as map records. They answer questions like, “What geometry and visual layout data can a map reference?” Then pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, is the map-side record. That is where we care about the header, object events, and warp events for that specific playable place.

Check your understanding here: if I change the dimensions or block layout for BattleColosseum_2P, am I primarily changing the map header or the layout record? Layout record. Good. If I change which NPC stands at coordinate x, y and which script they run, am I primarily changing the layout registry? No. That belongs to gameplay data in the map record, specifically object events. The visual floor plan and the behavioral population of the room are deliberately separated.

This separation is why tomorrow’s lecture is called Layout Data Versus Gameplay Data. Today we are already setting up that distinction, but we are doing it from the map header outward. The map header says, in effect, “Here is the layout ID I expect you to use, and here are the event arrays that make this place interactive.” Your lab loader must preserve that separation. If your schema loader treats the map as one flat blob, it may work for one test file, but it will not teach you the architecture. FireRed’s architecture is telling you that maps are composed from references.

Now, let’s talk about the first promise: identity. A map needs to be locatable in the global organization of maps. The packet specifically calls out pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, for the link map group. Map groups are not flavor text. They are how the engine organizes map identity into an addressable table structure. When a warp says, “send the player to another map,” or when save data needs to remember where the player is, the engine needs a stable map identity. That means group plus map number, or an equivalent symbolic ID after generation, not merely a filename.

So if I ask, “Where is BattleColosseum_2P?” an advanced answer is not, “It is in a JSON file.” The advanced answer is, “It has a map record under pokefirered/data/maps, it references a layout in pokefirered/data/layouts/layouts.json, and it participates in the global map grouping described by pokefirered/data/maps/map_groups.json.” That lets the runtime reach it, lets warps target it, and lets tools generate coherent tables.

Second promise: geometry. The map header points toward a layout, and the layout carries the physical facts the renderer and collision logic need. In a FireRed-like runtime, you can think of geometry as serving three clients. The renderer wants blocks and tilesets. The movement system wants passability and metatile behavior. The event system wants coordinates to mean something stable. If coordinate trigger number one says “x equals 5, y equals 8,” then the engine must agree on what x and y mean in the loaded layout.

Let me put that another way. A coordinate trigger without map geometry is just two numbers. A warp event without map geometry is just a destination attached to a point that may not exist. An object event without map geometry is a sprite request with no world to stand in. This is why your lab loader will read dimensions before trying to validate objects, warps, or coordinate triggers. You need the rectangle before you can say whether an event lies inside it.

Here is a concrete debugging example. Suppose a student builds a FireRed-like JSON loader, and their NPC loads correctly but appears one tile too far left. The beginner asks, “Is the sprite wrong?” The advanced debugger asks, “How are object event coordinates transformed into runtime local coordinates? Are border offsets involved? Is the loader confusing layout-space coordinates with camera-space coordinates? Did it parse width and height from the layout record before instantiating events?” That line of questioning comes directly from this architecture. The map record is static, but the runtime interpretation has coordinate conventions.

Third promise: behavior. This is where object events, warp events, coordinate triggers, and background events enter. The map record says, “These are the interactive affordances attached to this place.” Object events handle entities such as NPCs or sign-like interactable sprites, usually with local IDs, graphics IDs, movement types, coordinates, elevation, and script references. Warp events handle transfer points. Coordinate triggers handle stepping onto or through specific tiles under particular variable conditions. Background events handle interaction with non-sprite map positions, such as signs, hidden items, or scripted tile checks.

The packet gives us BattleColosseum_2P’s map record in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, as our anchor for map header, objects, and warps. It also separates coordinate triggers into pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. That split in the packet is useful pedagogically because it reminds us not to collapse all events into one mental category. They may all live near the map record, but the engine queries them through different paths.

Now, quick check. If the player presses A while facing an NPC, which event category are we primarily testing? Object event interaction. If the player walks onto a doorway tile, which category? Warp event, assuming it is a normal warp tile. If the player steps onto a tile and a cutscene starts only when a variable has a certain value, which category should you inspect first? Coordinate trigger. If the player presses A facing a bookshelf or sign embedded in the map, which category? Background event. Good. That distinction is going to save you hours when you debug labs.

Let’s connect this to source organization more broadly. The research packet’s synthesis says the course should repeatedly trace data table to runtime globals to callback, task, script, and visible behavior. That is exactly what a map record lets us practice. The data table begins in files like pokefirered/data/maps/BattleColosseum_2P/map.json and pokefirered/data/layouts/layouts.json. The runtime side is the overworld and event machinery. The packet identifies pokefirered/src/overworld.c, chunk_7ca719013, as a high-value overworld source area, and it identifies pokefirered/src/battle_setup.c, chunk_494d132a59422c85, as evidence for the way systems include and depend on overworld, script, event data, event object movement, metatile behavior, field control, and battle transition headers. I’m not citing battle_setup.c because today is about battles; I’m citing it because its include surface shows how map state is not isolated. Wild battles, trainer battles, field effects, scripts, and metatile behavior all need to talk through the overworld layer.

That point matters. The map system is not a “level file loader” in the narrow platformer sense. It is the spatial operating system for the RPG. It decides where the player is, which scripts are available, which encounters can happen, which NPCs exist, which transitions are legal, and what local behaviors are attached to the ground. Later in the course, when we reach battle setup, inventory, party, and save, they will keep reaching back into this spatial context.

Let’s do a board trace. I’ll write: “Player enters BattleColosseum_2P.” Under that, I’ll write: “Map identity resolved through map group.” Under that: “Header loaded.” Under that: “Layout ID followed to layout registry.” Under that: “Object, warp, coordinate, and background events made available.” And finally: “Runtime systems query event categories based on player action.”

That trace is more important than memorizing any one field name. Field names matter, yes, and by the end of this week you should be comfortable reading them. But architecture comes from knowing which subsystem consumes which part of the record. A renderer does not care about the NPC’s script pointer. A script interaction system does not care about the tileset art except indirectly through collision and facing. A warp handler cares deeply about warp coordinates and destination identity, but not about whether the room’s floor uses one decorative tile or another.

Now let’s compare BattleColosseum_2P and BattleColosseum_4P for a moment. The packet points us to the two-player layout in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, and the four-player layout in the same file, chunk_4a91aa64203b6d9a. Even without reprinting those records here, the architectural lesson is that similar gameplay spaces can have distinct layout registry entries. A two-player colosseum and a four-player colosseum may share a conceptual family, but the engine does not need to reason about that family dynamically. It follows the map’s layout reference. That is a very Game Boy Advance-friendly approach: compile the data, index into tables, avoid expensive interpretation.

And this is where week 2 comes forward. We already discussed generated data versus runtime JSON. So when I say “JSON record,” do not hear “the retail game is parsing JSON at runtime.” In our learning environment and decompilation workflow, JSON is an authoring and build-time representation. Your lab loader is intentionally runtime JSON-like because it is a teaching instrument. FireRed’s actual architecture wants compiled tables and C structs. The conceptual contract still holds: whether the record is read from JSON in your lab or generated into C for the game, the map header is a structured agreement among systems.

Let’s pause and test a subtle point. If your lab loader reads object events successfully but ignores the layout ID, can it claim to have loaded the map? No, not in the architectural sense. It has loaded a list of entities, but it has not loaded the playable place. If it reads dimensions and movement permissions but ignores warps, can it simulate walking? Maybe partially. Can it simulate map-to-map traversal? No. If it reads warps but not map groups or destination identifiers, it can detect that a warp exists, but it cannot resolve the destination. These are not optional extras. They are pieces of the map contract.

Now I want to introduce the phrase “static declaration, dynamic consequence.” A warp event is statically declared in the map record, but its consequence is dynamic: the player steps on a tile, the engine checks the tile and event list, establishes a destination, begins a transition, updates map identity, loads a new header, and resumes overworld control. Likewise, an object event is statically declared, but the actual visible NPC may depend on flags, local IDs, object event templates, movement state, and scripts. The record declares potential behavior; runtime state decides what is currently true.

This distinction is essential for dynamic warps. The week topic list includes warp events and dynamic warps, and we need to separate them carefully. A normal warp event encodes a coordinate and a destination. A dynamic warp uses runtime state to alter or supply the destination. The most common beginner mistake is to think of all warps as hard-coded door links. FireRed needs more flexibility than that. It has interiors, special transitions, link contexts, and scripted movement between locations. So, architecturally, your loader should represent a warp as a structured event with coordinates and destination fields, but your runtime model should leave room for a later layer to override or populate destination state.

Let’s say a door in a normal house sends you from map A to map B at warp number 2. That is a straightforward static warp. Now imagine a scripted facility where the exit returns you to “where you came from,” or a special link context where the destination is selected by connection state rather than by one ordinary door record. That is why we discuss state variables alongside data tables. The data record gives the engine a declared affordance; the state variable can decide the concrete destination at the moment of use.

And again, this ties to the packet’s broader source evidence. The battle setup source pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes event_data.h, script.h, event_scripts.h, fieldmap.h, field_control_avatar.h, field_player_avatar.h, metatile_behavior.h, and overworld.h. That include cluster is a fingerprint of the architecture. Systems do not simply “call the map.” They share a vocabulary of event data, scripts, field behavior, avatar control, and map metadata. When the player-visible game changes state, several of these layers cooperate.

Here is the concrete example I want you to keep for lab. Suppose your loader reads BattleColosseum_2P. Step one: resolve the map record from pokefirered/data/maps/BattleColosseum_2P/map.json, especially the header, object event, and warp material represented by chunk_919152bd3bfceb95. Step two: read its layout reference and resolve that against pokefirered/data/layouts/layouts.json, using the BattleColosseum_2P layout entry represented by chunk_76c0c20446e9e415. Step three: validate dimensions and movement permissions from the layout side before validating coordinate-bound events. Step four: load coordinate triggers from the map record material represented by chunk_237e353ea279eb27. Step five: if you want to resolve global identity, consult the map grouping structure represented by pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4.

Notice the order. Layout reference before coordinate validation. Map identity before destination resolution. Event category preservation before runtime dispatch. That order is not arbitrary; it reflects which systems depend on which facts.

Let me ask the room a diagnostic question. A player reports: “When I enter the colosseum, the floor looks correct, but I cannot leave through the expected exit.” What do you inspect first? Not the tileset. Not the object event scripts. You inspect warp events in the map record, then movement permissions or metatile behavior for the exit tile if the warp appears to exist but cannot be activated. If the warp destination is wrong, you inspect destination identity and map group resolution. If the warp is present and destination identity is valid, then you look at runtime state, such as whether a script has installed or modified a dynamic warp.

Another report: “The NPC appears and faces the right direction, but pressing A does nothing.” First candidate is the object event’s script association. Then facing and interaction logic. Then flags or conditions suppressing the interaction. The layout is probably not your first suspect unless the coordinate or elevation makes the player unable to face the NPC in the expected way.

Another report: “A cutscene should begin when I step onto the center tile, but it does not.” Coordinate triggers. In particular, you inspect the map record section represented by pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, and you ask whether the trigger condition matches the runtime variable state. Coordinate triggers are not just coordinates; they often depend on vars or script conditions. That is why a trigger can be present in data and still not fire.

Let’s recap the mental model before we move into field-by-field reading. A FireRed map record is a bridge. On one side are authoring records and generated tables. On the other side are runtime systems: rendering, collision, object events, scripts, warps, coordinate triggers, background interactions, save location, and transitions. The header sits at the crossing point. It points to layout, identifies the map, classifies the environment, and attaches event data. If you erase that structure, you can still make a toy map loader, but you will not be modeling FireRed’s architecture.

Now, for the next part, keep your eyes on those three board words: identity, geometry, behavior. Every field we inspect should land under one of those words, or explain how the engine moves between them. That is the discipline I want from you this week. When you see a map file, do not just say, “This is content.” Ask, “Which runtime system is this field feeding, and what promise is it making to that system?”

So let’s take that discipline and apply it to a real record. I’m going to keep us in the link room examples because they are small enough to hold in your head, but they are not toy examples. They are production map records from FireRed’s data set. Specifically, we are looking at pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, paired with pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415. Those two chunks together let us separate map identity, layout geometry, and gameplay behavior without guessing.

Now, eyes up here for a second. I’m writing three columns on the board again. Left column: identity. Middle column: geometry. Right column: behavior. Under identity, I write MAP_BATTLE_COLOSSEUM_2P and BattleColosseum_2P. Under geometry, I write LAYOUT_BATTLE_COLOSSEUM_2P, width fourteen, height nine, building tileset, Cable Club tileset. Under behavior, I write object events, warp events, coordinate triggers, and no background events. That is the whole lesson in miniature. A FireRed map record is not just “a room.” It is a contract between several subsystems that all need different answers.

The identity fields answer, “What map is this, and how does the rest of the game refer to it?” In the two-player Colosseum record, the id is MAP_BATTLE_COLOSSEUM_2P, and the name is BattleColosseum_2P. Those look redundant at first, but architecturally they are serving different pressures. The id is a constant-like identity used by generated tables and code-facing references. The name is a human and asset-organization identity. When the toolchain processes these records, it is not thinking in terms of a live JSON object being parsed by the Game Boy Advance. Remember the prior week’s distinction: this JSON is source data for generated C and assembly artifacts, not runtime JSON. So these symbolic names are there to keep source data stable, maintainable, and linkable through generated tables.

Now, the layout field is where many people make their first architectural mistake. They see "layout": "LAYOUT_BATTLE_COLOSSEUM_2P" and they mentally import the entire visible room into the map header. But that is not what the map record owns. The map record points to layout; it does not contain the block grid itself. If I open pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, the layout registry entry gives us the concrete geometry details for LAYOUT_BATTLE_COLOSSEUM_2P: width fourteen, height nine, border width two, border height two, primary tileset gTileset_Building, secondary tileset gTileset_CableClub, and file paths for border.bin and map.bin. So the map header says, “Use this layout identity.” The layout registry says, “That identity means this many blocks, these tilesets, these binary block assets.”

That separation is not cosmetic. It lets the engine and the build system treat geometry as reusable, indexed, and asset-oriented, while the map record remains event-oriented. Imagine I copied the Battle Colosseum room and wanted a version with the same shape but different scripts. In FireRed’s architecture, I should not need to duplicate the tileset metadata and blockdata paths inside every behavior record. I should point at a layout and then attach different object events, warps, or triggers. Conversely, if a room’s art changes but its scripts remain conceptually similar, the layout reference is the pressure point. This is why tomorrow’s lecture is called Layout Data Versus Gameplay Data. Today we are proving why that boundary exists.

Let’s make that concrete. In the two-player Colosseum map, the layout is fourteen by nine, but the player spot triggers are at coordinates three, five and ten, five, according to pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. Those trigger coordinates only make sense inside the coordinate system provided by the layout. If the layout were eight by six, coordinate ten, five would be nonsense. So behavior depends on geometry, but it is not stored inside geometry. The coordinate trigger says, “When the avatar reaches this tile under these conditions, run this script.” The layout says, “This tile position exists, and here is the visual and collision substrate around it.” That is a dependency, not a merger.

Pause there. Check for understanding. If I ask you, “Which file tells me that the two-player Colosseum is fourteen blocks wide?” the answer is not the map record; it is pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415. If I ask, “Which file tells me that stepping on coordinate three, five should run BattleColosseum_2P_EventScript_PlayerSpot0?” that is pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. Good. That distinction is going to save you from writing a bad lab loader this week.

Now look at the environmental fields in the map header. The two-player Colosseum declares music as MUS_POKE_CENTER, region_map_section as MAPSEC_SPECIAL_AREA, requires_flash false, weather WEATHER_NONE, map_type MAP_TYPE_INDOOR, allow_cycling false, allow_escaping false, allow_running false, show_map_name false, floor_number zero, and battle_scene MAP_BATTLE_SCENE_LINK. That cluster appears in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. These are not decorative tags. Each one is a small permission or presentation contract.

Map type tells the overworld what broad environmental category the player occupies. Weather tells the field effect system whether it needs to stage rain, ash, fog, or nothing. In this case, nothing. Movement permissions tell the control layer whether running shoes or cycling should be accepted. Here both running and cycling are disabled, which makes sense in a Cable Club link battle room. Escaping is also false, because this is not a dungeon escape-rope situation. The battle_scene value tells the battle transition side, “If a battle starts from here, use the link battle scene classification.” We are not diving into battle setup today, but keep the thread in mind: the source packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, as a battle transition and event-facing module. Later in the course, when we connect map context to battle setup, this kind of field is one of the bridges.

Now, the object_events array is where the map starts to feel alive. In the two-player Battle Colosseum, there is one object event: the Union Room receptionist. The record gives a graphics id, OBJ_EVENT_GFX_UNION_ROOM_RECEPTIONIST, a coordinate, x nine and y three, elevation three, movement type MOVEMENT_TYPE_FACE_LEFT, movement range zero by zero, no trainer type, a script named BattleColosseum_2P_EventScript_Attendant, and flag zero. That is all in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95.

Let’s slow down and unpack that as a data-to-runtime promise. The graphics id is not the NPC. It is the visual identity of the object event. The x and y coordinate are not the sprite’s pixel position; they are tile-space placement in the map coordinate system. Elevation is not height for drawing in the artistic sense; it participates in interaction and collision logic, deciding whether the player and an object are on compatible layers. Movement type is a behavior preset. Here, MOVEMENT_TYPE_FACE_LEFT says the receptionist is stationary and oriented left. Movement ranges of zero by zero reinforce that this object will not wander. The trainer_type being none means this object is not a trainer sightline encounter. The script is the interaction hook. If the player presses A facing this object, the event system has a label to run.

Notice the architecture here. The object event does not contain dialogue text. It does not contain a function pointer to C code. It does not contain a hand-authored little interpreter implementation. It contains identifiers that the generated event and script systems can connect later. This is exactly the kind of indirection that makes FireRed data-driven in the source sense. A content author can place an attendant and attach BattleColosseum_2P_EventScript_Attendant, while the shared object movement and script runtime know how to spawn, draw, face, and dispatch interactions.

Now I’m going to draw a little rectangle for the room. Fourteen across, nine down. I’m not drawing art, just coordinates. I place the receptionist at x nine, y three. I place the two player trigger spots at x three, y five and x ten, y five. I put two exits at x six, y eight and x seven, y eight. That sketch gives us almost the entire gameplay behavior of the room. The player enters, talks to or passes the attendant, moves to a designated position, the coordinate trigger fires, and the exit tiles route back through dynamic warps. It is a very small map, but it touches rendering, movement, scripting, link flow, and warp logic.

Let’s talk about those warps, because dynamic warps are one of the best clues that the map record is part of a larger state machine. In the two-player Colosseum, the warp_events array has two entries, both at y eight, elevations three, with x six and x seven. But instead of naming a fixed destination like MAP_CERULEAN_CITY or a fixed warp number, each entry declares dest_map MAP_DYNAMIC and dest_warp_id WARP_ID_DYNAMIC. Again, that is pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95.

A static warp says, “This door always goes to that map.” A dynamic warp says, “This tile is an exit point, but the destination has been staged elsewhere in runtime state.” That matters enormously for link rooms. You did not necessarily arrive here from one fixed door in the world. You came through a link-flow transition. The engine needs a way to say, “When the player leaves, return them to the appropriate prior location or negotiated destination.” So the map record provides the physical exit tiles, while the dynamic warp state supplies the target. Do you see the division? The map owns where the exits are. Runtime state owns where those exits currently lead.

This is the same identity, geometry, behavior triad, but now with state layered on top. The identity of the destination is dynamic. The geometry of the exit tile is fixed. The behavior is “warp when stepped on,” but the target is resolved through state. For your lab loader, you do not need to implement the whole FireRed dynamic warp stack. But you should preserve the distinction in your schema. Do not flatten MAP_DYNAMIC into a made-up map name. Treat it as a symbolic destination mode. Print it, store it, or model it as a tagged destination. That is the difference between a faithful minimal loader and a toy parser.

Now compare the four-player version. In pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_f18829d873bc1246, the header is structurally similar: MAP_BATTLE_COLOSSEUM_4P, BattleColosseum_4P, layout LAYOUT_BATTLE_COLOSSEUM_4P, same Poke Center music, special area section, no flash, no weather, indoor type, no cycling, no escaping, no running, no map name, floor zero, link battle scene. But the object_events array is empty. There is no attendant object in that record. The warp_events array has four dynamic exits, at x five, six, seven, and eight, all y eight, elevation three. So by changing data, the engine can represent a room with no visible attendant and four exit positions rather than two.

Now compare the layout registry entries. pokefirered/data/layouts/layouts.json, chunk_4a91aa64203b6d9a, gives LAYOUT_BATTLE_COLOSSEUM_4P the same width and height pattern: fourteen by nine, border two by two, primary tileset gTileset_Building, secondary tileset gTileset_CableClub, with its own BattleColosseum_4P border and map binaries. So the four-player room is not a totally different architectural species. It is a sibling record: same general environmental policy, same link battle scene, similar geometry dimensions, different behavior arrays and different blockdata asset. That is exactly the kind of comparison advanced students should be doing. You are not just reading fields. You are inferring which axes the designers expected to vary.

Here’s a quick check. What changed from two-player to four-player? Do not say “the room changed.” Be precise. The map id and name changed. The layout id changed. The object event list changed from one receptionist to empty. The warp list changed from two dynamic exits to four dynamic exits. The coordinate trigger list changed from two player spots to four player spots, as shown in pokefirered/data/maps/BattleColosseum_4P/map.json, chunk_30cd3fd6e624b8c6. What mostly stayed stable? Indoor map policy, disabled movement modes, no weather, no map name popup, link battle scene, and the fourteen-by-nine layout shape in the layout registry. That is a high-signal diff.

Now let’s turn to coordinate triggers, because they are subtle. In the two-player map, chunk_237e353ea279eb27 gives two trigger entries. Each has type trigger, x and y, elevation three, var VAR_TEMP_0, var_value zero, and a script label. This means the trigger is not simply “when you step here, always run.” It is “when you step here, and this variable has the expected value, run this script.” That variable gate is crucial. Without it, standing on the tile could repeatedly fire the script every time movement resolution checks the coordinate. The variable allows the script system to stage phases. A script can change VAR_TEMP_0, and suddenly the same coordinate no longer qualifies, or a later phase does.

This is where advanced architecture thinking matters. A coordinate trigger is not only spatial. It is spatial plus state. Location alone is not enough. Elevation alone is not enough. The trigger condition combines coordinate, elevation, variable identity, and variable value. So if you are implementing a minimal FireRed-like trigger lookup in lab, the function signature should not be just lookupTrigger(x, y). A more faithful shape is lookupTrigger(x, y, elevation, vars), or lookupTrigger(playerPosition, mapState). The FireRed data is teaching you the runtime API shape.

Let me say that another way, because it is an important design lesson beyond FireRed. Data schemas imply engine functions. If your data record has x, y, elevation, var, var_value, and script, then somewhere the engine must be capable of asking, “At this player coordinate and elevation, is there a trigger whose variable condition currently matches?” You can rename the function in your own engine, but you cannot avoid the responsibility. If your lab loader ignores elevation and variables, it is not just missing fields. It is failing to model the behavior contract.

Now, background events are empty in both these Colosseum records. That absence is itself meaningful. bg_events are the category for tile-based interactions such as signs, hidden items, or other A-button background checks, depending on the map. Here the room does not need them. The receptionist is an object event. The player spots are coordinate triggers. The exits are warp events. There is no background interaction layer. In a denser map, you might have all four event categories active. In this map, the taxonomy is clean enough to teach.

Let’s bring map groups into the picture. The file pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, places BattleColosseum_2P, TradeCenter, RecordCorner, BattleColosseum_4P, and UnionRoom in gMapGroup_Link. That is not geometry and not local behavior. It is indexing and organization. Map groups are how the map population is arranged into generated tables. So if the map record says, “I am BattleColosseum_2P,” the map group file helps answer, “Where does this map sit in the global map table organization?” It is another identity layer, but at collection scale rather than individual map scale.

This matters because major systems rarely consume a lone file. The overworld does not merely read a map JSON in isolation. The build process and generated data connect map groups, map constants, headers, layouts, scripts, and binary assets. That is why our course objective says you must identify files, structs, functions, constants, data tables, and state variables. You are not done when you name the map file. For today’s slice, the key files are the map record, the layout registry, and the map group registry. The key constants include MAP_BATTLE_COLOSSEUM_2P, LAYOUT_BATTLE_COLOSSEUM_2P, MAP_TYPE_INDOOR, MAP_BATTLE_SCENE_LINK, MAP_DYNAMIC, WARP_ID_DYNAMIC, VAR_TEMP_0, and the object graphics and movement constants. The implied structs are the generated equivalents of a map header, a map layout, object event records, warp event records, coordinate event records, and background event records. The state variables include at least the temporary event variable gate used by coordinate triggers, and the dynamic warp target state implied by MAP_DYNAMIC and WARP_ID_DYNAMIC.

I want to be careful with the word “implied” there. The chunks we are citing today are data chunks. They do not show every runtime C function that consumes them. The research packet itself warns that this packet is excellent for data structure but weaker for runtime flow, and it points us toward companion modules such as overworld.c, field_control_avatar.c, event_object_movement.c, and script.c for the full runtime path. So today I am not pretending that chunk_919152bd3bfceb95 shows the object movement engine. It does not. What it shows is the source data contract that such an engine must consume. That distinction is part of professional source reading.

Let’s run a small mental execution. The player is in BattleColosseum_2P. The current map header has selected LAYOUT_BATTLE_COLOSSEUM_2P. The renderer and collision systems have access to fourteen-by-nine blockdata using the Building and Cable Club tilesets. The object event loader sees one object and spawns the receptionist sprite at tile nine, three, elevation three, facing left. The movement control layer knows running and cycling are disallowed because the header says so. The player walks to x three, y five. The coordinate trigger system checks current elevation and VAR_TEMP_0. If VAR_TEMP_0 is zero, it selects BattleColosseum_2P_EventScript_PlayerSpot0. Later, if the player steps on x six, y eight, the warp system sees a warp tile with dynamic destination markers and resolves the actual target from dynamic warp state.

That whole story came out of a few dozen data fields. That is the power of data-driven architecture, but it is also the danger. If you read it casually, it looks like a list of labels. If you read it architecturally, it is a distributed program. Some instructions are spatial. Some are state-gated. Some select assets. Some select environmental policy. Some point to scripts. Some defer to runtime state. A map record is not code, but it is absolutely executable in the broader sense that the engine turns it into behavior.

Before we move to the next portion, I want a short recap. Identity tells us what the map is and where it sits in generated registries. Geometry tells us which layout, dimensions, tilesets, borders, and blockdata define the physical play surface. Behavior tells us which objects, warps, coordinate triggers, and background interactions attach gameplay to that surface. BattleColosseum_2P is valuable because it contains all three in a compact form: map identity and environment fields in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95; layout geometry in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415; coordinate trigger state gates in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27; and global link-room grouping in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4.

So for the next part of the lecture, we are going to stay with the same example, but we will shift from naming fields to designing a loader. Not a full FireRed engine. A loader small enough for this week’s lab, but honest enough that it preserves the architectural promises we just identified. And the test I want you to keep in your head is this: when your loader reads a field, can you say which runtime question that field answers? If yes, you are modeling the architecture. If no, you are probably just copying JSON into memory.

Good. Now let’s design the loader as a model of responsibility, not as a file parser wearing a costume.

I’m going to put three boxes on the board. First box: record identity. Second box: spatial surface. Third box: event surfaces. Those are not FireRed names; those are teaching names. FireRed’s concrete data is distributed across files like pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, and pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. But the reason I draw these three boxes is that your loader needs to keep separate questions separate.

Record identity answers, “Where am I in the world registry, and what kind of place is this?” Spatial surface answers, “What tiles, dimensions, border behavior, and movement permissions define the walkable world?” Event surfaces answer, “What can happen here when the player, an object, or a script asks the map to respond?”

Those sound similar at first because all of them are map data. But architecturally they are very different. If you collapse them into one vague Map object, you will be able to load the example, yes, but you will not understand why FireRed can reuse a layout, swap a script, move the player through a warp, or place link-room maps into a group. So in lab, when you build your minimal schema loader, I want you to make the separation visible in your own types, even if your implementation is small.

Let’s make this concrete. Suppose your loader opens the BattleColosseum_2P map record. From pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, it sees a map header-like record. That record is not the floor itself. It is a claim about which layout to use, which scripts and events belong to this named place, what music or region-style metadata may apply, and how the map participates in the larger overworld registry. Then the loader follows the layout reference into pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415. That layout entry is where your mental model should move from “place” to “surface.” Width, height, border, block data, tileset references, movement permissions: those belong to the walking plane. Then, after that, the loader comes back to the map record and reads object events, warp events, and coordinate triggers. For BattleColosseum_2P, the coordinate trigger material is separately visible in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27, and it is exactly the kind of data that proves a map is more than a tile grid.

Pause there. Check for understanding. If I ask, “Can two maps share a layout but differ in object events?” the answer should be yes in the architecture. If I ask, “Can a warp depend on the map record rather than the layout?” yes. If I ask, “Can a coordinate trigger depend on game state while the tile underneath stays unchanged?” also yes. Those are the boundaries your loader should preserve.

Now, for our lab version, I would not start by trying to duplicate FireRed’s entire generated C structure. That would distract you. I would start with a staged load. Stage one validates the map record identity and pulls out its layout identifier. Stage two resolves that layout identifier against the layout registry. Stage three reads the event arrays and normalizes them into runtime-friendly structures. Stage four checks consistency across those structures. And yes, the fourth stage matters, because a data-driven engine is only as trustworthy as the invariants you enforce before the runtime starts asking questions.

Let me write an example invariant on the board: every object event coordinate should be inside the layout dimensions, unless the original engine has a specific convention for off-map staging. For our lab, keep it strict. If the BattleColosseum_2P layout says the surface is a certain width and height in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, then an object event from pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, should not casually appear outside that rectangle. Another invariant: every coordinate trigger coordinate should also be inside that rectangle. Another: every warp should have a source position that exists on the current map, and a destination that can be represented by your map-group or map-name model.

Now, this is where map_groups.json enters the story. The packet points us to pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, for the link map group. That file is not a decoration around maps. It is part of the world registry. If individual map records are rooms, the map group table is one of the structures that lets the engine speak about rooms collectively and address them predictably. For a loader, that means you should not treat destination maps as arbitrary strings floating around with no registry. Even if your lab project only loads BattleColosseum_2P and BattleColosseum_4P, you should still have a small registry object that can answer, “Is this named map known? Which group contains it? Can a warp destination be resolved?”

This is an advanced point, so let’s slow it down. In a toy RPG, you might put destinationMap: “Lobby” inside a warp and call it done. That works until you need tooling, validation, save data, link rooms, or generated constants. FireRed’s architecture is not just “string points to map.” It is closer to “map identity participates in compiled tables, and those tables are used by runtime systems that need stable identifiers.” We already covered generated data versus runtime JSON in week 2, so I will not reopen that whole topic. Just carry the consequence forward: your loader is standing in for a toolchain boundary. The JSON is authoring data. The object you produce is runtime data.

Look at how that changes the way we talk about errors. If the layout ID is missing, that is not a rendering problem. It is a schema or registry problem. If a warp destination cannot be resolved, that is not a player movement problem yet. It is a world graph problem. If a coordinate trigger references a variable or flag condition you do not model, that is not a tile problem. It is an event-state problem. Mature engine design gets a lot easier once your errors have the right home.

Now let’s connect object events, warps, and coordinate triggers to runtime questions.

An object event answers, “What actor-like things are initially present on this map, and what metadata controls their appearance, movement, and script interaction?” In FireRed terms, these are not just sprites. They are data records that can become event objects in the overworld runtime. Some may stand still. Some may wander. Some may be hidden by flags. Some may run scripts when the player talks to them. In our BattleColosseum_2P example, the object event list in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, is part of that map-specific gameplay layer. It should not live in the layout registry because a layout can describe the same room shape without deciding which people are currently in it.

A warp event answers, “What happens if the player steps on this coordinate and accepts movement transfer?” It ties a source coordinate to a destination identity. And in FireRed, you also have to keep the idea of dynamic warps in mind. The course packet names warp events and dynamic warps as part of this week’s focus because warps are not always just static doors. Sometimes the engine needs to remember or compute where “return” means, or route the player according to a previous transition. For the lab, you do not need to implement every dynamic behavior. But you do need to leave room in the model for the distinction between a static destination and a runtime-resolved destination. If your WarpEvent type can only hold a literal destination, you have already made tomorrow’s lecture harder for yourself.

A coordinate trigger answers, “What script or behavior should fire when the player reaches a coordinate under a certain state condition?” This is where chunk_237e353ea279eb27 is especially useful. The coordinate trigger data for BattleColosseum_2P gives us a compact example of map behavior that is spatial but not visual. The player sees floor. The layout sees tile data. The event system sees a conditional activation point. That is the layered architecture in miniature.

Let me give a simple imagined flow. The player enters BattleColosseum_2P. The engine has already resolved the map header and layout. The camera is set up from the dimensions. The tilemap is drawn from the layout’s block data. Movement permissions tell the avatar which metatiles are passable. The object events are instantiated into runtime event objects. Now the player takes a step. The movement code checks collision and permissions against the spatial layer. After the step lands, the event layer can ask, “Is there a coordinate trigger at this x-y? Is its variable condition satisfied? If yes, which script should run?” Nothing about that requires the tile itself to change. That is the point.

And now I want to connect this back to the callback and task spine from week 1 without re-teaching it. The map record does not run itself. It feeds systems that run during the overworld callback and the per-frame task machinery we already studied. The same architecture pattern will keep returning: static data is resolved into runtime state; callbacks and tasks advance behavior; scripts and state variables decide what happens next. So when you read a coordinate trigger, do not imagine it executing during parsing. Imagine it being installed as queryable map state for the overworld runtime to consult later.

This is also why source navigation by include lists can be revealing. For example, pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes overworld, script, event_data, event_object_movement, metatile_behavior, fieldmap, field_control_avatar, field_player_avatar, and battle-related headers. That is not a map record file, but it shows the kind of cross-system seam where overworld state, scripts, metatile behavior, and battle transitions meet. When a trainer battle or field encounter eventually happens, it is not because one giant “map object” knows everything. It is because map data, script state, field control, and battle setup collaborate through defined interfaces. That is the level of connection I expect you to be able to explain by the end of week 3.

Now, for the loader, let’s be precise about movement permissions because this is where many people accidentally flatten the model. Movement permission is not the same as visual tile identity. A tile can look like a floor, a mat, a ledge, water, or a wall, but the engine needs a behavior-level answer: can the avatar enter, surf, jump, collide, trigger something, or interact? FireRed’s metatile behavior system is larger than today’s example, but for the lab, treat movement permissions as data derived from or associated with the layout surface. You are not writing a renderer; you are writing the piece that can answer, “At coordinate x-y, what movement category applies?” If you store only a pretty tile index, your loader cannot support gameplay.

Here is the check I would run during lab review. I might point to a single coordinate and ask three questions. What block or tile is at this coordinate? What movement permission applies there? What events are registered there? A strong implementation can answer all three separately. A weak implementation gives me one blob of data and says, “It’s in the JSON somewhere.” That is the difference between reading data and modeling a system.

Let’s bring BattleColosseum_4P into the comparison for a moment, because comparison reveals architecture. The packet gives us pokefirered/data/layouts/layouts.json, chunk_4a91aa64203b6d9a, for the BattleColosseum_4P layout. We have been focusing on BattleColosseum_2P, but the 4P layout is useful because it reminds us that related gameplay spaces may have related but distinct spatial requirements. In a link room context, player capacity is not just narrative flavor. It affects object placement, possible spawn positions, room shape, and sometimes scripts. Your loader should not have special hard-coded knowledge that “Colosseum means two players.” It should read the map and layout records and let the data say what the particular room supports.

That is another advanced habit: avoid naming assumptions that feel true in the first example. The first example is a sample, not the schema. If your code says if mapName contains “2P,” then expect two players, you have built a brittle inference. If the object events or scripts indicate participant slots, read those. If the layout dimensions differ, validate those. If map_groups.json, chunk_f22eb0b9c9a5d2a4, places these maps in a link-related group, then model that grouping explicitly. Do not scrape meaning out of English names unless the source architecture actually uses that convention as an interface.

Now, watch how this helps us connect week 3 to the rest of the course plan. Weeks 3 through 5 are the overworld map runtime. This week we are handling records, layouts, warps, and triggers. Next, we will spend more time on layout data versus gameplay data, and then the map runtime will become less static: loading, entering, transitioning, and responding. Later, when we reach the script VM and progression state, today’s coordinate trigger discussion will come back. Later still, when we study battle entry, inventory UI, party state, and save persistence, you will keep seeing the same architectural question: which data is authored, which data is generated, which data is mutable runtime state, and which callback or task consumes it?

The save system angle is worth previewing for just a moment. A map record says what the map is by default. The save state says what has changed. If an NPC disappears because an event flag was set, you do not edit the map record. You evaluate the object’s condition against event data. If a dynamic warp destination changes, you do not rewrite the layout. You change runtime state. So even now, while writing a loader, be careful not to confuse defaults with current world state. A loader should produce the base map definition. The running game overlays flags, variables, player position, object state, and script progress.

Let’s do one more concrete example. Imagine your minimal loader has produced a LoadedMap object. It has identity fields from the BattleColosseum_2P map header in chunk_919152bd3bfceb95. It has a resolved layout from layouts.json, chunk_76c0c20446e9e415. It has objectEvents, warpEvents, and coordTriggers from the map record, including the coordinate trigger material in chunk_237e353ea279eb27. Now the player enters at a warp destination. What should your runtime receive?

It should receive the loaded map definition, plus an entry context. The loaded definition says, “Here are the valid surfaces and registered events.” The entry context says, “Place the player at this coordinate, facing this direction if specified, because this is how they arrived.” That entry context may come from a warp. It may come from a saved game. It may come from a debug start. It may come from a script. If you bake entry context into the map definition, you will eventually fight your own design.

This is why I keep using the phrase “runtime question.” The field is not important because it exists. The field is important because some system will ask a question and needs a reliable answer. The map group answers addressability. The layout ID answers surface resolution. The dimensions answer bounds and camera constraints. Movement permissions answer collision and traversal. Object events answer actor instantiation. Warp events answer spatial transition. Coordinate triggers answer conditional script activation. Header environment fields answer presentation and behavior defaults for the place. Each one has a consumer.

All right, quick check. I’m going to say a field category, and you tell me whether it belongs primarily to identity, spatial surface, or event surface. Layout ID. Identity, because it points from the map record to the spatial surface. Width and height. Spatial surface. Object event script pointer or script label. Event surface. Warp destination. Event surface, but it also references world identity, so it crosses into the registry. Map group membership. Identity and registry. Movement permission. Spatial surface, consumed by movement logic. Coordinate trigger variable condition. Event surface, consumed with progression state. If those answers feel natural, you are tracking the architecture.

Now, there is one subtle mistake I want to prevent before it appears in homework_w03. Do not make your loader too clever. Validation is good. Guessing is not. If a field is absent, report that it is absent. If a layout reference does not resolve, fail clearly. If a coordinate trigger points outside bounds, fail clearly. But do not silently invent a default layout, silently clamp coordinates, or silently discard events. FireRed’s real toolchain has many conventions and generated structures, but in our educational loader, silent correction will hide the very architectural relationships we are trying to learn.

At the same time, do not make the loader too literal. If the source JSON uses names that are convenient for generation, your runtime model may use names that are convenient for questions. That is not betrayal; that is compilation. For example, you might load a raw warp record and normalize it into sourceX, sourceY, destinationMapId, destinationWarpId, and resolutionKind. You might load coordinate trigger records and normalize them into coordinate, condition, and scriptRef. You might load movement permissions into a grid-like query structure. The goal is not to preserve the authoring format as sacred. The goal is to preserve meaning.

Let’s tie this to the evidence packet one final time before we move on. BattleColosseum_2P is a compact case because its map header and event material are in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95, its coordinate trigger slice is identified in the same map file at chunk_237e353ea279eb27, its layout definition is in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415, and its broader link-room grouping appears in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4. BattleColosseum_4P gives us a nearby comparative layout in pokefirered/data/layouts/layouts.json, chunk_4a91aa64203b6d9a. Those are enough to build a loader that is small but architecturally honest.

So, as you think about the lab, do not ask, “Can I parse all the JSON?” Ask, “Can I reconstruct the engine-facing promises of the map record?” When you can answer that, you are no longer just reading data. You are beginning to see how FireRed turns authored records into a playable overworld. And that is the bridge we need for the next portion, where we will take the loaded record and start asking how the overworld runtime decides what to instantiate, what to ignore, and what to activate as the player moves.

Good. Now let’s step across that bridge carefully. When the engine has the map record in hand, it does not treat the record as one flat pile of information. It asks a sequence of questions. Where am I? What shape is this place? Which authored things are eligible to exist right now? Which authored things are passive until the player crosses a coordinate? Which authored things are exits? And which pieces are not gameplay at all, but visual or collision substrate?

I’m going to put that on the board as a runtime interrogation of the record. Not a JSON interrogation. A runtime interrogation. That distinction matters because, by this point in the course, you should be suspicious of any explanation that stops at “the file has a field.” FireRed is not interesting because it has fields. It is interesting because the fields participate in a set of engine contracts.

So, board cue here: I’m drawing four boxes. The first box is identity and routing. The second box is physical layout. The third box is event population. The fourth box is activation rules. Now I’m drawing arrows between them, and notice that the arrows do not all go in the same direction. The map header points toward the layout registry, yes. But the event rules also depend on coordinates inside that layout. Warps route out of the current map, but they also depend on the local tile coordinate where the player stands. Object events live inside the map, but their scripts can change flags and variables that affect which objects or triggers matter later. So this is not a neat hierarchy. It is a compact record that becomes meaningful only when the overworld runtime keeps asking, frame by frame and step by step, “what does this place now permit?”

Let’s use BattleColosseum_2P again because it is small enough to reason about without losing the shape of the system. Its map header, object events, and warps are in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_919152bd3bfceb95. Its coordinate triggers are in that same map record, specifically chunk_237e353ea279eb27. The layout it names is in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415. And the map’s broader link-room grouping is visible in pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4. Those citations are not decoration. They tell you the architectural split. The same playable room is not stored in one monolithic source location. Identity and grouping, physical map shape, and gameplay events are authored in related but distinct tables.

Now, what does the runtime instantiate? Let’s be precise. It does not instantiate the layout as an “object” in the same sense it instantiates an NPC or an event object. The layout is the spatial substrate. It provides dimensions and block data references through the layout registry. You should think of it as giving the engine the legal coordinate plane and the tile behavior field. If the player is at x equals some value and y equals some value, the engine needs to know whether that coordinate is inside the map, what metatile is there, what collision applies, and whether that tile’s behavior has special meaning. That answer comes from the layout side, not from the object-event side.

By contrast, an object event is an authored entity template. It says, in effect, “there may be an entity here with this graphics identity, this initial coordinate, this movement behavior, this script, and this visibility condition.” The exact field names vary as they move from JSON to generated C data, but conceptually that is the promise. The record is not saying that the object must always be visible, forever, unconditionally. It is saying that the map owns a possible object event. Runtime state decides whether that possible object becomes an active object instance.

Let me pause on that word “possible,” because it is one of the advanced ideas for today. In a simple student engine, the map loader often creates every object listed in the file. You parse an array of objects, allocate sprites for all of them, and call it done. That is acceptable for a toy. It is not sufficient for a FireRed-like architecture. FireRed’s event objects sit at the intersection of static templates and mutable progression state. An NPC may disappear after a flag is set. A rival may appear only before a story beat. An object may have movement, facing, elevation, and a script binding, but those do not guarantee current visibility. So, for the lab, if your schema loader simply returns “there are N object events,” that is a start, but it is not yet architecturally honest. An architecturally honest loader preserves enough information for a later runtime layer to answer, “which of these are eligible under current flags and variables?”

Check for understanding. If I give you two records, one layout record and one object-event record, and I ask which one decides whether the player can walk through a wall, which one do you choose? The layout side. More specifically, the block and metatile behavior data reached through the layout definition. If I ask which one decides whether an NPC script can run when you press A facing a character, which side do you choose? The object-event side, because the object event binds position, facing interaction, and script identity. Good. If you got those backward, your mental model is still treating the map as a picture. It is not a picture. It is a playfield plus event affordances.

Now let’s bring in warps. A warp event is also not just “a door.” It is a coordinate-bound transition promise. It says that at this local x, y, and elevation, there is an exit whose destination is some map group, some map number, and usually some destination warp index. The reason the map group matters is that FireRed’s map identity is not a single global string at runtime. The grouping table is part of the routing architecture, and that is why pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, belongs in the same lecture as the map header. The group table lets the engine organize maps into addressable regions of data. For the link-room examples, that grouping is especially visible because the Battle Colosseum maps are not just ordinary town interiors; they participate in a special multiplayer-facing part of the map catalog.

Here is a concrete example. Imagine the player standing in BattleColosseum_2P on a tile that has a warp event. The runtime does not need to scan every map in the game. It does not say, “which destination string looks right?” It consults the current map’s warp event table, finds whether the player’s local coordinate matches a warp, and then resolves the destination through the map group and map number style of identity. That is a huge design difference. It means each map record owns its local exits, and the global map registry owns the address space those exits point into.

Now, dynamic warps complicate this, and they are worth naming even if BattleColosseum_2P is not the biggest dramatic example. A dynamic warp is what you need when the destination is not fully fixed by the static warp table. The design pattern is that the static map data gives the engine ordinary local transitions, but runtime state can override or supply a destination when a script or system needs more flexibility. Think of escape ropes, return locations, link-room entry and exit flows, or script-controlled transitions. The broader lesson is that FireRed does not choose between static data and dynamic state. It layers them. Static data gives default authored geography. Dynamic state patches in the current story, session, or system-specific route.

This is where our earlier weeks matter, but only as foundation. In week 1, we built the idea that gameplay flows through a callback and task spine; in the packet, that earlier foundation is anchored in pokefirered/src/main.c, chunk_b35676322b169f85 for AgbMain, pokefirered/src/main.c, chunk_6cfd90f6d0136799 for SetMainCallback2, and pokefirered/src/task.c, chunks_cb0bb91a147bfb59 and_3ffebc0341225936 for task creation and per-frame dispatch. I am not re-teaching that today. I am pointing out how it changes your map interpretation. The map record does not “run itself.” It is loaded into a runtime that already has a main loop, callbacks, tasks, and state variables. The authored record supplies facts. The overworld runtime supplies time.

That phrase is important: data supplies facts; runtime supplies time. Write that down if you are taking notes.

Coordinate triggers make this especially clear. In BattleColosseum_2P, the coordinate trigger material is in pokefirered/data/maps/BattleColosseum_2P/map.json, chunk_237e353ea279eb27. A coordinate trigger is not an object you see. It is not a person, not a sign, not a door graphic. It is a condition attached to a coordinate, and often to a variable condition as well. The engine checks it when movement resolves. That timing is crucial. If the player is halfway through a step animation, you do not want to fire the trigger too early. If the player merely faces a coordinate, you usually do not want to fire it at all. The trigger becomes meaningful when the player’s movement state says, “the avatar has arrived here,” and the event condition says, “this coordinate is currently active.”

So, what should your lab loader preserve for coordinate triggers? It should preserve x, y, elevation if present, the variable or condition gate if present, the expected value if present, and the script identity. It should not eagerly execute the script. It should not turn the trigger into a visible object. It should not assume that every coordinate trigger is unconditional. Your loader is a schema loader, not a script VM. The honest output is a representation that a later movement system could query after each completed step.

Let me give a small invented walk-through using the real categories. Suppose the map has width and height from its layout reference, and suppose your player is at coordinate 5, 7. The player presses up. The movement system asks the layout, “is 5, 6 passable?” That is a layout and metatile behavior question. If passable, the avatar begins moving. After the step completes, the runtime asks, “did we land on a warp?” That is a warp-event question from the current map’s warp table. If not, it asks, “did we land on an active coordinate trigger?” That is a coordinate-event question from the current map’s coordinate trigger table. If no trigger fires, the overworld continues. If a trigger does fire, it hands control to the script system. Notice the ordering here. The layout constrains movement first. The event table reacts to resolved movement second. That ordering is how you avoid a class of bugs where scripts fire from impossible positions.

Now, someone may ask, “What about background events?” Good. Background events are another category of map gameplay data, and they are easy to misunderstand because the word background sounds visual. In this context, background events are often interaction affordances attached to map positions: signs, hidden items, secret-base-like checks in later engines, or other coordinate-bound interactions. For our purposes today, distinguish them from coordinate triggers by player intent and timing. A coordinate trigger can fire because you stepped onto a coordinate. A background event commonly matters because you inspect, press A, or otherwise interact with a coordinate or facing tile. In both cases the map owns coordinate-bound gameplay. But the activation rule is different.

That difference belongs in your loader design. If you collapse warps, coordinate triggers, and background events into one generic “events” list with no activation type, you have destroyed useful architecture. You can no longer answer whether an event should be checked on step completion, on button press, or during transition handling. Advanced schema design is not about preserving every source character. It is about preserving the distinctions the engine needs later.

Let’s connect that to the two colosseum layouts. BattleColosseum_2P’s layout is in pokefirered/data/layouts/layouts.json, chunk_76c0c20446e9e415. BattleColosseum_4P gives us a comparative layout in the same registry, chunk_4a91aa64203b6d9a. These are excellent for a design comparison because they remind us that two maps can belong to the same conceptual feature family while requiring different spatial substrates. A two-player colosseum and a four-player colosseum may share a theme, link-room context, and interaction pattern, but they do not have to share dimensions or block data. The layout registry lets those differences stay physical, while the map records carry the gameplay population and triggers specific to each room.

So if I ask you in the lab, “Why not just put width and height directly into every map event file and skip the layout registry?” the answer is not merely “because FireRed doesn’t.” The deeper answer is that layout reuse and layout identity are part of the data model. A map header naming a layout is an explicit dependency on a physical substrate. The layout can be compared, shared, generated, indexed, and reasoned about separately from the gameplay events. That is exactly the separation we will sharpen in the next lecture, “Layout Data Versus Gameplay Data.”

Now let me fold in one more piece from the research packet that points forward. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the include list itself is a useful architectural signal: the battle setup code reaches across overworld, event data, script, event object movement, metatile behavior, field map, field control, field player avatar, and related systems. I am not claiming that an include list is a full call graph. It is not. But at this level of course analysis, it is evidence that the boundary between “map” and “battle” is not a wall. The player-visible moment “a battle starts” may depend on where the player is standing, what tile behavior applies, which scripted event ran, what object initiated contact, and what global event state says. That is why our course thesis keeps returning to the path from data table to runtime globals to callback, task, script, and visible behavior. The map record is one of the major entry points into that path.

Check for understanding again. Suppose your loader reads BattleColosseum_2P and successfully prints the map’s layout id, dimensions, object event count, warp count, and coordinate trigger count. Is that enough for this week’s lab? It is enough for a first milestone. It is not enough for full credit if those values are untyped blobs. You need to preserve the meaning of each category. A warp is not a coordinate trigger. An object event is not a layout tile. A layout id is not a map id. A map group is not a visual layer. If your data structures keep those distinctions, your loader can grow into a runtime. If they do not, you will rewrite it the moment we add movement permissions or scripts.

Let me say that another way. The schema loader should produce a small model of FireRed’s architecture, not a small model of JSON syntax.

Now, as a board exercise, imagine I erase the JSON braces and keep only the engine-facing ideas. I write MapHeader at the top. Under it, I write layout reference, events reference, map type and behavior-related header fields, and routing identity. Off to the side, I write MapLayout with dimensions and block data references. Under events, I write object events, warp events, coordinate events, background events. Then, underneath that, I draw the runtime checks: load map, construct visible objects, resolve movement through layout, check warp on landing, check coordinate trigger on landing, check background event on interaction. That board diagram is more valuable than memorizing the exact order of fields in the JSON record.

And here is the advanced part: the same authored record supports multiple temporal phases. At load time, the engine needs header and layout information. During rendering and collision, it needs block and metatile-derived information. During object population, it needs object templates and event flags. During movement completion, it needs warp and coordinate trigger tables. During player interaction, it needs object scripts and background events. During cross-map transition, it needs map group routing. So one map record participates in several different moments of the overworld loop.

This is also why we are not treating the Battle Colosseum as a special curiosity. Link rooms are useful precisely because they make the routing and statefulness more visible. The map group chunk, pokefirered/data/maps/map_groups.json, chunk_f22eb0b9c9a5d2a4, reminds us that these maps sit in a catalog. The two-player and four-player layout chunks, chunk_76c0c20446e9e415 and chunk_4a91aa64203b6d9a, remind us that physical space can vary within a feature family. The BattleColosseum_2P map chunks, chunk_919152bd3bfceb95 and chunk_237e353ea279eb27, remind us that a single map record can combine header identity, local entities, exits, and triggers. That is the architecture in miniature.

For the homework this week, I want you to hold yourself to the standard we used in quiz_w02, where you traced behavior through concrete source structures rather than giving a vague system name. If you write, “the map handles warps,” that is too vague. Say, “the map record provides a warp-event table; the runtime checks the player’s resolved coordinate against that table; the destination is expressed through the map grouping/addressing scheme.” If you write, “the layout handles collisions,” sharpen it: “the map header’s layout reference leads to layout data that supplies the coordinate substrate and block/metatile information used by movement permission checks.” That is the level of explanation expected now.

Let’s do one final local synthesis for this segment. A FireRed map record is not merely a room description. It is a compact contract between authored data and overworld runtime. The header says how this map is identified and which layout it depends on. The layout says what physical coordinate space exists. Object events say which entities may be instantiated and interacted with. Warp events say which local coordinates route to other map addresses. Coordinate triggers say which positions can launch scripts when movement resolves. Background events say which positions can respond to inspection or interaction. And the map group table gives the larger address space that lets one local warp become a real transition through the game’s catalog of maps.

Carry that model into the next portion. We are going to separate two questions that beginners often blend together: “What does the room look like and permit physically?” versus “What gameplay happens in that room?” FireRed keeps those questions close enough to cooperate, but separate enough that the engine can load, query, and mutate them at different times. That separation is the heart of tomorrow’s lecture, but we are going to start building it now by looking more closely at movement permissions and the boundary between layout data and event data.

So, when I say “movement permissions,” I do not mean a vague design idea like “the player can walk here.” I mean a very specific layer of map interpretation: the runtime has a grid, and every tile position in that grid eventually answers questions like, “Can the avatar enter this metatile from this direction?” “Does this terrain have surf behavior?” “Is this a counter tile?” “Is this blocked by an object event?” “If the player presses A while facing it, is there a background event?” Those questions are related, but they are not all stored in the same part of the map record.

Let me put that on the board. On the left side, I am writing “layout.” Under that: width, height, border, block data, tileset references, movement permission information attached through metatile behavior. On the right side, I am writing “gameplay record.” Under that: object events, warp events, coordinate triggers, background events, map scripts, music, weather, map type, and flags that affect runtime behavior. Now draw one arrow between them, not five arrows. The arrow says “loaded together into overworld runtime.” That is the key. FireRed does not treat layout and gameplay data as unrelated; it treats them as cooperating records with different responsibilities.

For our concrete case, compare the two Battle Colosseum layouts listed in `pokefirered/data/layouts/layouts.json`: the `BattleColosseum_2P` layout in `chunk_76c0c20446e9e415` and the `BattleColosseum_4P` layout in `chunk_4a91aa64203b6d9a`. The important lesson is not merely that there are two layouts. The important lesson is that the link facility can present different physical arrangements for different multiplayer modes while still participating in the same map architecture. A two-player colosseum and a four-player colosseum are not special snowflakes in the engine. They are records. They have layout identities. They can be reached through map groups. They can own object events and warps. The runtime asks the same kind of questions of them that it asks of a town, a house, or a Pokémon Center room.

Now, pause there. If I changed only the layout dimensions and block data for the two-player colosseum, what category of behavior would I expect to change? I would expect the room’s physical footprint to change: what you see, where walls are, what tiles exist, which positions are physically traversable. If I changed only the object event list in `pokefirered/data/maps/BattleColosseum_2P/map.json` at `chunk_919152bd3bfceb95`, I would expect the actors and interactable standing entities to change: NPC-like objects, their initial positions, their movement types, their scripts or local IDs. If I changed only the coordinate trigger records in that same map’s `chunk_237e353ea279eb27`, I would expect the room to look identical, but to react differently when the player steps on particular coordinates. That distinction is the architecture.

And this is where advanced students should be careful. It is tempting to say, “The map record defines the map.” That sentence is true but too coarse. A better sentence is: the map header gathers references to records that define the map’s physical layout, its event surfaces, and its runtime classification. That wording matters because FireRed is built around indirection. We saw that in week two with generated data and headers as cross-module contracts, and we are not going to re-teach that machinery now, but we should reuse the idea. A JSON source record in `pokefirered/data/maps/BattleColosseum_2P/map.json` is not the runtime object walking around memory by itself. It is source data that participates in the build pipeline and becomes C-addressable data. The map header is the compiled meeting point.

Let us do a quick mental trace. The player transitions into the Battle Colosseum. The engine resolves a destination map using a group and map number. That larger catalog relationship is represented in `pokefirered/data/maps/map_groups.json`, and for the link map group our packet points us to `chunk_f22eb0b9c9a5d2a4`. Once the map identity is resolved, the engine can obtain the map header. From the map header, it can find the layout reference. From the layout reference, it can know how large the space is and what block data to load. Separately, from the map header, it can find the events list: objects, warps, coordinate triggers, background events. So the visible room and the reactive room are loaded together, but they are not the same data.

Check your understanding here. Suppose I tell you a player can see a doorway tile, but stepping on it does not move them anywhere. Where would you inspect first? Not the tileset art. Not the palette. Not the fact that the doorway is drawn on screen. You would inspect the warp event table for that map. A visual doorway is just visual until the map’s gameplay data gives that coordinate transition semantics. Conversely, suppose stepping onto a plain-looking tile unexpectedly moves the player to another room. Then the visual layer may be innocent; the warp event data is the suspect. That is why architecture beats screenshot reasoning.

Now, coordinate triggers are a subtler version of the same principle. A coordinate trigger is not a wall. It is not the floor graphic. It is a script activation condition tied to position and usually to some state condition. In `pokefirered/data/maps/BattleColosseum_2P/map.json`, the coordinate trigger material called out in `chunk_237e353ea279eb27` gives us a clean teaching example because link rooms often need invisible choreography. The game may need to start a sequence when the player reaches a waiting spot, aligns with another participant, or enters a zone where a link interaction should proceed. You do not want to encode that in the tileset. You want to encode it as event logic associated with coordinates.

This is also where object events become more than “NPCs.” An object event is a map-local actor declaration. It says, in effect, “When this map is instantiated, here is an object with an identity, graphics, position, movement behavior, and script-facing presence.” In ordinary towns, that might be a person pacing around. In a link colosseum, it might be a receptionist-like object, a participant marker, or a controlled actor related to the multiplayer flow. The exact content is in the map record, but the architectural point is stable: object events are data-driven actor seeds. They become runtime event objects only after the overworld engine reads and instantiates them.

Let me connect this to our earlier callback and task work without repeating it. The map record does not execute itself. Data is inert. The main callback spine and task system we studied in week one give the program its per-frame rhythm. The overworld systems use that rhythm to poll input, move the avatar, update objects, run scripts, and handle transitions. So when you read `pokefirered/data/maps/BattleColosseum_2P/map.json` at `chunk_919152bd3bfceb95`, do not imagine the JSON doing work. Imagine a compiled data table being consumed by overworld code that is already running inside the callback/task framework. That is the recurring course pattern: data table, runtime globals, callback or task or script, visible behavior.

Let us make this concrete for the lab. Your minimal schema loader this week is not expected to emulate all of FireRed. But it should respect the separation FireRed is teaching us. If your loader reads a map file and immediately smashes layout dimensions, objects, warps, coordinate triggers, and movement permissions into one undifferentiated dictionary, it may pass a toy test, but it will teach you the wrong architecture. Instead, your loader should produce something closer to two cooperating products: a layout model and an event model, joined by the map header. The layout model answers, “What is the size and physical tile grid?” The event model answers, “What happens at named or numbered positions?”

Here is the board version. I am drawing a rectangle for the map header. Inside it, I write layout ID, events pointer, scripts pointer, music, map type, weather or related classification fields. Then I draw an arrow from layout ID to the layout registry. That registry is represented in our source focus by `pokefirered/data/layouts/layouts.json`, including the Battle Colosseum entries at `chunk_76c0c20446e9e415` and `chunk_4a91aa64203b6d9a`. Then I draw another arrow from the map header to the event lists in `pokefirered/data/maps/BattleColosseum_2P/map.json`, especially the header, objects, and warps in `chunk_919152bd3bfceb95`, and the coordinate triggers in `chunk_237e353ea279eb27`. Then one final arrow goes outward to the map group catalog in `pokefirered/data/maps/map_groups.json` at `chunk_f22eb0b9c9a5d2a4`. That outward arrow says, “This map is not isolated; it has an address in the world catalog.”

A common mistake in student loaders is to start with coordinates before establishing coordinate spaces. FireRed map coordinates are not just abstract row-column pairs floating in nowhere. They live inside a layout of known dimensions, and they are interpreted by systems that also account for border behavior, object occupancy, movement permissions, and screen scrolling. If an event says x equals some value and y equals some value, your loader should be able to validate that the coordinate is meaningful for the layout it references. That is not a cosmetic validation. That is how you catch broken architecture early.

Now let me ask a sharper check-for-understanding question. Imagine your loader reads the `BattleColosseum_2P` map header and finds that it references the two-player layout. It also reads a warp event whose destination points into the link map group. What are the two separate validations you can perform? First, you can validate that the warp’s source coordinate lies inside the current map’s layout dimensions. Second, you can validate that the destination map group and map identifier resolve through the map group catalog, in our packet represented by `pokefirered/data/maps/map_groups.json` `chunk_f22eb0b9c9a5d2a4`. Notice the two validations cross different boundaries. One is local geometry. The other is global addressability.

This distinction becomes very powerful later in the course. In week four, when we dig into event scripting, we will treat scripts as programs whose entry points are attached to maps, objects, coordinates, and background interactions. In week five, when we talk more deeply about progression and flags, you will see why a coordinate trigger often has to be guarded by state. The tile does not change, but the meaning of stepping there can change. In the battle system weeks, this same architectural habit returns in a different form. For example, `pokefirered/src/battle_setup.c` at `chunk_494d132a59422c85` shows how battle setup is not one monolithic “start battle” blob; it includes headers for task, overworld, script, event data, field control, battle transition, and related systems. That include surface is evidence of a boundary-crossing workflow: field state hands off into battle state through explicit systems. Today’s map record is one of the places where that field-side story begins.

I want to be precise about movement permissions before we close this part. Movement permission is not the same as object collision, though the two combine in player experience. If a floor tile is walkable but an object event currently occupies it, the avatar may be blocked. If no object occupies a tile but the metatile behavior says it is a wall, water without surf, a ledge approached from the wrong side, or some other non-enterable behavior, the avatar may also be blocked. The player experiences both as “I cannot walk there,” but the engine benefits from keeping the causes separate. Physical terrain comes from layout and metatile behavior. Dynamic occupancy comes from runtime objects. Scripted reaction comes from events. Again, three layers cooperating.

Here is a small example. Picture a receptionist standing behind a counter in a multiplayer room. The counter tile itself might have a behavior that allows interaction across it but blocks walking through it. The receptionist object has a facing direction and a script. The background or coordinate event nearby might handle special cases if the player enters a connection area. The room graphic, the collision behavior, the object actor, and the script trigger are all part of one scene, but if you were implementing a patch, you would not want them tangled together. If you move the receptionist one tile left, you should edit the object event. If you make the counter passable, you should inspect layout or metatile behavior. If pressing A on the counter no longer opens the expected interaction, you inspect background events or object-facing script logic depending on how the interaction is modeled.

This also explains why the Battle Colosseum examples are such good teaching data. They are small enough to reason about, but they are not trivial. The two-player and four-player variants force us to see layout identity as a parameter of mode. The link map group forces us to see map identity as part of a larger catalog. The coordinate triggers force us to see invisible gameplay boundaries. The warps force us to distinguish graphic exits from actual transitions. This is exactly the kind of map that punishes a simplistic “map equals tile picture” model.

Let me give you the recap in spoken form. A FireRed map record is a hub of references. It names or points to a layout. The layout supplies dimensions and physical tile structure. The map’s event data supplies object events, warp events, coordinate triggers, and background interactions. The map group table supplies the larger addressing system that makes warps meaningful across the world. Runtime systems consume those records inside the callback and task-driven shell we studied earlier. The result is what the player experiences as a room: visible tiles, blocked and allowed movement, actors, scripts, and transitions.

For your lab, the standard is higher than “parse some JSON.” You are advanced students now, and the recent homework and quiz already asked you to trace behavior from records through runtime structures. So for this week’s loader, I want you to preserve architectural intent. Read the layout ID. Resolve it against the layout registry. Keep dimensions in the layout model. Read object events as actor declarations. Read warps as transition declarations. Read coordinate triggers as script activation declarations. Read movement permissions as physical traversal data, not as events. Then write validation that proves you understand which layer owns which fact.

And one final synthesis before we stop this lecture. The reason FireRed’s map architecture still repays close study is that it refuses to put all meaning in one place. That can feel indirect at first. You open a map file and it points somewhere else. You open a layout registry and it points to block data. You inspect a tile and the behavior is not necessarily the same thing as the graphic. You inspect a doorway and the transition is not necessarily in the art. But that indirection is the engine’s strength. It lets the same runtime load many maps, many layouts, many event sets, and many scripted interactions without custom code for every room.

Tomorrow, we will take the separation we have built today and press on it harder. We will ask what truly belongs to layout data, what belongs to gameplay data, and where the engine deliberately allows them to meet. Keep the Battle Colosseum examples in mind: `layouts.json` for the physical room variants, `BattleColosseum_2P/map.json` for map-local actors and triggers, and `map_groups.json` for world-level addressability. If you can hold those three records in your head at once, you are no longer reading FireRed maps as isolated files. You are reading them as architecture.

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