Files
gen1recomp/tests/engine/game3_gotonative_test.lua
T
Shane McGovern 41935aa2a2 fix(game3): wave 2 - 118 review fixes, seam work, and the gate to 282 suites
Second batch of the Gen 3 review work: the review lanes closed their queues
and the test gate grew with them.

Review census: 139 routed findings -- 118 fixed, 14 invalid (the report had
Gen 4+ semantics in more than one place), 6 struck as stale after
re-verification, 1 re-routed. Every fix carries its own pret citation in
docs/game3/review-v3-triage.md; the E10 opcode closure is itemised in
docs/game3/e10-opcode-spec.md (18 ops wired, 2 reclassified to the seam class).

Representative ROM-grounded changes:
- The POKéMON start-menu entry is now gated on FLAG_SYS_POKEMON_GET the way
  retail does it (pokefirered/src/start_menu.c:217-218, flag 0x828).
- Money ops read their amount raw and gate the change on the disable byte
  (pokefirered/src/scrcmd.c:1798-1830, pokefirered/asm/macros/event.inc:1166-1186);
  random and the warp family VarGet theirs
  (pokefirered/src/scrcmd.c:455-461, :719-731).
- The HM table matches FRLG: there is no Whirlpool HM
  (pokefirered/include/constants/items.h:411-418).
- The day-care party-full guard follows src/daycare.c:525, :1081.
- Knock Off keeps its battle-scoped send-out mask
  (pokefirered/src/battle_script_commands.c:2730-2752, :4489), carried from
  the first batch.

Structural work: the I6/I9/J1 architecture items landed as seams (profile
selector, capability flags, font provider, virtual-object layer), the adopted
footprint register keeps a KEEP verdict, the quantizer target is met, and the
30 drain items plus carves 1-4 are folded in.

Tests: T6 now runs 282 top-level suites (273 + 9 new), the re-sweep ends at
267 pass / 0 fail, the engine tier is 634 suites, modkit 37, and the full
gate ran green twice with T3 active on an imported Red cache. Documented
skips are the lua5.4 oversize-save oracle where lua5.4 is absent and the
config-partials listed in docs/game3/game3-artifact-conversions-v3.md.

Docs shipped: review-v3-triage.md, e10-opcode-spec.md, rse-seams.md,
game3-suite-sweep-v113.md, game3-artifact-conversions-v3.md,
test-baseline-v3.md (plus the first-batch docs already on the branch).
2026-09-22 18:23:06 +01:00

64 lines
2.5 KiB
Lua

-- rse-seams e10 spec 5.2: gotonative resolves a symbol instead of jumping to a
-- ROM C function pointer.
-- pret src/scrcmd.c:92-97 (SetupNativeScript(ctx, ScriptReadWord))
-- pret src/script.c:70 (the pointer is a bool8 (*)(void))
-- lua: luajit tests/engine/game3_gotonative_test.lua
-- The registry (Natives.NATIVE_SYMBOLS) stays empty until the extractor emits
-- addr -> symbol, so today every address skips with ONE log.
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 Ctx = require("src.core.game3.scripting.ctx")
local Flags = require("src.core.game3.scripting.flags")
local Ops = require("src.core.game3.scripting.ops_a")
local Natives = require("src.core.game3.scripting.natives")
local store = Flags.newStore()
local logs = {}
local function vm()
return {
ctx = (function() local c = Ctx.new({}); c.mode, c.status = "bytecode", "running"; return c end)(),
store = store,
adapters = { log = function(m) logs[#logs + 1] = m end },
setPc = function() end,
}
end
-- 1. Unmapped address: skip + exactly one log (the empty registry case).
local v1 = vm()
eq(Ops.dispatch(v1, { op = "gotonative", [1] = 0x1234 }), false, "unmapped gotonative skips")
eq(#logs, 1, "the miss is logged once")
Ops.dispatch(v1, { op = "gotonative", [1] = 0x1234 })
eq(#logs, 1, "a repeated miss does not spam the log")
-- 2. Registered symbol: the handler is invoked and its yield propagates.
local calls = 0
Natives.NATIVE_SYMBOLS[0x100] = "test_native"
local saved = Natives.ALLOW["native:test_native"]
Natives.ALLOW["native:test_native"] = function(ctx)
calls = calls + 1
return true -- handler yield contract (mirrors Natives.callnative)
end
local v2 = vm()
eq(Ops.dispatch(v2, { op = "gotonative", [1] = 0x100 }), true,
"a registered symbol yields like a native that parks the script")
eq(calls, 1, "the registered handler ran exactly once")
Natives.ALLOW["native:test_native"] = saved
Natives.NATIVE_SYMBOLS[0x100] = nil
-- 3. Direct registrations keep working (callnative's key form).
local calls2 = 0
Natives.ALLOW["native:0x77"] = nil
local saved2 = Natives.ALLOW["native:55"]
Natives.ALLOW["native:55"] = function() calls2 = calls2 + 1 return false end
local v3 = vm()
eq(Ops.dispatch(v3, { op = "gotonative", [1] = 55 }), false, "direct id registration resolves (no yield)")
eq(calls2, 1, "the direct handler ran")
Natives.ALLOW["native:55"] = saved2
T.finish("game3_gotonative_test")