Skip to content

Concepts Data Model

bryanthaboi edited this page Jul 19, 2026 · 1 revision

The data model

The ground-truth record shapes mods write, the one naming rule that prevents the classic silent failure, and the two deep registries that hold the rules the engine used to hard-code.

camelCase discipline

Base-data record fields are camelCase, exactly as the importer emits them (src/import/RomExtractor.lua): baseStats, catchRate, spriteFront, frontSize, growthRate, dexEntry. The only snake_case a modder meets is manifest keys (optional_dependencies, game_version — JSON convention) and registry names (map_scripts, type_chart). Never a record field.

The canonical mistake is base_stats:

-- WRONG: the engine never reads base_stats; the stat change silently no-ops
mod.content.pokemon:patch("PIKACHU", { base_stats = { speed = 90 } })

-- RIGHT
mod.content.pokemon:patch("PIKACHU", { baseStats = { speed = 90 } })

Under api = 2 the wrong form is a load error with the suggestion did you mean "baseStats"? — the schema normalizes case and underscores to catch exactly this class of typo (src/mods/Schemas.lua).

A species record

Every required field, from the pokemon schema:

mod.content.pokemon:register("MODMON", {
  id = "MODMON", name = "MODMON", dex = 152,
  types = { "NORMAL" },
  baseStats = { hp = 60, attack = 70, defense = 55, speed = 90, special = 65 },
  catchRate = 120, baseExp = 100,
  growthRate = "MEDIUM_FAST",
  level1Moves = { "TACKLE" },
  learnset = { { level = 12, move = "QUICK_ATTACK" } },
  evolutions = {},
  spriteFront = mod.assets:path("front.png"),
  spriteBack = mod.assets:path("back.png"),
  frontSize = 5,
})

Optional: index, tmhm, dexEntry ({ kind, heightFt, heightIn, weight, text }), icon, cry, palette, trueColor. Id-typed fields (types, growthRate, learnset[].move, evolutions[].species, ...) are resolved after the merge; a dangling reference is a load error naming the field path.

A move, an item, a trainer

mod.content.moves:patch("BLIZZARD", { accuracy = 70 })
mod.content.items:patch("POTION", { price = 100 })
mod.content.trainers:patch("OPP_BROCK", { baseMoney = 99 })

Field-by-field shapes for all 37 registries: Reference: Registries. Notable move fields: category ("physical" | "special" | "status") overrides the Gen 1 type-based split; effect names a move_effects record; priority, highCrit, multiHit, fixedDamage are all data, not code.

The rules as data: constants and field

Two deep registries hold the values that used to be Kanto/Red literals scattered through the engine. The id is a top-level key of the target table; register and patch merge into that key, override replaces it outright, lists append (__prepend reaches the front), and keys the catalog does not describe are accepted and merged unchanged, so a mod may stash its own data beside the engine's.

constantsData.constants

mod.content.constants:patch("levelCap", 80)
mod.content.constants:patch("bagSize", 40)
mod.content.constants:patch("dexSize", 251)

Seeded keys: bagSize, partyMax, boxCount, boxSize, moveMax, levelCap, coinCap, moneyCap, dexSize, dexDigits, hmMoves, encounterBuckets, badges. Each holds the number the engine hard-coded (bag 20, party 6, 4 moves, ...), so a mod-free boot behaves exactly as before — the faithful Game Boy limits are now the defaults, overridable in one sanctioned place.

dexSize is the upper bound the Pokedex counts to and dexDigits the zero-padded width dex numbers print with; the engine derives both at data load from the highest dex across the merged species (151 and 3 for vanilla), so a mod that adds species usually needs neither. Set them explicitly for a sparse or oversized dex.

badges is the ordered badge set — list position is the badge number the trainer card draws:

mod.content.items:register("NINTH_BADGE",
  { id = "NINTH_BADGE", name = "NINTH BADGE", price = 0 })
mod.content.constants:patch("badges", { { id = "NINTH_BADGE" } })  -- appends

Each entry is { id, name?, icon?, item? }; id must resolve to an items record. override("badges", { ... }) replaces the whole set — the total-conversion verb.

fieldData.field

The overworld's data grab bag: ledges, hidden items, badge gates, fly network, town map. Per-map dictionaries merge per map, so adding content never touches Kanto's:

mod.content.field:patch("hiddenItems", {
  SABLE_COVE = { { x = 3, y = 9, item = "NUGGET" } },
})
mod.content.field:patch("flyOrder", { "SABLE_COVE" })  -- appends
mod.content.field:patch("townMap", {
  locations = { SABLE_COVE = { x = 4, y = 17, name = "SABLE COVE" } },
})

field.boot — the new-game config

The one key a total conversion always writes. Read when a new game starts:

mod.content.field:patch("boot", {
  startMap = "SABLE_COVE", startX = 3, startY = 4, startFacing = "down",
  playerName = "ALEX", rivalName = "JESS",
  startMoney = 5000,
  namePresets = { player = { "ALEX", "SAM" }, rival = { "JESS", "KIT" } },
})
key meaning
startMap / startX / startY / startFacing where the new save spawns
playerName / rivalName default names before the naming screens
startMoney starting money (vanilla 3000)
lastHeal blackout return point; defaults to the spawn
namePresets { player = {...}, rival = {...} } naming-screen menus
screens { splash, title, newGame } boot-screen ids, resolved through the screens registry (src/core/Game.lua bootScreens)
starterScript a map_scripts entry to run in place of the Oak speech
title title-screen presentation

Every key falls back to its vanilla value, so a partial patch — a new spawn and nothing else — keeps the Red names, money, and boot screens. screens resolves through the screens registry at boot (src/core/Game.lua); starterScript and title are declared and merged but not yet read by the boot chain — drive an intro through screens.newGame instead (Tutorial 12).

text_pointersData.text_pointers

The third deep registry: per-map TEXT-constant bindings. Every key is a map label carrying the same shape, so one sign binding never restates the map:

mod.content.text_pointers:patch("PalletTown", {
  TEXT_PALLETTOWN_SIGN = { text = "_MySign" },
})

Entry fields: text, label, asm, mart (a list of item ids — how shop stock is data), nurse, pc, cableClub.

Clone this wiki locally