mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
e2454bf568
Second wave of fixes from the Gen 3 codebase review (210 findings), the
regressions the full-suite sweep surfaced, and a T6 tier so the top-level
tests/game3_*.lua suites run inside ./scripts/test.sh instead of outside it.
Battle: Knock Off and Thief/Trick item persistence follow the ROM, not the
review - the party slot is never written through during battle, and the
knocked-off mask suppresses the item on later send-outs
(pokefirered/src/battle_script_commands.c:2731 MOVE_EFFECT_KNOCK_OFF with the
STICKY_HOLD guard at :2732, opponent-steal guard at :2610-2622, mask use at
:4489). Rapid Spin keeps its one-per-use chain order (:8435-8474); Growl
keeps pret's target (src/data/battle_moves.h, include/battle.h:63).
Scripting: operand layouts match pret/asm/macros/event.inc - givemon 15 bytes
(:989-997), comparestat {B,W} (:1573-1576), setptr/loadbytefromptr/setptrbyte
take a word pointer (:118-137). 18 of the 20 E10 ops are wired per
src/scrcmd.c (per-op citations in docs/game3/e10-opcode-spec.md), money and
random and the warp family read their operands as VarGets
(src/scrcmd.c:1798-1830, :455-461, :719-731), and the day-care party-full
guard is in place (src/daycare.c:525,:1081).
Field and UI: fishing counts rounds as the ROM does
(src/field_player_avatar.c:1740-1765), an unresolved map section no longer
reports Pallet Town (src/region_map.c:3782), and a font provider sits behind
a FireRed capability/profile seam.
Save data: profile-driven Options.block, a save version round-trip, and slot
id validation that blocks path traversal (slotDiskPath("firered", "../evil")
returns nil).
Importer: the parallel import path now writes both completion markers,
object kind/clone bytes decode as pret defines them
(include/global.fieldmap.h:110-130), and the HM table drops Whirlpool
(include/constants/items.h:411-418).
Tests: six new scenario suites (battle_ai, capture, event, menu, move,
overworld) and eight engine suites (profile, capabilities, cache paths, font
provider, options block, save version round-trip, version dispatch,
versions_game). T6 runs every top-level game3 suite at GAME3_JOBS default 8
with per-suite logs, failure cause classification and a KNOWN_GAME3_FAILURES
ceiling of 23 as a shrinking guard.
Docs: the working notes under docs/game3 (triage ledger, pret citation audit,
e10 opcode spec, RSE seams, sweep v3/v113, test baseline, artifact
conversions, merge trial).
Full gate at this snapshot: exit 0, all tiers passed - engine 624/624,
T6 279/279 (0 known, 0 fresh failures), gen2 146/146, modkit 37/37, luacheck
clean, privacy gate 3/3 over a 9,711-file publication set.
60 lines
2.8 KiB
Lua
60 lines
2.8 KiB
Lua
-- T1.1 handoff (docs/game3/rse-seams.md section 4, finding J2): the engine
|
|
-- options block key comes from the game profile, not the `"firered"` literal.
|
|
--
|
|
-- Regression: Options.BLOCK was a hardcoded "firered" and every read/write went
|
|
-- through it, so a second Gen 3 game would share FireRed's options block.
|
|
-- FireRed resolves to "firered" in every path, so existing files are unchanged.
|
|
-- luajit tests/engine/game3_options_block_test.lua
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
love = love or require("tests.love_stub")
|
|
|
|
local Options = require("src.core.game3.options")
|
|
local Profile = require("src.core.game3.profile")
|
|
|
|
-- 1. Compatibility field stays, sourced from the profile.
|
|
eq(Options.BLOCK, Profile.FALLBACK_ID, "Options.BLOCK is the profile fallback id")
|
|
eq(Profile.active().optionsBlock, "firered", "the active profile resolves to firered headless")
|
|
|
|
-- 2. Defaults still fill, and the block lands under the profile's key.
|
|
local engine = {}
|
|
local o = Options.block(engine)
|
|
eq(o.textSpeed, 1, "defaults are filled")
|
|
check(type(engine.firered) == "table", "the block is created under the profile's block id")
|
|
|
|
-- 3. An explicit block id is honoured (the seam RSE callers use).
|
|
local engine2 = {}
|
|
Options.block(engine2, "other")
|
|
check(type(engine2.other) == "table", "an explicit block id creates that block")
|
|
check(engine2.firered == nil, "no firered block is created when an explicit id is given")
|
|
|
|
-- 4. bind() keys off the session's stored game (T0.2 version field).
|
|
local session = { version = "firered" }
|
|
local engine3 = { firered = { textSpeed = 2, buttonMode = 2 } }
|
|
local bound = Options.bind(session, engine3)
|
|
eq(bound.textSpeed, 2, "bind reads the stored game's block")
|
|
eq(bound.text_speed, 2, "the text_speed alias is set")
|
|
eq(bound.l_equals_a, true, "the l_equals_a alias is set")
|
|
|
|
-- 5. ensure() reads a nested engine options table by the session's block id.
|
|
local nestedOpts = { firered = { textSpeed = 0 } }
|
|
local nested = { version = "firered", options = nestedOpts }
|
|
eq(Options.ensure(nested).textSpeed, 0, "ensure reads the stored game's nested block")
|
|
eq(nested.engineOptions, nestedOpts, "ensure records the engine options table")
|
|
|
|
-- 6. Unknown ids fail closed to the active profile without raising.
|
|
eq(Options.blockId({ version = "not-a-real-game" }), Profile.active().optionsBlock,
|
|
"an unknown game id fails closed to the active block")
|
|
eq(Options.blockId({}), Profile.active().optionsBlock, "no version means the active block")
|
|
|
|
-- 7. The legacy root-level migration still runs when a block is created.
|
|
local legacy = { battleStyle = 1 }
|
|
local migrated = Options.block(legacy)
|
|
eq(migrated.battleStyle, 1, "legacy root keys migrate into the block")
|
|
eq(legacy.battleStyle, nil, "the legacy root key is removed")
|
|
|
|
T.finish("game3_options_block_test")
|