# Loading and Saving FireRed State

Lecture ID: lecture_w20_d02
Week 20, Day 2

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

## Transcript
Good afternoon, everyone. Today we are continuing week 20, and we are no longer asking the abstract question from last time, “what must persist?” We are asking the operational question: when the player turns the Game Boy Advance off and later turns it back on, what exact machinery rebuilds a believable world from bytes in flash?

That distinction matters. Last time we classified durable state. Player identity, party, bag, flags, variables, location, progress bits, and enough metadata to decide whether the save is valid. Today we move from classification to load and save flow. In other words, not just “the party must persist,” but “which owner writes party state, when is it considered valid, how does loading recover it, and what happens if flash contains old, partial, or damaged data?”

Let me start by putting a sentence on the board. Here is the mental model for today: persistence is not a file dump; persistence is a negotiated boundary between live runtime ownership and stable storage layout. I want you to keep that sentence in view, because it is the mistake advanced programmers still make when they first design save systems. They imagine a save file as a snapshot of memory. That is rarely what a serious game actually wants. FireRed is especially useful here because it is constrained by hardware, by flash sector behavior, and by a large codebase whose systems each believe they own their own state.

Now, quick continuity check. In earlier weeks we traced a path from data records to runtime globals, then through callbacks, tasks, scripts, structs, and finally player-visible behavior. We used that method for maps, event scripts, items, menu state machines, the party model, battle setup, and UI. Week 20 is where that method pays off. A save file is the place where all of those systems meet. If you cannot decompose the runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems, then save/load will look like a magical blob. But if you can decompose them, then save/load becomes an integration problem.

Let’s anchor that in today’s source packet. The course packet points us to pokefirered/src/load_save.c, especially chunk_0755fbe98f753bb7 for the load/save entry flow, chunk_3efebaad2e0f31e0 for validation support, and chunk_d615017937c86ab6 for continuation of the load/save behavior. It also points us to pokefirered/src/save.c, chunk_ab80cd8f4fa87d77 for the save system and chunk_852fbc242f85cc74 for save sector flow. Finally, it names pokefirered/src/agb_flash.c as the hardware-facing flash persistence boundary. So, when I say “FireRed’s save system,” I do not mean one monolithic routine. I mean a layer cake: load_save.c deciding how the game should enter or restore runtime state, save.c organizing serialized regions and sector-level validity, and agb_flash.c handling the flash read/write boundary.

Let’s be precise about that phrase, “load/save entry flow.” At boot, a game cannot simply jump into the overworld and hope globals are meaningful. The runtime shell that we studied back in week 1 has to establish main callbacks, allocate or reset work areas, initialize save block pointers, validate storage, and decide whether the player is starting fresh, continuing, or possibly recovering from a damaged save. The fact that pokefirered/src/load_save.c chunk_0755fbe98f753bb7 is identified as the entry flow is important because it tells us where persistence meets the startup shell. This is not inventory code. It is not party code. It is the bridge between durable state and the rest of the engine.

Now, I’m going to draw three boxes on the board. Left box: “live owners.” Middle box: “save blocks.” Right box: “flash sectors.” Live owners are runtime systems. Overworld owns location and map-related runtime context. Event scripting owns flags and variables, or at least consumes them as its decision substrate. The creature system owns party data. Inventory owns bag contents and registered item state. UI reads from many of these but usually should not own the durable truth. The save blocks are structured memory regions where durable state is assembled. Flash sectors are the hardware storage units, with their own constraints and failure modes.

The key architectural move is that FireRed does not allow every live owner to scribble arbitrarily into flash. That would be chaos. The system has save block ownership: stable regions of memory representing categories of persistent state. We discussed that concept last time, so I won’t re-teach it, but today we need to use it. Save block ownership means there is a controlled handoff from runtime data to persistable data. It also means that during load, the game can rebuild global pointers and subsystems around known structured blocks rather than around a bag of loose fields.

A concrete example. Imagine the player saves in Pewter City with a party of three, a specific bag inventory, Brock defeated, and the player facing upward near the museum. Those are not one kind of state. The location touches map and overworld systems. The party touches the creature model. Brock defeated is probably a flag consumed by scripts and map object visibility. The bag touches item and menu systems. The facing direction touches player avatar or overworld state. Yet from the player’s perspective, all of that is one thing: “my save.” The architecture must make that illusion true.

So the save system has to gather data that belongs conceptually to separate modules. This is exactly why we spent so much time earlier in the course refusing to treat “game state” as one undifferentiated global. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the include list itself shows how battle setup sits among overworld, item, script, event data, player avatar, battle transition, and load_save. That source chunk is not our main save file today, but it is evidence of the larger architecture: systems are cross-connected at transitions. Battle setup needs to know about save/load not because battles are save files, but because entering, leaving, or preparing battle depends on stable world and player state. Persistence is an integration surface.

Let’s pause for a check. If I ask, “why not let the bag menu write directly to flash every time the player uses a Potion?” what should make you uncomfortable? Think for a moment. The answer is not merely “performance,” although performance matters. The deeper answer is ownership and atomicity. The bag menu is a UI/task-driven surface that mutates inventory state. We covered menu callbacks and task states recently, and you should remember that these surfaces are often temporary control flows. They are not the right authority for committing a complete, validated world snapshot. If the bag writes immediately but the event flags, party, and map position do not, you can create a save that represents no real moment in the game. The save operation must define a coherent boundary.

That brings us to validation. In the packet, pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 is identified as load/save validation support. Validation is the part many toy projects omit, and it is the part that separates “serialization” from “persistence.” Serialization asks, “can I turn this structure into bytes?” Persistence asks, “can I later prove these bytes are the right bytes, complete enough, current enough, and safe enough to trust?”

On hardware-backed flash, validation is not optional. Flash writes happen across boundaries. There are sectors. There can be interrupted writes. There can be multiple copies or sector identifiers. There can be checksums. There can be version-like metadata or counters that help choose which copy is current. The packet points us to pokefirered/src/save.c chunk_852fbc242f85cc74 for save sector flow, and that phrase should immediately make you think in chunks, not in a single continuous file. The game has to decide which sectors belong to a save instance, whether their checksums match, whether the sector IDs form a coherent set, and whether the save should be accepted, rejected, or treated as recoverable.

Let me put a second sentence on the board: load is a skeptical reconstruction process. Say that to yourself. Load is not trust. Load is skepticism followed by reconstruction. It reads flash through the boundary represented by pokefirered/src/agb_flash.c, then save.c helps evaluate sector-level structure, and load_save.c decides how to continue into game initialization based on the result.

Here is a simple model. Suppose your save schema has four blocks: player, party, bag, and world flags. A naive loader reads four regions and populates memory. A real loader asks: did all four regions come from the same save operation? Are their checksums valid? Does their version match the schema the ROM expects? Is the location valid? Is the party count within allowed bounds? Are item quantities within legal ranges? Are flags within the allocated range? Some of those checks may be explicit in a production game, some implicit by layout and checksum, but architecturally the question is the same: can this state be allowed to become the runtime truth?

Now, notice the transition from storage validity to semantic validity. A checksum can tell you that bytes match what was written. It cannot tell you that a level 255 creature belongs in slot 2 unless the semantic layer checks that. A sector ID can tell you that a piece belongs to save block data. It cannot tell you that the player’s map group and map number refer to a usable map unless the overworld loader validates or normalizes that later. This distinction will matter in your lab. When you design your save schema with blocks for player, party, bag, flags, variables, location, and checksum/version metadata, do not pretend a checksum solves all problems. It solves one class of corruption problem.

Let’s connect this to week 20’s overall role in the course. We are in the Persistence, Link, and System Integration module, covering weeks 20 and 21. Persistence is first because it teaches system boundaries under failure. Link comes next because multiplayer and communication also force boundaries under failure: remote state, packet timing, session validity, and disagreement between machines. If you understand why save/load cannot just trust arbitrary memory, you will be better prepared to understand why link code cannot just trust arbitrary remote input.

And this is also why we are increasing sophistication now. Your week 19 homework asked you to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. At that stage, it was acceptable to have a simplified save. But by week 20, the homework and quiz expect you to reason about ownership and validation. Your answer cannot be “dump a JSON object.” JSON is a reasonable tool in modern projects, yes, but the architectural question is unchanged. Who owns each field? When is the snapshot coherent? What metadata proves it is loadable? What recovery path exists if the latest save attempt is incomplete?

Let’s use the Pewter City example again, but now as an actual load sequence. The player boots the game. The runtime shell comes up. Before the overworld can safely display the player, the load path must establish save block memory and inspect flash. Through the lower flash boundary, bytes are read from storage. Through the sector flow in pokefirered/src/save.c chunk_852fbc242f85cc74, the game can reason about pieces of stored state rather than treating storage as a magical array. Through validation support in pokefirered/src/load_save.c chunk_3efebaad2e0f31e0, it can classify the result. Then the continuation flow in pokefirered/src/load_save.c chunk_d615017937c86ab6 carries that result forward into “continue existing game” or “initialize new game” behavior.

I’m deliberately phrasing that as a pipeline because that is the pattern you should use in your own engine diagrams. Read boundary. Validate physical/storage structure. Validate or initialize logical save structures. Reconnect runtime pointers and systems. Resume into the correct callback flow. The callback point matters. Remember from week 1, the main callback slot is the runtime shell’s way of deciding what kind of program the game is this frame. A loaded save must eventually land in an overworld callback or a title/continue callback, depending on the path. Save/load is therefore not outside the callback architecture. It feeds it.

Now, somebody might ask, “Where exactly does battle fit into this? Do we save mid-battle?” For FireRed as a design subject, the important lesson is not to assume every runtime state should persist. The battle engine has enormous transient state: turn order, animation state, command menus, temporary stat stages, volatile effects. Some games support battle saves. Many creature RPGs do not persist arbitrary mid-battle execution. Instead, they persist the durable world and party state around battle boundaries. That is an architectural choice. It keeps the save schema smaller and avoids needing to serialize every intermediate state machine. We saw in prior lectures that battle setup is a transition-heavy module, and pokefirered/src/battle_setup.c chunk_494d132a59422c85 shows its dependencies on overworld, events, items, scripts, and load_save-related infrastructure. That is the integration point, not necessarily a promise that every battle frame is durable.

Same for UI. Recent lectures covered menus as task-backed state machines, summary screen reads, and move relearner states. The summary screen, for example, reads creature data to display it. The move relearner flow mutates moves through a controlled state machine. The source packet cited pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 as evidence for summary screen integration with pokemon, menu, battle, link, overworld, and party systems, and pokefirered/src/learn_move.c chunk_af3598df6f092723 as evidence for the move relearner state machine. But the UI state itself, such as which summary page is open at a given animation frame, is not usually durable save truth. The durable truth is the party member’s data after a confirmed mutation. This is the distinction between interaction state and game progress state.

Check for understanding. If a player opens the bag, moves the cursor to Antidote, then turns off the console without saving, should the cursor position persist? Usually no. If the player uses Antidote, cures poison, then saves, should the cured party status persist? Yes. If the player merely highlights Antidote and saves through a different menu, should that highlight persist? Probably no, unless your game specifically saves UI convenience state. The point is that persistence follows design ownership, not incidental memory.

Let me now talk about save block ownership more concretely. In a mature C codebase like FireRed, save blocks are not just a serialization convenience. They are a way to stabilize addresses and ownership across many systems. A flag system needs to know where flags live. A variable system needs to know where script variables live. The party system needs access to party data. The bag system needs access to item pockets. The overworld needs player location. If all of these were scattered as independent globals with no save block discipline, saving would become a scavenger hunt, and loading would become dangerous because initialization order would be fragile.

This is why load-save initialization is paired with pointer setup. Before code can safely say “read the player’s party” or “check this event flag,” the global pointers or structures backing those concepts must refer to valid memory. On a new game path, that memory must be initialized to defaults. On a continue path, it must be populated from validated storage. On a failed or absent save path, it must not accidentally contain stale heap bytes that look plausible. That last case is subtle. Plausible garbage is worse than obvious garbage because it creates bugs that look like content errors.

Imagine a flag block that is uninitialized. Maybe a random bit says Brock is defeated. Now the Pewter Gym script behaves as if the badge was earned. A beginner might debug the script. An advanced engineer asks, “was the persistent flag block initialized before script code consumed it?” That is the kind of reasoning this week is training. Event scripts are not wrong if their input substrate is invalid. The loader is wrong if it allows invalid input to masquerade as progress.

Now let’s consider flash write boundaries. The packet names pokefirered/src/agb_flash.c as the flash persistence file. That boundary is important because hardware APIs tend to expose operations that are inconvenient compared with high-level game concepts. The game wants to save “the player’s progress.” Flash wants reads and writes at particular granularities, with erase/write behavior and timing constraints. The save layer has to translate. That translation is not merely technical; it shapes the save format. If storage is sector-oriented, then your save architecture becomes sector-aware. If writes can fail midway, then your validation metadata and recovery strategy become central.

A common robust pattern is to avoid making the currently valid save invalid until the replacement is complete. I’m stating that as a general persistence principle, not as a claim about every exact line of FireRed. The source packet’s emphasis on sector validation and save sector flow in pokefirered/src/save.c chunk_852fbc242f85cc74 is enough for us to discuss the principle: sectorized saves need a way to distinguish complete coherent data from incomplete data. You can do that with checksums, sector IDs, save indices, signatures, or combinations of those. The design goal is that a power loss during save should not silently produce a trusted half-save.

Let’s make that concrete. Suppose the save consists of fourteen sectors. The game writes sector 1, sector 2, sector 3, and then the battery dies. On next boot, flash contains a mixture of older sectors and newer sectors. A naive loader reads all sectors by position and constructs a hybrid state. That hybrid might have the new party but old flags, or new location but old bag. That is catastrophic because it represents no actual moment the player saved. A sector validation scheme instead asks which sectors form a complete set and which generation or index should be trusted. If the new generation is incomplete, the loader should prefer the previous coherent generation, if available, or reject the save.

This is the point where I want to connect to modern systems. In a desktop RPG, you might save to a file with a temporary filename, fsync it, then atomically rename it. In a database, you might use a transaction log. In a browser game, you might write versioned records to IndexedDB. In FireRed, the primitive is flash sectors. Different tools, same architectural idea: preserve a coherent committed state and validate before trusting.

Now, look again at our decomposition objective for the whole course. Runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Persistence is the first system that forces all of these to be named together. A save file is not just “save system content.” It is a projection of the whole game into durable form. But it must be a selective projection. Runtime shell state is mostly re-created. Overworld position and progress persist. Event flags and variables persist. Creature party data persists. Bag contents persist. Battle transient data mostly does not. UI transient state mostly does not. Link session state definitely should not be resumed blindly after reboot.

That last one is worth one sentence now and more next week. Link systems involve another machine. You do not persist “I am currently in a valid live trade session” across power cycles as if the remote peer still exists. You may persist the results of completed trades, but not the temporary communication session. This is the same distinction we just made for UI and battle: durable outcome versus transient process.

Let’s do a small board exercise. I’ll write “save schema” in the center. Under it I’ll write player, party, bag, flags, variables, location, metadata. Now I’ll ask: which of these should have checksums? At the physical save level, you often checksum sectors or serialized chunks, not every semantic category independently. But from a design standpoint, you should be able to explain what corruption in each category would do. Corrupt party data risks invalid creatures or crashes in summary/battle. Corrupt bag data risks impossible item quantities or bad item IDs. Corrupt flags can break story progression. Corrupt location can place the player on an invalid map. Corrupt metadata can cause the loader to trust the wrong generation. So even if the implementation checksum is sector-based, your architecture review should be category-aware.

Here is a useful habit: during design, write restore obligations next to each saved category. Player data restores identity and trainer-facing information. Party data restores creature roster and battle readiness. Bag data restores inventory affordances and unlock inputs. Flags and variables restore script decisions and world changes. Location restores the overworld entry point. Metadata restores trust: version, checksum, sector identity, save index, or whatever your engine uses to decide validity. This is how you prevent “we saved the bytes” from becoming a false success.

Let me check the room. If a save validates at the sector checksum level but the stored location refers to a deleted map in a later ROM revision, is that a storage validation failure or a semantic compatibility failure? It is semantic compatibility. The bytes may be intact. They may even be from a real save. But the current program may not know how to interpret them safely. That is why version metadata matters in your lab design. It gives the loader a chance to migrate, reject, or fall back instead of pretending all schemas are eternal.

FireRed, of course, is not a live-service game receiving weekly schema migrations, but we are studying it architecturally. The lesson generalizes. Every save format is a contract between the writer and the future reader. The reader might be the same ROM next hour, or it might be a patched game, a localization, a remake, a mod, or a testing harness. Advanced architecture means you identify that contract explicitly.

We will keep moving from here into the exact responsibilities of initialization, validation, and restoration. For now, take away the layered picture. load_save.c is where boot and continuation decisions meet persistent state, with the packet specifically highlighting pokefirered/src/load_save.c chunk_0755fbe98f753bb7, chunk_3efebaad2e0f31e0, and chunk_d615017937c86ab6. save.c is where the save system and sector flow live, highlighted by pokefirered/src/save.c chunk_ab80cd8f4fa87d77 and chunk_852fbc242f85cc74. agb_flash.c is the hardware persistence boundary. And the rest of the game supplies the owned state that makes the player’s world feel continuous.

So, as we go into the next segment, I want you listening for two questions in every function and every diagram: first, who owns this piece of state while the game is running? Second, what evidence must exist before we trust its saved representation during load? Those two questions are the spine of today’s lecture.

Good. So now let’s tighten that layered picture into an execution story.

Imagine the player has just powered on the game. The cartridge does not get to assume that memory contains yesterday’s world. Runtime memory is volatile. The save chip is persistent, but persistence is not the same as trust. So the loader has to ask a very careful sequence of questions. Is there saved data at all? Is it structurally recognizable? Are the sectors present? Do the checksums agree? If there are duplicate or rotating sectors, which copy is the newest valid one? And only after those questions are answered do we allow the rest of the game to behave as if there is a player, a party, a bag, a map position, and a world history.

Let me put that on the board as a spoken diagram. On the left, I’m writing “flash sectors.” In the middle, I’m writing “load and validation.” On the right, I’m writing “runtime save blocks and globals.” The important arrow is not simply “read.” The important arrow is “read, validate, select, then restore.” If you collapse that whole arrow into “load the save file,” you hide the architecture. And hiding that architecture is exactly how save systems become fragile.

The context packet for today points us to pokefirered/src/load_save.c chunk_0755fbe98f753bb7 as part of the load and save entry flow, then pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 as validation support, and pokefirered/src/load_save.c chunk_d615017937c86ab6 as continuation of that loading and saving work. I want you to hear those not as isolated snippets but as the boundary layer between boot-time uncertainty and runtime certainty. Once we are inside normal gameplay, other modules can read the party, bag, flags, variables, and location as if they are ordinary game state. But at boot, load_save.c has to earn that confidence.

Now, compare that with save.c. The packet highlights pokefirered/src/save.c chunk_ab80cd8f4fa87d77 for the save system and pokefirered/src/save.c chunk_852fbc242f85cc74 for save sector flow. That tells us the architecture is split in a sensible way. load_save.c is involved in deciding how the game enters or resumes a state. save.c is where the persistence mechanism itself is organized. And pokefirered/src/agb_flash.c is the lower hardware-facing boundary named in the packet, the place where the conceptual “save this world” eventually has to become flash reads and writes with real device constraints.

Let’s slow down on the ownership idea, because this is where advanced architecture thinking matters. When we say “the save block owns player data,” that does not mean every module is free to mutate every field whenever it wants. Ownership means the save block is the canonical persistent container for that category of state. The overworld may temporarily interpret map position. The menu may display bag contents. Battle may read and modify party data. Event scripts may set flags and variables. But when the system asks, “What must survive power-off?” those scattered behaviors have to converge into owned regions of persistent state.

Here is a concrete example. Suppose the player receives the Bicycle. The field item behavior and the inventory UI are prior foundations for us, so we are not going to re-teach item dispatch today. But architecturally, the event script or reward flow changes inventory state, and probably sets or relies on a flag depending on the game event. During the same play session, the bag menu can display the Bicycle because runtime bag state has changed. After power-off, the player expects the Bicycle still to exist. That means the bag’s persistent representation must be in a save-owned region, and the relevant flags or variables must also be restored. If you only save the bag item list but not the event flag that says the NPC has already given the item, the player may duplicate the reward. If you save the flag but not the item, the NPC refuses to give it again and the item is lost. That is not a UI bug. That is an ownership and persistence contract bug.

Check for understanding here. If I ask, “Which module owns the player-visible fact that the Bicycle has been acquired?” the best answer is not a single module name. The item is owned persistently by the bag data, the event history is owned by flags or variables, and the behavior is surfaced through scripts, menu callbacks, and field-use logic. The save system’s job is to preserve the owned data, not to preserve every temporary path by which the player saw or used it.

Now look at load validation through that same lens. Validation is not just a checksum fetish. It is the gatekeeper that prevents corrupted persistent bytes from masquerading as valid runtime state. A creature-collector RPG is especially vulnerable here because the state is deeply interconnected. Party count must match party entries. Box data must be structurally sane. Bag pockets must not contain impossible quantities or dangling item identifiers. Flags and variables can unlock maps, scripts, and story branches. Location data can place the avatar into a map whose layout, object events, and scripts expect certain conditions. So validating the saved representation is a defense against nonsense spreading across the whole engine.

I’ll write another small board example. Let’s say the saved location says the player is in a map group and map number, facing down, with coordinates x and y. If that location is restored blindly and the map is not valid, or the coordinates land outside the expected layout, then the overworld loader inherits a broken premise. The map system might be well-written, the object event system might be well-written, and the script system might be well-written, but all of them are now operating from corrupt input. That is why load validation belongs near the front door.

This connects back to our course-wide tracing model: data table to runtime globals to callback, task, or script to visible behavior. Earlier in the course, with map headers and layout registries, we studied how static data describes navigable places. Here, the save system stores a reference into that world. It does not store the whole map. It stores enough to re-enter the map runtime. So saved location is not a copy of the overworld; it is a rehydration instruction. And that distinction is essential. You persist the player’s position and world history, then the overworld system reconstructs active map runtime from the normal map data.

That is also why this week sits near the end of the course. In week 16 through week 19 assessments, you were asked to decompose the creature RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems, and then to trace concrete behaviors through files, structs, globals, callbacks, tasks, scripts, and source functions. Persistence is where that decomposition gets tested. If your architecture map is vague, saving exposes the vagueness immediately. You cannot save “the game.” You can only save specific owned state from specific systems, with clear restoration rules.

Let’s now talk about initialization. A mature load path has to handle more than “valid save exists.” It also has to handle “new game,” “corrupt or absent save,” and “partial initialization before the game knows which path it is on.” At startup, there are pieces of global state that need default values regardless of whether a save is found. There are other pieces that should only be populated from a trusted save. And there are still others that are derived after load. Advanced students sometimes make the mistake of treating initialization as a single blanket memset followed by read-from-disk. In an RPG, that is too crude.

Think of initialization as staged. First, prepare enough runtime shell to make decisions. That reaches back to AgbMain and callback setup from earlier in the course, where the game’s execution shell has to exist before high-level screens can run. Then inspect persistence. Then either restore saved state or create clean defaults. Then transition into the appropriate main callback or screen flow. The research packet’s course thesis repeatedly frames FireRed as cooperating state machines plus data tables, and this is a perfect example. Loading is not a passive file operation. It is a state transition in the outer runtime shell.

The load-save entry flow in pokefirered/src/load_save.c chunk_0755fbe98f753bb7 should be read with that question in mind: what state is needed before the game can decide where to go next? The validation support in pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 should be read with the complementary question: what evidence makes a persistent representation trustworthy? And the continuation material in pokefirered/src/load_save.c chunk_d615017937c86ab6 belongs to the practical follow-through: after trust is established, how does the game continue into a usable runtime state?

Now, I want to distinguish validation from repair. Validation asks whether bytes correspond to an expected saved structure. Repair or recovery asks what to do when they do not. FireRed’s sector-based save flow, highlighted in pokefirered/src/save.c chunk_852fbc242f85cc74, matters here because sector organization allows the system to reason about pieces rather than one monolithic blob. A monolithic save file has one giant pass or fail boundary. Sector flow gives you structure. It lets the save system write, identify, and validate portions, and in many cartridge save designs it also supports selecting the most recent valid set when there are multiple candidates.

Do not overstate that into magic, though. Sectoring does not mean arbitrary corruption is harmless. It means the save layer has a format rich enough to detect and choose among known representations. That is a much more precise claim. The abstraction we should take into the lab is not “sectors fix corruption.” The abstraction is “sectors carry metadata and boundaries that make validation and recovery possible.”

Let me give a toy example, not as a claim about exact FireRed field names, but as a design model for your workshop. Suppose our clone RPG divides persistence into player profile, party, bag, flags and variables, location, and checksum metadata. We might store each category in a separate sector-like record. Each record includes an id, a version, a size, and a checksum. During load, we do not simply deserialize all records into runtime memory. We first collect candidates. We check whether each id is recognized. We check the size against the expected schema. We compute the checksum. We compare version metadata. Only then do we assign data into runtime save blocks. If the bag sector fails but the rest succeed, your recovery policy has to be explicit. Do you reject the whole save? Do you restore defaults for bag? Do you use an older copy? There is no universally correct answer, but there must be a defined answer.

Now pause and test yourself. Why might “default the bag if bag validation fails” be dangerous? Because the bag is not isolated from progression. Key items, HMs, story objects, and unlock items can be prerequisites for scripts and map access. If flags say the player has passed a story gate but the bag loses the required key item, you can strand the player. So in a creature RPG, recovery policy has to respect cross-system invariants, not merely local data validity.

That phrase, cross-system invariant, is worth writing down. I’m putting it on the board. A cross-system invariant is a truth that must hold across two or more owned state regions. The party count agrees with party entries. The selected registered item refers to an item the player owns or a safe empty value. The current map reference agrees with a valid map table entry. A quest flag implies certain NPC scripts will branch differently. The player’s PC storage and party together define where creatures exist. And because the save system restores many of these regions at once, it becomes the place where broken invariants can enter the game all at once.

This is why restoring world, party, bag, and flags is not just copying bytes. The loader is effectively reconstructing a coherent world snapshot. Once that snapshot is active, ordinary systems take over. The summary screen can read creature data, as we saw in prior work around pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46. The move relearner state machine can operate over party and move data, with the prior lecture grounded in pokefirered/src/learn_move.c chunk_af3598df6f092723. The battle setup path can include load-save-related headers in its broader dependency environment, as visible in pokefirered/src/battle_setup.c chunk_494d132a59422c85, where battle transition and overworld concerns meet many global systems. Today we are not re-teaching those modules. We are noticing that they all rely on restored state being coherent before they touch it.

Let’s bring in flash boundaries now. On a desktop game, a save system may have a filesystem, rename semantics, temporary files, and relatively large storage. On the Game Boy Advance, you are dealing with cartridge save hardware. The packet names pokefirered/src/agb_flash.c as the flash persistence file. Architecturally, that file is a boundary. Above it, the game thinks in save blocks and sectors. At or below it, the implementation thinks in flash operations. That boundary matters because flash memory has constraints. Reads and writes are not just arbitrary structure assignments. Timing, sector erase behavior, device commands, and write granularity all shape what the higher-level save system can safely promise.

So if I ask, “Why not let every module write its own state directly to flash?” the answer should now feel obvious. First, it would scatter hardware knowledge everywhere. Second, it would destroy atomicity and validation discipline. Third, it would make ownership ambiguous. The battle system should not know how to write flash sectors. The bag menu should not know flash commands. The event script VM should not choose persistence layout. They mutate runtime state through their normal contracts. Then the save system packages that state and passes through the hardware boundary in a controlled flow.

There is an important design humility here. Save systems are boring only when they work. When they fail, they become the entire game. A corrupt save can erase a player’s emotional investment, and in a creature-collector RPG that investment may include dozens or hundreds of named creatures, rare encounters, story progress, inventory history, and multiplayer eligibility. That is why FireRed’s source organization around load_save.c, save.c, and agb_flash.c deserves close study. The architecture is protecting player trust.

Let’s take a concrete restoration walk. The player saves in a town after healing at a center. Runtime state includes the current map, coordinates, facing direction, party creatures with current HP and status, bag contents, money, flags for story events, variables for scripts, maybe registered item state, options, and other profile data. When saving, the game has to serialize the owned persistent parts. It does not need to serialize the current text box animation frame. It does not need to serialize the exact task id that happened to own a fading menu. It does not need to serialize transient decompressed graphics buffers. Those are runtime artifacts. On load, the game restores the save blocks, then rebuilds the visible world through normal map loading, callbacks, tasks, and graphics setup.

This gives us a clean rule: persist semantic state, regenerate presentation state. Say that back to yourself. Persist semantic state, regenerate presentation state. The player cares that they are in Cerulean City with a particular party and story progress. They do not care whether the previous session’s window tilemap buffer is byte-for-byte preserved. In fact, trying to preserve presentation state would make the save more fragile, because it would bind the save format to temporary implementation details.

Now, there are exceptions and edge cases. Some games save mid-menu or mid-dungeon transition. Some games save battle state. Some games support suspend saves. But FireRed-style durable saving is mainly world-state persistence, not arbitrary emulator snapshotting. That difference is central to your lab. You are not designing a memory dump. You are designing a schema.

Let’s do a quick check for understanding with a deliberately tricky example. Suppose a player opens the party menu, moves the cursor to the third creature, then saves through some hypothetical debug save call without closing the menu. Should the save schema include “party menu cursor index equals three”? In a normal durable save system, no. The selected cursor is UI state. The party order and creature data are semantic state. When the player reloads, the menu can be rebuilt from the party. If the game has a specific design requirement to resume inside that menu, then cursor index becomes part of a separate suspend-state contract, but not part of the ordinary world save contract.

That distinction also helps with versioning. Semantic schemas can evolve if they are deliberate. Presentation snapshots are brittle because every UI change becomes a save compatibility problem. When we talk about version metadata in the workshop, that is what we are guarding against. A version number is not decoration. It is a promise that the loader knows which structure it is reading and how to interpret it.

Now I want to connect this to link systems, because we are in the Persistence, Link, and System Integration module, and week 21 will push harder on connected behavior. Link features depend heavily on trustworthy local state. If the party data is invalid, trading and battling become unsafe. If item or creature ownership is incoherent, multiplayer interactions can duplicate or destroy value. So persistence is not just a local convenience. It is preparation for systems where another game instance may trust some representation of your state. We will not jump fully into link architecture yet, but keep that dependency in mind: robust link behavior begins with robust local state ownership and validation.

Let me return to the board diagram and add one more layer. Under “runtime save blocks,” I’m writing “consumers.” The consumers include overworld, scripts, party menu, summary screen, battle setup, inventory, and link. Each consumer has its own runtime logic. But none of those consumers should have to ask, every frame, “Was the save sector checksum valid?” That question belongs at load time. After load, the consumer should be able to rely on normalized runtime state. That is what boundaries buy us.

This is also why the source focus for today is narrow even though the implications are broad. We cite pokefirered/src/load_save.c chunk_0755fbe98f753bb7, chunk_3efebaad2e0f31e0, and chunk_d615017937c86ab6 because they sit at the entry and validation layer. We cite pokefirered/src/save.c chunk_ab80cd8f4fa87d77 and chunk_852fbc242f85cc74 because they show the save system and sector flow. And we remember pokefirered/src/agb_flash.c as the hardware persistence boundary. That is enough source grounding for today’s main argument: FireRed’s persistence architecture is not a bag of global variables dumped to storage. It is a staged handoff from owned runtime state into validated sectorized persistence and back again.

For the next stretch, we are going to go function-by-function at a higher resolution. As we do that, I want you to annotate each function with three labels in your notes: entry decision, evidence check, or restoration effect. Entry decision means the function helps choose new game, continue, or save-related flow. Evidence check means it proves or rejects trust in stored bytes. Restoration effect means it moves trusted persistent data back into the runtime shape that other systems consume. Those labels will keep us from getting lost in names and will make the lab simulation much easier.

Now, keep those three labels visible. Entry decision, evidence check, restoration effect. I’m going to draw them as three columns on the board, and for the next few minutes every load-save function we mention has to land in one of those columns. If a function seems to belong in two columns, that is not a failure. That is usually where the architecture is doing something interesting.

Let’s start with the entry decision side. When FireRed boots, it cannot simply say, “Do I have a save file?” in the ordinary desktop sense. There is no filesystem directory to scan. There is flash memory, there are sectors, and there is a convention for what valid saved state looks like. So the entry decision depends on evidence. The title screen and continue menu can only offer meaningful choices after the load-save layer has made a judgment about the stored sectors. This is why the source focus for today starts in pokefirered/src/load_save.c, especially chunk_0755fbe98f753bb7. That chunk is important because it sits near the load-save entry flow, the place where the program moves from startup and shell control into “what persistent state is available?”

Now, notice the phrase I just used: available, not merely present. That distinction matters. Bytes can be present and still not be trusted. A half-written save can be present. An old save layout can be present. A save from an interrupted write can be present. Corrupt data can be present. So a robust creature RPG does not ask only, “Is there data?” It asks, “Is there enough consistent evidence to restore the runtime world?”

I want to connect that to the lab you are about to do. In your save schema simulation, if you model the saved game as one JSON blob and then load it if the file opens, you will miss the architectural lesson. For the lab, your version field, checksum, sector id, save index, or equivalent metadata are not decoration. They are the evidence layer. The game’s load path is allowed to be skeptical. In fact, it must be skeptical.

Let me make this concrete. Suppose a player saves in a Pokémon Center. Runtime state includes the map group and map number, the player’s x and y position, facing direction, party data, bag pockets, money, Pokédex flags, story flags, script variables, maybe poison step counters, registered item state, options, and other system state. During play, different systems own those pieces. The overworld owns the active map runtime. The party system owns the current party array. The bag system owns inventory. Event data owns flags and variables. UI code reads options. Battle setup may consult party state and flags. But when the player saves, the persistence layer needs a serialized representation that can survive power loss.

If we write those pieces into flash, and then the player turns off the console halfway through, what should happen on next boot? If your answer is “load whatever was written most recently,” you have already lost. The latest partial write may be worse than the previous complete write. FireRed’s sectorized save design exists to avoid making recency the only source of truth. The save system in pokefirered/src/save.c, especially chunk_ab80cd8f4fa87d77 and chunk_852fbc242f85, is where that policy becomes concrete. The save system is not just a serializer. It is a reliability protocol over constrained storage.

Here is the board picture. I am drawing two horizontal bands. The top band is runtime memory. The bottom band is flash. In the top band I’ll write save block ownership: player and world state, party and inventory state, flags and variables, system options. In the bottom band I’ll draw sectors. Each sector has payload plus metadata. The arrow downward is save. The arrow upward is load. But the upward arrow has a filter on it. That filter is validation.

Check for understanding: if I ask you where the party is “real,” what is the answer? During normal play, the real party is the runtime structure the party and battle systems consume. In persistence, the party is a saved representation inside the save blocks. Those are not two independent truths. They are two forms of the same game state, with controlled transfer points between them.

That transfer point is why save block ownership matters. We introduced this last time, so I will not re-teach the whole ownership model, but I do want to sharpen it. Ownership is not just “which C file has a pointer.” Ownership means which subsystem is allowed to interpret and mutate a region of state during normal gameplay. The persistence layer’s job is to preserve that region without becoming the owner of every gameplay rule inside it. If the bag contains three Potions and one Bicycle, save code should not know the semantics of Potion healing or Bicycle field movement. We covered item-use dispatch earlier in the course. Today, the persistence question is simpler and stricter: can the bag state be restored so that the item module later sees the same inventory it expects?

That is one of the cleanest ways to separate architecture from behavior. Save code preserves item quantities and item identities. Item code decides what they do. Save code preserves party structures. Battle code decides how moves, stats, and fainting behave. Save code preserves flags and variables. Script code decides how those flags block or unlock events. The same state crosses multiple modules, but the meaning is not centralized in save.c.

Now let’s go into the evidence checks. The validation support in pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 is the part I want you to associate with “do not trust raw storage.” In a cartridge-era design, corruption is not an abstract possibility. Flash writes have boundaries. They happen in units and sequences. The implementation has to account for the fact that a write can fail or be interrupted at a boundary that does not line up with your friendly gameplay concept of “the player saved.” That is why the lower-level flash persistence file, pokefirered/src/agb_flash.c, matters as the hardware-facing boundary. We are not going to promote a specific chunk id for that file today because the packet names the file but does not promote chunk ids for synthesis, but architecturally it is the layer that reminds us that persistence is physical.

Let’s pause on that physicality. On a PC, you might save a file and imagine the operating system absorbing all the ugliness. On the GBA, the game is much closer to the storage protocol. You have to think about read boundaries, write boundaries, sector erase behavior, and the fact that writing is not the same kind of operation as assigning a struct in RAM. This is a major reason FireRed’s save architecture is more carefully staged than a beginner clone usually is.

A common student design is to create one giant SaveGame struct and write it directly. That is not automatically wrong for a toy project, but it hides important risks. What happens when the struct layout changes? What happens when only half the write completes? What happens when you need two copies? What happens when different subsystems have different lifetimes? What happens when one block should be cleared for a new game but another system needs default initialization before the first map loads? FireRed’s architecture forces us to confront those questions.

Now, look back at the three labels. An evidence check asks, “Can these bytes be believed?” Usually that means checking identifiers, checksums, signatures, save indices, sector ordering, and sometimes version-like metadata. A restoration effect asks, “Once believed, how do these bytes become runtime state again?” These are separate decisions. You may validate data without immediately activating it. Or you may initialize defaults first and then overlay restored values.

That brings us to load-save initialization. In a mature RPG, a new game and a continued game share a lot of initialization machinery, but they do not share the same source of state. New game initialization creates defaults. Continue initialization restores trusted saved values. Both must leave the runtime in a shape acceptable to the overworld, UI, script engine, and other systems. This is where pokefirered/src/load_save.c chunk_d615017937c86ab6 becomes relevant, because that continuation portion of the load-save logic is part of the handoff from validated persistence back into the running game.

Let me give a concrete version. Suppose the saved location says the player is on Route 3 at coordinates 12, 18. The load code cannot stop at “we copied map group, map number, x, and y.” The overworld must later be able to load the correct map header, layout, tilesets, object events, scripts, and connections. Earlier in the course, when we studied map headers and object events, we emphasized the path from data record to runtime behavior. This is the same path, but entered from the other side. Instead of a script or door warp changing location during play, the saved location seeds the overworld at boot.

So restoration is not just memory copying. It is rebuilding enough runtime context that the next callback frame can behave as if the world had always been there. That connects directly to the runtime shell material from week 1. The main loop and callback mechanism, grounded in pokefirered/src/main.c chunk_b35676322b169f85 for AgbMain and chunk_6cfd90f6d0136799 for SetMainCallback2, do not care why the overworld state exists. They care that the next callback has coherent state to process. Similarly, task dispatch, grounded in pokefirered/src/task.c chunk_cb0bb91a147bfb59 for CreateTask and chunk_3ffebc0341225936 for RunTasks, assumes task data is coherent once a screen or mode has been entered. Save loading has to prepare the world before those systems start consuming it.

Let’s check that with a question. If a corrupted save gives us a valid-looking party but an invalid map id, should the game continue because the party is good? No. At least not as a normal continue flow. A creature-collector RPG is not just party data. It is party plus world plus progression flags plus inventory plus runtime shell assumptions. You can build recovery tools that salvage partial data, but the normal player-facing load path needs a coherent whole. This is why your lab should not validate each subsystem in isolation and then blindly mix whatever passes. You need a policy for the complete save image.

Now, there is a subtlety here. “Complete” does not mean every byte must be gameplay-critical. Some bytes may be padding. Some may be derived. Some may be reinitialized after load. Some may be caches. Advanced architecture asks you to distinguish persistent authority from reconstructible runtime convenience. If a value can be recalculated from the map header and player position, it may not need to persist. If a value represents player progress, such as an event flag, it almost certainly must persist. If a value is UI transition state inside a menu task, it probably should not survive power-off. Remember our menu lectures: menu callbacks and task states are runtime control structure. They are not usually save-game truth. When we looked at summary screen reads in pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 and move relearner state in pokefirered/src/learn_move.c chunk_af3598df6f092723, those modules consumed party and move data, but their temporary cursor positions and task phases were not the same as long-term progression.

That distinction is one of the reasons the week 20 homework is more demanding than the week 19 homework. Week 19 asked you to design minimal clone versions of map loading, script execution, party data, battle turns, item use, and save persistence. That was breadth. Week 20 asks you to show judgment about what must persist, who owns it, and how validation prevents false restoration. The bar is higher because you have already traced enough systems to know that everything cannot simply dump itself into storage whenever it wants.

Let’s talk about battle for a moment, but only as a consumer of restored state. In pokefirered/src/battle_setup.c chunk_494d132a59422c85, the include list itself is revealing: task, overworld, item, pokemon, load_save, safari zone, quest log, script, event data, event object movement, field control, player avatar, battle transition, and so on. I’m not asking you to memorize an include list. I’m asking you to see the dependency shape. Battle setup is a junction. It needs party data, world context, event state, and transition control. If load restoration leaves any of those in an impossible configuration, battle setup may be the place where the bug becomes visible, even though the bug was born in persistence.

That is a major debugging lesson. The symptom may appear in battle, inventory, or UI, but the cause may be a bad restored invariant. A Pokémon summary screen displaying nonsense may not mean the summary screen is wrong. It may mean the party data was restored with a bad personality value, move list, species id, or checksum-like internal expectation. A key item failing to unlock a script may not mean the script is wrong. It may mean flags or inventory were not restored together. In a system this interconnected, save/load bugs often masquerade as gameplay bugs.

Now, I’m going to write the word “invariant” under restoration effect. An invariant is a condition that must be true for the rest of the program to operate safely. After loading, valid party count is an invariant. Valid map identity is an invariant. Bag pocket counts within capacity are invariants. Event flag storage having the expected size and initialized backing memory is an invariant. The player object existing on the loaded map is an invariant. The callback chosen after load is an invariant. If you restore bytes but violate invariants, you have not really loaded the game. You have merely copied corruption into RAM.

Here is a small example suitable for your simulation. Imagine your saved bag block says the medicine pocket has capacity 30, but the count field says 255. If your load code trusts that count, the inventory UI may later iterate past the pocket array. The UI bug will be downstream, but the evidence check should have caught the impossible count. Now imagine the opposite case: the count is valid, but one item id is unknown for the current version. Your policy might reject the save, clamp the item, migrate it, or treat it as empty. The important point is that you write that policy explicitly. FireRed’s original code is working within a fixed retail schema, but your lab schema should still model version and validation because you are learning the architecture, not merely copying constants.

Let’s bring in the sector flow more directly. In pokefirered/src/save.c chunk_852fbc242f85, the save sector flow is central because the save image is distributed. That means validation must also be able to reason across pieces. If sector 3 is from one save attempt and sector 4 is from another, the assembled image may be inconsistent even if both sectors individually contain plausible bytes. So the save system needs metadata that lets it decide which set of sectors belongs together or which save attempt is newest and complete.

This is where students often confuse checksum with identity. A checksum can tell you whether a payload matches its checksum. It does not by itself tell you whether the sector belongs to the same complete save set as another sector. You need ordering or id metadata for that. Conversely, a matching id does not prove the bytes are uncorrupted. You need integrity evidence too. The robust design layers evidence: identity, ordering, integrity, and expected layout.

Check for understanding: why not just store two complete saves and pick the one with the newest timestamp? Because on this platform you may not have a real timestamp, and even if you invent a monotonically increasing counter, newness alone does not prove completeness. You want the newest complete valid set, not the newest bytes.

Now we can connect this to flash write/read boundaries. The hardware-facing layer, pokefirered/src/agb_flash.c, is not where the game should know what a badge means or what a Pokémon move means. But it is where the persistence system must respect the storage medium. The save layer above it organizes gameplay state into sectors and metadata. The flash layer performs the actual persistence operations. This is a classic architecture boundary: the low layer knows how to write, the middle layer knows what a valid save set looks like, and the high layer knows what the restored state means to gameplay.

I want to make that separation visible. On the board, I’ll draw three boxes vertically. Bottom: flash operations. Middle: save sector protocol. Top: gameplay save blocks. The bottom box answers, “Can I read and write this storage?” The middle answers, “Which stored sectors form a valid save?” The top answers, “What world, party, bag, flags, and variables should runtime modules see?” If you merge those layers in your clone, the first version may work, but the moment you add migration, backup saves, or partial-write recovery, the design will fight you.

Let’s now revisit save block ownership in a slightly more advanced way. Save blocks are not just a storage convenience; they are a modularity strategy. They let the game group persistent state according to broad ownership and lifetime. Player identity and location belong near one group of concerns. Party and box-like creature data belong near another. Flags and variables support script progression. Bag state supports inventory. Options support UI and system behavior. The exact grouping in FireRed is specific, but the principle generalizes: group data so validation, initialization, and restoration can be reasoned about without making every subsystem serialize itself independently.

There is a tradeoff. If you centralize save block layout, you risk coupling many systems to one giant structure. If every subsystem writes itself independently, you risk inconsistent saves. FireRed leans toward centralized save blocks with subsystem-owned interpretation. That is the right shape for a small cartridge RPG engine because it allows atomic-ish save attempts over sectorized storage while still giving gameplay systems stable memory to read.

Now, one more concrete path. The player loads a save. The validation layer selects a valid sector set. The load-save continuation code restores save block data. The overworld receives a location and map identity. Event flags and variables become available to scripts. Party data becomes available to party menu, summary screen, and battle setup. Bag data becomes available to inventory UI and field-use item dispatch. The runtime shell sets or reaches the appropriate callback so the map can render and accept input. From the player’s perspective, they chose “Continue” and appeared where they saved. From the architecture perspective, a dozen contracts have been reestablished.

That is why I keep using the word handoff. Persistence is not a passive warehouse. It is an active handoff between time slices of the program. Yesterday’s runtime state was transformed into stored evidence. Today’s boot sequence transforms that evidence back into runtime state. If either transformation is sloppy, the player experiences it as lost progress, broken maps, strange party data, or impossible scripts.

Before we move on, let’s do a quick oral diagnostic. I’ll say a piece of data, and you classify it as persistent authority, reconstructible runtime state, or temporary UI state. Player’s current map? Persistent authority, at least the location identity and coordinates. Currently fading palette during a transition? Temporary runtime state. Whether a gym leader has been defeated? Persistent authority, usually flag-backed. The current row highlighted in a bag menu? Temporary UI state. The object events defined by a map header? Reconstructible from map data, though their local dynamic state may have persistent or runtime components depending on the event. The party’s HP after a battle? Persistent authority if the player can save after that battle, because party condition must survive.

Good. That exercise is exactly the mental model you need for the workshop. You are not merely listing fields. You are assigning authority.

Now, to tie this back to the overall course plan, week 20 is where the architecture stops being a set of separate engines and becomes a promise across sessions. In earlier weeks, we learned the runtime shell, maps, scripts, creature data, battle, inventory, and UI as cooperating systems. In week 20, save/load asks whether all of those systems can leave behind enough state to resume later without remembering their entire call stack. That is the advanced part. A saved game does not preserve the call stack. It preserves a designed state surface. The callback/task machinery will be rebuilt. Menus will reopen only when the game explicitly opens them. Scripts will resume only if the engine has designed a resumable script state. Most ordinary transient computation is gone. What remains is the durable model of the player’s world.

So when you design your schema, do not ask, “What variables exist?” Ask, “What facts must still be true after power-off?” That question will give you a cleaner save file than scanning globals. It will also make your validation story clearer. Facts can be checked. Random runtime leftovers usually cannot.

Let’s take one last check before the next segment. Why is FireRed’s persistence architecture not just a global memory dump? Because the game has to separate subsystem ownership from serialized representation, validate sectorized flash data before trusting it, respect hardware write boundaries, and restore only the durable state needed by the runtime shell, overworld, scripts, creature systems, battle, inventory, and UI. That sentence is dense, but by week 20 you should be able to unpack every noun in it.

When we continue, we are going to zoom in on failure modes. Not dramatic hypothetical failures, but ordinary engineering failures: mismatched save block assumptions, valid sectors assembled in the wrong order, default initialization that accidentally overwrites restored values, and UI consumers that reveal corrupted state several screens after load. Keep the three labels on your page, because the next examples will deliberately blur the line between evidence check and restoration effect.

Now, let’s make those failure modes concrete.

Suppose I draw the save system on the board as three layers. At the top, I write “runtime owners.” That means the overworld owns where the player is, the script system owns flags and variables, the creature system owns party and PC-related creature records, the inventory system owns bag quantities and pockets, and the UI reads from all of those owners without necessarily knowing how they got restored. In the middle layer, I write “save block layout.” This is the structured memory image that says, in effect, these bytes belong to this durable category, these other bytes belong to another durable category. And at the bottom, I write “flash sectors.” That is the physical persistence layer, where the data is not simply one big friendly file, but a sectorized storage medium with validation metadata and write boundaries.

The engineering mistake is to collapse those three layers into one mental object. If you say, “the bag is saved in flash,” that is true, but too imprecise for architecture. The better sentence is, “the inventory subsystem owns bag state at runtime, the save block representation includes bag fields at a particular structural location, and the save system serializes that representation into validated flash sectors.” That is the level of precision I want from you now, because week 20 is where vague architecture descriptions stop being useful.

The source packet gives us exactly the modules we need for this separation. The lecture focus points to pokefirered/src/load_save.c chunk_0755fbe98f753bb7 for the load and save entry flow, pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 for validation support, and pokefirered/src/load_save.c chunk_d615017937c86ab6 for the continuation of the load-save path. Then pokefirered/src/save.c chunk_ab80cd8f4fa87d77 and pokefirered/src/save.c chunk_852fbc242f85cc74 anchor the lower-level save system and sector flow. The flash boundary is named in pokefirered/src/agb_flash.c. So when I say this is layered, I am not making a clean-room design recommendation. I am describing the pressure visible in the source organization itself.

First failure mode: mismatched save block assumptions. Imagine you add a new durable field, let’s say a “last used repel step count” or a “custom quest chain progress marker.” Where should it live? If you answer, “wherever there is room,” you have just proposed a corruption bug with a friendly face. You have to ask which runtime subsystem owns it. Is it a script variable? Is it a field effect? Is it player profile state? Is it party-derived state that should be recomputed rather than stored? If it belongs to event scripting, then flags and variables are probably the conceptual home. If it belongs to the bag, perhaps it should be derived from inventory and map step state. If it is purely UI history, perhaps it should not persist at all.

Let me pause there, because this is one of the most common errors in student save schema designs. You store a value because it is convenient to store, not because it has a durable owner. Convenience is not ownership. Persistence is not a junk drawer.

Here is the concrete example. Suppose a player uses a key item that unlocks an overworld obstacle. We already covered item-use dispatch and field-use behavior earlier, so I am not going to re-teach that path. Just remember the shape: the item behavior eventually influences script-visible or map-visible state. The durable result is usually not “the item-use menu was open” or “the callback was in state 4.” The durable result is something like a flag, a variable, a changed object condition, or an inventory count. On reload, the world should appear unlocked because the script and map systems read durable progress state, not because you saved the transient menu state that happened to produce it.

Check for understanding: if the player saves immediately after selecting a field-use item but before the resulting script completes, what is the dangerous question? The dangerous question is, “Can I just save the current callback and resume it?” In this architecture, you should be suspicious of that. A callback is part of the runtime shell. It may be restartable in some controlled flows, but it is not generally the durable truth of the game. The durable truth is the completed state mutation, or the fact that the mutation has not yet occurred. That is why persistence architecture and event scripting architecture have to agree about commit points.

Now let’s talk about valid sectors assembled in the wrong order. This is a subtler bug because every individual piece can look healthy. A sector can have plausible metadata. Its checksum can pass. Its identifier can be in range. But if your reconstruction logic treats “valid” as “globally coherent,” you can accidentally assemble a save image from a mixture of generations. Think about a save that spans multiple sectors. A power loss during writing may leave some sectors from the newer save attempt and some sectors from the previous save attempt. The point of sector validation is not only to reject nonsense. It is also to decide which coherent generation of data should be trusted.

That is why pokefirered/src/save.c chunk_852fbc242f85, the sector-flow area called out in the packet, matters so much. Sectorized persistence is not a decorative implementation detail. It changes the semantics of saving. You cannot reason about save correctness by saying, “Did we write the data?” You have to ask, “Did we write all of the sectors required for one recoverable save generation, and can the loader later distinguish that generation from an older or partial one?” Validation support in pokefirered/src/load_save.c chunk_3efebaad2e0f31e0 sits right at this boundary between evidence and trust.

Let’s put a small timeline on the board. At time A, the player has three badges, a party of four, and is standing in Celadon. At time B, the player earns a fourth badge, catches a fifth creature, changes the bag, and saves. During the save, sector one is written successfully, sector two is written successfully, and then power is lost. On reboot, the loader sees some valid sectors from time B and some valid sectors from time A. If it merely says, “valid checksum, good enough,” it can create a state that never existed: four badges, old party, new bag, wrong map, or any other impossible mixture depending on sector boundaries.

That impossible mixture is what I want you to hear as a systems problem, not as a Pokémon-specific trivia problem. In any game with sectorized or chunked saves, you need generation identity. Sometimes that is a counter. Sometimes it is a transaction marker. Sometimes it is a manifest. Sometimes it is two alternating slots. FireRed’s save system, as represented in the packet’s save.c and load_save.c chunks, belongs to that family of designs: validate the pieces, then recover a coherent persisted state rather than trusting arbitrary bytes.

Now, a check for understanding. If a checksum passes, does that prove the save is the most recent complete save? No. It proves only that the checked data matches the checksum calculation for that sector or record. It is an evidence check, not a full restoration policy. Good. That distinction is one of today’s central ideas.

Third failure mode: default initialization that overwrites restored values. This one happens when startup code and load code are not ordered with enough discipline. When a Game Boy Advance title boots, it has to initialize runtime memory, set pointers, prepare heap or save block locations, configure the main callback path, and decide whether a previous save exists. But after restoration, many systems also have default initialization routines. Some defaults are correct only before loading. Other defaults are correct after loading because they reset transient runtime state. The mistake is failing to separate those categories.

Let’s use our course vocabulary. Runtime shell state, callback state, task state, and many UI working buffers are ephemeral. They should be initialized fresh. Party data, bag contents, script flags, location, player identity, options, and progress markers are durable. They should be restored from validated save data if that data exists. If the same function, or the same mental phase, treats both groups as “things to initialize,” then you are one bad call order away from erasing the restored world.

The load-save entry flow in pokefirered/src/load_save.c chunk_0755fbe98f753bb7 is important here because entry flow is about sequencing. Before the player sees the loaded game, the code has to establish a trustworthy base. Load validation support, again in chunk_3efebaad2e0f31e0, answers, “Do we have durable evidence?” The continuation chunk, pokefirered/src/load_save.c chunk_d615017937c86ab6, belongs to the “what happens next” side of that story. And the next step must respect ownership: restore durable state, initialize transient state, and then let systems resume from durable facts.

Here is a simple example. Suppose your clone has a function called InitPlayerForNewGame. It sets the player’s map to the starting bedroom, gives the player no badges, clears flags, initializes the bag, and sets the party count to zero. Fine for a new file. Now suppose your boot path calls InitPlayerForNewGame before trying to load. That can be okay if the load fully overwrites every durable field and no later default routine re-clears them. But now suppose there is a second routine called InitFieldSystem that helpfully clears script flags because early in development that solved a bug when starting a new file. If InitFieldSystem runs after loading, congratulations, the player may keep their party and bag while the world forgets every story event. This is how architectural sloppiness produces bizarre user-facing defects.

And notice how late the defect may appear. The title screen loads. The player appears on the correct map. The party menu opens. The bag looks plausible. Then an NPC blocks a doorway that should be open, or a rival event triggers again, or a badge-gated script behaves as though the badge was never obtained. That is not “a script bug” in isolation. It may be a save restoration ordering bug revealed through the script system.

That brings us to the fourth failure mode: UI consumers revealing corrupted state several screens after load. A useful UI is often a consistency tester wearing nicer clothes. The summary screen, the party menu, the bag menu, the trainer card, the PC interface, the move relearner, the map name popup, all of these read durable state that has been restored and then interpreted. Earlier, when we studied menu callbacks and task-backed state machines, we saw that UI code often does not own the canonical data. It reads it, formats it, and navigates through it. The packet’s pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 is a good reminder of that kind of module boundary: summary UI draws from Pokémon, item, move, region, party, and other systems. It is not the save loader, but it can expose save loader mistakes.

So if a summary screen shows a creature with an impossible move, do not immediately blame the summary screen. Ask where the creature record came from. Was it restored from the party sector? Was its data decrypted or interpreted correctly? Was its species valid? Were move slots restored as durable creature data rather than regenerated incorrectly? Did a post-load sanitizer run? Did it run too aggressively? Advanced debugging is often the discipline of not stopping at the first visible symptom.

Let me give you a concrete debugging walk. The player reports, “After loading, my party looks fine, but when I open the move relearner, one creature has wrong available moves.” We know from earlier that the move relearner is a state machine and menu flow, with pokefirered/src/learn_move.c chunk_af3598df6f092723 called out in the research packet for that role. But we do not re-teach the move relearner here. We ask: is the move relearner computing from species and level? Is it reading the party creature’s current species correctly? Is the party creature restored correctly? Did the save loader restore party data and then some migration or initialization code alter it? Did the bug come from persistent creature data, derived learnset logic, or UI state? That is the week 20 version of tracing: not just data table to behavior, but saved data to restored runtime object to later UI interpretation.

Now connect this to the whole course. In week 1, we started with the runtime shell. We asked how AgbMain, callbacks, and per-frame task dispatch make the game move at all. Later, we studied maps and object events, then scripts and flags, then party data and battle, then inventory and menus. If you look backward from today, persistence is the system that has to remember enough of all of those modules to let the runtime shell rebuild the illusion of continuity. A save file is not the game. It is a carefully chosen seed from which the game can reconstruct a legal runtime state.

Let me say that again because it is worth writing down in your own words: a save file is not the game. It is a durable commitment record. It records commitments that survive power-off. It does not record every animation, every callback, every temporary menu cursor, every DMA setup, every decompressed tile buffer, or every battle calculation scratch variable. The art of persistence is deciding which facts are commitments and which are temporary consequences.

Battle gives us another useful boundary example. The packet points to pokefirered/src/battle_setup.c chunk_494d132a59422c85 as a module that includes battle transition, overworld, load_save, script, event object movement, and related systems. That is an integration-heavy source area. But most battle runtime state is not something you want to blindly persist mid-frame. The durable consequences of ordinary battle are things like party HP, experience, status, item consumption, money changes, flags, trainer defeat state, and location or script progression after the battle. The battle engine has many transient turn variables that make sense only inside the active battle loop. Persisting the wrong layer would make reload behavior fragile.

So when you design your lab save schema this week, I do not want just a table of fields. I want a reason for each field. Player name and trainer ID persist because they define identity. Location persists because the overworld needs a reentry point. Party persists because creature ownership and condition are durable. Bag persists because inventory changes survive. Flags and variables persist because the script system’s world progression depends on them. Options may persist because they define player preference. Checksums, version fields, sector IDs, and counters persist not as gameplay, but as evidence that lets the loader decide what it is allowed to trust.

Now, let’s do a quick oral exercise. I will name a value, and you tell me whether it is durable, transient, or derived. The player’s current map group and map number. Durable, assuming we need to restore the player’s position. The currently fading palette during a transition. Transient. The fact that a gym leader has been defeated. Durable, probably a flag or event variable. The exact task ID assigned to the bag menu task. Transient. The quantity of Potions in the bag. Durable. The displayed string in a text window saying “Potion.” Transient, derived from item data and UI state. A creature’s current HP. Durable, because party condition persists. A battle damage roll from two frames ago. Transient. Good.

If you hesitated on any of those, that hesitation is useful. It means you are seeing that persistence is not about nouns. It is about responsibilities. “Potion” appears in the bag, the item database, the UI, and possibly a script. Which one persists depends on which responsibility we are talking about. The item definition is static data. The bag quantity is durable player state. The menu row is transient UI state. The script condition may be durable if it is represented as a flag or variable. Same word, different architectural roles.

Now I want to return to flash write and read boundaries, because the hardware detail matters here. In a desktop game, many students imagine saving as opening a file and writing a JSON object. That is not FireRed’s world. The packet specifically names pokefirered/src/agb_flash.c as the flash persistence file. Flash has erase and write constraints. You do not get to pretend every byte is an independent little notebook entry. The save system has to respect the medium. That is why sector-level design is not overengineering. It is an accommodation to the physical storage model.

And this is where the advanced lesson is broader than FireRed. Every persistence medium pushes back. Flash sectors push back one way. Cloud saves push back another way, with latency, conflict resolution, and authentication. Database-backed saves push back with schema migration and transaction boundaries. Local files push back with partial writes and platform-specific filesystem behavior. The architectural pattern remains: define ownership, serialize durable commitments, validate evidence, recover coherently, and initialize runtime state around the restored commitments.

Let’s make the wrong-order assembly example more formal. On the board, I will write “validity is local; coherence is global.” Local validity says this sector’s checksum matches, this sector’s ID is plausible, this block is within size bounds. Global coherence says all restored pieces belong to the same save generation and together describe a possible game state. You can pass local validity and fail global coherence. Many real bugs live in that gap.

What would a global coherence test look like in your simplified lab? You might store a save generation number in every block. You might store a manifest that says which blocks belong to the transaction. You might write to an inactive slot first, then mark it current after all sectors succeed. You might keep two save copies and choose the newest fully valid one. You might validate cross-field invariants after load: party count is between zero and six, bag quantities are legal, location points to a known map, flags array size matches the version, and checksums pass. The exact mechanism can vary. The principle cannot.

Now, one caution. Do not turn validation into gameplay repair unless you mean to. If party count is seven, clamping it to six may stop a crash, but it may also hide corruption. If map ID is unknown, sending the player home may be kinder than refusing to load, but that is a recovery policy. If a checksum fails, falling back to the older save copy may be correct, but only if you know the older copy is coherent. Validation tells you what evidence you have. Recovery policy decides what to do with missing or failed evidence.

This is why I keep using the phrase “before trusting it.” The loader’s job is not merely to move bytes into RAM. It is to establish trust boundaries. Unvalidated flash data is untrusted input, even though it came from the game’s own previous write path. Power loss, interrupted writes, version mismatch, memory bugs, and hardware edge cases can all produce data that the current runtime must not blindly believe.

Let’s check understanding with a slightly harder question. If a save block contains a valid checksum but a version number from an older schema, should the loader reject it automatically? Not necessarily. It depends on whether the game supports migration. If there is no migration path, rejection or fallback may be necessary. If there is a migration path, the loader can treat the old version as valid evidence under old rules, convert it, and then initialize any new fields with carefully chosen defaults. But the key is that default initialization happens as schema migration, not as an accidental post-load wipe.

That distinction will matter in your lab. When you design a version field, do not just place it in metadata and ignore it. Explain what version one means, what version two changes, and how the loader treats missing or older fields. A version field without loader behavior is decorative metadata. In real systems, decorative metadata is a liability because it makes readers think a problem has been solved.

Now let’s connect inventory, party, bag, and flags in one restoration scenario. A player saved after receiving a gift creature from an NPC. The script should have set a received-gift flag, added the creature to the party or PC, maybe removed or added an item, and updated dialogue state. On reload, four systems must agree. The creature system must contain the gift creature. The event script flags must say the gift has been claimed. The inventory must reflect any item exchange. The overworld NPC must choose the post-gift dialogue. If only the party persists and the flag does not, the player may receive the gift again. If only the flag persists and the party does not, the player loses the creature. If the bag persists from the new save but flags persist from an old save, you get a cross-system contradiction.

This is why save block ownership is not an administrative concern. It protects game meaning. Creature-collector RPGs are especially sensitive to this because the player’s mental model is collection continuity. Losing a party member, duplicating a rare item, resetting a story flag, or relocating the player to an impossible map is not just a data bug. It breaks the contract of the genre.

And now you can see why week 20 belongs near the end of the course. We could not responsibly teach this in week 2. You needed callbacks and tasks first. You needed maps and scripts. You needed creature data and battle consequences. You needed inventory and UI readers. You needed to see that menus are state machines and that summary screens read shared data. Only now does “save the game” mean something more precise than “write everything down.”

Let me give you the synthesis for this segment before we push into the next part. A robust FireRed-style load path answers four questions in order. First, what storage evidence exists in flash, and does it pass local validation? Second, which set of sectors or blocks forms the coherent save generation we should trust? Third, how do we restore durable subsystem-owned state into runtime memory without confusing it with transient callback, task, or UI state? Fourth, after restoration, how do the overworld, scripts, party, bag, battle setup, and UI consume that state without revealing contradictions?

Keep those four questions visible. In the next stretch, we will use them to reason through the lab schema you are designing: player, party, bag, flags, variables, location, checksum, version, and recovery metadata. And I want you to notice something as we do that. The schema is not just a storage format. It is a claim about what the game is.

So let us put that claim on the board. I am writing, “The save schema is the game’s durable self-description.” That sounds a little abstract, so let me make it concrete. If your schema says that player position persists, party persists, bag persists, event flags persist, and script variables persist, then your game is saying, “The world is a place with remembered location, remembered companions, remembered possessions, and remembered narrative decisions.” If your schema also persists battle-local animation state or the current menu cursor position in a generic pause menu, then your game is probably saying too much. It is confusing a temporary presentation detail with an authored world fact.

This is exactly why FireRed’s organization is so useful for us as an architecture case study. In the load and save material for today, especially pokefirered/src/load_save.c chunk_0755fbe98f753bb7, chunk_3efebaad2e0f31e0, and chunk_d615017937c86ab6, the important idea is not merely that there are functions with names about loading and saving. The important idea is that the load path sits between nonvolatile flash evidence and many runtime systems that each believe they own some piece of game truth. Then pokefirered/src/save.c chunk_ab80cd8f4fa87d77 and chunk_852fbc242f85 give us the other side of that same bargain: how the save system packages state into sectors, validates it, and treats flash as a constrained medium rather than as a friendly heap.

Now, before we go further, quick check for understanding. If I ask, “Why not just serialize all of RAM?” what is the advanced answer? Not the beginner answer, which is, “Because it would be too big.” That may be true, but it is not enough. The advanced answer is that RAM contains mixed-lifetime state. It contains durable game facts, yes, but it also contains callback slots, task records, decompression buffers, temporary menu selections, animation counters, link handshake details, and frame-local scratch values. We spent earlier weeks tracing AgbMain startup, the main callback slots in pokefirered/src/main.c chunk_b35676322b169f85 and chunk_6cfd90f6d0136799, and the task scheduler in pokefirered/src/task.c chunk_cb0bb91a147bfb59 and chunk_3ffebc0341225936. Those systems are supposed to be rebuilt into a valid runtime shape. They are not supposed to be blindly resurrected from flash as if every byte in memory were equally meaningful.

So when your lab schema has a save block for player, party, bag, flags, variables, location, checksum, version, and recovery metadata, I want you to ask a more precise question than, “Can this data be saved?” Ask, “Who owns this data while the game is running, and who is allowed to reconstruct it on load?” Ownership is the word that prevents a persistence layer from becoming a dumping ground.

Let us do a board example. Imagine a very small FireRed-style clone. The player is standing in Cerulean City, has a party of three creatures, has obtained the bike, has beaten Misty, has a bag containing potions and key items, and has a pending script variable that records which dialogue branch an NPC should use next. Your schema has a player block with name, gender, trainer ID, options, money, and play time. It has a location block with map group, map number, x, y, facing direction, and warp metadata. It has a party block with creature records. It has a bag block with item pockets. It has an event block with flags and variables. And around those blocks, it has version, checksum, save index, and maybe a block ID or sector ID.

Now suppose the player saves, powers off, and reloads. What must be true after load? The game should not merely have the same bytes. It should have the same meaning. The player should appear in Cerulean City at the correct tile. The bike should be usable because the key item is present. Misty should not challenge the player as if undefeated because the relevant flag is set. The creature party should be valid enough that the summary screen can inspect each creature without crashing or displaying nonsense. The bag should be valid enough that the item-use dispatcher can decide whether an item can be used in the field or in battle. And the overworld should be valid enough that object events, scripts, and collision checks can resume normal operation.

Notice how many previous parts of the course are compressed into that sentence. I am not re-teaching item-use dispatch today, but I do want you to connect it forward. Earlier, when we talked about field-use versus battle-use items and healing versus key item behavior, we saw that item behavior is not just data and not just code. It is data interpreted by context. On load, the bag block does not need to remember, “The player is currently highlighting the third row of the bag menu.” That is a UI fact. But it absolutely must remember, “The player owns the bicycle,” because that durable item state feeds scripts, field behavior, and progression gates. That is the difference.

Same thing with the party. When we covered summary screen reads and move replacement, especially in the context of pokefirered/src/pokemon_summary_screen.c chunk_e8e17a42bbf17d46 and pokefirered/src/learn_move.c chunk_af3598df6f092723, the visible menus were task-backed state machines reading structured party data. On load, you do not persist the whole move relearner state machine. You persist the creature’s learned moves, species, experience, personality, ownership, current HP, status, and other durable creature fields. Then, if the player later opens the summary screen or move relearner, those task and menu systems are rebuilt from the restored creature records. That is the architectural separation.

Let me pause and ask it another way. If a student’s lab schema includes a field called “currentSummaryScreenPage,” is that wrong? Maybe. If the game supports saving from inside the summary screen and restoring exactly into that UI, then perhaps it is part of a suspended UI state. But FireRed-style saving generally does not treat every arbitrary screen as a resumable operating system process. It saves game world facts, then re-enters through controlled runtime initialization. So in our design, unless you have explicitly built suspend-resume as a feature, that field is a smell. It is probably transient.

Now, let us connect this to sector validation. Flash persistence is not like writing a JSON file in a comfortable desktop environment. The source packet names pokefirered/src/agb_flash.c as part of the persistence focus, and the save chunks in pokefirered/src/save.c are where we should keep our mental model grounded. The game has to read and write through hardware-shaped boundaries. Sectors can be independently written, validation must survive interruption, and the system needs a way to decide which stored generation is coherent. That is why metadata is not decorative. Checksums, sector identifiers, save counters, and version-like fields are not bureaucracy. They are the evidence the loader uses to distinguish “this is a usable saved game” from “this is stale, incomplete, or corrupt storage.”

Here is the common mistake in lab designs. A student will create a beautiful block layout with player, party, bag, flags, variables, and location, and then put one checksum at the end of the entire save file. That is fine for a toy file format, but it does not teach the FireRed lesson deeply enough. In a sector-based medium, each sector or block needs enough local evidence for the loader to reason about partial success and partial failure. If writing sector seven fails, you do not want the loader to be forced into total ignorance. You want it to be able to say, “These sectors belong to save generation N, these sectors belong to save generation N minus one, this one has an invalid checksum, and therefore the newest complete coherent set is the older generation.” That is the recovery story.

On the board, I am drawing two columns. Left column: generation 41. Right column: generation 42. Under each column, imagine sector entries: player, party, bag, flags, variables, location, metadata. If generation 42 has valid player, valid party, valid bag, invalid flags, valid variables, valid location, what should the loader do? The naive answer is, “Load the valid blocks and use defaults for flags.” That sounds graceful, but it may create contradictions. The player might be standing beyond a story gate that only opens if a flag is set. The bag might contain a key item from an event that the flags now say never happened. The party might include a gift creature whose received flag is cleared. So a robust FireRed-style loader should prefer coherent generations over optimistic mixing, unless the schema explicitly defines cross-generation repair rules.

That is a key principle, and I want you to write it in your notes in whatever language works for you: validity is local, but trust is global. A checksum tells you a sector’s bytes are internally plausible. It does not, by itself, tell you that the restored game state is semantically coherent. Coherence comes from grouping, versioning, ownership, and careful initialization.

This is where load-save initialization becomes more than startup plumbing. We have already covered the runtime shell: AgbMain sets up the process, callback slots define the active mode, tasks execute per-frame behavior. When loading succeeds, the game is not just copying bytes into globals and then hoping the next frame works. It is re-establishing a world. That means the loader has to restore durable state into the save blocks and then allow systems like the overworld, script engine, party code, bag code, and UI setup to consume it through their normal initialization paths.

So, for example, if your location block says map group 3, map number 12, x equals 8, y equals 14, facing down, that is not yet an on-screen map. The map runtime still has to load the map header, select the layout, decompress or copy graphics, instantiate object events, set camera position, and prepare script context. We studied this pattern back in the map weeks, with the recurring architecture of data table to runtime globals to callback or script to visible behavior. The save file stores the durable address of the place. It does not store every loaded tile buffer as a sacred object.

And that lets us see why persistence connects the entire course. FireRed is not a set of isolated modules. It is a set of modules that agree on what can be rebuilt. The overworld agrees that map identity and player coordinates are enough to reconstruct a playable scene. The script system agrees that flags and variables are enough to reconstruct narrative progress. The party and creature systems agree that creature records are enough for battle setup and summary screens. The inventory agrees that item pocket contents are enough for use dispatch and menu display. The battle system, in most ordinary saves, is not resumed mid-turn from battle-local execution state; battle setup later reads party, location, trainer flags, and encounter context to create a new battle when appropriate. The inclusion of load_save in pokefirered/src/battle_setup.c chunk_494d132a59422c85 is a useful reminder that battle entry is adjacent to persistence and overworld state, but it is not the same as saying every battle frame is persisted.

Let us run a concrete failure scenario. The player saves just after receiving a key item from an NPC. The game needs to persist at least two durable facts: the bag now contains the key item, and the event flag or variable says the NPC has already given it. If only the bag persists and the flag does not, the player may be able to receive duplicates. If only the flag persists and the bag does not, the player may be permanently locked out. So, where should the transaction boundary be? In a perfect world, those two facts become durable together. In a sector-based world, they may physically live in different sectors. That means your save-generation grouping becomes the logical transaction. You cannot guarantee that hardware writes are magical, but you can design the loader to accept only a complete coherent generation.

Check for understanding: why is “complete coherent generation” stronger than “all required sectors have valid checksums”? Because all required sectors might be valid but from different save attempts. Sector A from generation 42 and sector B from generation 41 may both pass their checksums. They are locally valid. But together they may describe a world that never existed. The save counter or generation index is what lets us avoid stitching together a false history.

Now, what about defaults? Defaults are still important, especially for new game initialization and version migration. But defaults belong at controlled boundaries. If no valid save exists, the game initializes a new game state. If a newer schema adds a field, migration may set a default for that new field while preserving the old coherent state. But defaulting should not be a silent substitute for a missing critical sector in an otherwise claimed saved game. In your lab, I would rather see you fail over to the previous coherent generation than invent half a story state from defaults.

Let me bring version metadata into this. Version is often taught as a single integer, and in small projects that is acceptable. But architecturally, version answers several different questions. What schema layout is this? Which game build wrote it? Which generation of save attempt is it? Which sector identity is this fragment claiming to be? Which checksum algorithm applies? In a polished design, you do not have to overbuild all of these, but you do have to know which question your metadata is answering. If you use one field named “version” to mean all of them, your loader will eventually become confused.

For the lab, a disciplined design could say: each sector has a sector ID, a schema version, a save generation counter, a payload size, and a checksum. The whole save set is considered loadable only when one valid sector for each required sector ID exists with the same generation counter and compatible schema version. Then, after selecting that set, the loader copies payloads into the appropriate save blocks or deserializes them into owned structures. Only after that does runtime initialization begin: map load from location, script engine from flags and variables, party validation from creature records, bag validation from pocket contents, options from player settings.

Now, I said “party validation,” so let us be careful. Validation does not mean recomputing the entire legality of every creature as if we were a competitive simulator. It means confirming that the data is structurally safe and semantically acceptable for this game. Species values must be within known bounds. Moves must be within known move IDs or empty. Current HP should not exceed max HP after stats are derived. The party count should not exceed the maximum. Held item IDs should be valid. You can repair some fields, reject others, and clamp still others, but you need a policy. Advanced architecture is mostly policy made explicit.

This is a good moment to distinguish FireRed from a modern server-authoritative creature game. In a server game, persistence may be normalized across database tables: player table, creature table, inventory rows, quest flags, audit logs. FireRed’s model is closer to a packed snapshot split across flash sectors. But the conceptual question is the same: which facts define the player’s world, and how do you prevent partial updates from fabricating impossible states? That is why this week is not merely retro hardware trivia. It is a general systems lesson.

Let us now connect back to the course objective that asks you to decompose a complete creature-collector RPG into runtime shell, overworld maps, event scripting, creature data, battle, inventory, UI, save, and link systems. Persistence is the moment when that decomposition either proves itself or collapses. If each system owns clear durable state, saving is a coordinated snapshot. If every system has leaked half of its internals into every other system, saving becomes guesswork. When you load, the runtime shell should not need to know the internal shape of every item behavior. The bag system should not need to know how the map renderer stores tile animation counters. The script system should not need to know the summary screen’s page cursor. Boundaries make restoration possible.

And yes, link systems complicate this. We are about to move more explicitly into link and system integration as week 20 turns toward week 21, so I will only foreshadow it here. Link features often introduce state that feels important because it is high stakes in the moment: trading, battling, records, wireless negotiation, peer identity, and so on. But much of link session state is transient protocol state. The durable results, such as a completed trade’s changed party or storage data, must be committed carefully. That is a familiar pattern now: do not persist the whole conversation; persist the agreed outcome, and only once the outcome is safe to treat as game history.

Let us do one more example with flags and variables, because this is where advanced students sometimes overcorrect. They say, “Flags and variables are just script implementation details, so maybe we should derive them from a quest log.” That is a reasonable instinct in a modern design, but in FireRed-style architecture, flags and variables are part of durable authored state. They are not merely cache. Map scripts, NPC dialogue, item gates, trainer rematches, object visibility, and progression checks may all consume them. If you do not persist them, the world forgets what it has said and done. So the distinction is not “script data is transient.” The distinction is that script execution stack and current text box animation may be transient, while script flags and variables are durable world memory.

I am going to ask you to answer this silently first, then I will answer it aloud. Suppose a player saves immediately after a cutscene moves an NPC out of the way. Should you persist the NPC’s exact x and y position? Think. The answer depends on the ownership model. If that NPC movement is a durable world change, FireRed-style games often represent the durable result with a flag or variable that causes the map to spawn or hide or place the object appropriately on future loads. You may not need to persist the object event’s current runtime movement step. If the movement is mid-animation and saving is not allowed there, the problem disappears. If saving is allowed during arbitrary object motion, then your schema must support suspended object state, which is a much larger promise. FireRed avoids many impossible persistence questions by controlling where saving is allowed and by separating durable event results from live animation.

That is an important design lever: restrict save points or save timing, and your persistence model becomes simpler. Allow saving at any frame, and your persistence model becomes a suspend system. Neither is automatically wrong. But they are different products. A FireRed-style save is not an emulator save state. It is a game-state save, and that difference is one of the central lessons of the week.

So, coming back to the four questions from the end of the previous segment, we can now refine them in lab terms. What storage evidence exists? That is your sector metadata, checksums, IDs, and generation counters. Which coherent generation should we trust? That is your grouping and recovery algorithm. How do we restore durable subsystem-owned state? That is your mapping from sector payloads into player, party, bag, flags, variables, and location ownership. How do the rest of the systems consume it without contradiction? That is your initialization sequence and your validation policy.

The research packet’s emphasis on pokefirered/src/load_save.c chunk_0755fbe98f753bb7 for entry flow, chunk_3efebaad2e0f31e0 for validation support, and chunk_d615017937c86ab6 for continuation is useful because it reminds us that load is not a single magic operation. It is staged. Entry, validation, selection, continuation, restoration. The save system chunks in pokefirered/src/save.c chunk_ab80cd8f4fa87d77 and chunk_852fbc242f85 likewise remind us to treat writing as a system with flow, not as one call to “dump state.” That is the mental model I want you to carry into your schemas.

Let me give you a compact pseudo-flow verbally, without turning this into a code lecture. On boot or load request, initialize the runtime enough to read flash safely. Scan the expected sectors. For each candidate sector, validate header fields and checksum. Group valid sectors by generation. Choose the newest generation that contains every required sector exactly once, or according to your duplicate policy. If no generation exists, initialize new game defaults. If one exists, restore save blocks into their owning structures. Then validate cross-block invariants: location references known map, party count matches records, bag item IDs are valid, flags and variables fit allocated ranges. Then start the appropriate callback path to enter the overworld or title continuation. Runtime tasks and UI screens are created fresh.

Now, a student might ask, “Should cross-block validation happen before or after copying into runtime memory?” Good question. In a robust engine, you often parse into a temporary candidate structure, validate, then commit into live global state. FireRed’s exact implementation is constrained by its codebase and hardware context, but as an architectural principle, separate candidate load state from committed runtime state when you can. That avoids a half-loaded game contaminating the running process. In your lab simulation, you can model this cleanly: read sectors into candidate blocks, validate the candidate save, then assign it to the active game state.

Here is your final check for this part. I will describe three fields, and you decide whether each is durable, transient, or conditional. First, the player’s trainer ID. Durable. It defines identity and ownership. Second, the fade-to-black animation counter while opening the menu. Transient. Rebuild or restart the presentation. Third, the fact that an NPC gift has already been claimed. Durable, but probably represented as an event flag rather than as “NPC object currently has no gift function.” See the pattern? We are not asking whether a value exists in memory. We are asking whether the game’s future meaning depends on remembering it across power loss.

We will continue from here by applying this directly to the workshop schema and walking through a simulated corrupt-save recovery. The key idea to hold onto is that saving is not the end of runtime architecture. It is the audit of runtime architecture. It exposes whether your systems have clean ownership, whether your world facts are represented deliberately, and whether your initialization path can rebuild a playable game from durable truth rather than from accidental memory.

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