mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 06:55:29 +02:00
01c5eb2189
gate_meta_coverage asks every extension point for a unit test through the public mod API, a no-mod parity test, and docs. The seams commit shipped the call sites and owed the rest. Both cases drive the real engine path, and both assert the unhooked build first, so they fail if the seam is deleted and if it changes vanilla behaviour. world_talk stands an object on the faced cell: with no mod the A press reaches talkTo, with a mod that owns the object it does not, and an object the mod ignores still falls through. link_battle_ended parks a session at the end of a battle and checks the event carries the result, the role, and both party copies. The parity side needed nothing. gate_hooks and gate_events walk the live catalog, so both seams were covered structurally as soon as the call sites existed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
100 lines
3.5 KiB
Lua
100 lines
3.5 KiB
Lua
-- A sandboxed mod can read the outcome of a link battle -- who won, and the
|
|
-- lockstep party copies -- through the public event surface.
|
|
--
|
|
-- The copies are the point. Cable rules leave the real party untouched, so
|
|
-- a mode built on link battles (a tournament ladder, a battle royale) has no
|
|
-- other way to learn what the fight cost; by the time the state unwinds the
|
|
-- battle object is gone.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
love = love or require("tests.love_stub")
|
|
|
|
local T = require("tests.modkit")
|
|
local LinkState = require("src.link.LinkState")
|
|
|
|
local FIXTURE = {
|
|
["mods/outcome_probe/manifest.json"] = [[{
|
|
"id": "outcome_probe",
|
|
"name": "Outcome Probe",
|
|
"version": "1.0.0",
|
|
"entry": "main.lua",
|
|
"api": 2
|
|
}]],
|
|
["mods/outcome_probe/main.lua"] = [[
|
|
local mod = ...
|
|
mod.exports.seen = 0
|
|
mod.events:on("link.battle_ended", function(ev)
|
|
mod.exports.seen = mod.exports.seen + 1
|
|
mod.exports.result = ev.result
|
|
mod.exports.role = ev.role
|
|
mod.exports.peerName = ev.peerName
|
|
mod.exports.myLead = ev.myParty and ev.myParty[1]
|
|
mod.exports.theirLead = ev.theirParty and ev.theirParty[1]
|
|
end)
|
|
]],
|
|
}
|
|
|
|
-- a link session parked at the end of a battle: no transport, so update()
|
|
-- goes straight to the stage that reports the outcome
|
|
local function finishedSession(result, isHost)
|
|
local ls
|
|
ls = setmetatable({
|
|
stage = "battleRunning",
|
|
isHost = isHost,
|
|
peerName = "BLUE",
|
|
net = nil,
|
|
battle = {
|
|
result = result,
|
|
playerParty = { { species = "RATTATA", hp = 3 } },
|
|
enemyParty = { { species = "PIDGEY", hp = 0 } },
|
|
},
|
|
game = { input = {}, stack = { top = function() return ls end } },
|
|
}, { __index = LinkState })
|
|
-- the real exit unwinds the whole link stack; the event is what is under
|
|
-- test, so record the exit rather than run it
|
|
ls.exitWith = function() ls.exited = true end
|
|
return ls
|
|
end
|
|
|
|
-- ------- no mod: the battle still ends, nothing observes it
|
|
|
|
local vanilla = T.sdk.loadNone({})
|
|
local quiet = finishedSession("win", true)
|
|
quiet:update(0)
|
|
T.check(quiet.exited, "with no mod loaded the finished battle still unwinds")
|
|
T.eq(quiet.battle, nil, "and lets go of the battle")
|
|
vanilla.release()
|
|
|
|
-- ------- a mod reads the outcome and both party copies
|
|
|
|
local run = T.sdk.loadMods({ "mods/outcome_probe" }, { fs = T.sdk.memfs(FIXTURE) })
|
|
T.eq(#run.errors, 0,
|
|
"the public outcome probe loads clean (" .. tostring(run.errors[1]) .. ")")
|
|
|
|
local session = finishedSession("win", true)
|
|
session:update(0)
|
|
local out = run.loader.exports.outcome_probe or {}
|
|
T.eq(out.seen, 1, "a finished link battle raises the event once")
|
|
T.eq(out.result, "win", "the outcome is reported")
|
|
T.eq(out.role, "host", "so is which side of the cable we were")
|
|
T.eq(out.peerName, "BLUE", "and who we played")
|
|
T.eq(out.myLead and out.myLead.species, "RATTATA",
|
|
"the lockstep copy of our party comes with it")
|
|
T.eq(out.myLead and out.myLead.hp, 3,
|
|
"carrying the damage the real party never took")
|
|
T.eq(out.theirLead and out.theirLead.species, "PIDGEY",
|
|
"and the copy of theirs")
|
|
T.check(session.exited, "the state unwinds afterwards, as it always did")
|
|
T.eq(session.battle, nil, "and the battle is released")
|
|
|
|
-- the guest side says so
|
|
local guest = finishedSession("lose", false)
|
|
guest:update(0)
|
|
T.eq(run.loader.exports.outcome_probe.seen, 2, "the guest reports too")
|
|
T.eq(run.loader.exports.outcome_probe.role, "guest", "as the guest")
|
|
T.eq(run.loader.exports.outcome_probe.result, "lose", "with its own outcome")
|
|
|
|
run.release()
|
|
|
|
T.finish("link_battle_ended")
|