# Data-Driven Does Not Mean Runtime JSON

Lecture ID: lecture_w02_d02
Week 2, Day 2

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

## Transcript
All right, let’s pick up from where we left off, not by restarting the architecture, but by tightening one distinction that will matter for the rest of the course. Last time, when we treated headers as architecture maps, the point was that a header is not just a bag of declarations. In a C codebase like FireRed, a header tells you which module is promising which vocabulary to the rest of the program. Today I want to attach that idea to data. The title is “Data-Driven Does Not Mean Runtime JSON,” and I want you to hear that as a warning against a very modern reflex.

If you have built web tools, backend services, mod loaders, or even modern game prototypes, you may hear “data-driven” and immediately picture JSON files being opened while the program runs. You imagine a runtime parser, maybe a schema validator, maybe some dynamic dispatch based on string keys. That is one possible design. It is not the design FireRed is using in the Game Boy Advance runtime. FireRed can absolutely be data-driven, and it often is, but the runtime usually wants arrays, structs, constants, bytecode, pointers, and tables already shaped into the form that the engine can consume cheaply.

So I am going to put two columns on the board. Left column: build time. Right column: runtime. Under build time I am writing JSON, PNG, MIDI, templates, generated headers, generated assembly includes, conversion tools. Under runtime I am writing C arrays, packed structs, global state variables, callbacks, task functions, script pointers, and constants. Now the central question for today is this: when we point to a piece of behavior in the game, which side of the board owns the flexibility, and which side owns the execution?

That is not a philosophical question. It is a source-navigation question.

Let’s start with the toolchain, because that is where the misunderstanding usually begins. The file pokefirered/build_tools.sh, chunk chunk_1a4a3316f8593aaf, is almost comically direct. It builds tools. It runs make in tools/aif2pcm, tools/bin2c, tools/gbafix, tools/gbagfx, tools/preproc, tools/ramscrgen, tools/rsfont, tools/scaninc, tools/mapjson, and tools/jsonproc. Notice what that list is telling us. The engine is surrounded by little compilers and converters. These are not gameplay systems in the runtime loop. They are part of the production pipeline that turns designer-friendly or asset-authoring-friendly forms into the kinds of objects the GBA program can link.

Now, pause there. If someone says, “FireRed has JSON in its repo, therefore FireRed is a JSON-driven runtime,” what would you say? You would say, no, that conclusion skips the pipeline. JSON may be an authoring format. It may be an input to generation. But the runtime artifact is something else.

The Makefile makes this distinction explicit. In pokefirered/Makefile, chunk chunk_30eb2e53e9597446, the build names tool executables like MAPJSON and JSONPROC, includes several rule files, and has a generated target wired through AUTO_GEN_TARGETS. The Makefile also forces tool generation and generated map-related sources before dependency scanning and compilation. That ordering matters. The source tree can contain JSON, but by the time the game image is linked, the program is not asking the cartridge, “Please parse this JSON object and discover my item schema.” It is using generated C or assembly artifacts that have already been made compatible with the rest of the engine.

Let me make that concrete. In pokefirered/json_data_rules.mk, which is part of the Makefile generation path represented in the packet around chunk chunk_30eb2e53e9597446, JSON files are run through jsonproc to produce output files based on templates. The rules generate files such as src/data/wild_encounters.h, src/data/items.h, src/data/heal_locations.h, and include/constants/heal_locations.h. That is a huge architectural clue. The JSON does not become a runtime document tree. It becomes headers or source fragments. The C compiler then sees those fragments. The linker places the resulting data into the ROM. The runtime code indexes it.

Now let’s look at jsonproc itself. In pokefirered/tools/jsonproc/jsonproc.cpp, chunk chunk_dd905c2bfaba76d6, the tool takes exactly three arguments: a JSON filepath, a template filepath, and an output filepath. It creates an Inja environment, adds callbacks like doNotModifyHeader, contains, subtract, setVar, getVar, concat, removePrefix, removeSuffix, isEmpty, isEmptyString, and cleanString, and then calls write_with_json_file. That is not a game engine subsystem. That is a source generator. The tool’s job is to translate authoring data into code-shaped text.

I want to stress that because it changes how you debug. Suppose an item’s generated constant is wrong. You do not start by putting a breakpoint in a hypothetical runtime JSON parser. There is no such central runtime parser in this path. You inspect the authoring JSON, the template, the jsonproc callbacks, the Makefile dependency rule, the generated header, and then the C module that includes it. That is a very different debugging route.

Check for understanding here. If the file src/data/items.json is changed, and the game’s Bag still behaves as if the old item data exists, what are the likely categories of failure? Think in terms of our two board columns. One category is build-time: the generated file was not regenerated, or the template did not emit what you expected, or the object file did not rebuild. Another category is runtime: the generated data is correct, but the UI or item-use code is indexing the wrong pocket, using a stale quantity, or applying a different constant. What is not the first assumption? The first assumption is not “the runtime JSON parser misread the object,” because that is not the runtime architecture we are seeing.

Now let’s cross from pipeline to gameplay systems. I want to use battle and bag today because they show two different faces of the same principle. Battle is deeply stateful and simulation-heavy. Bag is UI-facing and task/menu-heavy. Both are data-driven in places, but neither one exposes its architecture as “load arbitrary JSON and branch on strings.”

Open, mentally, pokefirered/include/battle.h, chunk chunk_4079f42fde2b4370. This header is dense because battle is a contract-heavy subsystem. Right near the top it defines battle actions: B_ACTION_USE_MOVE, B_ACTION_USE_ITEM, B_ACTION_SWITCH, B_ACTION_RUN, the Safari actions, B_ACTION_EXEC_SCRIPT, B_ACTION_TRY_FINISH, B_ACTION_FINISHED, and B_ACTION_NONE. Those constants are the legal vocabulary for what a battler can decide to do in a turn. That is data-driven in a very C-like way. Instead of string action names like “use_move” or “switch,” the engine uses small numeric constants and shared macros.

Then the same header defines move targeting constants: MOVE_TARGET_SELECTED, MOVE_TARGET_DEPENDS, MOVE_TARGET_USER_OR_SELECTED, MOVE_TARGET_RANDOM, MOVE_TARGET_BOTH, MOVE_TARGET_USER, MOVE_TARGET_FOES_AND_ALLY, and MOVE_TARGET_OPPONENTS_FIELD. Again, no runtime string dispatch. The target model is encoded in constants and consumed by battle functions that already know those constants.

A little lower in the same battle header, still pokefirered/include/battle.h, chunk chunk_4079f42fde2b4370, you see trainer party structs: TrainerMonNoItemDefaultMoves, TrainerMonItemDefaultMoves, TrainerMonNoItemCustomMoves, and TrainerMonItemCustomMoves. You also see the union TrainerMonPtr and struct Trainer, with fields like partyFlags, trainerClass, encounterMusic_gender, trainerPic, trainerName, items, doubleBattle, aiFlags, partySize, and party. This is an excellent example for today’s thesis. Trainer data is data. It is absolutely driving runtime behavior. But it is driving behavior as typed C data behind a known contract. The engine is not discovering that a trainer has a party by asking a JSON object whether it contains a property called party. The engine is reading a struct Trainer whose layout has already been agreed upon by the compiler, the data table, and the battle setup code.

Now connect that to pokefirered/src/battle_setup.c, chunk chunk_494d132a59422c85. The excerpt shows this module pulling in overworld, item, sound, pokemon, load_save, safari_zone, quest_log, script, script_pokemon_util, strings, event_data, event_object_movement, metatile_behavior, event_scripts, field systems, battle.h, battle_transition.h, battle_controllers.h, and constants for battle setup, event objects, items, maps, songs, pokemon, and trainers. That include list is not noise. It is a map of the crossing from overworld into battle. Battle setup is where a trainer object, a script pointer, a map context, a transition type, and battle flags have to become an initialized battle session.

And battle_setup.c also declares state variables like sTrainerBattleMode, gTrainerBattleOpponent_A, sTrainerObjectEventLocalId, sTrainerAIntroSpeech, sTrainerADefeatSpeech, sTrainerVictorySpeech, sTrainerCannotBattleSpeech, sTrainerBattleEndScript, sTrainerABattleScriptRetAddr, and sRivalBattleFlags. It defines transition tables for wild and trainer battles. So here is the pattern: data tables define possible transitions and trainer configurations; state variables remember the particular battle being staged right now; functions such as DoSafariBattle, DoGhostBattle, DoStandardWildBattle, CB2_EndWildBattle, GetWildBattleTransition, GetTrainerBattleTransition, and CB2_EndTrainerBattle mediate the lifecycle. That is a runtime system. It is data-driven, but it is not dynamically schema-driven.

Let me say that in a more general form. A data-driven C game often means the code is written to interpret compact, precompiled data structures. It does not necessarily mean the code is written to interpret self-describing text. Those are different engineering choices. FireRed’s choice is heavily shaped by the platform. The GBA has limited CPU, limited RAM, and a cartridge ROM model where fixed tables are cheap to read compared with general-purpose parsing.

Now look at the battle runtime state in that same battle header. We have extern declarations for gActiveBattler, gBattlerTarget, gAbsentBattlerFlags, gBattleMons, gBattleResources, gBattleResults, gBattleStruct, gBattleSpritesDataPtr, and several arrays like gDisableStructs, gProtectStructs, gSpecialStatuses, and gSideTimers. These are not just variables. They are the living state of the battle machine. gBattleMons holds the active battle-ready version of the creatures. gDisableStructs carries temporary constraints like disabled moves, encore, substitute HP, taunt timers, perish song timers, rollout timers, and mimicked move bits. gProtectStructs records whether the turn produced protection, endurance, flinching, target-not-affected outcomes, and damage attribution. gBattleStruct is a large per-battle scratch and coordination object with fields for chosen move positions, battler party indexes, run tries, Safari counters, switch-in abilities, selected items, last taken moves, saved callbacks, and more.

Notice the layering. Species, moves, trainers, items, and maps can originate in tables or generated data. But during play, the battle is not repeatedly querying “what does this JSON say about the current battler?” It has normalized the current situation into gBattleMons and companion state structures. That is the runtime representation.

Let’s do a concrete example. A trainer battle begins because an overworld event or script says this trainer should fight. battle_setup.c, chunk chunk_494d132a59422c85, holds the staging state: which trainer opponent, what local object event id, what intro and defeat text, what script return address, what transition. The trainer’s party definition is available through the trainer data contract described by struct Trainer in include/battle.h, chunk chunk_4079f42fde2b4370. Once battle begins, the active monsters are represented as BattlePokemon records in gBattleMons, while current control decisions are represented through constants such as B_ACTION_USE_MOVE or B_ACTION_SWITCH. When a move is selected, the target vocabulary comes from the MOVE_TARGET constants. During execution, temporary turn effects accumulate in gDisableStructs, gProtectStructs, gSpecialStatuses, gSideTimers, gWishFutureKnock, and gBattleStruct. When the battle ends, result data is captured in gBattleResults and the callback path returns the game to the appropriate overworld or script continuation.

That whole chain is data-driven. The specific trainer, party, moves, text, transition, and outcome are all represented as data. But the runtime does not need a generic parser to understand “trainer battle.” It has a compiled architecture for trainer battle.

Let’s contrast that with the Bag header, because it is much smaller and very revealing. In pokefirered/include/bag.h, chunk chunk_8f2c0bdfc3224a33, the exposed contract is a UI interface: InitBagWindows, BagPrintTextOnWindow, BagPrintTextOnWin1CenteredColor0, BagDrawDepositItemTextBox, ShowBagWindow, HideBagWindow, OpenBagWindow, CloseBagWindow, GetBagWindow, BagCreateYesNoMenuBottomRight, BagCreateYesNoMenuTopRight, BagPrintMoneyAmount, and BagDrawTextBoxOnWindow. The Bag system’s public face here is not “parse item database.” It is “manage windows, text, yes/no menus, money display, and text boxes.” That tells us the Bag module is downstream of item data. By the time you are in this interface, the player is interacting with a UI over already-structured inventory state. The data tables for items may be generated from JSON at build time, as the json_data_rules path shows for src/data/items.h, but the Bag UI contract is expressed as functions over windows, task ids, strings, and menu helper tables.

So if I ask you, “Where is item flexibility located?” the answer is split. The authoring flexibility may be in src/data/items.json and its template path. The generated C header becomes the stable compiled data. The runtime Bag behavior sits in C functions and task/menu state. If you change an item name, that is likely data. If you change how the Bag draws the yes/no confirmation, that is UI code behind bag.h’s contract. If you add a new item pocket, you may cross both boundaries: generated item constants and runtime UI behavior.

Let me bring in one secondary comparison, briefly, because it helps you avoid overgeneralizing from modern projects. In Pokemon Showdown, the data files can include rich JavaScript or TypeScript objects with behavior-like callbacks. For example, pokemon-showdown/data/abilities.ts, chunk chunk_d5bdb17c16d6f1a4, contains ability entries such as Stance Change with an onModifyMove hook that can inspect the attacker, move category, and forme-change conditions. That is a server-side simulation architecture with a very different runtime environment. FireRed is not doing that kind of late-bound TypeScript object interpretation. FireRed’s battle system has function tables, scripts, constants, and global state, but the platform and codebase push toward compiled representations. The comparison is useful only if it sharpens the distinction; it should not replace FireRed as our authority.

Now, check for understanding again. I am going to describe a bug, and I want you to locate it on the board. The player opens the Bag. The item list appears correctly. The money amount prints correctly. But when choosing an item that should initiate a battle-related effect, the confirmation menu appears in the wrong position. Is that a generated-data problem? Probably not. Given pokefirered/include/bag.h, chunk chunk_8f2c0bdfc3224a33, you would inspect BagCreateYesNoMenuBottomRight versus BagCreateYesNoMenuTopRight, window setup, and the caller’s task state. Now a different bug: the same item has the wrong price or wrong pocket after a clean build. That points you toward item data generation, the items JSON and template rules represented by pokefirered/json_data_rules.mk under the Makefile generation path, chunk chunk_30eb2e53e9597446, and then the item code that consumes the generated table. Same feature area, different architectural layer.

This is why week 2 sits where it does in the course. Week 1 gave us the runtime shell: AgbMain establishes the program, the main callback slot lets the game swap top-level modes, tasks are lightweight per-frame state machines, and visible behavior can be traced from data to callback to task to screen. We are not re-teaching that today; we are using it. Week 2 adds the source-contract and pipeline view. Headers tell us what a module promises. Build rules tell us which artifacts are authored and which are generated. Tools tell us how human-friendly forms become ROM-friendly forms. Together, those let us trace a system without guessing.

And this matters for every major system in the creature RPG architecture. Overworld maps use JSON authoring files and mapjson-generated includes, as shown by the map generation rules in the Makefile family. Battle uses battle.h contracts, battle_setup.c staging, battle constants, trainer structs, global battle state, and battle callbacks. Inventory uses generated item data plus Bag UI contracts like those in bag.h. Creature data flows through species, move, party, and battle representations, and we see dependent modules such as pokefirered/src/pokemon_summary_screen.c, chunk chunk_e8e17a42bbf17d46, including pokemon.h, data.h, item constants, move constants, battle_main.h, party_menu.h, and region_map systems because the summary screen is a display surface over creature data, move data, items, and progression context. Daycare, in pokefirered/src/daycare.c, chunk chunk_119931105f546346, similarly includes data.h, battle.h, item constants, move constants, pokemon storage, event data, scripts, party menu, list menu, overworld, pokedex, naming screen, help system, and trade, because breeding and daycare behavior is not isolated from the creature model or progression systems.

So the architecture lesson is not “everything is generated,” and it is not “everything is hardcoded.” It is more precise than that. FireRed uses build-time generation where authored data benefits from validation, templating, and conversion. It uses headers to make the generated and handwritten parts meet at stable C contracts. It uses runtime globals, callbacks, tasks, scripts, and structs to execute behavior efficiently on the GBA. Data-driven means the behavior is parameterized by tables and records. It does not require the runtime to carry the authoring format around with it.

Before we move deeper, I want you to write one sentence in your notes, because it will save you a lot of confusion in the lab. Write: “The runtime consumes the generated contract, not necessarily the authoring file.” That sentence is the bridge into the workshop where you will build a mini JSON-to-C table generator for creature species records. The important part of that lab is not merely producing a header. The important part is documenting which part runs before compilation, which part is compiled into the ROM, and which part is read during play. When you can label those boundaries cleanly, you are no longer treating “data-driven” as a vague aesthetic. You are reading it as architecture.

Now, with that distinction established, we can spend the next segment walking one full generated-data path slowly: from an authoring record, through jsonproc or a similar generator, into a C table, across a header contract, and finally into a runtime system that never needed to know that JSON existed.

So let us walk that path with a deliberately small mental model first, and then we will map it back onto FireRed.

Imagine an authoring file that says, in a comfortable designer-facing shape, here is a species called Embercalf, here are its base stats, here is its type, here is the growth curve, here are the moves it can learn by level. In a modern web application, you might be tempted to leave that as JSON, ship the JSON, parse it at runtime, keep a dictionary in memory, and then have your game ask that dictionary for Embercalf’s attack stat. That is a perfectly ordinary architecture on a PC or server. But in the FireRed style, and especially on the Game Boy Advance, that is not the usual boundary. The JSON is an authoring convenience. The runtime wants compact, addressable, statically compiled tables.

Now, board cue here: I’m going to draw four boxes from left to right. First box: authoring data. Second box: generator. Third box: C or assembly output. Fourth box: runtime table access. The mistake I want you to avoid is drawing the arrow directly from authoring JSON to runtime behavior. That arrow is psychologically tempting, but architecturally false for this style of project. The runtime behavior is downstream of compiled artifacts.

In the FireRed source focus for today, the clearest build-time example is not a battle function. It is the toolchain. In pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, jsonproc takes exactly three arguments: a JSON file path, a template file path, and an output file path. That signature matters. It tells us the tool is not a library linked into the game. It is a command-line build tool. It reads structured authoring input, applies an Inja template, and writes a source artifact. The generated artifact is then part of the build product. The GBA runtime does not call jsonproc. The runtime does not carry nlohmann JSON. It carries the consequences.

And this is one of those places where advanced students should pause and ask, what is the real interface? Is it the JSON schema? Is it the template? Is it the generated C file? Is it the header included by other modules? The answer is layered. The JSON schema is an authoring contract between content and generation. The template is a transformation contract. The generated C or include file is a compilation contract. The header is the cross-module runtime contract. And the table itself, once compiled, is the memory contract.

Let’s make that less abstract. Suppose our workshop generator reads a JSON creature record and emits a C table named gSpeciesInfo. A runtime function might later do something like, give me species number 25, and then index gSpeciesInfo[25].baseAttack. That function is data-driven because species 25’s base attack is not hardcoded inside the function. But it is not runtime-JSON-driven. The function is reading a statically compiled array whose layout is known to C.

That is exactly the kind of distinction we see repeatedly in FireRed. In pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the summary screen code reaches into tables such as gSpeciesInfo and gExperienceTables, and also reads move metadata through gBattleMoves. When the summary screen displays a creature’s growth information or move power, it is not parsing a designer document. It is consuming runtime tables through C structs, constants, and globals. The player sees a nice summary page. Underneath, the path is table lookup, buffer formatting, window printing, callback-driven UI.

Now, check for understanding. If I change a species’ base experience in an authoring JSON file, when does the GBA game learn about that change? Not when the player opens the summary screen. Not when the ROM boots. The game learns about it when the build pipeline regenerates the source artifact and the ROM is rebuilt. At runtime, there is only the compiled result. If your answer included “the JSON parser loads it,” you are describing a different architecture.

Let’s connect this to the Makefile, because the Makefile is not just build plumbing. It is architecture in executable form. In pokefirered/Makefile, chunk_30eb2e53e9597446, the tool variables include MAPJSON and JSONPROC alongside graphics, audio, preprocessing, scaninc, and fix tools. The build starts by ensuring tools exist, and it can invoke generation before dependency scanning. That tells us the project treats generated data as a prerequisite for compiling the ROM. Again, not a runtime subsystem. A build subsystem.

And the companion script, pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, reinforces the same idea by building tools like bin2c, gbagfx, mapjson, and jsonproc. Notice the company jsonproc keeps. It is next to graphics conversion, map conversion, font processing, and binary embedding utilities. That placement is pedagogically useful. It prevents us from thinking of JSON as special magic. JSON is just one authoring format among several. PNGs, MIDI files, map JSON, raw binary blobs, font inputs, and table descriptions can all pass through build-time tools and become ROM-compatible data.

Let me give you a concrete example using the major systems we are tracking in this course. The battle system’s runtime contract is heavily exposed in pokefirered/include/battle.h, chunk_4079f42fde2b4370. That header declares battle action constants such as B_ACTION_USE_MOVE, B_ACTION_USE_ITEM, B_ACTION_SWITCH, and B_ACTION_RUN. It declares trainer party structs such as TrainerMonNoItemDefaultMoves, TrainerMonItemCustomMoves, and the Trainer struct. It declares runtime globals like gBattleMons, gBattleResources, gBattleStruct, gBattleTypeFlags, gCurrentMove, gChosenMove, gBattleMoveDamage, gBattlerAttacker, gBattlerTarget, and gChosenActionByBattler.

Now ask: where is JSON in that header? It is absent. The battle engine’s public contract is not “give me a JSON trainer.” It is “here are the C structs, constants, arrays, and globals that battle modules agree to use.” If trainer data originated in an authoring file, the battle engine’s direct dependency is still the generated C shape or compiled data table, not the authoring document.

This is why headers are so important in week 2. Last time, we treated headers as architecture maps. Today we sharpen that: a header often shows the runtime boundary after generation has already happened. The header does not need to reveal whether the data came from hand-written C, generated C, assembly includes, binary conversion, or some mixture. It says, at runtime and link time, these names and layouts exist.

Let’s put battle.h beside bag.h for contrast. pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, is much smaller. It declares UI-facing functions such as InitBagWindows, BagPrintTextOnWindow, ShowBagWindow, HideBagWindow, OpenBagWindow, CloseBagWindow, BagCreateYesNoMenuBottomRight, BagPrintMoneyAmount, and BagDrawTextBoxOnWindow. That header is not trying to expose the entire item database. It exposes operations that other modules need in order to use the bag interface. So battle.h is broad because battle is a dense shared simulation contract. Bag.h is narrow because it is largely a UI service boundary. Both are headers. Both are contracts. But their shape tells you the dependency pressure of the system.

Now imagine we add generated item descriptions. Where should the generated table be visible? Maybe item code needs it. Maybe bag UI needs formatted item names or pocket metadata. But bag.h should not automatically become a dumping ground for every generated item artifact. If only one module consumes a generated table, keep it local. If many modules need the table, expose it through the appropriate header. This is the design discipline behind source contracts.

Let’s return to the four boxes on the board. Authoring data, generator, generated C, runtime access. I’ll add a fifth box below the last one: visible behavior. For a battle move, visible behavior could be damage, animation, targeting, text. For a bag item, visible behavior could be pocket placement, use restrictions, price, and menu text. For a species, visible behavior could be the stats shown on the summary screen, the experience curve, learnable moves, evolution, or battle typing.

The advanced move is to trace backward from visible behavior to the table and then to the build process. If the summary screen shows the wrong move power, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, suggests one route: the screen reads gBattleMoves for move type, PP, power, and accuracy. So you ask, is gBattleMoves wrong? Is the move id wrong in the creature’s move slots? Is the UI formatting wrong? Is the generated move data stale? Each question points to a different layer. A beginner says, “The summary screen is bugged.” An advanced engineer says, “Which contract is failing?”

Same with learn moves. In pokefirered/src/learn_move.c, chunk_af3598df6f092723, the move relearner menu uses task and callback flow, but when it presents move information it reads gBattleMoves for power, accuracy, PP, and type. So the menu state machine is runtime behavior, but the move metadata it displays is table-driven. If we changed the authoring source for a move’s PP but did not regenerate and rebuild, the move relearner would still show the old compiled value. That is not a UI mystery. That is a pipeline boundary.

Let’s do a quick check. I’ll say a system, and you identify the likely runtime contract. Battle turn choice. You should be thinking B_ACTION constants and gChosenActionByBattler from battle.h. Trainer party. You should be thinking Trainer, TrainerMon variants, partyFlags, partySize, and gTrainers from battle.h. Bag windows. You should be thinking InitBagWindows, ShowBagWindow, OpenBagWindow, and the bag UI function contract from bag.h. Species stat display. You should be thinking gSpeciesInfo consumed by summary or Pokemon logic, not JSON at runtime. Map source generation. You should be thinking MAPJSON in the Makefile and generated map-related sources, not a map parser inside the overworld loop.

Now, why does this matter for the overall course plan? Because every major system we study will have this split between authoring convenience, compiled representation, and runtime state. The overworld has maps and events. Battle has moves, trainer parties, scripts, battlers, and turn state. Inventory has item tables and bag UI state. Save has persistent serialized state. Link play has communication buffers and protocol contracts. UI has tiles, tilemaps, palettes, window templates, sprites, and callbacks. If you blur build-time data and runtime state, you will misread all of those systems.

The battle setup file shows the cross-system nature of this. pokefirered/src/battle_setup.c, chunk_494d132a59422c85, includes overworld, item, pokemon, load_save, safari_zone, quest_log, script, event data, field effects, battle, battle transition, and more. That include list is itself a map of a boundary-crossing operation: entering battle is not just a battle-engine concern. It coordinates field state, scripts, player context, transition effects, trainer identity, party data, and outcome handling. But even there, the system is not saying, “Let us parse all content dynamically.” It is composing existing runtime contracts.

This is where FireRed differs from a fully data-interpreted engine. It is data-driven, yes, but much of the data is driven through compiled arrays, constants, and specialized bytecode-like scripts. The flexibility is real, but it is constrained by the generated and compiled shapes. That constraint is not a weakness. On a fixed platform, it is the source of reliability, speed, and memory predictability.

Let me say this as a rule you can use in your lab writeup: data-driven means behavior is parameterized by data; it does not specify when or how that data is loaded. Runtime JSON is one implementation choice. FireRed’s pattern is generally build-time transformation into runtime-native representations.

Now, I want you to notice a subtle thing in jsonproc.cpp, chunk_dd905c2bfaba76d6. The tool adds template callbacks like doNotModifyHeader, contains, subtract, setVar, concat, removePrefix, removeSuffix, isEmpty, isEmptyString, and cleanString. That is not gameplay logic. That is source-generation assistance. cleanString, for example, exists so an authoring value can become a valid generated identifier-like string. That is a build-time naming problem. If you find yourself wanting that sort of string cleanup during battle damage calculation, something has gone seriously sideways. Battle damage should already have numeric ids, enum values, flags, and table entries.

A concrete workshop analogy: your mini generator might read this conceptual record: species name, numeric id, base HP, base attack, type one, type two, growth rate. The generator should validate or normalize names, maybe emit a do-not-edit banner, and produce a C initializer. Your C header might declare struct MiniSpeciesInfo and extern const struct MiniSpeciesInfo gMiniSpeciesInfo[]. Your runtime demo might ask for a species id and print or calculate with the table. In your documentation, you should label name cleanup and template rendering as build-time. You should label the compiled array as ROM data. You should label indexing into the array as runtime.

Let me ask a comprehension question in a slightly adversarial way. Suppose a student says, “I made the game data-driven because I added a JSON file.” Is that sufficient? No. The JSON file by itself proves almost nothing about runtime architecture. If the JSON is never connected to the build, it does nothing. If it is parsed at runtime, that is one architecture. If it generates C, that is another. If it generates assembly includes, that is another. If it generates documentation only, that is another. The architectural claim depends on the path from authoring source to behavior.

And suppose another student says, “This is not data-driven because the final data is C.” Also no. That is the opposite mistake. C source can absolutely contain data tables. A const struct array is data. A move table is data. A trainer table is data. The fact that the compiler sees it does not make it less data-driven. The important question is whether behavior is parameterized by records rather than scattered hardcoded special cases.

Now, special cases still exist. FireRed absolutely has code paths for specific modes, battle types, scripts, menus, and edge conditions. This is not a pure data engine. But the high-level architecture relies on a repeated compromise: put high-volume content into tables and generated assets; put state transitions and hardware orchestration into C; use headers to make those pieces meet.

Daycare is a nice example of that mixed reality. pokefirered/src/daycare.c, chunk_119931105f546346, includes data.h, battle.h, item constants, mail data, Pokemon storage, event data, random, moves constants, menu, script, strings, party menu, list menu, overworld, Pokedex, decompression, graphics, naming screen, help system, field transitions, and trade. That file is a feature system. It is not only one table and not only one callback. It draws on creature data, UI, scripts, items, storage, and progression state. When you inspect a system like daycare later in the course, you should ask: which values are persistent save data, which are generated constants, which are runtime UI state, which are script-facing variables, and which are temporary task fields?

That question scales. For battle, battle.h exposes gDisableStructs, gProtectStructs, gSpecialStatuses, gSideTimers, gWishFutureKnock, gBattleResources, gBattleStruct, gBattleScripting, and many battler-indexed arrays. Those are runtime state variables. They change during play. They are not generated records. But they often refer to generated or compiled data by id: move ids, item ids, species ids, ability ids, trainer ids. So the runtime state is dynamic, while the content tables are relatively static. The engine lives at the join.

Let me put that in plain language. A battler’s current HP is state. A move’s base power is data. A chosen move id is state pointing at data. A battle action constant is a contract for interpreting state. A generated table initializer is a build artifact. A JSON species record is authoring input. If you can classify those five things correctly, you can read a large fraction of FireRed without getting lost.

We should also compare briefly with Pokemon Showdown, because our research packet includes it as a secondary comparison, not the primary authority. In pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, ability records can include executable callbacks such as onDamagingHit or onModifyMove. That is a different data model: TypeScript data files can mix declarative fields with functions. In pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, damage calculation logic works in a much more dynamic host environment. Showdown is designed for a server-side simulator with a modern language runtime and testability constraints. FireRed’s GBA architecture is more static, more memory-bound, and more build-pipeline-heavy. The comparison is useful precisely because it prevents us from pretending all creature RPGs express data in the same way.

But do not let the comparison distract you. For this course, FireRed is the anchor. When in doubt, trace source paths like pokefirered/include/battle.h, pokefirered/include/bag.h, pokefirered/Makefile, pokefirered/build_tools.sh, and pokefirered/tools/jsonproc/jsonproc.cpp. Then ask what layer you are standing in.

Let’s do one more concrete trace before we pause. Player opens a Pokemon summary screen. The runtime enters the summary UI through functions in pokemon_summary_screen.c. That file allocates summary screen data, saves a callback, sets up backgrounds and windows, creates tasks for input handling, and eventually uses RunTasks as part of the screen loop, which connects back to our week 1 callback-and-task foundation without rehashing it. When the page needs species or experience information, it looks at the current Pokemon struct and table data like gSpeciesInfo and gExperienceTables. When it needs move display values, it looks at gBattleMoves. The visible behavior is a formatted UI. The runtime mechanism is callback plus task plus table lookup. The content source may have passed through generated data rules earlier. The player never sees that boundary, but you, as engine readers, must.

Now, if the displayed move type is wrong, possible causes include the wrong move id stored on the Pokemon, incorrect gBattleMoves table data, wrong type constants, or a UI mapping issue when blitting the icon. Those are not interchangeable. Debugging architecture means narrowing the layer before changing code. That is why this week’s homework and quiz are going to be more demanding than week 1. Week 1 asked you to decompose systems and trace visible behavior through callbacks and tasks. Week 2 asks you to add the build boundary. You must now say not only “this function reads that table,” but also “this table is hand-authored or generated, this header exposes it, this build rule produces it, and this runtime module consumes it.”

For the lab, I do not want a generator that merely works by accident. I want a small report that says, before compilation, my JSON-to-C tool reads the authoring species records and emits a C table. During compilation, the table and header become part of the program image. During runtime, the game indexes the compiled table and never opens the JSON file. That three-sentence distinction is the whole point.

And one final check before we move into the next segment: if I ask, “Where would you look to understand battle action choices?” you should not begin in jsonproc. You begin in pokefirered/include/battle.h, chunk_4079f42fde2b4370, because B_ACTION_USE_MOVE, B_ACTION_USE_ITEM, B_ACTION_SWITCH, and their associated chosen-action globals are runtime battle contracts. If I ask, “Where would you look to understand how JSON becomes generated source?” you begin in pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, and the generated-data rules in pokefirered/Makefile, chunk_30eb2e53e9597446. If I ask, “Where would you look to understand bag window operations?” you begin in pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33. Different question, different layer, different source file.

Hold onto that separation, because next we are going to take the same generated-data idea and apply it to assets that are not obviously “records”: binary blobs, tilemaps, graphics, and the bin2c style of embedding. That will make the pattern even clearer: FireRed’s runtime is surrounded by build-time machinery, but it is not carrying that machinery around while the player walks through Kanto.

So, put this on the board: build-time asset conversion is still data-driven design. It is just not record-driven in the same shape as a species table or an item table. A PNG, a tilemap, a compressed blob, or a binary palette is still data. The important architectural question is, “When does this data become something the game can address with a C symbol?” In FireRed, the answer is usually before the cartridge image is produced, not while the game is running.

Now, let me slow that down, because this is where people often compress three separate ideas into one vague phrase. “The game uses data.” True. “The game has JSON files somewhere in the source tree.” Also true in some parts of the project. “Therefore the Game Boy Advance executable parses JSON at runtime.” False. That last inference is exactly the mistake this lecture is designed to remove.

When we looked at pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, the important point was not merely, “Here is a JSON parser.” The point was, “Here is a host-side tool.” It runs on your development machine as part of the build pipeline. It consumes structured source material and templates, and it emits C or assembly-like output that becomes part of the compiled program. Likewise, the generated-data rules in pokefirered/Makefile, chunk_30eb2e53e9597446, are not runtime gameplay code. They are instructions for producing runtime-addressable artifacts.

So if I draw a vertical line on the board, left side build machine, right side GBA runtime, jsonproc belongs on the left. The C arrays, constants, included headers, and compiled objects belong on the right after the build has finished. That line is not decorative. It is one of the key architecture boundaries in this codebase.

Now, assets make this easier to see because nobody reasonably expects the GBA to run a modern image conversion tool every frame. The source tree may contain graphics in a format convenient for artists and hackers. The build system transforms those graphics into something the game can store, decompress, copy into VRAM, index by tile number, or attach to an object template. The same pattern applies to binary embedding tools like bin2c. The job is to take bytes that live outside the C language and present them to the compiled program as ordinary addressable symbols.

Here is the phrase I want you to remember: generated data is a compilation strategy, not a runtime service.

Let’s make that concrete. Suppose we are adding a new creature icon, or a new small UI graphic for a bag pocket. There are at least three conceptual stages. First, there is source material: maybe a PNG, maybe a binary blob, maybe a JSON record describing how something should be arranged. Second, there is build-time conversion: tools listed and prepared by pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, plus build rules in pokefirered/Makefile, chunk_30eb2e53e9597446, transform the source material. Third, there is runtime use: C code refers to symbols, tables, constants, windows, callbacks, and tasks. The runtime does not ask, “Where is the JSON parser?” It asks, “What pointer, index, palette tag, window id, task id, or battle action value am I using right now?”

Check for understanding here. If the player opens the bag and the bag draws item names and windows, which side of the board is jsonproc on? Left side, build machine. Which side is pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, when it declares the bag interface the UI code relies on? Right side as a runtime contract, though of course the header itself is seen by the compiler during building. And that last qualification matters. Headers straddle the process in a different way: they are compile-time text, but they define the shape of runtime linkage. They are not runtime parsers either.

This is why I keep asking you to name the layer before naming the file. If the question is “How do bag operations communicate across modules?” then pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, is an appropriate first stop. If the question is “How do battle action choices get represented?” then pokefirered/include/battle.h, chunk_4079f42fde2b4370, is the appropriate first stop because constants such as B_ACTION_USE_MOVE, B_ACTION_USE_ITEM, and B_ACTION_SWITCH are part of the battle control contract. But if the question is “How does structured build input become source output?” then the trail points back to pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, and pokefirered/Makefile, chunk_30eb2e53e9597446.

Now, let’s connect this to the bigger creature RPG decomposition from week 1, without reteaching that decomposition. We said the game is not one monolithic loop. It is a shell that hands control to systems: overworld, scripts, battle, inventory, party, save, link, menus, and so on. Today’s refinement is that each of those systems has a different relationship to data.

The battle system has constants, globals, chosen-action arrays, battler identifiers, move effects, and stateful turn execution. Its public contract is visible in pokefirered/include/battle.h, chunk_4079f42fde2b4370. The bag system has UI entry points, pocket logic, item display behavior, and menu callbacks; its cross-module surface is visible in pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33. The transition into battle reaches across overworld, script state, trainer data, battle setup, and visual transition logic. You can see that breadth just from the include structure in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, where battle setup is not isolated from overworld, event data, scripts, field effects, and battle transition concerns. That include list is an architectural X-ray. It tells you this file is a bridge, not a pure calculation module.

Now, compare that to generated data. Generated data is not usually a bridge in the same sense. It is more like prepared terrain. By the time runtime code arrives, the expensive interpretation has already happened. Tables are shaped. Symbols exist. Indices are stable. Constants match. The runtime can be small, predictable, and fast.

That constraint is not nostalgia. It is architecture under hardware pressure. The GBA gives you limited CPU, limited memory, specialized graphics memory, and a cartridge-oriented execution model. Even if you could wedge a general JSON parser into the game, you would have to ask why. What benefit would you get at runtime that you could not get with generated C tables? Would players edit the JSON on cartridge while walking around? No. Would the game need schema validation during battle? No. Would the UI benefit from allocating parser structures while opening the bag? Definitely not. So the rational design is to move flexible authoring upstream and keep runtime representation compact.

Let me give you a more advanced framing. Data-driven design has two halves: authoring flexibility and execution discipline. The authoring side wants readable source inputs, named records, convenient templates, and repeatable generation. The execution side wants fixed layouts, stable offsets, simple pointers, and no surprise allocation. FireRed leans hard into that split.

If you have used modern engines, you may be used to runtime asset databases, reflection systems, hot reload, and scriptable data objects. Those systems are powerful, but they live in a different constraint environment. FireRed’s codebase shows an older and very disciplined version of the same architectural desire: make designers and maintainers work with structured data, but compile that structure down before it reaches the device.

Now, I want to bring in a comparison from the research packet because it helps sharpen the distinction. Pokemon Showdown’s data files, such as pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, represent abilities with executable callbacks and structured JavaScript or TypeScript objects. That is a server-side simulator model. It can express behavior like “on damaging hit, boost defense” directly in a data-adjacent callback. Similarly, pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, shows damage logic living in a high-level runtime where moves can be objects, strings, or numbers before being normalized through simulator logic. That is useful for a battle simulator with a modern runtime. But it is not the architectural model FireRed uses on the GBA.

So when we compare FireRed with Showdown, do not flatten them into “both are data-driven Pokemon engines.” That statement is too coarse for this course. Showdown often keeps rich dynamic objects alive at runtime. FireRed tends to compile, encode, index, and dispatch through C constants, arrays, state variables, callbacks, and tasks. Both are data-driven in useful ways. They just place the boundary between data authoring and runtime interpretation in different locations.

Let’s do a quick board trace. I’ll write four boxes: source asset, build tool, generated symbol, runtime system. Now fill those boxes for a hypothetical species record generator from your lab. Source asset: a JSON file with species name, base stats, type ids, catch rate, and learnset references. Build tool: your mini generator, analogous in spirit to jsonproc from pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6. Generated symbol: a C table, maybe an array of struct species records. Runtime system: party creation, summary display, battle stat calculation, Pokédex display, and so on.

Now, what must not be in the runtime box? The JSON schema. The parser. The command-line options. The template-expansion rules. Error messages about malformed authoring data. Those belong to the build side. What belongs in the runtime box? The final struct layout, indices, constants, and functions that consume them.

Here is the check. Imagine a student says, “I made it more data-driven by loading JSON whenever a wild encounter starts.” What is your response? Not, “JSON is bad.” That is too simplistic. Your response is, “What runtime problem are you solving, and why must that work happen during wild encounter setup?” In FireRed terms, wild battle setup already sits at a busy boundary. The source excerpt for pokefirered/src/battle_setup.c, chunk_494d132a59422c85, shows it coordinating with overworld state, item logic, scripts, event data, field control, metatile behavior, field effects, battle transition, and battle configuration. Adding a parser there would increase latency, memory pressure, and failure modes at exactly the point where the engine should be moving from overworld state into battle state predictably.

That is the advanced-level answer. You are not rejecting data-driven design. You are protecting the runtime boundary.

Let’s also tie this to headers again, but briefly. A generated C table is only useful if the rest of the program knows how to talk about it. That usually means a struct definition, an extern declaration, an enum, a constant range, or an include path. Headers are where those agreements become visible. That is why week 2 pairs “headers as architecture maps” with “generated data versus runtime JSON.” These are not separate topics. A generated source file without a stable header contract is just an isolated artifact. A header contract without data behind it is just a promise. The architecture emerges when the generated data, hand-written code, and headers line up.

For example, in battle, the action constants in pokefirered/include/battle.h, chunk_4079f42fde2b4370, do not by themselves execute a turn. But they define the vocabulary that turn-selection code, AI code, command handling, item use, switching, and battle controllers can share. If some generated table eventually points to legal actions, or if UI code presents action choices, those choices must collapse into the same runtime vocabulary. The constants are the narrow waist.

In the bag, pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, plays a similar role for inventory UI operations. The actual item records, graphics, pocket organization, and callbacks may live across multiple files, but the header tells outside modules what they are allowed to call or depend on. That is why, when you document your lab generator, I do not want only a paragraph saying “this creates C.” I want you to name the generated table, the struct that describes one row, the header that exposes it, and the runtime functions that consume it.

Now, let’s speak about state variables, because generated data is inert until state selects from it. This is another place advanced students can be too vague. You might say, “The game uses a table of moves.” Fine, but which battler chose which move? Which action slot is being resolved? Which controller owns input? Which task is active? Which callback is currently driving the screen? Data tables do not replace state. They constrain and inform state transitions.

In the battle system, action constants such as B_ACTION_USE_MOVE or B_ACTION_SWITCH are meaningful because chosen-action state records what each battler intends to do. In inventory, a bag pocket table is meaningful because the current selection, scroll position, registered item state, and menu task determine what the player sees and what command fires. In overworld-to-battle transition code, the existence of trainer or wild encounter data is meaningful because script variables, event flags, map state, and battle setup functions decide when that data becomes active. You saw the include breadth of pokefirered/src/battle_setup.c, chunk_494d132a59422c85; now interpret it as state convergence.

Let’s take a concrete example from a player’s perspective. The player steps in grass. The visible behavior is a wild encounter transition. Architecturally, a lot has to line up. Map and metatile behavior identify where the player is. Event and overworld state decide whether an encounter can occur. Battle setup code prepares the encounter. Battle transition code takes over the visual handoff. Battle contracts define the later action vocabulary. None of that requires runtime JSON. It requires runtime state machines consuming already-prepared data.

Now, another example: the player opens the Pokémon summary screen. The source packet points us to pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, whose includes connect pokemon data, help system, tasks, menus, link behavior, overworld, item constants, move constants, battle main, region map sections, field specials, and party menu behavior. That is not a JSON interpretation module. It is a UI composition point. It reads existing Pokémon structures, item ids, move ids, region metadata, and menu state, then presents them through windows, sprites, and callbacks. If a species table or move table was generated upstream, the summary screen benefits from that preparation. It does not need the authoring format.

Notice the repeated pattern: source data becomes compiled tables; compiled tables become runtime references; runtime references become visible behavior through callbacks, tasks, scripts, and UI code. That sentence is basically the spine of this course.

Let me pause and ask a diagnostic question. If I hand you a bug report saying, “The wrong move name appears on the summary screen,” where do you start? You do not start by rewriting the build system. You first decide whether the wrong name is in generated data, in the move constants, in the Pokémon’s stored move id, or in the summary screen rendering path. pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, is relevant because it is the display consumer. Constants for moves are relevant because ids must align. Generated data rules may become relevant if the table itself was produced incorrectly. The advanced move is not guessing the file; it is classifying the failure by layer.

One more example, because it shows cross-system coupling. The daycare system, referenced in pokefirered/src/daycare.c, chunk_119931105f546346, includes data, battle, item constants, mail, storage, event data, random, move constants, menu, script, strings, party menu, overworld, Pokédex, decompression, music, graphics, naming, help, field fade transition, and trade. That sounds sprawling, but the gameplay feature is also sprawling: Pokémon are deposited, levels may change, eggs may be generated, moves may be inherited or learned, menus display choices, scripts gate interaction, and storage or party systems may receive results. If you ask whether daycare is “data-driven,” the answer is yes in several ways. But again, the daycare runtime is not a general JSON runtime. It is a coordinator over prepared data, persistent state, random decisions, script context, and UI flow.

This is where the course plan starts to become visible. Week 1 gave you the runtime shell: main callbacks, task dispatch, per-frame control. Week 2 gives you source contracts and build pipelines: headers, generated data, assets, jsonproc, bin2c-style embedding. Next, when we move deeper into battle, overworld scripting, inventory, and save data, we will keep using the same two questions. First, what runtime state machine owns the current behavior? Second, what prepared data does it consume?

If you can answer those two questions, the codebase becomes navigable. If you cannot, every file feels equally important, and you end up reading randomly.

Now, for your lab, I want you to be strict with vocabulary. When you build your mini JSON-to-C table generator for creature species records, do not say “the game loads JSON” unless your generated demo actually does that at runtime. Say, “The build tool reads JSON and emits a C table.” Say, “The runtime includes the generated table.” Say, “The header defines the struct and extern declaration.” Say, “The gameplay code indexes the table by species id.” Those distinctions are not pedantry. They are the architecture.

And if you want a test for whether your design is FireRed-like, ask this: could the runtime still work if the original JSON files were deleted after compilation? In a generated-data architecture, yes. The build would no longer be reproducible, so that would be bad for development, but the already-built ROM would not care. The runtime artifact contains the compiled representation. That is the point.

Let me synthesize this section before we turn to the next layer. FireRed is data-driven, but its data-driven design is mostly expressed through build-time transformation, compile-time contracts, static tables, generated C, binary embedding, constants, and runtime state machines. The source tree may use JSON or other convenient formats, and tools such as pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, help turn those formats into code. The Makefile rules in pokefirered/Makefile, chunk_30eb2e53e9597446, tell us that generation is part of the build. Headers such as pokefirered/include/battle.h, chunk_4079f42fde2b4370, and pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, tell us how runtime modules agree to speak to each other. Coordinator files such as pokefirered/src/battle_setup.c, chunk_494d132a59422c85, pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, and pokefirered/src/daycare.c, chunk_119931105f546346, show prepared data meeting actual gameplay state.

So the next time someone says, “Why not just use JSON?” you should hear an incomplete architecture proposal. The correct follow-up is, “At what stage, for which system, with what runtime cost, and behind which contract?” That question will carry us into the next part, where we will look more closely at how generated artifacts and embedded binary assets become ordinary participants in the same callback, task, and table-driven world we have been mapping.

So let’s now make that next step concrete. I’m going to put a little pipeline on the board. On the far left, I’ll write “author-friendly source.” In the middle, “build tools.” On the right, “compiled runtime contract.” And then under runtime contract, I’ll write “tables, constants, structs, globals, callbacks.” That last phrase is the important one. Once the game is running, the battle engine is not asking, “Where is my JSON parser?” The battle engine is asking, “What is the value of this action constant? What is in this table? Which battler index am I operating on? Which controller function owns this battler this frame?”

That distinction sounds simple, but architecturally it is doing a lot of work.

Look again at the battle side of the system. In pokefirered/include/battle.h, chunk_a8a612bb3994f875, we do not merely see vague battle declarations. We see a compact vocabulary for legal turn decisions: B_ACTION_USE_MOVE, B_ACTION_USE_ITEM, B_ACTION_SWITCH, B_ACTION_RUN, Safari Zone-specific actions, script execution, finish states, and the sentinel B_ACTION_NONE. That is not runtime JSON. That is a closed runtime action alphabet. When a battler chooses what to do, the rest of the engine can reason over small integers and fixed arrays. The data-driven part is that the content and state select among these actions. The non-negotiable part is that the runtime has an exact contract for what those actions mean.

Now, check your understanding here. If I give you a battle log and say, “The player selected Fight, then picked Tackle,” where would you expect flexibility, and where would you expect rigidity? Flexibility appears in the move table, the Pokémon’s learned moves, the current battle state, and the input path. Rigidity appears in the action slot: this is a use-move action, not an arbitrary string command. The engine should not receive “useMove” as text and then negotiate its meaning. It should receive the enum-like value and execute the corresponding path.

And this is why headers matter so much in this course. We already spent time on headers as architecture maps, so I won’t re-teach that, but I want to carry it forward. pokefirered/include/battle.h, chunk_4e3097f639b05787 and chunk_403d30271e2270d6, defines trainer party shapes: trainer mons with or without held items, with default moves or custom moves, then a union of party pointers, then struct Trainer, then the external trainer table gTrainers. That is an entire content model expressed as a runtime contract. A trainer can be data-authored somewhere else, possibly generated or assembled through build rules, but by the time the battle setup code touches it, the shape is fixed: party flags, trainer class, music and gender field, trainer picture, name, item list, double battle flag, AI flags, party size, and party pointer.

Notice how different that is from saying, “The trainer is a JSON object.” The trainer may have originated in a convenient source format somewhere in the project’s broader pipeline, but the runtime trainer is not “whatever fields happen to be in an object.” It is struct Trainer. The battle engine’s strength comes from that finality.

Let me give you a concrete example. Suppose we add a new trainer who has a level 32 Ivysaur holding an item and using a custom moveset. At authoring time, you might want a nice declarative format. Fine. But once that content reaches battle, the relevant distinction is not that the original author typed braces or commas. The distinction is that this trainer’s party lands in the item-plus-custom-moves representation, and the partyFlags say that held items and custom moves are present. Then battle code can follow the same branch it already understands. If you add fields at the source level that never become part of this contract, they do not exist for battle. If you add runtime behavior that is not represented by this contract, then you have changed the battle engine, not merely the data.

Now I’ll draw another arrow on the board, from trainer table to battle globals. The battle header does something very revealing in later chunks. In pokefirered/include/battle.h, chunk_825c6c75270bc90c through chunk_f5cd027bf13ab47d, we see BattleResources, BattleResults, and the large BattleStruct. These are not decorative structs. They tell us how the engine remembers a battle while it is in progress and how it summarizes what happened after the battle. BattleResults keeps counters and outcomes: faint counts, switches, healing item usage, caught species, last used moves, catch attempts. BattleStruct keeps turn machinery and cross-turn state: selected move positions, party indexes, run tries, Safari counters, money multiplier, saved callback, battle type flags, chosen items, changed items, experience state, and more.

So when we say “major game systems,” I want you to hear this mapping. Battle is not one file. Battle is a contract in battle.h, a trainer table gTrainers, active state such as gBattleMons, gBattleStruct, gBattleResources, and gBattleResults, action constants such as B_ACTION_USE_MOVE, battler identity helpers like GET_BATTLER_POSITION, and coordinator code such as pokefirered/src/battle_setup.c, chunk_494d132a59422c85. That source file’s include list is already a clue: it pulls in task, overworld, item, Pokémon, save loading, Safari Zone, quest log, script, event data, object movement, metatile behavior, field effects, battle, battle transition, and battle communication. That is exactly where “prepared data meets actual gameplay state.”

Pause there. If you are taking notes, don’t write “battle system equals battle.c.” Write “battle system equals data tables plus state structs plus controller functions plus setup bridge plus visible UI.” That is the level of architecture we need for the rest of the course.

Now let’s contrast that with the bag. The bag is smaller in the excerpt, but it is extremely useful pedagogically because the interface is clean. pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, exposes functions such as InitBagWindows, BagPrintTextOnWindow, ShowBagWindow, HideBagWindow, OpenBagWindow, CloseBagWindow, GetBagWindow, BagCreateYesNoMenuBottomRight, BagCreateYesNoMenuTopRight, BagPrintMoneyAmount, and BagDrawTextBoxOnWindow. That is a UI service boundary. The bag system has item data behind it, yes. The item records may be generated from JSON into a header as the Makefile’s generated-data rules indicate in pokefirered/Makefile, chunk_30eb2e53e9597446. But the runtime bag interface is not “render arbitrary JSON.” It is windows, text, menus, money display, and task-connected user choices.

Here is the check for understanding. Why does BagCreateYesNoMenuBottomRight take a task ID and a YesNoFuncTable pointer rather than taking a JSON menu description? Because the bag is participating in the callback and task world we established in week 1. It needs to bind a visible menu choice to small runtime functions and task state. The menu is not a document viewer. It is part of a frame-by-frame interactive state machine.

That is also why pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, is such a good coordinator example. Its includes connect Pokémon data, summary screen declarations, the task system, menu helpers, link state, overworld, songs, strings, item constants, data tables, item functions, move constants, placeholder text, region map sections, field specials, party menu, and battle constants. A summary screen looks like “just UI” to a player. Architecturally, it is a read-only or mostly read-only convergence point for many contracts: party data, item data, move data, region text, link constraints, battle-adjacent constants, and task-driven screen behavior.

So when your workshop generator emits a tiny C table for creature species records, I want you to document not only “this JSON became this array.” That is the easy part. I want you to document who is allowed to include the generated header, which constants identify records, what struct layout the records must satisfy, what runtime functions consume those records, and which state variables are allowed to point into them or copy from them. That is the difference between a file conversion exercise and an architecture exercise.

Let me use the board again. I’ll write four labels vertically: source, generated artifact, contract, consumer. Under source: items.json, wild_encounters.json, region_map_sections.json, heal_locations.json. Under generated artifact: items.h, wild_encounters.h, region map entry headers, constants headers. Under contract: C arrays, struct initializers, numeric constants. Under consumer: item system, wild encounter system, region map, heal location logic. The Makefile chunk, pokefirered/Makefile, chunk_30eb2e53e9597446, is what makes this an enforced build relationship rather than a human convention. And pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, is the generic transformation tool sitting in that middle lane.

Now, jsonproc itself is worth studying for what it does and what it refuses to do. In pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6 and chunk_fc2aea317b4867eb, the program takes exactly three arguments: a JSON filepath, a template filepath, and an output filepath. It sets up an Inja environment, adds callbacks, and writes an output file using JSON plus a template. Later chunks, chunk_8ff706613290bd94 and chunk_a18acad2d3659114, show custom helpers for string cleanup and error reporting around template execution. In other words, the JSON is not a runtime object model. It is input to a source generator.

That is why the “do not modify” header callback matters conceptually. It marks generated files as products, not design authorities. The design authority is split between source data and template. The generated C header is an intermediate artifact that the compiler and game consume. If you hand-edit it, you are fighting the pipeline.

Now let’s put bin2c into the same frame. The build tool list in pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, includes bin2c, gbagfx, preproc, scaninc, mapjson, jsonproc, and other tools. That tells us the project does not have one data path. It has a suite of specialized build-time translators. In pokefirered/tools/bin2c/bin2c.c, chunks chunk_e9da40ef795d48ed through chunk_838bf0f5d271873c, the pattern is deliberately simple: read the whole binary file, interpret bytes as 1-, 2-, or 4-byte values, and print a generated const C array, with options for columns, padding, signedness, static storage, decimal or hex output. Again, this is data-driven, but it is not dynamic file loading inside gameplay.

A useful way to phrase this is: FireRed often pays complexity before runtime so runtime can stay simple. Tools can parse, validate, normalize, and format. The game can index arrays, follow pointers, and advance state machines. That tradeoff is especially appropriate on the Game Boy Advance, but it remains a good architecture principle in modern engines too. You do not parse expensive, flexible formats in the hot path unless there is a real reason.

Let’s compare briefly to Pokémon Showdown, just to sharpen the distinction. In pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, ability data can sit near executable callback-style behavior such as onDamagingHit or onModifyMove. That is natural in a server-side TypeScript simulator. The runtime language can represent data and behavior in a different way. FireRed’s architecture is different. FireRed wants compact tables, fixed constants, C structs, script bytes, global battle state, and controller callbacks. Both are data-driven, but they locate the boundary between data and behavior differently.

That sentence is worth repeating because it prevents a lot of sloppy comparisons: data-driven is not a file extension. Data-driven is a decision about where variation is represented and how the engine consumes it.

Now, another concrete example. Consider daycare. pokefirered/src/daycare.c, chunk_119931105f546346, includes data.h, battle.h, item constants, mail data, Pokémon storage, event data, random, move constants, menus, scripts, strings, party menu, list menu, overworld, Pokédex, decompression, songs, text windows, naming screen, help system, field fade transition, and trade. That include list tells you daycare is not merely “two Pokémon in a building.” It is a system that touches party state, storage state, move inheritance, item and mail constraints, script entry points, UI selection, randomness, naming, Pokédex visibility, and overworld progression.

If we were designing a source record for daycare compatibility, maybe in your lab generator, the mistake would be to ask only, “What JSON fields should we give daycare?” The better question is, “Which runtime contracts does daycare already depend on?” It depends on Pokémon records and moves. It depends on event flags or variables. It depends on scripts. It depends on UI menus and party selection. So a generated table might help with egg groups or species metadata, but it will not replace the daycare system. The system is the network of contracts plus state transitions.

Same for battle setup. It is tempting to say, “Wild encounter data creates battles.” But pokefirered/src/battle_setup.c, chunk_494d132a59422c85, shows a much wider bridge. It includes overworld, event data, map behavior, Safari Zone, quest log, scripts, field effects, item, sound, save loading, Pokémon utilities, battle transition, and battle communication. So the true statement is more like this: encounter data contributes candidate opponents, but battle setup validates context, coordinates transition, selects battle type, interacts with scripts and map state, and hands control into the battle runtime. That is much more precise.

Let me ask a diagnostic question. If a player steps in tall grass and a wild battle begins, what are the minimum architectural layers involved? Don’t answer with source files yet; answer with roles. We need map or metatile context, encounter data, random selection or probability logic, party and player state, battle setup, transition graphics or screen effects, the battle runtime’s global state, and eventually the callback/task shell that lets the visible frame loop continue. Good. Now map those to files and contracts. That is the habit this course is building.

And this is why week 2 is placed where it is in the course. Week 1 gave us the runtime shell: the main callback spine, tasks, and per-frame dispatch. We are not redoing that today, but every example now plugs into it. Week 2 gives us source contracts and generated data. Once you combine those two weeks, you can trace a real feature. You can say: source data is transformed by a build rule; the generated artifact is compiled into a typed table; a header exposes the contract; a coordinator module reads the table and writes runtime state; a callback or task makes the behavior visible.

That sentence is basically your mental debugger for FireRed.

Let’s do one more check. Suppose your mini JSON-to-C generator emits this: a species ID, base HP, attack, defense, type one, type two, and catch rate. At runtime, should the battle damage formula open the JSON file to find base attack? No. Should the summary screen parse the JSON to show a species name? No. Should the compiler see a generated table whose layout is known to the code that consumes it? Yes. Should constants provide stable names for species IDs so scripts, UI, and battle do not pass magic strings around? Yes. Should your documentation say which stage owns validation of missing fields and invalid type names? Absolutely.

Now I’ll underline “validation stage” on the board. Build-time generation gives you a place to fail early. jsonproc catches exceptions and reports JSON or template generation errors, as seen in pokefirered/tools/jsonproc/jsonproc.cpp, chunk_a18acad2d3659114. bin2c rejects bad arguments and file-size mismatches for selected element sizes, in pokefirered/tools/bin2c/bin2c.c, chunk_65c57aad256d0c13 and chunk_082e66138fa0fb45. That is not just tool hygiene. That is architecture. A bad content record should ideally fail the build, not become a corrupted encounter at runtime.

We can now refine the phrase “each major game system.” Overworld maps are data files, generated map includes, constants, scripts, event state, and field callbacks. Battle is trainer tables, move tables, battler globals, BattleStruct, BattleResults, battle scripts, controller functions, and setup bridges. Inventory is item data, bag UI functions, money display, menu windows, item effects, and party or field use callbacks. Creature data is species tables, party structs, move lists, growth and evolution logic, summary UI consumers, daycare consumers, and battle consumers. Save is persistent state layout plus load/save functions plus systems that write into that layout. Link is communication buffers, battler headers, link-specific battle state, and UI restrictions. The important pattern is that no system is “just data” and no system is “just code.” Each system is a contract between prepared data and runtime state.

So, as we move toward the lab, the bar is higher than “make a JSON parser.” I want your generator to demonstrate stage separation. The source JSON is allowed to be convenient. The template or generator is allowed to be a little opinionated. The output must be boring C. Boring is a compliment here. Boring means the rest of the game can include it, compile it, index it, and trust it.

We’ll continue from that point by walking through the lab shape more explicitly: what the creature species input should contain, what the generated C table should look like, what constants should be emitted alongside it, and how to write the short design note that proves you know which parts run during the build and which parts survive into the ROM.

All right, so let’s make the lab concrete now. I’m going to talk through it as if we were designing a very small FireRed-style subsystem, not as if we were building a modern web service. That distinction matters. The point is not, “Can we store creature species in JSON?” Of course we can. The point is, “Can we use JSON where it is helpful to the developer, and then remove it from the runtime shape of the game?”

So, imagine your input file has three species. Not one hundred fifty-one, not one thousand, just three. Maybe Sproutlet, Cindermouse, and Pebbletoad. Each record has a symbolic species name, a display name, base stats, one or two types, a catch rate, an experience yield, and maybe a small learnset. That is enough. Do not make this lab large by adding lore text, animation metadata, encounter tables, evolution chains, breeding compatibility, UI icons, and cries. Those are interesting, but they distract from the architecture question. The architecture question is stage separation.

Now, I’m going to write this on the board in words rather than full syntax. On the left side, I’ll put “authoring data.” That is your JSON file. In the middle, I’ll put “build tool.” That is your generator. On the right side, I’ll put “compiled runtime data.” That is a C header and a C source file, or one source file plus a header, depending on how you structure it. Under runtime, I am not writing “JSON parser.” I am not writing “dictionary lookup.” I am not writing “reflection.” I am writing “array indexed by species constant.” That is the whole lecture in miniature.

This is exactly the pattern we have been extracting from FireRed’s source. When we look at the build side, the source focus for today includes pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, and pokefirered/Makefile, chunk_30eb2e53e9597446. Those are not gameplay systems in the player-facing sense, but architecturally they are part of the game. They decide what developer-friendly inputs become before the ROM exists. And when we look at pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, we are seeing the pattern in its more explicit form: structured input plus template-driven generation produces source artifacts. The data-driven quality is real, but it is build-time data-driven, not runtime schema-driven.

Let’s pause there for a check. If your creature battle code opens a JSON file during an attack calculation, have you copied the FireRed pattern? No. You may have built a working prototype, but you have not copied this architecture. If your build step reads JSON and emits a C table that battle code indexes through constants, then you are much closer. The test is not whether JSON appears somewhere in the project. The test is where JSON disappears.

Now let’s design the generated C table. A reasonable output would be a struct array, something like a species info table. I am not asking you to match FireRed’s exact production structs yet. We will study creature records more deeply later. For this lab, the important thing is that the output has stable fields and stable indexing. So your generated table might contain hp, attack, defense, speed, special attack, special defense, type one, type two, catch rate, experience yield, and name. The names of the species should become constants, not strings that are compared in runtime gameplay logic. In other words, the generator should emit something equivalent to SPECIES_NONE, SPECIES_SPROUTLET, SPECIES_CINDERMOUSE, SPECIES_PEBBLETOAD, and SPECIES_COUNT.

Why include SPECIES_NONE? Because real game engines tend to reserve sentinel values. You saw this kind of thinking already in the headers-as-contracts discussion. A constant is not just a convenience. It creates a shared language between modules. When battle, summary screen, party menu, storage, save logic, and script utilities all need to refer to a creature, they should not each invent their own naming scheme. That is the same architectural reason we looked at pokefirered/include/battle.h, chunk_4079f42fde2b4370, as more than a pile of declarations. It defines a contract for battler identity, action choice, turn state, and shared battle-facing data. Likewise, pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, is not “just UI stuff.” It tells other modules what the bag interface promises to expose and what they are allowed to call.

So in your lab, your generated header is not decorative. It is the contract. If your generated source contains the species table, the generated header should declare the struct type, declare the extern table, and define or enumerate the species constants. Then a tiny runtime file can include that header and ask, “What is the base attack of SPECIES_CINDERMOUSE?” That runtime file should not know or care that the value once came from JSON. By the time the game code sees it, it is just C data.

Now, I want to be precise about something advanced students often get wrong. “Generated” does not mean “unstructured.” Some people hear “generated code” and imagine a big ugly text dump. That is not the goal. Generated output should be boring, deterministic, and reviewable. If I run your generator twice with the same input, I should get the same output. If you reorder fields randomly, include timestamps, or produce unstable formatting, you have made the build noisier. FireRed’s build-oriented architecture depends on generated artifacts being dependable inputs to the next stage. That is why the Makefile matters here. The Makefile rules in pokefirered/Makefile, chunk_30eb2e53e9597446, are part of the contract between source assets and compiled objects. They allow the project to say, “This file is derived from that file by this tool,” and then the compiler sees the derived result.

Let’s use a concrete example. Suppose your JSON says that Cindermouse has attack 62 and type FIRE. During authoring, FIRE might be a string, because strings are humane for the person writing data. But in generated C, you should not preserve “FIRE” as a runtime string to be interpreted every time. The generator should map it to a constant such as TYPE_FIRE. If the input contains “FIER” because somebody mistyped it, the build should fail. That failure is a gift. It happens before the game runs, before a player enters battle, before a corrupted value silently reaches the damage formula. This is one of the core payoffs of build-time data processing: you move errors earlier.

And notice the consequence for your design note. I want you to document not just what files you created, but where mistakes are caught. If a species has five base stats instead of six, does your generator reject it? If a type name is unknown, does your generator reject it? If two species use the same symbolic ID, does your generator reject it? If a learnset references a move constant that does not exist, do you detect it at build time, or do you let the runtime discover it later? You do not need perfect industrial validation for this lab, but you do need to show that you understand the boundary.

Let me connect that back to FireRed. The source packet points us to pokefirered/src/battle_setup.c, chunk_494d132a59422c85, which includes battle, overworld, script, event data, transition, and many other headers. That include list is a map of a boundary crossing. A battle does not emerge from nowhere. The overworld and script layer set up conditions; battle setup prepares the transition; battle code then runs under its own contracts. If the game had to parse flexible species JSON at that moment, right when a wild encounter becomes a battle, it would be mixing authoring concerns into a performance-sensitive runtime state transition. FireRed’s architecture avoids that kind of mixing. The runtime transition consumes prepared constants, prepared tables, and known global state.

Here is another check for understanding. If I ask, “Where should the rule ‘Cindermouse’s base speed is 70’ live at runtime?” the answer is not “in the generator.” The generator does not exist at runtime. The answer is, “in the generated species table compiled into the program.” If I ask, “Where should the rule ‘type names in JSON must match allowed type constants’ live?” that rule belongs in the generator, because it is a validation rule over authoring data. If I ask, “Where should the player’s current Cindermouse level live?” that is runtime state, probably in a party creature struct or save-backed data, not in the species table. Those are three different categories: generated immutable data, build-time validation logic, and mutable runtime state.

That distinction will keep paying rent as the course moves forward. When we study party data and save data, we will keep asking, “Is this species-wide data or individual creature data?” A species-wide value is something like base HP. Every Cindermouse shares that species definition. An individual value is something like current HP, level, experience, nickname, personality value, held item, move PP, and status. FireRed’s architecture repeatedly separates definitions from instances. You can already glimpse this separation in modules like pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46. That file includes pokemon, data, item constants, move constants, battle-related headers, party menu, region map, and more. The summary screen is a consumer of many prepared systems. It is not where species data should be authored. It is where already-existing creature instance state and compiled reference data are presented to the player.

So for the lab, if your generated species table contains “currentHp,” I am going to mark that as a design smell. Current HP belongs to a creature instance. Base HP belongs to the species record. The table defines what a species is. The save data records what has happened to a particular creature. That is not just domain modeling; it is the difference between immutable ROM-like data and mutable RAM or save-backed state.

Now let’s talk about the generated constants more carefully. I want your generated header to make illegal states harder to express. A weak version is to generate only an array and then rely on comments saying that index one is Sproutlet. A stronger version is to generate named constants. A stronger version still is to generate a count constant and use it in loops and bounds checks. So if your species table has four entries including SPECIES_NONE, your generated constant SPECIES_COUNT should match the array length. Then a test or compile-time assertion, if your environment supports it, can verify that the table and enum stay aligned. Even without a fancy assertion, your code should be written so the count is not hand-maintained in two places.

This is where advanced students should pay attention to a subtle point. Data-driven design does not remove the need for types and contracts. It increases the need for them. Once data can be edited outside the core C file, you need stronger generated constants, validation, and structure to keep the rest of the program honest. That is why “just load JSON” is often less disciplined, not more disciplined. It can defer mistakes until the most chaotic possible time: during gameplay.

Let me give you a bad version and a good version in speech. Bad version: battle code receives the string “Cindermouse,” searches a parsed JSON document, finds the object, reads the field “attack,” converts it from a dynamic value, and continues. Good version: battle code receives species ID SPECIES_CINDERMOUSE, indexes gSpeciesInfo[SPECIES_CINDERMOUSE].baseAttack, and continues. The second version is less glamorous, but it is closer to how a constrained game engine wants to breathe.

You can compare that with the broader research packet’s note about FireRed as “a stack of cooperating state machines plus data tables.” That phrase is useful because it prevents us from over-centering any one mechanism. The callback shell matters. Tasks matter. Scripts matter. Tables matter. Headers matter. But the architecture emerges from their cooperation. Week one gave us the runtime shell. Week two gives us the source contracts and build-time pipelines that feed that shell. The lab sits exactly at that joint: take authoring data, generate source, compile it into a table, and then access it through a small runtime program as if it had always been ordinary C.

Now, for your design note, I want a short but rigorous explanation. Not a long essay. Say what runs before compilation, what is generated, what is compiled into the program, and what the runtime reads. For example, you might say, “species.json is an authoring file. gen_species reads it during the build. It validates required fields, maps symbolic names to constants, and emits species_data.h and species_data.c. The compiled program includes species_data.h and reads gSpeciesInfo by species ID. No JSON parser is linked into the runtime.” That is the kind of sentence I want. It proves stage separation.

Also include one paragraph about failure modes. If the generator sees an invalid type, it exits with an error. If a required field is missing, it exits with an error. If two species IDs collide, it exits with an error. If the runtime receives a species ID greater than or equal to SPECIES_COUNT, that is a runtime bounds issue, not an authoring issue, and it should be handled differently. See how that gives us a clean map of responsibility?

Now I’m going to connect this briefly to bin2c, because it is the same idea with a different kind of input. We already discussed binary embedding, so I will not re-teach the whole toolchain. The important forward link is that bin2c-style workflows take non-C assets and turn them into C- or object-compatible data. jsonproc-style workflows take structured authoring data and templates and turn them into source. In both cases, the runtime does not need to understand the authoring format. That is the shared architectural move. The source packet names pokefirered/tools as a recurring module around tools, json, jsonproc, and bin2c, and the specific source focus on pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, is our evidence for the JSON/template side of that move.

Let’s do one more check. I’ll ask it the way I might ask it on quiz_w02. “A project stores monster definitions in JSON, and the shipped game parses that JSON whenever a battle begins. Is this data-driven in the FireRed build-pipeline sense?” The correct answer is no, or at least not sufficiently. It is data-driven in a generic sense, but it has not adopted the stage separation we are studying. A better answer would say, “The JSON should be consumed by a build tool that emits C constants and tables, and battle should use compiled data through headers.” That answer connects the concept to the source architecture.

And because this is an advanced course, I want you to be careful with the word “should.” There are engines where runtime JSON is a valid choice. If you are making a moddable PC game, or a live service tool, or an editor that hot-reloads content, runtime parsing may be the right tradeoff. But FireRed is not that environment. It is a GBA game architecture with constrained memory, static linking, ROM data, and carefully staged runtime systems. Our job is not to declare runtime JSON morally wrong. Our job is to understand why it is the wrong default for this architecture.

Now, bring your attention back to headers. The generated header from your lab is a miniature version of the contract idea. It says, “Here are the species IDs. Here is the shape of a species record. Here is the table you may read.” That header lets the rest of the program stop caring about the authoring format. This is why week two belongs immediately after week one. Last week, you learned that gameplay in FireRed proceeds through callbacks, tasks, and state transitions rather than through one giant main loop that directly knows everything. This week, you are learning that the data those systems consume is also staged. Source contracts and generated artifacts let those runtime systems remain small, predictable, and specialized.

Let’s close this segment with a local synthesis, not the whole lecture conclusion yet. The lab is not asking you to build a content system because JSON is fashionable. It is asking you to practice an older and stricter idea: friendly inputs for humans, boring outputs for the compiler, stable contracts for runtime code. When you can explain that path clearly, you can read FireRed more accurately. Battle setup in pokefirered/src/battle_setup.c, chunk_494d132a59422c85, is easier to reason about because it stands on prepared constants and shared headers. The summary screen in pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, is easier to reason about because it consumes creature data through established interfaces rather than becoming an authoring-data interpreter. And the build machinery in pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, pokefirered/Makefile, chunk_30eb2e53e9597446, and pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6, reminds us that architecture begins before the first frame of gameplay.

So when you start the workshop, begin with the boundary. Write down what exists only before compilation. Write down what gets generated. Write down what is compiled into the game. Write down what changes while the game is running. If those four boxes are clean, your implementation can be modest and still architecturally correct. If those four boxes are muddy, adding more features will only make the confusion harder to see.

Now, for the last stretch today, I want to make that boundary useful across the whole game, not just in the narrow workshop example. Because if you only learn the slogan, “generated data is not runtime JSON,” you will remember it for maybe a week. If you learn how that slogan lets you locate responsibility in a large C codebase, you will use it every time you open FireRed.

Let me put a map on the board. Imagine four columns again: authoring input, generated or converted source, compiled runtime data, mutable runtime state. Now, under that map, write the systems we have been naming since week one: battle, overworld, event scripting, creature data, inventory, UI screens, save, link. The discipline is to ask the same question for every system. Where does its mostly stable information live? Where does the engine contract live? Where does the per-frame or per-interaction state live? And where does the player actually see the consequence?

Take battle first, because it is the richest example and the easiest one to over-romanticize. Battle is data-driven, yes, but battle is not a little JSON interpreter sitting in the ROM politely asking for move effects at runtime. The battle code depends on headers, constants, compiled tables, globals, controller buffers, and callback-driven state transitions. The contract is visible in pokefirered/include/battle.h, chunk_4079f42fde2b4370, where the battle-facing declarations and shared battler concepts establish what other modules are allowed to know. That header is not “just declarations.” It is an architectural border. It tells the overworld, setup code, menus, and other systems, “Here is the vocabulary by which you may participate in battle.”

Now compare that with battle setup. In pokefirered/src/battle_setup.c, chunk_494d132a59422c85, the include list alone is revealing: task, overworld, item, pokemon, load_save, safari_zone, quest_log, script, event data, movement, metatile behavior, field control, battle, battle transition, and so on. I am not asking you to memorize that include list. I am asking you to notice its shape. Battle setup is a crossing point. It has to know enough about the field to decide whether a battle begins, enough about script and flags to handle story conditions, enough about creature data to form the opposing side, and enough about the battle subsystem to hand control over. That does not mean it owns all those systems. It means it coordinates between contracts.

So if you are debugging a wild encounter bug, and you see the wrong species appear, your first question should not be, “Where is the JSON parser?” There is no such runtime center. Your first question should be, “Which compiled encounter or setup data selected this opponent, which constants classify this battle, which setup function copied that choice into battle state, and which battle initialization path consumed it?” That question is architecture. It is also how you avoid wasting three hours reading unrelated UI code.

Let’s do a concrete example aloud. Suppose a student says, “I added a new creature species to my project, and it appears correctly in the party menu, but when I enter battle its icon or stats are wrong.” Good. We would split the problem. If the party menu displays the species name correctly, some compiled species identity is present. If battle stats are wrong, the error might be in generated species data, compiled constants, party struct population, battle-side copy logic, or graphics lookup. You would check the source contract for creature access, then the generated or compiled tables, then the runtime state that battle actually uses. You would not add a runtime JSON loader to “make it more data-driven.” That would add a fifth box to our boundary diagram and, unless you are building a very different platform, it would fight the design rather than clarify it.

Check for understanding here. If a move’s power is authored in an external structured file in your own engine, transformed by a tool into a C table, compiled into the ROM, and then read during damage calculation, is the runtime data-driven? Yes. Is the runtime JSON-driven? No. Those are different claims. The first claim is about separating content from code. The second is about the file format and timing of interpretation. FireRed teaches us to separate those ideas.

Now shift to inventory. Inventory is less dramatic than battle, but it is a clean architectural test because it touches data, UI, save state, and player input. The interface in pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33, is the sort of file I want you to read as a contract rather than as an index. It exposes bag-facing functions and types so other modules can open the bag, query or manipulate item behavior through proper boundaries, and return from the bag UI without every caller knowing the bag’s internal screen machinery. Items themselves are not made flexible because the bag screen parses item definitions at runtime. They are flexible because constants, item records, pocket rules, registered item behavior, and UI callbacks meet through a stable interface.

Notice also the design implication. The bag has persistent state, because the player owns items. It has transient UI state, because the cursor position, pocket, scroll offset, and menu mode change while the screen is open. It has compiled data, because item names, descriptions, prices, and behavior classifications must be available without doing file I/O. And it has cross-module contracts, because the overworld, battle, shops, scripts, and menus all need to interact with items. Those are not the same layer. If you put them all in one “inventory manager” in your own project, you may get something working quickly, but you will lose the ability to reason about which parts should be saved, which parts should be rebuilt at compile time, and which parts are screen-local.

Let me say that more plainly: if a quantity changes when the player uses a Potion, that is runtime state. If the item’s name is Potion, that is compiled data. If a script awards a Potion, that is script-facing behavior using an item constant. If the bag shows the Potion in a certain pocket, that is UI logic consuming item classification. One word, “item,” spans four architectural roles. Advanced programmers get into trouble when they assume one noun means one layer.

Now, creature data. This is where today’s lecture connects most directly to your workshop. The creature model is an excellent case for generated source, because species records are repetitive, highly structured, and referenced by many systems. Base stats, types, abilities, growth rates, learnsets, evolution rules, sprites, palettes, cries, Pokédex text, and party-instance values do not all live at the same conceptual level. Some are species-level constants. Some are generated tables. Some are assets embedded by conversion tools. Some are per-individual fields in a Pokémon struct. Some are view-specific presentation choices.

When we looked at pokefirered/src/pokemon_summary_screen.c, chunk_e8e17a42bbf17d46, the important lesson was not simply that the summary screen includes pokemon, items, moves, region map sections, battle constants, and menu helpers. The lesson was that a UI screen is a consumer of many prepared systems. It should not become the authoring system. It should not know how to parse the source format for species records. It should know how to ask established runtime structures and tables for the information it needs to present. That is why the summary screen can be complex without being architecturally lawless. It coordinates display, input, and transitions around data whose deeper meanings are owned elsewhere.

You will see the same idea in pokefirered/src/daycare.c, chunk_119931105f546346. Day care touches Pokémon data, moves, mail, storage, events, scripts, menus, naming, trade, and more. Again, the include pattern is a map of dependency pressure. Day care is not merely a menu. It is a gameplay subsystem where persistent party data, event flags, generated move constants, UI flows, and script interactions meet. If you were writing this in a modern engine, you might be tempted to make a DayCare.json file and have the runtime read rules from it. That can be valid on a desktop engine. But FireRed’s style is different: author or derive structured content before runtime, compile stable records and constants, and keep the runtime work focused on game-state transitions.

Now I want to bring in the comparison that will matter later in the course when we look at Pokémon Showdown as a secondary reference. In Pokemon Showdown, data files like pokemon-showdown/data/abilities.ts, chunk_d5bdb17c16d6f1a4, can include executable callback behavior directly inside TypeScript objects. That is also data-driven in a broad sense, but it is a very different architecture. An ability such as Stamina can express behavior through event hooks in the object definition. A form-changing ability can inspect a move and alter species form through scripted logic. That makes sense in a server simulator written in TypeScript, with a very different runtime model, tooling expectation, and test environment. FireRed does not have that same runtime language substrate. So do not collapse these two styles into one vague idea called “data.” In this course, when we use Showdown for comparison, we use it to sharpen our questions. We do not use it to pretend FireRed secretly has the same architecture.

Let’s pause there. What is the difference between FireRed’s compiled move or species data and Showdown’s TypeScript ability objects? The difference is not just syntax. It is the timing of interpretation, the kind of executable behavior allowed inside the data layer, the platform constraints, and the way tests and tools surround the engine. If you can say that back, you are beginning to think architecturally rather than cosmetically.

Now let’s connect to the build machinery one last time. The week’s central artifact trio is pokefirered/build_tools.sh, chunk_1a4a3316f8593aaf, pokefirered/Makefile, chunk_30eb2e53e9597446, and pokefirered/tools/jsonproc/jsonproc.cpp, chunk_dd905c2bfaba76d6. The shell script tells us that tools are part of the project, not an accidental convenience. The Makefile tells us that generated data is part of the dependency graph, not a manual ritual you do when you remember. The jsonproc source tells us that JSON can absolutely appear in the project while still not being a runtime dependency. That is the key nuance. JSON is not banned. Runtime JSON is not the default answer.

This is why your lab generator should be humble. It does not need to support every creature field. It does not need a heroic schema system. It needs to demonstrate the boundary. You might author three species records in JSON: identifier, display name, base HP, attack, defense, type one, type two, and maybe a front sprite asset name if you want to be ambitious. Your tool reads that JSON on your development machine. It emits a C header with constants and a C source file with a const species table. Your game code includes the header and indexes the table. At runtime, the game never asks, “Where is species.json?” It asks, “What does gSpeciesInfo[SPECIES_SOMETHING] contain?” That is the architectural win.

And yes, there are edge cases. What if you want modding? What if you want downloadable content? What if you want hot reload? What if you want balance patches without recompilation? Those are real product requirements, and they change the answer. But they are not free. They introduce runtime parsing, validation, versioning, error recovery, memory ownership, and save compatibility questions. FireRed’s architecture is not primitive because it avoids those problems. It is coherent for a cartridge-era game with strict memory and performance expectations. Your job as advanced learners is not to worship that constraint. Your job is to understand it clearly enough that, when you choose differently, you can name the cost.

Let’s put another example on the board. I will write “new held item effect.” In a runtime-JSON mental model, someone might say, “Add an item object with an effect field.” Fine, but in FireRed style, we ask more precise questions. Does the item need a new constant in an item constants header? Does it need a compiled item data entry? Does it need a bag pocket classification? Does it need battle behavior, overworld behavior, or both? Does the summary screen need to display anything special? Does save data need to preserve only the item id, or also some per-instance item state? Which module owns the effect logic? Which header exposes the function or enum needed by callers?

That is a much stronger implementation plan than “add item to data.” It gives you a search strategy. You inspect the item constants. You inspect the item data table. You inspect bag interfaces such as pokefirered/include/bag.h, chunk_8f2c0bdfc3224a33. You inspect battle-facing contracts if the item changes battle behavior, with pokefirered/include/battle.h, chunk_4079f42fde2b4370, as one of the architectural entry points. You inspect UI consumers, such as summary or bag screens. And you inspect save ownership if the player can possess the item. Each step has a layer.

Here is the check I would use on a quiz. I would give you a short scenario: “A newly generated species appears in the Pokédex list, can be placed in the party, but crashes when the player opens the summary screen.” Then I would ask you to classify likely causes by layer. A weak answer says, “The JSON is wrong.” A better answer says, “The generated constants may not align with the compiled table indices; the summary screen may index a graphics or string table that was not generated for the new species; the party instance may contain a species id that is valid for one table but not another; or the UI code may assume a bounded species range.” That is the level I expect now. We are past week one’s system tour. You should be able to trace across layers without flattening them.

Let me briefly tie this back to the callback and task spine from earlier, without reteaching it. Generated tables do not draw screens. They become visible when a callback, task, script, or subsystem reads them and mutates runtime state. The callback shell gives the game a place to run. The task system gives screens and flows lightweight state machines. Headers give modules contracts. Generated data gives those modules stable content. That sequence is the backbone of the first two weeks. If you understand only the callback shell, you can describe motion but not content. If you understand only generated data, you can describe content but not behavior. The course is about joining those two halves.

Now, because this is an architecture course, I want you to practice naming state variables carefully. “State” is not a magical word. Battle has battler state, action state, turn state, controller state, animation state, and outcome state. Inventory has saved item quantities and screen-local cursor state. The overworld has map identity, player object state, script context, movement state, and encounter-related state. Creature data has species-level compiled data and individual runtime data. Save has serialized persistent state plus transient loaded copies. Link has local and remote communication state. When you document your workshop project, do not write, “The game stores data in arrays.” Write which data, whose lifetime, and which system is allowed to mutate it.

The phrase “allowed to mutate” matters. Constants and generated tables should be treated as read-only. Runtime state should be narrow and purposeful. UI state should die when the screen dies unless there is an explicit reason to preserve it. Save state should change only through gameplay operations that actually affect the player’s progress. Battle temporary state should not leak back into species definitions. If your architecture makes it easy for a menu to rewrite base stats, that is not flexibility. That is a boundary failure.

One more comparison, and then we will synthesize. Pokemon Showdown’s battle scripts, such as the kind of damage and move logic visible in pokemon-showdown/data/mods/gen2stadium2/scripts.ts, chunk_3480e574205d5d0f, show a simulator architecture where move behavior can be expressed and overridden in a high-level runtime environment. That is tremendously useful for testing competitive mechanics across generations. FireRed, by contrast, spreads equivalent concerns across constants, battle engine code, generated or static tables, and platform-specific control flow. Neither is “more data-driven” in the abstract. They optimize for different worlds. Showdown optimizes for simulation, maintainability across rulesets, and server-side testing. FireRed optimizes for a compiled game running on fixed hardware with a particular asset pipeline and a particular interaction loop.

That distinction will matter in week three. We are moving from source contracts and generated data into more focused creature data and party mechanics. You will see that a party Pokémon is not just “a species record with current HP.” It is an individual data structure with personality, experience, moves, PP, IV-like values, EV-like values, status, held item, friendship, met data, and more. Some of that is generated from species defaults. Some of it is assigned at creation. Some of it is modified by battle. Some of it is displayed in summary screens. Some of it is serialized. The clean boundary you practiced this week is what will keep that complexity understandable.

So, when you open pokefirered/src/learn_move.c, chunk_af3598df6f092723, later, do not look at it as isolated move-learning code. Look at it as a meeting point among species identity, move constants, learnset data, party instance mutation, player-facing prompts, and rule enforcement. The same pattern repeats: authored or compiled data says what can happen; runtime state says what is currently true for this creature; UI and scripts negotiate the player interaction; engine code enforces the rule. That is the course thesis in miniature.

Let me give you the final board pass for today. In FireRed, headers are the public contracts. Generated source and converted assets are the build-time products. Constants are the shared vocabulary. Data tables are the compiled facts. Structs are the memory layout of facts and state. Globals and task data are the runtime working memory. Functions are the transitions. Callbacks and scripts are the control paths that make those transitions visible. If you can point to each of those for a feature, you understand the feature architecturally. If you can only point to the screen where the feature appears, you understand the feature as a player, not yet as an engineer.

For your workshop, I want your documentation to be almost annoyingly explicit. Say, “This JSON file exists only at build time.” Say, “This generator emits this header and this C table.” Say, “This enum or define is the stable identifier used by runtime code.” Say, “This const table is compiled into the program.” Say, “This struct field is mutable per creature instance.” Say, “This UI function reads the compiled table but does not parse authoring data.” That language is not ceremony. It trains the mental separation that lets you build larger systems without losing track of responsibility.

And as you work, please resist the urge to overbuild the generator. The goal is not to create a perfect content pipeline in one lab. The goal is to make one clean slice from authoring input to compiled table to runtime use. A small, correct slice teaches more than a broad, muddy one. If you finish early, add validation. For example, reject duplicate species identifiers. Reject base stats outside a chosen range. Reject unknown type names. Emit useful errors from the generator before compilation. That is where build-time tooling shines: it catches content mistakes before the game boots.

Let’s close the lecture there. Today’s title was “Data-Driven Does Not Mean Runtime JSON,” and the serious version of that sentence is this: data-driven architecture is about moving stable variation into explicit records and tables, while keeping runtime systems focused on state transitions under clear contracts. FireRed shows one historically grounded version of that architecture. Its build tools, Makefile rules, jsonproc path, headers, constants, and C tables form a pipeline that begins before compilation and ends in player-visible behavior. Battle setup, bag UI, summary screens, day care, move learning, and creature records all become easier to reason about once you ask which layer owns which fact.

Next time, we will use that discipline on creature records and individual Pokémon state in more detail. For now, when you leave, keep the four boxes in your head: before compilation, generated output, compiled runtime facts, mutable runtime state. Most architecture mistakes in this part of the course are just one of those boxes pretending to be another.

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