# Flags and Variables as World Memory

Lecture ID: lecture_w08_d02
Week 8, Day 2

## Objectives
- Design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence.
- Connect week 8 material to the overall course plan.

## Transcript
All right, let’s pick up exactly where yesterday’s interpreter discussion left us. We are not going to re-walk the A-button lookup path, and we are not going to re-teach object events from scratch. You already have that mental model: some field interaction resolves to a script pointer, and the overworld hands control to the script system. Today we are asking the next architectural question. Once a script starts running, how does the game remember that the world has changed?

That phrase, “the world has changed,” is more technical than it sounds. In a creature RPG, the map geometry is mostly static. The NPC records are mostly static. The item definitions are static. The trainer data is static. But the player’s experience is not static. The rival has already challenged you, or has not. A door is locked, or it is open. A parcel has been delivered, or it has not. A one-time item ball is visible, or it has already been collected. The world you see is therefore static data filtered through progression memory.

So, if I write on the board, “world equals map data plus event memory,” that is the core of today’s lecture. Not the whole engine, of course, but the core of progression. Map loading tells us what could exist. Flags and variables tell us what currently matters.

In FireRed terms, we are grounding today’s discussion in the script engine chunks from pokefirered/src/script.c, especially chunk_1be14bbdaf5c3400 for the script engine, chunk_03258f0288d89251 for script control flow, and chunk_d424688149aa3bcb for execution support. Then we connect that to pokefirered/src/scrcmd.c chunk_c2a54fb6031b5886, where script commands become concrete operations, and pokefirered/src/event_data.c chunk_a13cdbcb283dbf3b, where flags and variables live as event data. Those paths matter because this is a separation-of-responsibilities lecture. The interpreter knows how to walk instructions. The command table knows what individual verbs mean. Event data is the memory those verbs mutate.

Let me slow that down.

If a script says, conceptually, “set flag 42,” the script engine should not need to understand the story meaning of flag 42. It should not know that this means “the player got the lab parcel,” or “the gym door can now open,” or “that item ball should disappear.” It only needs to dispatch the command. The command implementation translates the bytecode arguments into an event-data operation. The event-data layer stores the resulting bit or variable value.

Now, that sounds ordinary, but it is the architectural move that lets a data-driven RPG scale. If every door, NPC, pickup, cutscene, and trainer rematch were hard-coded as a C branch in the overworld loop, the game would become unmaintainable almost immediately. Instead, the overworld can say, “I found an event. Run its script.” The script can say, “Check a flag. Branch.” The flag system can answer, “Yes or no.” That tiny protocol is enough to encode a huge amount of story logic.

Let’s use a concrete locked-door example, because it is simple enough to fit in our heads but representative enough to matter.

Imagine a house door in a town. The map data says there is a door tile at coordinate 12, 8. The object or coordinate event says, when the player interacts here, run script DoorScript. The script says: compare variable VAR_STORY_STAGE with 3. If less than 3, show message, “It is locked.” End. Otherwise, warp the player to the inside map.

Notice the important thing. The door itself did not become a different kind of tile. The player did not acquire a special “door opening” function. The world changed because one integer changed. Somewhere else, maybe after a rival fight or a parcel delivery, the game ran setvar VAR_STORY_STAGE, 3. From that point onward, the same interaction reaches the same script pointer, but the script takes a different branch. That is world memory.

Check for understanding here: if I ask, “Where is the door’s unlocked state stored?” the answer is not “in the door.” At least, not in this minimal model. It is stored in event memory, usually as a flag or variable. The door reads that memory when its script runs.

Now, flags and variables are related, but they are not interchangeable. A flag is a compact yes-or-no memory. Did the player beat this trainer? Did this item vanish? Did this cutscene already happen? A variable stores a small numeric value. What stage is the story in? Which option did the player choose? Which trigger mode is active for this map? You can force everything into flags, but advanced architecture means choosing the smaller, clearer state representation.

Let me put a design rule on the board: use flags for independent facts, use variables for ordered or multi-valued state.

Suppose we have a three-step story sequence. First, the professor asks you to retrieve a parcel. Second, you receive the parcel. Third, you deliver it and the rival route opens. You could represent this with three flags: asked_for_parcel, got_parcel, delivered_parcel. Sometimes that is fine. But if those states are strictly ordered, a variable like VAR_STORY_STAGE can be cleaner: 0 means not started, 1 means asked, 2 means parcel acquired, 3 means delivered. That gives scripts a natural way to compare progress. The tradeoff is that variables require discipline. If you accidentally set the stage backward, multiple systems may regress. Flags are harder to order, but easier to reason about locally.

This is why event data becomes one of the most important systems in the entire architecture. It is not glamorous. It is not a battle animation. It is not creature growth. But it is the memory layer that allows static content to behave as if it has history.

Now connect this back to our course plan. In weeks 1 through 2, we treated the game as a runtime shell plus data pipeline. We traced startup, callback slots, task dispatch, generated data, and asset inclusion. In weeks 6 through 8, we moved into input, events, and story progression. Last week and earlier this week, you traced from field input to object events to script pointers. Today, we attach memory to that path. So the full line becomes: input selects an interaction, the interaction identifies a script pointer, the script interpreter executes commands, the command handlers mutate flags and variables, and later map or script behavior reads those mutations back.

That is a complete state loop. Player action mutates world memory. World memory changes future player-visible behavior.

And now let’s make this more rigorous. A minimal clone of this system needs at least five pieces.

First, it needs map loading. Not full FireRed map loading with every layout, metatile behavior, object template, connection, and warp. Minimal map loading means: load a map identifier, fetch its static definition, instantiate its interactable events, and initialize whatever local runtime state is needed to let the player stand somewhere. In the broader FireRed source context, the course packet reminds us that pokefirered/data is a recurring module for map and layout data, including map JSON and layout registries. That is the static side of the world.

Second, the clone needs script execution. That means a script pointer or script id, an instruction pointer, a table of command handlers, and a run loop. This is exactly why pokefirered/src/script.c chunk_1be14bbdaf5c3400 and chunk_d424688149aa3bcb are central today. At the clone level, the interpreter can be tiny: read opcode, decode operands, call handler, update instruction pointer, stop when blocked or ended.

Third, the clone needs event memory. That is the flag and variable store. FireRed’s relevant source focus is pokefirered/src/event_data.c chunk_a13cdbcb283dbf3b. In a clone, this can be a bitset for flags and an integer array for variables. The important part is not the container type. The important part is that scripts do not privately store progression state inside themselves. They mutate a shared event memory layer.

Fourth, the clone needs systems that consult event memory. This is where design becomes interesting. If only scripts read flags, flags are useful. But if map loading also reads flags, the world becomes richer. An item ball can be hidden if FLAG_GOT_POTION is set. An NPC can use a different script if VAR_STORY_STAGE is high enough. A coordinate trigger can be disabled after a cutscene runs once. So event memory becomes a shared contract between map loading, script execution, and field behavior.

Fifth, the clone needs save persistence. If flags and variables define world history, then saving the game without them is not saving the world. It is only saving a position. This is one of the easiest mistakes to make in a student clone. You save player x and y, party data, and inventory, then reload into a world where every one-time item has respawned and every story gate has reset. That is not a save system. That is a teleport with amnesia.

Let’s pause for a check. If your clone has a locked door that opens after a boss fight, which systems must cooperate? Take ten seconds.

You need battle or encounter resolution to set some event memory, probably a flag or story variable. You need the door script to read that memory. You need the script interpreter to execute the compare and branch commands. You need map interaction to launch the door script. And you need save persistence to preserve the memory after reload. That is the architecture. Not the door. The cooperation.

Now, since this is an advanced class, let’s talk about why this design is stronger than a direct method call. You could code the boss fight like this: after victory, set door.unlocked equals true. That may work in a tiny game. But now ask: what if the door is not currently loaded? What if three doors unlock? What if an NPC dialogue changes? What if a fly destination appears? What if the same fact must survive a save and be visible across maps? Direct object references collapse when the world is not all resident in memory. Event flags solve that by naming facts, not objects.

That sentence is worth keeping: flags name facts, not objects.

A flag such as FLAG_BEAT_BROCK does not need Brock’s map loaded. It does not need the gym object loaded. It does not need the badge UI loaded. It is just a persistent fact. Later, any system can ask about that fact and adjust behavior. That is why story progression as state mutation is so powerful.

Now let’s tie in party data, battle turns, and item use, because the objective for this week is not only “build a script interpreter.” It is to design minimal clone versions of the major creature RPG systems and see how they exchange state.

Party data is the player’s creature memory. It answers: which creatures do I have, what level are they, what moves do they know, what current HP do they have, what status condition applies? Battle turns consume and mutate that data, sometimes temporarily and sometimes persistently. Inventory stores item quantities. Item use often bridges inventory, party, battle, and script state. A potion reduces inventory count and changes a creature’s HP. A key item may set a flag. A badge may be represented as a flag and also affect obedience or field permissions. A story item may be in the bag, but the fact that it was delivered may be a separate flag or story variable.

This is why you should resist making “the script system” an isolated toy. In the lab, yes, we build a small bytecode-style interpreter with setflag, clearflag, setvar, compare, branch, warp, and message. But the architectural lesson is bigger. Script commands are the glue layer between content and engine systems. The command handler file, represented in today’s source focus by pokefirered/src/scrcmd.c chunk_c2a54fb6031b5886, is where verbs become engine actions. Message touches UI. Warp touches map loading. Setflag touches event memory. Giveitem would touch inventory. Startbattle would touch battle setup.

And we can see that broader pattern in the research packet’s high-value chunks. For example, pokefirered/src/battle_setup.c chunk_494d132a59422c85 includes dependencies across overworld, item, load_save, safari zone, quest log, script, event_data, field control, battle, and battle transition. That include list is not merely trivia. It tells us battle entry is not a pure isolated function. It sits at the boundary between overworld state, script state, save-related state, and battle runtime. Similarly, pokefirered/src/battle_util.c chunk_9335e5727aa0657d includes event_data alongside battle, item, moves, abilities, weather, and battle scripts, which supports the broader claim that event state can matter beyond one dialogue box. The architecture is a mesh of systems, but it avoids chaos by routing progression through shared, named state.

Now, I want to draw a small bytecode script on the board. Not FireRed’s exact byte encoding, just a clone-level version.

Instruction zero: compare VAR_STORY_STAGE, 2. Instruction one: branch_if_less locked_label. Instruction two: message “The door opened.” Instruction three: warp MAP_HOUSE_INTERIOR, 4, 7. Instruction four: end. Locked label: message “It is locked.” Instruction six: end.

When the interpreter starts, it has an instruction pointer at zero. The compare command stores a comparison result somewhere. That might be a dedicated compare register, or it might be returned directly to the branch command depending on your design. Then branch_if_less either changes the instruction pointer to the locked label or allows execution to continue. Message may block while the text box is open. Warp may request a map transition. End releases script control back to the field.

The subtle point is that not every command is equally synchronous. Setflag can complete immediately. Setvar can complete immediately. Compare can complete immediately. But message may need to wait for the text system, and warp may hand control to a fade or map transition. In FireRed, this is part of why script execution support and field systems have to coordinate instead of simply running one giant function call to completion. We saw the foundation for that yesterday in the script engine material in pokefirered/src/script.c chunk_1be14bbdaf5c3400 and chunk_d424688149aa3bcb. Today, focus on what the script is waiting for and what memory it changes before it waits.

Here is a common clone design mistake: students implement the interpreter as a while loop that runs until end, and then they add message as “print to console.” That is fine for a command-line prototype. But in a graphical RPG, message is a blocking interaction across frames. The script may need to pause until the player dismisses the box. So your script context needs a state like running, waiting_for_message, waiting_for_movement, waiting_for_battle, or stopped. You do not need FireRed’s full machinery to learn the lesson. You need the idea that script execution has continuation state.

Let’s check that. Why can’t every script command just run instantly in one frame? Because some commands depend on player acknowledgment, animation, map transition, or battle completion. The interpreter must be able to pause and resume with its instruction pointer intact.

Now, where do flags and variables fit into pausing? They are stable memory outside the temporary call stack. If a script sets FLAG_CUTSCENE_STARTED and then waits for movement, that flag remains true while other frame systems continue. If the game saves after the cutscene completes, that flag persists. If the map reloads, object visibility may consult that flag. That is world memory doing its job.

Let’s be precise about persistence. In a minimal save file, I would expect at least this: player location, party data, inventory, flags, variables, and maybe a small amount of system state like money, play time, options, and registered item. For this course objective, focus on the first five. Player location lets map loading resume. Party data lets creature progression resume. Inventory lets item use resume. Flags and variables let world history resume. Without any one of those, the RPG may boot, but it will not preserve the player’s actual journey.

This also gives us a test strategy. If you are building the lab interpreter, do not only test that setflag writes true. Test a scenario. Start with VAR_STORY_STAGE equal to 1. Interact with the door. Expect locked message and no warp. Run a separate script that sets VAR_STORY_STAGE to 3. Interact with the same door. Expect warp. Save. Reload. Interact again. Expect warp still occurs. That last assertion is the difference between a script demo and a progression system.

Notice how this connects to prior assessments. In homework_w07, you traced FireRed gameplay from data records through globals, callbacks, tasks, scripts, structs, and concrete source functions. So I am going to hold you to that level now. Do not say, “the door opens because the story advanced.” That is a narrative answer. The architecture answer is: a prior script command mutated an event variable; the save layer preserved it; the door interaction launched a script pointer; the script compared that variable; the branch selected the warp path. That is the expected granularity at this point in the course.

Let me give another example, this time an item ball.

The map data places an item object at coordinate 5, 10. The object has a script pointer. When the player presses A, the script checks FLAG_GOT_ROUTE_POTION. If set, maybe the object should not even be visible; ideally map/object initialization hides it before interaction. But if the script runs, it can still guard itself: if flag set, end. Otherwise, give item POTION, quantity 1; set flag FLAG_GOT_ROUTE_POTION; hide object; message “You found a Potion.” In a robust engine, the visibility and the script both respect the same fact. That redundancy is not always waste. It can be defensive consistency.

Now look at how item use complicates the picture. A Potion in the bag is not a progression flag. It is inventory quantity. Using it changes party data, specifically HP, and decrements inventory. But picking up a one-time Potion from the field also sets an event flag. So one user-visible object touches three categories of state: inventory, party if later used, and event memory so the pickup does not respawn. That is why we study these systems together instead of in isolation.

This is also where Pokemon Showdown is useful only as a secondary comparison, not as the main architecture source. Showdown’s data and simulator are excellent for battle modeling and regression-style thinking, and the packet points us to pokemon-showdown/data/abilities.ts chunks like chunk_da22cf969179258c for ability rating data. But Showdown is not our authority for overworld progression memory. FireRed is. For this lecture, Showdown can remind us that battle data can be declarative and testable, but the flags-and-variables architecture is anchored in FireRed’s script and event data modules.

So, stepping back, the core pattern for week 8 is: content issues commands, commands mutate shared state, shared state changes future content behavior. That is not unique to FireRed. You will find versions of it in many RPGs, adventure games, visual novels, and quest systems. What FireRed gives us is a compact, production-proven example on constrained hardware, with a clean enough division that we can build a clone version without copying the whole engine.

I want to end this first stretch by setting up the next board section, because we are going to get more specific about representation. We will ask: how large should the flag store be in a clone? How do we name flags so scripts remain readable? Should variables be global, local, or map-scoped? How does a branch command find its target? And how do we prevent story variables from becoming a pile of magic numbers that nobody can maintain?

Keep the locked door in your head as the anchor example. Same map interaction, same script pointer, different world memory, different outcome. That is the mental model we will keep refining.

So let’s put representation on the board. I’m going to write three boxes: flags, variables, and durable records. Flags are single-bit facts. Variables are small numeric facts. Durable records are structured facts, like the player party, bag inventory, map location, options, play time, and save metadata. The reason I’m drawing those as separate boxes is that beginners often collapse all persistent state into one dictionary and call it a day. That can work for a prototype, but it hides an important architectural distinction. A flag answers, “Has this yes-or-no condition happened?” A variable answers, “Which value, stage, counter, or selected option is current?” A durable record answers, “What is the current state of this subsystem?” In FireRed terms, the flag and variable machinery lives in the event data layer, and our source focus names pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, as the relevant storage implementation. The script interpreter then mutates that storage through commands, with the script engine in pokefirered/src/script.c, especially chunk_1be14bbdaf5c3400 for execution, chunk_03258f0288d89251 for control flow, and chunk_d424688149aa3bcb for execution support. The command layer that exposes this to scripts is pokefirered/src/scrcmd.c, chunk_c2a54fb6031b5886.

Now, for a minimal clone, I do not want you to start with a giant save file schema. Start smaller. Use a fixed-size bitset for flags, maybe 1024 flags for a toy engine. Use a fixed-size array of signed or unsigned 16-bit variables, maybe 256 variables. Then use typed records for the party, bag, player location, and save header. That gives you enough structure to build a real locked-door scenario without turning world memory into a loose pile of strings.

Let’s work the locked door example in slow motion. The map loader reads a map header, a layout reference, and an event list. We are not re-teaching map headers today, but remember from the map and object-event lectures that the map is not just tiles. It contains object events and script entry points. The player presses A facing the door. The interaction system, which we covered earlier, resolves the front tile or object event and finds a script pointer. Today we enter after that lookup. We already have the pointer. The script VM begins reading bytecode.

On the board, I’ll write a tiny script:

message “The door is locked.”
compare VAR_HAS_KEY, 1
branch_if_equal open_door
end
open_door:
message “You used the key.”
setflag FLAG_DOOR_OPEN
warp MAP_HOUSE, 4, 7
end

Now, that script is intentionally a little awkward, because I want you to notice the first design flaw. “Has key” should probably not be a variable. If the player either has or does not have a story key, a flag is enough. But if the key is an item in the bag, then neither a flag nor a story variable is the authoritative source. The bag record is. So a better version might have a command called checkitem ITEM_RUSTY_KEY, 1, and that command writes the result into a comparison register, or into a conventional variable, and then the branch follows.

This is where advanced architecture judgment comes in. The script VM should not know every detail of the inventory structure. The script command handler may call an inventory API. The inventory API checks the bag. The script command then stores a result in the script context. In FireRed, this separation is visible across modules. The packet points us to battle_setup.c, chunk_494d132a59422c85, where the include list crosses overworld, item, load_save, script, event_data, field control, battle, and transition systems. That is a useful clue: production gameplay features are not isolated academic modules. They are coordinated through narrow calls and shared state records. The danger is not cross-module communication itself. The danger is uncontrolled communication where every system reaches into every other system’s private representation.

Check for understanding here. If a locked door should remain open after saving, quitting, and reloading, where must the fact “door is open” live? Not in the current script pointer. Not only in the map object’s temporary runtime state. Not only in the animation task that moved the door. It must be represented in durable world memory, usually as a flag, and the save system must persist that flag. Then, when the map loads again, the map or object-event initialization checks that flag and chooses the correct initial state: door present, door hidden, door opened, collision removed, whatever your clone’s map model uses.

Let me now separate three lifetimes, because this is the part that prevents a lot of bad designs. First, there is frame lifetime. That is callback and task state. It answers, “What am I doing this frame?” Second, there is session lifetime. That is runtime state since boot or since entering a mode. It answers, “What is currently loaded?” Third, there is save lifetime. That answers, “What should survive power-off?” Flags and variables usually belong to save lifetime, but not always. Some engines have temporary flags or local script variables that reset on map reload. FireRed’s event data idea is powerful because it makes story progression cheap and globally queryable, but when building a clone you should be intentional about which facts deserve global durability.

So, for the clone architecture, I would create a WorldState structure. Inside it, flags, vars, party, bag, current map id, current position, and story-related counters. Then I would create a RuntimeState structure. Inside that, active map, object instances, script context, message box state, transition state, and battle state if a battle is active. The save file serializes WorldState, plus maybe options and metadata. It does not serialize the raw script instruction pointer unless you are deliberately supporting mid-script saves, which is a more advanced feature and usually not worth it in a teaching engine.

Now I’m going to write a branch command on the board as if it were bytecode. Imagine each command starts with an opcode byte. SETFLAG reads a two-byte flag id. SETVAR reads a two-byte variable id and a two-byte value. COMPARE reads a variable id and a value. BRANCH_IF_EQUAL reads a signed offset or an absolute script address. MESSAGE reads a string id. WARP reads a map id and coordinates. END stops the script. The VM loop is very small: fetch opcode, dispatch to handler, let handler update context, repeat unless blocked or ended.

That “unless blocked” clause matters. A message command often does not finish instantly. It opens a text box and waits for player input. A warp command may start a fade transition and hand control to the field transition system. A battle command may start the battle setup flow. The script VM therefore needs a way to pause and resume. FireRed’s script engine chunks in pokefirered/src/script.c are important here because the interpreter is not just a parser; it is part of a larger frame-based runtime. The course packet specifically calls out script pointer execution and command handlers as the week’s source focus, not because bytecode dispatch is exotic, but because bytecode dispatch inside a callback-task-overworld runtime is the architectural lesson.

Let’s connect that to minimal battle turns, because this is one of the objectives for this week’s assessment. A clone battle turn also mutates state, but the state is not primarily flags and variables. It is party data and battle runtime data. The player chooses a move. The opponent chooses a move. The battle engine orders actions, applies damage, applies status, checks fainting, awards experience, and eventually returns a result. At the boundary back to the overworld, the battle result may set flags or variables. For example, defeating a rival sets FLAG_RIVAL_ROUTE22_DEFEATED. Losing might warp the player to a healing location and update money. Winning a trainer battle marks that trainer as beaten so the trainer does not challenge you again.

Notice the pattern. The battle engine owns battle mechanics. The event system owns story memory. The bridge is a controlled result handoff. The packet’s battle_setup.c chunk_494d132a59422c85 is useful evidence for this kind of bridge because it includes script.h and event_data.h alongside battle transition and field modules. And battle_util.c, chunk_9335e5727aa0657d, includes event_data.h alongside battle scripts, battle messages, moves, items, abilities, and weather constants. That does not mean “put all progression logic into battle utility.” It means the real engine has necessary contact points between battle behavior and event state. In a clone, make those contact points explicit.

Let’s do a concrete example. Suppose an NPC blocks a bridge until you defeat a gym leader. The NPC script says, “Only trainers with the Boulder Badge may pass.” Internally, that could be FLAG_BADGE_1_GET. When you defeat the gym leader, the battle result returns WIN, the gym leader post-battle script runs, the script displays badge text, gives a TM item, sets FLAG_BADGE_1_GET, maybe sets a variable for obedience level, and then releases control. Later, when the bridge NPC script runs, it checks FLAG_BADGE_1_GET. Same NPC local id, same script label, same interaction path we learned earlier, but different world memory means different branch.

Now I’ll pause for a check. Why should the bridge NPC check the badge flag instead of checking whether the gym leader trainer object is currently defeated on the loaded map? Because the bridge may be on a different map, loaded hours later, after a save and reload. The gym leader’s local object state is not a good global authority. The story fact is global and durable. That is exactly what flags are for.

Let’s talk naming, because naming is not cosmetic in a script-heavy RPG. If you name flags FLAG_001, FLAG_002, and FLAG_003, you have created a maintenance problem disguised as compactness. If you name them FLAG_PEWTER_GYM_BROCK_DEFEATED, FLAG_HAS_BOULDER_BADGE, and FLAG_ROUTE3_AIDE_REWARD_CLAIMED, scripts become readable. In the compiled game, those names become numbers. At runtime, the VM only needs ids. But your source scripts and tools need semantic names. This is the same data-driven lesson we have been returning to since the early weeks: human-readable authored data becomes compact runtime representation through the build pipeline. You saw that pattern with generated data and asset conversion. Here it returns as script constants.

Variables need even more discipline. A flag can only be wrong in a few ways. A variable can become a junk drawer. For a clone, I would divide variable ids into ranges: system variables, map trigger variables, story chapter variables, temporary script variables, and minigame counters. You do not need a complex allocator yet, but you do need a naming convention. VAR_STORY_CHAPTER is better than VAR_42. VAR_LAST_TALKED_NPC may be a temporary runtime variable. VAR_ROUTE_EVENT_STAGE may be map-scoped or global depending on whether the route state must persist.

Now, should variables be global, local, or map-scoped? For a FireRed-inspired minimal clone, I would implement global variables first, because that matches the simple script model and makes persistence easy. Then I would add temporary script locals only when you feel pain from overusing globals. A local variable stack is elegant, but it increases your bytecode and debugger complexity. Map-scoped variables are useful, but they introduce lifecycle questions: do they reset when leaving the map, or persist per map forever? That is not impossible, but it is a design commitment. Start with global event vars and a small script-local scratch area, and document which commands write to scratch.

Let’s return to branch targets. A branch command can encode its destination as an absolute pointer, an offset, or a label resolved by the script compiler. In source scripts, authors should write labels. In bytecode, labels should be resolved away. For a small interpreter in the workshop, you can store instructions in an array and let branch targets be instruction indices. That is perfectly fine. If you are building a binary bytecode format, relative offsets are compact and relocatable. Absolute pointers are fast in C on a fixed ROM layout, but they are not friendly if you want portable tooling.

The important concept is that branching is not magic. Compare sets a condition. Branch reads the condition and changes the script pointer. That pointer mutation is story control flow. In the previous lecture, when we discussed FireRed’s script interpreter, we framed command handlers as tiny bridges between bytecode and engine behavior. Today, see branch as the purest example: it does not draw, move, battle, or save. It just decides what instruction comes next based on world memory.

Now imagine we are designing save persistence for the locked door, the party, the bag, and a battle result. I’ll sketch a save operation verbally. The player opens the menu and chooses save. The save system gathers WorldState. It writes a version number. It writes flags bitset. It writes vars array. It writes party count and party creatures. It writes bag pockets and item counts. It writes current map id and coordinates. It writes checksum. When loading, it validates version and checksum, reconstructs WorldState, then asks the overworld loader to load the current map. The map loader uses the restored flags and variables to decide which objects, triggers, and blockers are active.

This is why world memory is not “just scripting.” It is the connective tissue across map loading, script execution, party data, battle turns, item use, and save persistence. If the player uses a key item, item use may run a field-use callback, which may start a script, which may set a flag, which may alter a map object, which must persist. If a battle ends, the result may run a script, which may set a trainer defeated flag, which changes future interaction. If the party changes because a creature faints or gains a level, that party record persists independently of the story flag system, but scripts may still query it through commands like checkparty, checkspecies, or checkmove in a richer engine.

The source packet gives a nice reminder of this cross-system reality in pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. That chunk’s include list reaches pokemon.h, item.h, party_menu.h, battle_main.h, field_specials.h, and other UI and data modules. A summary screen is not world progression by itself, but it sits on the same party data that battles mutate, menus display, items affect, and saves persist. In your clone, do not store “party for battle,” “party for menu,” and “party for save” as separate truths. Store one party model, then let battle runtime create temporary battle-facing views when needed.

Let me ask another understanding check. If the player uses a potion outside battle, what state changes? The bag quantity decreases, and a party creature’s HP increases. Does a story flag need to change? Usually no. If the player uses a special key item to unlock a story door, what state changes? The bag might change, if the item is consumed. The door flag changes. The map collision or object state changes in runtime. The save file must later preserve the durable parts. Same command surface, very different state responsibilities.

That distinction is how you avoid turning every action into a flag. You do not set FLAG_USED_POTION_ON_SLOT_2. You update party and bag records. You do set FLAG_SECRET_DOOR_OPENED if future map loading and dialogue depend on that fact. The rule of thumb is: flags and variables represent progression facts, not ordinary subsystem data. Party data belongs to the party model. Inventory belongs to the bag model. Battle turn order belongs to battle runtime. Current text box animation belongs to UI runtime. Story facts belong to event data.

Now, in advanced projects, you will be tempted to replace flags and variables with a high-level quest graph. That can be appropriate for some games. But FireRed’s architecture teaches us the power of low-level, composable state mutation. A quest graph often becomes a second scripting language. Flags and vars are crude, but they are transparent. Any script can ask a small question. Any map can conditionally hide an object. Any NPC can branch. The cost is discipline: naming, documentation, allocation, debugging tools, and tests.

For the workshop interpreter, I want you to build a debug view. Even if it is just printed text, show the current script pointer, last comparison result, changed flag ids, changed variable ids, and whether the VM is running, blocked, or ended. Then run the locked-door scenario three times. First with no key and no flag. Second with the key but door not opened. Third after the door flag has been set and the map is reloaded. If your system is correct, the same map interaction will produce different visible behavior because world memory changed. That is the thesis of today’s lecture in executable form.

And as we connect week 8 to the overall course plan, notice how many earlier layers are now active at once. Week 1 gave us the runtime shell: main callbacks, tasks, and the idea that the game advances through scheduled per-frame work. Week 2 gave us data and build pipelines. The map weeks gave us headers, layouts, object events, and coordinate triggers. The interaction lectures gave us A-button lookup and movement gating. Last time, the script interpreter gave us pointer execution and command dispatch. Today, flags and variables explain why those interactions have memory. Next, when we move toward fuller clone integration, party, battle, item, UI, and save systems will stop looking like separate assignments and start looking like cooperating state machines around one durable world.

So keep your mental model tight as we move into the next section. A script is not the world. A flag is not the world. A variable is not the world. The world, in an RPG engine, is the combination of authored data, durable state, and runtime interpreters that repeatedly turn those facts into behavior. The locked door is only our smallest example. The same pattern scales to gym badges, rival encounters, trainer rematches, item gifts, map transitions, post-battle rewards, and late-game blockers. Now I’m going to shift from representation to failure modes, because once you can mutate world memory, the next professional problem is proving you mutated the right memory at the right time and saved exactly what needed to survive.

Good. Failure mode number one is treating flags as a junk drawer. Once every blocker, reward, cutscene, and tutorial note becomes “some flag somewhere,” your world memory becomes write-only. FireRed avoids that problem by centralizing the machinery. The interesting part is not that there are flags and variables. The interesting part is that scripts, battles, overworld setup, daycare logic, and item-adjacent systems can all include event_data.h and talk to the same progression surface. You can see that dependency pattern in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, which pulls in event_data.h alongside overworld, script, field control, and battle transition headers. That is architectural evidence. It tells us battle entry is not a sealed-off mini-game; it is a stateful transition out of the overworld, often conditioned by story memory.

Now, pause there. If you are building your minimal clone, do not copy FireRed’s total flag universe. Copy the role. Your clone needs a WorldState object with flags, vars, inventory, party, current map, player position, and maybe a small “pending transition” field. The script VM should not own all of those things privately. The script VM should receive a reference to the world state, mutate it through narrow operations, and then yield control back to the overworld loop. That distinction matters. The VM is a machine that executes commands. The world state is the memory that remains meaningful after the VM stops.

Let me put a small example on the board. Imagine a town hall door. The map data says there is a door object at coordinate ten, five. The object event points to script TownHallDoorScript. The script says, in human form, check variable STORY_STAGE. If STORY_STAGE is less than three, show “The meeting has not started.” If STORY_STAGE is at least three, warp to TownHallInterior. After the player defeats the rival, a different script sets STORY_STAGE to three. Notice the causality. The door script does not know how the rival battle worked. The rival battle script does not know how the door works. They cooperate through a small durable variable.

Check your understanding here. If I delete the rival NPC from the map after the battle, but I forget to set STORY_STAGE, what happens? The visible rival is gone, so part of the world looks progressed, but the town hall door still blocks the player. That is not a graphics bug. That is not an input bug. That is a world-memory consistency bug. Advanced architecture work is recognizing that category quickly.

This is why I want you to think in terms of invariants. An invariant is not just “the flag is true.” It is a statement like, “After the rival defeat script completes, the rival’s visible event is disabled, the reward item is granted exactly once, STORY_STAGE is advanced, and the save file can reconstruct that progressed state after reboot.” That is the professional unit of correctness. Not the individual command. The whole state transition.

FireRed’s script engine pieces make this separation visible. The script interpreter logic in pokefirered/src/script.c, chunk_1be14bbdaf5c3400, is part of the machinery that advances through script pointers and dispatches commands. The control-flow material in pokefirered/src/script.c, chunk_03258f0288d89251, is where we should mentally place comparisons, branches, and flow redirection. The support layer in pokefirered/src/script.c, chunk_d424688149aa3bcb, reinforces that scripts are not just text boxes; they are resumable runtime procedures participating in the field engine. Then pokefirered/src/scrcmd.c, chunk_c2a54fb6031b5886, is the natural place to look when you want to see script commands as implementation hooks rather than as magic words. And pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, is the source focus for the flag and variable storage itself.

So the clone version should mirror the roles, not the size. You need a bytecode array or command list. You need an instruction pointer. You need command handlers. You need a compare result or condition register. You need a branch command that changes the instruction pointer. And you need commands that mutate world memory: setflag, clearflag, setvar, additem, warp, message, maybe startbattle. When we add save persistence, we are not saving the script pointer of every script in the world. Most scripts are transient. We save the durable facts that allow the world to be rebuilt into the correct visible configuration.

That last sentence is worth repeating. You save durable facts, not every runtime detail.

Now let’s connect this to party data and battle turns, because this is where some of you are still designing too many separate toy programs. Your battle system has creature structs, moves, HP, stat stages, turn order, and damage resolution. Your overworld has maps, NPCs, and scripts. Your inventory has items and quantities. Your save system has serialization. Those are separate modules, yes, but they are not separate games. The bridge is state mutation.

Suppose the locked door requires a badge. In a minimal clone, the badge can be a flag, BADGE_1. The gym leader script starts a battle. The battle system runs turns. When the battle returns a victory result, the script continues and sets BADGE_1, gives TM01, and maybe sets STORY_STAGE to four. The item system receives an addItem call. The party system may have fainted creatures that need healing later. The save system persists the flag, variable, inventory, party, map, and position. That is the same architectural rhythm we have used all course: authored data points to runtime behavior; runtime behavior mutates globals or structured state; later systems read that state and produce visible consequences.

FireRed’s source dependencies show this cross-system conversation. In pokefirered/src/daycare.c, chunk_119931105f546346, the module includes event_data.h along with pokemon, party menu, overworld, script, item constants, and many UI-related headers. I am not claiming that every one of those includes is beautiful modern layering. This is a GBA-era C codebase. But architecturally, the include cluster is evidence that creature data, menus, scripts, and progression memory meet in real gameplay features. Likewise, pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, includes event_data.h among battle, item, move, ability, weather, and controller headers. Again, the point is not “copy this include list.” The point is “battle logic may need access to persistent event facts.” That is the concept you should carry into your clone design.

Let’s do a concrete failure analysis. The player talks to an NPC who says, “Take this key.” The script gives KEY_ITEM_BASEMENT_KEY and sets FLAG_GOT_BASEMENT_KEY. The basement door checks FLAG_GOT_BASEMENT_KEY. What can go wrong? First, the item can be added without the flag. Then the inventory looks right, but the door still rejects the player. Second, the flag can be set without the item. Then the door opens, but the player’s inventory lies. Third, both can happen, but the save system persists only inventory and not flags. Then the player can open the door until reboot, and after reload the door locks again. Fourth, the save system persists the flag but not the inventory, so after reload the door opens but the key item is gone. Some games intentionally allow that, but if your design says the key is visible in the bag, that is a bug. Fifth, the script can set the flag before confirming inventory space, if your clone has inventory capacity. Then a failed item grant can still advance story state.

These are not theoretical. They are exactly the kinds of errors that appear when teams treat script commands as isolated conveniences instead of as transactions over world state. I do not mean you need a database transaction engine in your project. I mean you need to decide which mutations belong together and order them defensibly.

For your lab interpreter, one clean design is to have each command return a status: continue, wait, branch, end, or transition. Message can return wait because the text box owns the screen for a while. Warp can return transition because the map loader needs to take over. Battle can return wait or transition because battle may replace the main callback or enter a separate state machine. Setflag and setvar usually return continue. Branch changes the instruction pointer and returns continue. This lets the overworld loop run the VM until the VM says, “I am blocked on UI,” or “I am done,” or “the world is changing maps.”

This is where week 8 plugs directly into the whole course plan. In week 1, we decomposed the creature RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. At that time, the list may have felt like taxonomy. Now it should feel like a set of cooperating state machines. In week 2, headers and generated data gave us the contract idea: data is authored elsewhere, transformed, and consumed through stable structures. In the map weeks, map headers, layouts, and object events taught us that the world begins as data. Last week, object events and step triggers showed how authored records become runtime actors and script entry points. Yesterday, the script interpreter showed how a pointer and command handlers turn script bytes into behavior. Today, flags and variables explain how those behaviors accumulate into a remembered world.

Let me check the room. If a script says “open the door” by directly changing the door tile to an open-door graphic, is that enough for progression? No. It may be enough for a one-frame effect or a temporary animation, but it is not enough for durable progression unless some saved state records that the door should be open when the map loads again. The map loader needs something to consult. That may be a flag, a variable, or a serialized object state. Without that, the map reload resets the world back to authored defaults.

Now, there is an advanced nuance here. Not all changes should be durable. If an NPC turns to face the player during a conversation, you probably do not save that. If grass rustles for an encounter effect, you do not save it. If a door opens as an animation before a warp, you may not save the animation frame. Durable state should represent player-meaningful facts: obtained items, defeated trainers, story stage, unlocked maps, current party, current location, money, Pokedex progress, and so on. Runtime state represents the ongoing performance of those facts: animation frames, temporary messages, active callbacks, task timers, menu cursor position, and transient script waits.

So when you design save persistence, ask this: “If the player powers off and reloads, what facts must still be true for the game to be honest?” That question is better than “what variables exist in memory?” because many variables exist only to make the engine run this frame.

Let’s bring the battle example back. During a battle turn, you have temporary state: selected move, action order, damage rolls, current animation command, maybe a message queue. Much of that does not belong in a normal overworld save. But after battle, if the party lost HP, gained experience, evolved, learned moves, or consumed an item, those facts are durable. If the battle was a trainer battle tied to progression, a defeated-trainer flag is durable. FireRed’s battle setup code including event_data.h in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is a useful anchor for this idea: battle entry and battle consequences are not isolated from event memory.

On the board, I’m drawing two columns. Left column: transient runtime. Right column: durable world. Under transient runtime I put script instruction pointer, text box animation, battle turn queue, fade timer, movement task, menu cursor. Under durable world I put flags, variables, party, inventory, current map, player coordinates, defeated trainers, story stage. Now I draw arrows from transient to durable only at commit points. A script command commits a flag. A battle result commits party changes and trainer defeat. An item-use command commits inventory count and maybe party status. A warp commits current map and position after the map transition is accepted. That commit-point vocabulary is how you prevent vague thinking.

For your minimal clone, implement save as a serialization of WorldState, not as a dump of every subsystem object. That means your SaveData might contain version, currentMapId, playerX, playerY, flags bitset, vars array, party array, inventory map, money, and maybe play time. Your ScriptVM object should not be serialized unless you deliberately support saving mid-script. And if you do support saving mid-script, that becomes much harder, because now you need to persist instruction pointers, waiting command state, message state, and compatibility across script edits. FireRed has sophisticated runtime machinery, but for our course clone, the disciplined answer is to save at stable boundaries: outside active script execution, outside battle turns, outside partial map transitions.

Here is a good advanced design smell: if your save file needs to know which text box character is currently being printed, your save boundary is probably too permissive for this project. Save after the conversation resolves. Save after the battle resolves. Save after the warp resolves. You can autosave at those boundaries if you want, but the boundary matters.

Let’s use item use as the integration example. A potion in battle and a potion in the field both touch inventory and party data, but their control context differs. In the field, the bag UI selects potion, the party menu selects a creature, item logic validates that the creature is injured, inventory decrements, HP increases, and the UI reports the result. In battle, the battle controller or command menu selects item use, battle rules validate whether item use is allowed, inventory decrements, HP increases, and the turn may be consumed. Same item database, same party struct, same inventory count, different runtime state machine. If your code duplicates the potion effect entirely in both places, you are creating two chances to disagree. If your code centralizes the effect but leaves context-specific validation outside, you are closer to the architecture we want.

And yes, this connects to flags. Maybe an NPC gives you the first potion only once. That is a script flag. Maybe a tutorial variable records which lesson you have completed. That is a script variable. Maybe an item unlocks a field move after a badge. That is a cross-system rule: item or move availability reads progression memory. This is why pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, including party, item, battle, overworld, field specials, and region/map-related headers is not surprising in a mature RPG codebase. Creature information screens are not just static stat pages. They sit near party state, moves, items, field affordances, and progression-sensitive behavior.

Now, I want to stress something for the week 8 homework and quiz. You are past the point where “I made a flag dictionary” is enough. The assessment is checking whether you can design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence as one coherent architecture. So when you describe your locked-door scenario, I expect to see the chain. The map loader instantiates the door event from map data. The interaction system enters the door script. The script VM compares a variable or flag. The branch chooses message or warp. The warp requests a map transition. The save data persists the flag or variable that made the decision meaningful. If a battle grants the unlocking flag, the battle result must return to a script or event handler that commits that flag.

That is the level now. Not “here is my door.” Show me the state pathway.

Let me give you a compact trace, spoken as if we were debugging. Player presses A near the locked lab door. We are not re-teaching the A-button lookup; you already have that from the interaction lecture. The result of that lookup is a script pointer or script identifier. ScriptVM starts LabDoorScript with ip at zero. Command zero compares VAR_STORY_STAGE against five. The compare handler stores LESS, EQUAL, or GREATER in a small VM condition field. Command one branches if less to label LockedMessage. If not less, command two issues warp to LabInterior at coordinates four, eight. The VM returns transition. The field engine fades out, map loader loads LabInterior, player position updates, and control resumes. In the locked branch, command three displays “The professor is still preparing the lab.” The VM returns wait while the message box is active. When the player dismisses the message, the VM resumes and ends. No durable state changed in that branch. In the unlocked branch, current map and position changed and should be represented in save data after the transition.

Now suppose the unlock comes from a rival battle. RivalScript starts. It shows a message. It starts battle with trainer RIVAL_LAB. Battle engine runs its own turn state machine. The result returns victory. RivalScript resumes. It sets FLAG_RIVAL_LAB_DEFEATED. It sets VAR_STORY_STAGE to five. It maybe hides the rival object or sets a local event flag. It gives the player access to the lab door indirectly because the lab door reads VAR_STORY_STAGE. Then the script ends. On save, the durable facts persist. On map reload, object visibility and door behavior are reconstructed from those facts.

Question for you: should the door script check FLAG_RIVAL_LAB_DEFEATED or VAR_STORY_STAGE? Either can be valid, but they communicate different design intent. A flag says this one event happened. A story-stage variable says the world is at or beyond a phase. If five different blockers all open after the rival battle, a story-stage variable may be cleaner. If only one rematch NPC disappears after that battle, a specific flag may be cleaner. Overusing a single story variable creates brittle global ordering. Overusing individual flags creates scattered condition logic. Good progression architecture balances both.

This is exactly why FireRed has both flags and variables in the event data system, and why our source focus includes pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, rather than only the script command file. Commands need storage semantics. Storage semantics need command access. The two pieces are joined but not identical.

Before we move on, recap the professional lesson. Flags are best for boolean facts: obtained, defeated, opened, introduced, enabled, hidden. Variables are best for ordered or numeric facts: story stage, selected fossil, puzzle counter, trigger value, rematch tier, tutorial step. Scripts are best for expressing conditional procedures over those facts. The map loader is best for turning authored data plus durable facts into current visible world. The save system is best for preserving durable facts at stable boundaries. Battle, party, and item systems are best seen as state machines that sometimes read or commit those same facts.

And that brings us to the next piece, which is debugging strategy. When a player reports, “The game forgot that I beat the rival,” you should not randomly inspect the rival sprite first. You should ask: was the victory result produced, did the post-battle script resume, did the flag or variable commit, did object visibility read the committed state, did save serialization include it, and did load reconstruction restore it? That sequence is the week 8 architecture in operational form. We are going to keep using that sequence as we move toward the later systems, because the same thinking applies when the state is not a door but a party member, an item effect, a battle reward, or a whole region transition.

So, let’s take that debugging sequence and turn it into design work. Not FireRed-sized design yet. Minimal clone design. The point is not to reproduce every command, every edge case, every save block, or every battle rule. The point is to preserve the architectural contracts.

If I were drawing this on the board, I would put six boxes from left to right: map loader, script VM, world memory, party model, battle loop, save system. Then I would draw arrows through world memory, not around it. The map loader reads flags to decide what appears. The script VM mutates flags and variables. The battle loop may produce a result that a script consumes. Item use may mutate party data or world flags. The save system serializes the persistent subset of all of that.

Now, notice the word subset. This is where advanced architecture starts. Not every piece of state is world memory. The current animation frame of an NPC is not story progress. The current text printer character index is not story progress. The fact that a rival was defeated, or that the player chose the fossil, or that a door has been unlocked, that is story progress. In a clone, you need to protect that boundary carefully, because if you save too little, the world forgets. If you save too much, you freeze transient runtime machinery into your permanent game state.

FireRed’s source organization reinforces that separation. The week’s main sources put script execution in pokefirered/src/script.c, with the script engine and control flow chunks identified in chunk_1be14bbdaf5c3400 and chunk_03258f0288d89251, while concrete script commands live in pokefirered/src/scrcmd.c chunk_c2a54fb6031b5886, and the flag and variable backing store is centered in pokefirered/src/event_data.c chunk_a13cdbcb283dbf3b. That is a useful architectural split. One module advances the instruction pointer. One module implements verbs. One module owns the memory those verbs modify.

Let’s make that concrete.

Suppose our clone has a locked laboratory door. On the map, the door tile or door object has a script pointer. We are not going back through A-button lookup today; we already did that. The important thing is that interaction gives us an entry point into script code. The script says, in bytecode terms: check flag got_lab_key; if false, message “It’s locked”; end. If true, message “The key fits”; set variable destination_map to LAB_INTERIOR; warp LAB_INTERIOR at coordinate 5, 8; end.

That tiny example already contains the whole week. There is a script pointer. There are command handlers. There is a flag. There is a branch. There is a map transition. The player experiences it as a door. The engine experiences it as state mutation and control flow.

Now pause and check your understanding. If that same door is still locked after the player picks up the lab key, where should you look first? Not at the map artwork. Not at collision. First, ask whether picking up the key set the flag you think it set. Second, ask whether the door script checks the same flag. Third, ask whether the flag survived any transition or reload. If those three facts line up, then you inspect the object or tile behavior. In other words, debug the memory contract before you debug the presentation.

For a minimal implementation, I would write world memory as two arrays or maps. One boolean set for flags, one integer map for variables. You can make the names symbolic at the data level, but at runtime you want cheap and predictable access. In a teaching clone, maybe flags are stored as a dictionary from string to bool. In a production-style clone, maybe flags compile to numeric IDs. FireRed is much closer to that second style: flags and vars are not little heap objects attached to individual NPCs; they are centralized event data, and that is why pokefirered/src/event_data.c chunk_a13cdbcb283dbf3b matters so much for this lecture.

And here is the subtle design lesson. Centralized does not mean global chaos if the access paths are disciplined. The script command says setflag. The script command handler resolves the operand. The event data system stores the bit. The map object visibility system later reads the bit. Those systems are coupled through a small vocabulary, not through arbitrary direct mutation everywhere.

Let me say that again in a slightly different way, because this becomes important when you design your homework architecture. A flag is not merely a boolean. A flag is a named agreement among independent subsystems. The script agrees to set it. The map loader agrees to hide or show things based on it. The save system agrees to persist it. The designer agrees not to reuse it accidentally for an unrelated event. Most bugs in progression systems are broken agreements.

Now let’s connect battle to this, because battle often tricks people into thinking it is separate. In FireRed, battle setup sits near overworld, scripts, event data, and field control. The research packet excerpt for pokefirered/src/battle_setup.c chunk_494d132a59422c85 shows includes such as overworld, item, load_save, script, event_data, event_scripts, field control, and battle transition. That is not random. A trainer battle begins in the field, enters a battle mode, then returns a result to field progression. In clone terms, your battle system is allowed to be its own state machine, but it must have a clean reentry contract with scripts.

So for our clone, imagine a rival NPC script. The player presses A, or crosses a trigger tile, and the script runs: message “You’re late”; startbattle RIVAL_1; waitbattle; if battle_won branch RivalDefeated; message “Come back after training”; end. At RivalDefeated: setflag defeated_rival_route1; giveitem potion 1; message “Fine, take this”; end.

This is why I do not want you to design battle rewards as magical side effects sprinkled inside the battle loop. Some rewards are battle-native, yes, experience, fainted states, maybe money. But story consequences belong in the return path, usually script-controlled. The battle loop should produce a result. The script should interpret that result in world terms.

Now, there are exceptions in real engines. You will find shortcuts, special cases, and old code. FireRed is a production game with history and platform constraints, not a classroom-perfect microkernel. But the dominant architecture is still legible: scripts orchestrate progression; flags and variables remember progression; other systems read that memory.

Let’s bring party data into the same mental model. Party data is persistent, but it is not the same kind of persistent as a story flag. A party member has species, level, moves, HP, status, experience, held item, nickname, and so on. That is structured domain state. A flag is usually a one-bit world fact. A variable is a small numeric world fact. The save system persists both, but they serve different semantic roles.

For the clone assignment, your party model should be boring in the best possible way. Define a Creature struct or record with species ID, level, current HP, max HP, move list, experience, and status. Define party as an ordered list with a maximum size. Do not store “has starter” only as a flag if the actual starter is in the party. The party is the source of truth for party membership. You may also set a story flag like received_starter because map scripts need to know whether Professor Oak should still offer the choice. Those two facts are related, but they are not identical.

Here is a common bug. The player receives a starter. The script adds a creature to the party, then sets received_starter. The game saves. Later, a bug in loading party data drops the creature, but the flag remains set. What does the world say? The world says, “You already received your starter,” while the party says, “There is no starter here.” That mismatch is not solved by saying flags are bad. It is solved by deciding which state is authoritative for each question, and by writing validation or recovery logic where necessary.

Check for understanding here. If an NPC asks, “How is your first partner doing?” should that script check received_starter, or inspect the party? It depends on the line. If the NPC is asking whether the story event happened, check the flag. If the NPC wants to comment on the actual creature you carry, inspect the party. Advanced architecture is often that simple and that strict.

Now item use. Item use is another bridge system. A potion reads inventory, reads party target, mutates HP, decrements item count, and produces a message. A key item may read a flag, set a flag, or invoke a field script. A repel-like item may set a step counter variable. A badge or HM gate may read story flags and party moves. So item use should not be a pile of one-off UI callbacks. It should be a command surface over state.

In a minimal clone, I would split item definitions from item effects. The item table says Potion has effect HEAL_HP with parameter 20. Lab Key has effect KEY_ITEM, maybe no direct use in the bag, but door scripts check the corresponding possession or flag. Rare Candy has effect LEVEL_UP with parameter 1. The item use system resolves the effect and calls a handler. That handler mutates party or inventory. If it mutates world memory, make that explicit.

This is where we can briefly compare to the broader course arc. In week 2, we talked about headers and data generation as contracts. In week 6, we decomposed the whole creature RPG into runtime shell, overworld maps, scripting, creature data, battle, inventory, UI, save, and link systems. In week 7, we traced behavior from data records through globals, callbacks, tasks, scripts, structs, and source functions. Week 8 is the point where those threads meet. Flags and variables are not a side topic. They are the memory surface that lets the decomposed systems act like one coherent world.

If I erase the board and redraw the whole course stack, I would put runtime shell underneath, because callbacks and tasks keep the program breathing. Above that, overworld and UI decide what mode the player is in. Beside that, battle has its own loop. Data tables feed maps, creatures, items, trainers, and messages. But progression state runs vertically through the diagram. It touches map loading, script execution, encounters, trainer rematches, gifts, shops, blocked paths, and save files.

Let’s design the save format for the clone now. Again, minimal, but principled. Save data should include current map ID and player position, world flags, world variables, party, inventory, and maybe story-facing metadata such as play time. It should not include the current instruction pointer of a normal NPC conversation unless you intentionally support saving during scripts. It should not include temporary compare results unless your VM requires them across save boundaries, and I would strongly recommend that your clone does not.

Why? Because the script VM is execution state, not world state. In FireRed, the script engine in pokefirered/src/script.c chunks like chunk_1be14bbdaf5c3400 and chunk_d424688149aa3bcb is responsible for running scripts and supporting execution, but the durable facts are routed through event data and other persistent game structures. For your clone, keep the same conceptual line: if power is lost after the script completes, the world should remember the committed facts. If power is lost halfway through a text box, you generally reload at a stable boundary.

Now, what about atomicity? Advanced question. Suppose the rival defeat script gives an item, sets a flag, and warps the player. If the save occurs after the item but before the flag, you may create a duplicated reward bug. If the save occurs after the flag but before the item, the player loses the reward. On the GBA, save timing and script flow are constrained by the game’s UX, but in your clone, you still need to think in transactions.

The simplest classroom answer is: only allow manual saving outside scripts and battle transitions. That gives you stable boundaries. A more robust answer is to batch script mutations into a transaction and commit at command boundaries or script end. That is more complex, and I would not require it for the week 8 lab, but I want you to see the problem. Persistence is not just serialization. Persistence is choosing when state becomes durable.

Let’s do a concrete board trace. I’ll say it aloud as if we are stepping through the VM.

Instruction pointer points at compare_flag got_lab_key true. The command handler reads event data. If false, it sets the VM comparison result to not equal, or whatever representation your VM uses. Next instruction is branch_if_false LockedLabel. The control-flow command updates the script pointer. Now we execute message “It’s locked.” The text system runs, maybe yields for player input. Script resumes. End command releases field control.

Now the player obtains the key from an NPC. That script executes giveitem lab_key 1, setflag got_lab_key, message “Use it on the lab door.” The inventory now contains the key, and world memory now contains the progression fact. Later, the door script reads that fact. If the player saves and reloads, save data restores inventory and flags. The door still opens.

That is the happy path. Debugging is the unhappy path with the same trace. If the door does not open, print or inspect the flag after the key script. If the flag is true before saving and false after loading, inspect save persistence. If the flag is true but the door script branches as if false, inspect the operand ID or comparison command. If the script opens the door but the map reload shows the old blocked object, inspect the map loader’s visibility read. The sequence tells you where to look.

This is why I am pushing you to build bytecode-style scripts in the workshop rather than just writing if statements in a Door class. The bytecode style forces you to separate data, interpreter, command handlers, and storage. That separation is exactly what FireRed’s architecture is teaching us. The source focus for this week, especially pokefirered/src/scrcmd.c chunk_c2a54fb6031b5886 beside pokefirered/src/event_data.c chunk_a13cdbcb283dbf3b, is evidence of that separation: script verbs are not the same thing as the memory store they mutate.

Now, let’s briefly mention variables, because flags get all the attention. Variables are world memory when the question has more than two answers. Which fossil did the player choose? How many switches are active? Which gym puzzle state is current? Which rival starter species should be used? A flag can say “choice made,” but a variable can say “choice value equals Helix,” or “choice value equals Dome,” or in a clone, “starter_choice equals water.”

Variables also support trigger systems, and we used trigger variables in prior lectures. I’m not going to reteach coordinate event dispatch, but remember the design: a tile trigger may only fire when a variable equals a certain value, and the script may advance that variable after the scene. That is how you get a route entrance cutscene that happens once, a second scene that happens later, and no scene afterward, all on the same map. The map does not need a new hardcoded version for every story phase. It reads world memory.

In a minimal clone, implement compare as a VM operation that can compare a variable to an immediate value. Then implement branch_if_equal and branch_if_not_equal. You do not need an elaborate expression language yet. In fact, I would rather see a small, boring instruction set that works than a clever script language with undefined persistence semantics. The lab command list is enough: setflag, clearflag, setvar, compare, branch, warp, and message. Add giveitem or startbattle only after the basic memory loop is solid.

Let’s connect this to assessments. The previous homework and quiz milestones asked you to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. That was not six unrelated mini-designs. It was one systems question asked six ways. Can you make the map load from data? Can a script mutate world state? Can party and inventory mutate through controlled handlers? Can battle return a result? Can save and load preserve the right facts? Week 8’s homework and quiz will expect you to join those answers, not merely list modules.

So, for example, if I ask you on quiz_w08, “A trainer can be challenged repeatedly after saving and reloading. Where are the likely architectural failures?” I expect an answer like this: the battle result may not resume the post-battle script; the defeated trainer flag may not be set; the object or trainer script may check the wrong flag; the flag may not be saved; or load may restore an older event data buffer. That is stronger than “the trainer code is broken.” It shows architectural locality.

Let’s also be honest about naming. In small projects, people underestimate naming of flags and variables. If you name a flag f_0234 in your hand-written data, you will make mistakes. If you name it DEFEATED_RIVAL_ROUTE1 or GOT_LAB_KEY, your scripts become inspectable. FireRed’s compiled form can use numeric constants because the source and tooling give meaning around those constants. Your clone needs the same discipline at whatever scale you choose.

One more check for understanding. Imagine a player says, “After I used the potion in battle, the HP came back during battle, but after the battle ended my creature was hurt again.” What category of state bug is that? That is probably not a flag bug. That is a party data synchronization bug between battle-local battler state and persistent party state. Different memory region, same principle. The battle system had a working temporary model, but the result did not commit back to the durable party model. Compare that to “the door forgot it was unlocked,” which is likely event data or save persistence. Same debugging discipline, different store.

The research packet gives us another good signal here. pokefirered/src/battle_util.c chunk_9335e5727aa0657d includes battle, item, pokemon, event_data, battle scripts, constants for moves, items, weather, abilities, and effects. That density tells you battle is a highly connected system. But high connectivity does not excuse vague ownership. In your clone, battle may read party and item definitions, but you should still decide exactly when it writes back party HP, consumed items, experience, and script-visible battle result variables.

And if we look at user-facing creature inspection, pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 sits around pokemon, party menu, item, battle constants, strings, and UI helpers. That reminds us that the same party data is viewed by menus, mutated by item use, changed by battle, and persisted by save. The menu should not own the data just because it displays it. Display systems are readers and presenters. Mutation should go through domain operations.

So the larger lesson is this: world memory is not only flags and variables in a narrow file. It is the design habit of distinguishing durable facts from transient execution, and of routing durable facts through clear owners. Flags and variables are the simplest and most visible version of that habit. Party data, inventory counts, current map, and save blocks are richer versions.

Now I’m going to leave us at the edge of the next step rather than closing the lecture, because we still have more ground before the full two-hour arc is complete. When we continue, we will take this memory model and pressure-test it against larger progression chains: multi-map quests, NPC disappearance and reappearance, battle rewards, key item gates, and save/load reconstruction. Keep the debugging sequence in your notes: result produced, script resumed, state committed, readers observed it, save included it, load restored it. That sequence is becoming our working method for the rest of this module.

All right, let’s pick that thread back up and make it more demanding. A locked door is useful because it is small enough to reason about. But if our architecture only works for one door, we have not learned much. The real test is whether the same memory model scales to a chain of consequences across maps, subsystems, and time.

So, on the board, I’m going to draw a slightly larger scenario. The player defeats a trainer in a warehouse. That battle sets a flag. An NPC outside the warehouse disappears after that flag is set. A guard in another town checks the same flag and now permits passage. A key item appears in the player’s bag. Later, after saving and loading, all of those facts remain true.

Notice what I did not say. I did not say the trainer object “tells” the guard directly. I did not say the map loader remembers that the NPC should be gone because it saw the battle happen. I did not say the inventory screen has special knowledge of the warehouse quest. The connective tissue is world memory. The battle reward script mutates durable state, and later systems observe that state through known access paths.

This is the design lesson underneath FireRed’s flags and variables. The source focus for today gives us the direct version of that idea: pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, is the anchor for flag and variable storage; pokefirered/src/script.c, chunks chunk_1be14bbdaf5c3400, chunk_03258f0288d89251, and chunk_d424688149aa3bcb, are the anchor for script pointer execution, control flow, and execution support; and pokefirered/src/scrcmd.c, chunk_c2a54fb6031b5886, is the place where script commands become actual state mutations or observations. Do not treat those as isolated files. Treat them as a triangle. Storage, interpreter, command handlers. That triangle is the smallest useful architecture for story progression.

Let me say that again in a more implementation-oriented way. The script engine should not itself know the meaning of every quest. It should know how to advance a script pointer, decode a command, call the appropriate handler, and respond to the handler’s result. The command handler should know how to perform one operation, such as setting a flag, comparing a variable, showing a message, or requesting a warp. The event data layer should know how to store and retrieve persistent progression facts. Once those responsibilities blur together, debugging becomes much harder.

Now, because you are advanced students, I want to push past the beginner interpretation. A flag is not just a Boolean. A flag is a public contract. When we define FLAG_DEFEATED_WAREHOUSE_TRAINER, or whatever name our clone uses, we are creating a named fact that multiple systems may depend on. That means the flag’s meaning must be precise. Is it set when the battle starts? When the battle ends? Only when the player wins? Does it remain set forever? Can it be cleared? Is it saved? Does it hide the trainer, unlock dialogue, or both?

Those questions are architectural questions, not just scripting questions.

Let’s walk through the concrete example. Suppose the player talks to a trainer blocking a hallway. The object event has a local ID and a script label, which we covered in the object-event lecture, so I am not going to re-teach that dispatch path. We already know how A-button interaction or coordinate dispatch gets us into a script. Start from the moment the script pointer is active.

The script says, in plain English: if warehouse trainer defeated flag is set, jump to after-defeat dialogue; otherwise start trainer battle; if the player wins, set the defeated flag, give the key item, and remove the object. In a bytecode-style clone, that might be a compare-and-branch command, a battle command, a setflag command, a giveitem command, and maybe a hideobject command. The important part is that each command has one clean responsibility. The battle command may suspend script execution while the battle subsystem runs. When battle returns, the script resumes at a continuation point. Then the script commits the durable result.

Please pay attention to the ordering. If you set the defeated flag before battle starts, then losing, resetting, or aborting can produce nonsense. If you give the item before confirming victory, the inventory becomes inconsistent with the story. If you hide the object without saving the flag that causes the object to be hidden on reload, the player can leave and return to a broken scene. This is why our debugging sequence matters: result produced, script resumed, state committed, readers observed it, save included it, load restored it.

Check for understanding here. If your clone has a trainer vanish immediately after a battle, but the trainer reappears after saving and loading, where do you look first? Not at rendering. Not at collision. First you ask whether the durable flag was set, whether the map load path observes that flag, and whether the save block includes that flag storage. Rendering might be doing exactly what it was told. The world memory is what failed.

Now let’s connect this to map loading. In a minimal clone, your map loader probably takes a map ID, loads tiles, places objects, places warps, maybe initializes triggers. The naive version simply instantiates every object listed in the map data. That is enough for a prototype, but it is not enough for progression. A real creature RPG needs conditional existence. An NPC might disappear after a quest. A rival might appear only before a battle. A blocked path might open after a badge. A hidden item might vanish after pickup.

So our map loading rule becomes: static map data proposes objects; world memory filters or modifies them. The map JSON, or C data table, or whatever representation your clone uses, says this map can contain these object events. Then the runtime asks, for each object with a hide flag or presence condition, should this object be instantiated now? This is the same data-table-to-runtime behavior pattern we have used all course. The map data gives the possibility space. The progression state selects the current reality.

That is a very powerful phrase: current reality. Flags and variables are not the story itself. They are the compact representation from which the current story-facing world is reconstructed.

FireRed’s broader codebase reinforces that state is not isolated to one subsystem. The research packet notes pokefirered/src/battle_setup.c, chunk_494d132a59422c85, and even in the include list there we see battle setup sitting near overworld, item, load_save, script, event_data, and field control concerns. That does not mean every battle file should become a story manager. It means battle entry and battle exit live at a boundary where several systems coordinate. The battle may be mechanically separate, but the result often feeds progression state. And the progression state then affects overworld behavior.

Let’s add party data to the picture. Party data is not usually a flag, but it participates in the same durable/transient distinction. Your party’s species, level, moves, HP, held item, and status are durable gameplay facts. The temporary animation state during a move is not. The selected menu row in the party screen may or may not be durable, depending on the design, but generally it is transient UI state. So when we design a minimal clone version of party data, we should ask the same ownership question. Who owns the party array? Who is allowed to mutate it? Battle can read and write HP and status. Item use can heal or modify a creature. The summary screen can read it. Save persistence must serialize it. Scripts may query party conditions or give a creature, but they should do so through command handlers or helper APIs rather than reaching through arbitrary globals.

That last point matters. The reason FireRed-style architecture survives complexity is not that it avoids globals entirely. It absolutely has global state. The reason it remains tractable is that important categories of global state tend to have conventions and access paths. When pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, includes pokemon, party menu, item constants, battle constants, and related UI modules, that tells us the summary screen is a reader and presenter of creature state, with some controlled interactions. It is not the source of quest truth. Similarly, pokefirered/src/daycare.c, chunk_119931105f546346, includes event_data along with pokemon, party_menu, trade, pokedex, and other systems, which is a useful reminder that long-lived world services often combine creature data with event progression. The daycare is not just a menu. It is a persistent world facility.

Now, back to our clone. I would like you to imagine six minimal modules on the board. MapRuntime. ScriptVM. EventData. Party. Battle. Save. Inventory can be a seventh, but for the moment I’ll attach it next to Party because both are durable player-owned data.

The mistake many first versions make is to let ScriptVM become a god object. It loads maps, starts battles, edits inventory, modifies party, and writes saves directly. It feels efficient at first because every command is easy to implement. But by week eight, that should make you suspicious. The script VM should orchestrate. It should not own all state. A script command like giveitem can call Inventory_AddItem. A script command like setflag can call EventData_SetFlag. A script command like startbattle can configure battle setup and then yield. A script command like warp can request a map transition. This gives us a clean testing surface. We can unit test EventData separately from ScriptVM. We can test a script by swapping in fake command effects. We can test save/load by serializing EventData, Party, Inventory, and current location without running the whole game.

Let’s pressure-test that with item use. Suppose the player uses a key on a locked door. You have two possible designs. In the first design, the item itself has a use handler. The handler checks current map and facing tile, then sets a flag or triggers a message. In the second design, the door script checks whether the inventory contains the key item, then opens. Which is better? The answer is not universal. Field-use items, like a rod or a bike or a field move equivalent, often need item-level behavior. But for story locks, it is usually cleaner for the world script to own the gate condition. The door knows what it requires. The inventory merely reports whether the player possesses the key.

So the script might say: checkitem WAREHOUSE_KEY; if false, message “It’s locked”; if true, setflag WAREHOUSE_DOOR_OPEN; play sound; warp or open passage. Once the door-open flag is set, the map loader or door interaction code reads that durable fact. The item is an input to the story transition, not necessarily the permanent truth. The permanent truth is that the door has been opened.

Here is a subtle bug that shows up in student projects. The student removes the key from the inventory when the door opens, and later uses “has key” as the condition for whether the door should stay open. After save and load, the player no longer has the key, so the door relocks. The local interaction seemed correct in the moment, but the durable state model was wrong. The right state is not “player has key.” The right state is “door has been opened.” Those can overlap, but they are not the same fact.

Pause and check yourself. If a condition is used to decide whether a past event already happened, it probably wants a flag. If a condition is used to count or rank progress, it probably wants a variable. If a condition is a property of a creature or item stack, it belongs in party or inventory data. And if a condition only exists while a command is running, it is transient execution state and should not be saved as world truth.

Now let’s talk about variables, because flags get all the attention. Variables are how we avoid exploding the flag space when the state is ordinal, counted, or selected. A badge count, a current quest stage, a chosen fossil, an active trigger variable, or a rival-name-related selection may fit better as a variable than as several mutually exclusive flags. FireRed’s event variable storage, again anchored for us today in pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, is part of that same world-memory layer. Scripts compare variables and branch accordingly through the interpreter and command handlers in pokefirered/src/script.c and pokefirered/src/scrcmd.c, especially the control-flow and command implementation chunks named in today’s source focus.

But variables carry their own danger. A Boolean flag has one axis. A variable can hold many values, including invalid values. If VAR_QUEST_STAGE can be 0, 1, 2, 3, and 4, what happens if it is 99? What happens if an old save has stage 2 but a new script expects stage 20? What happens if two quests accidentally share the same variable? In a minimal clone, you can reduce this risk by reserving variable IDs by domain and by defining symbolic names. Do not scatter raw numbers through scripts. Even in a small bytecode interpreter, “setvar QUEST_WAREHOUSE_STAGE, 2” is materially better than “setvar 17, 2.”

Let me draw the execution model as a small loop. Fetch command at script pointer. Decode opcode. Read operands. Dispatch handler. Handler returns continue, wait, jump, stop, or error. If continue, advance pointer. If jump, replace pointer. If wait, preserve script context and let the main runtime continue frames. If stop, release the script context. That is the machine. The sophistication is not in the loop itself. The sophistication is in how it interacts with state owners.

This is why today’s lab is not merely “write a bytecode interpreter.” The lab asks you to implement setflag, clearflag, setvar, compare, branch, warp, and message, then drive a locked-door scenario from it. The hidden assessment is whether your door behavior is data-driven by script and durable state, or whether you hard-code the door in the map update loop. By week four, hard-coding the door might have looked acceptable. By week eight, it is architectural debt.

And that ties back to the course plan. In the first weeks, we decomposed the RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. In week seven, you traced visible behavior through globals, callbacks, tasks, scripts, structs, and source functions. This week, we are naming the memory contract that allows those systems to cooperate without collapsing into one giant procedure. Flags and variables are the simplest form of that contract. Save blocks, party structs, inventory pockets, map headers, object events, and battle setup are richer forms.

One more concrete scenario before we move on. Imagine a guard who blocks the road until you have delivered a parcel. The guard’s script should not inspect the entire inventory history. It should not ask whether the player has ever visited a particular coordinate. It should not depend on whether a message box was shown. It should ask a small number of durable questions. Is the parcel delivery quest at stage delivered? Or is FLAG_PARCEL_DELIVERED set? If yes, let the player pass. If no, block and show dialogue.

If the player saves before talking to the guard, loads, and then talks, the same durable question should produce the same answer. If the player walks to another map and returns, the same durable question should produce the same answer. If the player opens the party menu or fights a wild battle in between, the same durable question should produce the same answer unless one of those systems intentionally changed the relevant state. That is the practical meaning of world memory.

So, mini-recap before we extend this into multi-map chains. Durable facts need clear owners. Scripts coordinate mutations, but they should not personally own every subsystem’s data. Flags represent named facts, variables represent compact progression values, party and inventory represent player-owned gameplay state, and save persistence reconstructs the world by serializing those owners. When a visible behavior is wrong, trace backward from the reader to the durable fact, then to the command that mutated it, then to the execution path that decided to run that command.

Keep that model in front of you. Next, we are going to make it less tidy on purpose. We will look at a progression chain where one script starts a battle, the battle result resumes a script, that script changes inventory and flags, a different map observes those flags during object creation, and save/load has to preserve the whole chain without preserving any of the transient script machinery.

So let’s dirty up the clean model.

Imagine the player walks into a rival room. The map loader creates object events from map data. One object event points at a rival script. We are not going to re-teach A-button lookup or coordinate triggers here; you already built that mental path last time. The important part today is what happens after the script pointer becomes active. The script says, in effect, “if the rival has already been defeated, do the post-battle version; otherwise face the player, speak, start a battle, then after the battle set the defeated flag, give an item, and move the rival away.” That one scene crosses the overworld, script VM, party data, battle engine, item system, object visibility rules, and save system.

Now, if we design this in a clone, the wrong implementation is to let the rival object store all of that state inside itself. Do not put “battle has happened,” “item was given,” and “rival is gone” as private booleans on the NPC instance if those facts need to survive map reloads. The rival object is temporary. The map object list is reconstructed. The durable truth belongs in flags, variables, inventory, party, and save data. That is the architectural point behind FireRed’s event data layer. In the packet, the source focus points us directly to pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, for flags and variables, and that is not an incidental helper file. It is the shared memory surface that lets map scripts, object visibility, story gates, and save reconstruction agree about what the world currently means.

Let me put the progression chain on the board. I’m writing this as a sequence, not as final code: load map, spawn objects, enter script, branch on flag, start battle, suspend field script, run battle, return result, resume script, mutate flag and inventory, reload or refresh map objects, save durable owners. Now look at the verbs. Load, spawn, enter, branch, start, suspend, run, return, resume, mutate, refresh, save. Every verb has a different owner. If you collapse those owners, your clone may work for one room, but it will not scale to a full creature RPG.

Here is a concrete minimal design that I would accept for the workshop. Your map loader owns a MapState with a map id, a layout reference, and a list of runtime object instances derived from map event records. Those instances can hold ephemeral things like current x, current y, facing, animation frame, and maybe a local script pointer reference. Your script engine owns a ScriptContext: instruction pointer, stack if you support calls, comparison result, waiting mode, and maybe a pending continuation. Your global event memory owns flags and variables. Your party module owns party creatures. Your inventory module owns item quantities. Your battle module owns active battle state while a battle is running. Your save module owns serialization and deserialization of the durable pieces.

Notice what is not saved. The current script instruction pointer usually should not be your main durable story state. In a small clone, you may choose to save during an active script by forcing the script to complete first, or by disabling save during modal sequences, or by storing a very careful continuation. But as a default architecture, you should not make the save file depend on “the rival script was at byte offset 37.” That is brittle. The durable facts are “rival defeated flag is set,” “player received item X,” “party has these creatures,” “inventory has these quantities,” “player is at map and coordinates.” FireRed’s script engine material in pokefirered/src/script.c, especially chunk_1be14bbdaf5c3400 for script engine execution, chunk_03258f0288d89251 for script control flow, and chunk_d424688149aa3bcb for script execution support, is useful here because it shows the script machinery as an execution system around pointers and commands. It is not the whole world model. It drives mutations into other systems.

Check your understanding for a second. Suppose the player defeats the rival, receives a key, saves, turns the game off, reloads, and returns to the room. Which facts must be reconstructed from durable storage? The defeated flag, the key in inventory or the key-received flag depending on your design, party state after the battle, player location, and any global variables relevant to the scene. Which facts should be reconstructed from map data plus those durable facts? The rival object’s presence or absence, the door’s locked or unlocked behavior, and the text branch shown by nearby NPCs. Which facts are transient? The script pointer that happened to run the victory speech, the battle turn queue after the battle is over, and the temporary message box state. If that separation feels natural, you are seeing the architecture.

Now let’s make the battle handoff precise. In your minimal clone, a script command called startbattle can create a BattleRequest containing opponent party id, battle type, return label or continuation, and perhaps a win flag policy. The script engine then enters a waiting state. It does not continue to the next command on the same frame as if the battle were a normal function call, because the battle is an entirely different mode of the game. In FireRed terms, we have already talked about callbacks and tasks enough that you should hear the shape immediately. The runtime shell changes what per-frame code is active. The battle setup code includes script and event dependencies; the packet’s pokefirered/src/battle_setup.c, chunk_494d132a59422c85, shows battle setup including headers such as script.h, event_data.h, overworld.h, battle.h, battle_transition.h, and field control modules. That include neighborhood is not proof of every control path by itself, but it is evidence for the architectural coupling we care about: battle entry is not isolated from field progression state.

So on the board I’ll write a clone-level state transition. FieldMode runs. Script command startbattle creates BattleState and stores a ScriptContinuation. Main mode switches to BattleMode. BattleMode runs turns until win, loss, capture, flee, or special outcome. BattleMode writes result into a durable or semi-durable place that the script can read, often a variable or a dedicated battle outcome field. FieldMode resumes. Script continues at the post-battle command. Then the script sets FLAG_RIVAL_DEFEATED and gives ITEM_KEY.

You can implement that with bytecode. Maybe opcode 01 is message, 02 is setflag, 03 is clearflag, 04 is setvar, 05 is compare, 06 is branch_if_equal, 07 is warp, 08 is startbattle, 09 is giveitem, 0A is end. That is enough. The advanced part is not the opcode list. The advanced part is the ownership boundary. The message opcode talks to UI. The setflag opcode talks to event memory. The warp opcode talks to overworld/map loading. The startbattle opcode talks to battle setup and suspends script execution. The giveitem opcode talks to inventory. The command handlers are adapters between script bytecode and subsystem APIs. That is exactly why the source focus includes pokefirered/src/scrcmd.c, chunk_c2a54fb6031b5886, for script command implementation. Script commands are not magic language features. They are C handlers that translate byte-level script intent into engine mutations.

Let’s use the locked door scenario from lab, but now connect it to battle and saving. Initially, FLAG_RIVAL_DEFEATED is false. VAR_STORY_CHAPTER might be 2. The locked door script checks: does player have ITEM_KEY, or is FLAG_DOOR_UNLOCKED set? If no, show “It is locked.” The rival script checks defeated flag. If false, starts battle. On victory, it gives ITEM_KEY and sets FLAG_RIVAL_DEFEATED. Now, should it also set FLAG_DOOR_UNLOCKED? Maybe not. That depends on design. If the key is a reusable inventory item and the player must walk to the door and use it, then the item is the durable fact. The door script checks inventory. If the door unlocks permanently after use, then the door-use script consumes or observes the key and sets FLAG_DOOR_UNLOCKED. The rival does not need to know the door exists. That keeps scripts decoupled. The rival script changes “rival defeated” and “player received key.” The door script changes “door unlocked.” Those are different story facts.

This is where many student clones become hard to debug. They write one big story manager with phase numbers: phase 0, phase 1, phase 2, phase 3. Then every NPC checks the same phase. That can work for a tiny prototype, but it becomes too coarse. Variables are excellent for compact chapter-level progression, but flags are better for independent facts. Use a variable when the states are mutually exclusive and ordered, such as VAR_GYM_BADGE_COUNT or VAR_STORY_CHAPTER. Use flags when facts can accumulate independently, such as defeated rival, opened lab door, received parcel, unlocked bridge, cleared tutorial. FireRed’s event data focus, again pokefirered/src/event_data.c, chunk_a13cdbcb283dbf3b, supports this split between flags and variables. The lecture title calls them world memory because they are not merely script local storage. They are the shared durable vocabulary of the world.

Let’s pause for a check. If I ask you, “Should the party module read FLAG_RIVAL_DEFEATED?” the answer should usually be no. The party module should know creatures, stats, moves, HP, status, held items, maybe original trainer data. It should not know that a rival scene happened. The script can call party APIs, the battle system can mutate party HP and experience, but party data itself should not depend on story flags. Conversely, should a script directly edit raw party memory? Also usually no. In a minimal clone, the script command can call AddCreature or HealParty or GiveExperience, but you still want the party module to own its invariants. That is how you avoid creating invalid creatures from random script mistakes.

The same applies to item use. A key item use is not just “decrement item count.” In a creature RPG, item use can be a script entry point. You select the item from a menu, the item system identifies the effect, and it may call a field-use handler, a party-use handler, or a battle-use handler. A potion in battle mutates active battle and party HP through battle rules. A potion in the overworld mutates party HP through menu flow. A key item near a door may call a field script or set a flag. FireRed’s broader source packet points to item and battle integration in several places; for example pokefirered/src/battle_util.c, chunk_9335e5727aa0657d, includes item.h, event_data.h, battle.h, constants/items.h, and battle script headers, which is useful evidence that item effects and battle logic participate in a larger state machine rather than living as isolated menu code. For party-facing UI, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, shows summary screen code depending on pokemon.h, item.h, party_menu.h, battle_main.h, and constants, reminding us that party data is viewed and manipulated through several modes while still remaining a distinct owner.

Now, in your clone, I want you to be careful with the save layer. Saving is not “dump every object.” Saving is choosing the durable owners. A minimal save record might include player map id, player coordinates, facing, flags bitset, variables array, party array, inventory map, money, PC storage if you have it, and maybe random seed or play time. It should not include message box cursor, current animation timer, temporary object movement queue unless you have a very deliberate reason, or battle menu cursor after battle has ended. If save/load preserves too much transient machinery, old saves become incompatible with harmless script edits. If save/load preserves too little durable state, the world forgets what the player accomplished.

Let me give you a failure example. A student implements a rival battle and, after victory, removes the rival object from the current map object list. They test immediately, and it works. The rival disappears. Then they leave the map and come back, and the rival returns. Why? Because map loading recreated object instances from map data. The fix is not “also delete it from map JSON at runtime.” The fix is to set FLAG_RIVAL_DEFEATED and have object creation or visibility filtering observe that flag. The runtime object disappeared because the durable fact changed, not because the data definition was edited. This is the same data-to-runtime-to-visible-behavior trace we have used all course. Map data defines possible objects. Flags and variables select which possibilities are currently realized. Runtime instances animate and interact. Save persists the selector facts, not the whole temporary scene.

Now connect this back to week 8 as a whole. Monday’s script interpreter lecture gave us the execution mechanism: script pointer, command fetch, handler dispatch, branching, waiting. Today’s lecture gives us the memory model that makes that mechanism meaningful across time. A VM without event memory can display text and maybe warp the player, but it cannot express lasting story. Event memory without scripts is just a bag of facts no one mutates. Together, they form a progression system: scripts read facts, choose branches, call subsystem APIs, and write new facts. Then maps, NPCs, doors, battles, items, and save/load all interpret those facts.

This is also where weeks 6 through 8 become one module, not three unrelated topics. In week 6, we worked through runtime shell ideas: callbacks, tasks, per-frame dispatch. In week 7, we worked through maps and object-backed actors: object-event records, local IDs, script labels, coordinate events. This week, we made those actors and triggers durable by giving them shared world memory. The player presses A, yes, but we are not re-teaching the polling path. The object has a local script pointer, yes, but we are not re-teaching object-event records. What matters now is that the script does not merely react. It mutates the long-term world, and later systems observe those mutations.

I’ll say it more sharply: progression is not a timeline. Progression is state mutation plus interpretation. Designers experience it as a story. Players experience it as events becoming possible or impossible. Programmers should model it as durable facts changing over time.

One last advanced detail before we close. Branching should be boring. That sounds strange, but it matters. If every branch command has special story logic, your VM becomes untestable. compare should compare a variable to a value, or maybe compare a variable to another variable. branch_if_true should move the instruction pointer. checkflag should set a comparison result. The interesting behavior belongs at the command-handler boundaries: startbattle, giveitem, warp, applymovement, special. FireRed’s script control flow source focus, pokefirered/src/script.c chunk_03258f0288d89251, is the place to keep in mind for this separation. Control flow is generic; command handlers connect to game semantics; event data stores durable facts.

For the quiz, expect me to ask you to classify state. I might describe a visible behavior and ask, “Where should this live?” If I say, “The text window is halfway through drawing a sentence,” that is transient UI state. If I say, “The player has defeated the rival,” that is a flag. If I say, “The story chapter is currently three,” that is a variable. If I say, “The player’s starter has 12 HP and knows Ember,” that is party data. If I say, “The player has two potions,” that is inventory. If I say, “The next battle turn is waiting for the enemy move animation,” that is battle runtime state. If I say, “After reload, the rival should not appear,” that is a map visibility rule reading a durable flag.

And for the homework, when you build the small bytecode interpreter, I will be looking for that separation. I do not need a giant engine. I do need a clean one. setflag should touch event memory. setvar should touch event memory. compare and branch should control the script pointer. warp should request a map transition. message should request UI and wait appropriately. startbattle, if you include it, should suspend and resume rather than pretending a battle is a single synchronous arithmetic operation. giveitem should call inventory. Save should serialize the durable owners. If you do those pieces, your clone will be small, but it will have the same architectural bones as the larger system.

So, recap with me. Flags are named durable facts. Variables are compact durable values. Scripts are executable decision paths that read and write those facts. Command handlers are the bridge from bytecode to engine subsystems. Map loading creates temporary runtime objects from data, filtered or altered by durable progression state. Party, inventory, battle, and save each own their own invariants. The save file records the durable owners, not every moving part of the current frame.

That is the core of “flags and variables as world memory.” They are humble structures, often just bitsets and arrays, but they let a data-driven world remember. Without them, every map load is amnesia. With them, the same map data can become a different place because the player has changed the world. That is the design pattern I want you to carry forward into the next module. When we move deeper into creature data, battle turns, inventory use, and persistence, keep asking the same question: which system owns this fact, who is allowed to mutate it, who is allowed to observe it, and does it need to survive save/load? If you can answer that cleanly, you are no longer just copying FireRed’s architecture. You are thinking in the same architectural language.

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