mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
6.5 KiB
6.5 KiB
Game Boy Hardware Limitations Carried Into This Port
This is a 1:1 parity port of Pokémon Red onto a modern engine (LÖVE2D), so a lot of the original game's design isn't "design" at all, it's a direct consequence of what the actual Game Boy hardware could physically do. None of these constraints apply to a Lua table or a 2026 GPU. They're kept here purely for faithfulness to the original, and documented below so it's clear which limits are load-bearing history rather than intentional choices for this port.
Kept faithfully (gameplay-visible limits reproduced on purpose)
| # | Mechanic | Value | Why the Game Boy had this limit | Where it lives here |
|---|---|---|---|---|
| 1 | Bag capacity | 20 item slots by default | wNumBagItems save block was a fixed 20-entry id/quantity array in SRAM |
Data.constants.bagSize, read by src/inventory/Bag.lua (Bag.capacity) |
| 2 | Party size | 6 Pokémon | wPartyMon1..6 were 6 fixed save-RAM slots |
src/pokemon/Party.lua:5 (Party.MAX = 6) |
| 3 | PC storage | 12 boxes × 20 Pokémon | wBoxDataStart / Bill's PC allocated a fixed 12×20 SRAM block |
src/pokemon/Boxes.lua:7-8 |
| 4 | Moves per Pokémon | 4 | Fixed 4-move-slot field in the party/box Pokémon struct | src/pokemon/Pokemon.lua:20, enforced again in src/battle/BattleState.lua:1941 |
| 5 | Screen resolution / tile grid | 160×144 px, 8×8 tiles, 20×18 visible tiles | The Game Boy PPU's actual pixel and tile-map dimensions | src/render/Renderer.lua:13-14, src/render/TileRenderer.lua:64, src/render/BattleTransition.lua:25 |
| 6 | Name length | Nickname 10 chars, trainer/rival name 7 chars | Fixed-width wPlayerName and nickname byte buffers in SRAM |
src/ui/OakSpeech.lua:91,108, src/battle/BattleState.lua:2240, src/ui/NamingScreen.lua:50 |
| 7 | Text box size / word wrap | 20×6 tile dialogue window, 18-column wrap | Text rendered directly into the tile-map grid | src/render/TextBox.lua:14,17 |
| 8 | Text print speed | 1/3/5-frame character delay | Text was drawn character-by-character into VRAM on a fixed 60Hz frame budget | src/render/TextBox.lua:133-136, src/core/SaveData.lua (textSpeed = 3 default) |
| 9 | Audio channel behavior | 4 channels (2 pulse, 1 wave, 1 noise); fanfares "steal" the music's tone channels | The GB APU only has 4 physical sound channels | src/core/Sound.lua:14-21, docs/behavior-porting-notes.md:225-235 |
| 10 | Stat/damage byte-overflow bugs | Atk/Def quartered when either exceeds 255; Focus Energy crit bug; stat-exp capped at 255 pre-scale | Original math ran on 8-bit registers and overflowed/wrapped exactly this way, these are bugs, kept for authenticity | src/battle/Damage.lua:19-25,143-148, src/pokemon/Stats.lua:25-27 (toggleable via gen1_faithful ruleset) |
| 11 | Fixed 60Hz update step | STEP = 1/60 |
The Game Boy's actual refresh rate | src/core/FixedStep.lua |
Explicitly NOT carried over (the hardware cause disappeared, so the effect was dropped)
| # | Mechanic | Original GB constraint | Status here |
|---|---|---|---|
| 1 | OAM sprite limits | Max 40 sprites on screen, max 10 per scanline (causes the classic flicker) | Not modeled, LÖVE draws every sprite unconditionally, no scanline budget or cycling exists |
| 2 | Money cap of 999999 | Money was packed as 3 BCD bytes in SRAM, capping at 999,999 | Not enforced, money is only floored at 0, can grow unbounded |
| 3 | DMA transfer / VBlank timing | Sprite/tile updates had to be batched into the VBlank window via DMA | Not applicable, no equivalent constraint in a modern renderer |
| 4 | SRAM save layout | Save data was a precise byte-for-byte struct fit to a small battery-backed SRAM chip | Save file is a plain serialized Lua table (src/core/SaveData.lua), not an SRAM layout |
| 5 | Weak/deterministic RNG | The GB's RNG was driven by timer/divider registers, not a real PRNG | Replaced with love.math.random / math.random, only the value ranges/thresholds the original produced are kept. Exception: link battles use a deterministic Park-Miller LCG (src/link/LinkBattle.lua:22-33) so both sides can reproduce identical rolls, that's a design choice, not a GB replication |
| 6 | VRAM tile budget | 256 tiles per bank, 2 VRAM banks total | Not applicable, generated tile sheets aren't memory-budget constrained |
| 7 | Old man glitch (MissingNo. / 'M, +128 item duplication, Hall of Fame corruption) | The catch tutorial stashes the 11-byte player name over wGrassRate/wGrassMons (core.asm:2024-2037) and restores it from there after the throw (item_effects.asm:159-164), leaving name bytes in the grass table; LoadWildData skips rewriting wGrassMons on maps with grass rate 0 (wild_mons.asm:13-16), so Cinnabar/Route 21 shore tiles read name characters as level/species pairs, out-of-range species IDs render garbage base-stat/sprite memory as MissingNo./'M, the dex-#0 seen-flag write lands 255 bits past wPokedexSeen onto bag slot 6's quantity byte (+128 items), and the oversized glitch sprite overflows the decompression buffer into Hall of Fame SRAM |
Not modeled, encounters are a pure per-map data lookup (src/world/Encounter.lua + data/generated/encounters.lua) with no stale copied buffer, the species table is closed (no out-of-range reads to render), and dex flags / bag / HoF data are separate Lua tables with no address adjacency; the tutorial's OLD MAN name swap is kept only in its visible text (BattleState:oldManThrow) |
Notes
- Mods may patch
constants.bagSizethrough the public content registry. The nativesave.luaformat keeps every existing item when the configured limit changes; exporting to a cartridge.savstill writes only the first 20 bag slots because the original SRAM layout has no room for more. - PC Box overflow handling was deliberately changed even though the 20×12 box shape was kept faithful: instead of Gen 1's "full box discards or blocks the deposit," this port spills into the next box with room.
- Several battle-related overflow "bugs" (byte overflow, Focus Energy) are
gated behind a
gen1_faithfulvsmodern_cleanruleset toggle insrc/battle/rulesets/, so the faithful-but-buggy behavior can be turned off without losing the option to replay it exactly as it shipped in 1996. - Hardware-terminology comments referencing OAM, VRAM, DMA, BCD, SGB
colorization, etc. are scattered across ~14 files for context even where
the constraint itself isn't enforced (e.g.
src/battle/AnimPlayer.lua:310-312documents OAM sprite-hiding coordinate quirks used for animation correctness, without implementing the underlying 40-sprite cap).