potentially CLOSES #758

This commit is contained in:
bryanthaboi
2026-08-03 17:08:42 -04:00
parent b831076839
commit 76aab74bf1
5 changed files with 57 additions and 6 deletions
+1 -1
View File
@@ -332,7 +332,7 @@ MoveEffects.primary = {
battle.data.moves[id].name) }
end,
SPLASH_EFFECT = function()
SPLASH_EFFECT = function(battle)
return { romText(battle.data, "_NoEffectText", "No effect!") }
end,
}
+31 -1
View File
@@ -169,6 +169,8 @@ end
-- full identical link surfaces: nothing to negotiate, lockstep is safe
-- vanilla_peer an old build, and we are unmodified, so it is right about us
-- engine_skew both v2 on the same major, but different releases: trade
-- still negotiates, battle is refused (see below)
-- subset both v2 but the surfaces differ: negotiated trade, no battle
-- refused an old build we would silently corrupt, or a different engine
function Handshake.checkCompat(localHello, remoteHello)
@@ -182,6 +184,16 @@ function Handshake.checkCompat(localHello, remoteHello)
if major(remoteHello.engineVersion) ~= major(localHello.engineVersion) then
return "refused", "engine_mismatch"
end
-- A lockstep battle needs the same engine RELEASE, not just the same
-- major: the fingerprint only covers the data/mod link surface, and
-- battle logic changes between minor releases (parity fixes, move
-- effect rework...), so two honest vanilla installs a release apart
-- pair as "full" and then diverge a few turns in -- the mid-battle
-- "same mods?" desync draw of #758. Trade doesn't lockstep a
-- simulation, so it stays negotiable across releases.
if tostring(remoteHello.engineVersion) ~= tostring(localHello.engineVersion) then
return "engine_skew", "engine_release_mismatch"
end
if remoteHello.fingerprint == localHello.fingerprint then
return "full", nil
end
@@ -191,7 +203,7 @@ end
-- only two v2 peers that agreed on a verdict may reject a mon outright; a v1
-- peer keeps the old substitute-a-move behaviour it was built against
function Handshake.strict(verdict)
return verdict == "full" or verdict == "subset"
return verdict == "full" or verdict == "subset" or verdict == "engine_skew"
end
function Handshake.battleAllowed(verdict)
@@ -278,6 +290,24 @@ function Handshake.describe(localHello, remoteHello, verdict, mode)
end
return lines
end
if verdict == "engine_skew" then
-- name both releases so two friends can tell WHO updates: this used
-- to surface three turns in as a desync draw blaming mods (#758)
wrap(lines, "Your game versions")
wrap(lines, "differ:")
wrap(lines, (" you: v%s"):format(tostring(localHello.engineVersion)))
wrap(lines, (" %s: v%s"):format(peer:sub(1, 8),
tostring(remoteHello.engineVersion)))
if mode == "battle" then
wrap(lines, "Battle needs the")
wrap(lines, "same version on")
wrap(lines, "both games.")
else
wrap(lines, "Trading is limited")
wrap(lines, "to shared POKéMON.")
end
return lines
end
wrap(lines, "Your games differ.")
local diff = Handshake.modDiff(localHello, remoteHello)
listMods(lines, peer .. " has:", diff.onlyTheirs)
+3 -3
View File
@@ -202,7 +202,7 @@ function LinkBattle.new(game, net, opts)
local theirName = opts.theirName or "FOE"
if not Handshake.battleAllowed(opts.verdict) then
return nil, Strings("Link battle needs\nthe same mods on\nboth games.")
return nil, Strings("Link battle needs\nthe same version\nand mods.")
end
-- both parties pass through the same pack->unpack clamp on both
@@ -342,7 +342,7 @@ function LinkBattle.new(game, net, opts)
localHash = localH, remoteHash = remoteH,
fatal = true })
endAsDraw(s, Strings(
"Link desync!\n%s differs.\fAre both games\nrunning the same\nmods?",
"Link desync!\n%s differs.\fAre both games\nthe same version\nand mods?",
component))
end
@@ -692,7 +692,7 @@ function LinkBattle.newSpectator(game, net, opts)
local guestName = opts.guestName or "GUEST"
if not Handshake.battleAllowed(opts.verdict) then
return nil, Strings("Link battle needs\nthe same mods on\nboth games.")
return nil, Strings("Link battle needs\nthe same version\nand mods.")
end
local unpackOpts = { strict = opts.strict or false, forceLevel = opts.forceLevel }
+3 -1
View File
@@ -714,7 +714,9 @@ function LinkState:draw()
end
elseif self.stage == "notice" then
drawTitle("CHECK YOUR MODS")
-- a version-skew notice has nothing to do with mods (#758)
drawTitle(self.verdict == "engine_skew" and "UPDATE YOUR GAME"
or "CHECK YOUR MODS")
for i, line in ipairs(self.noticeLines or {}) do
if i > 8 then break end -- what fits above the prompt row
Font.draw(line, 8, 24 + (i - 1) * 12)
+19
View File
@@ -378,6 +378,25 @@ local nextEngine = Handshake.hello(fakeGame(vanilla, "BLUE"), nil)
nextEngine.engineVersion = "2.0.0"
eq(Handshake.checkCompat(helloA, nextEngine), "refused", "engine major mismatch refuses")
-- same major, different release: the fingerprint can't see engine code,
-- and battle logic changes between releases, so lockstep would desync a
-- few turns in (#758) -- battle is refused up front, trade still works
local skewed = Handshake.hello(fakeGame(vanilla, "BLUE"), nil)
skewed.engineVersion = (tostring(helloA.engineVersion):match("^(%d+)") or "0") .. ".999.0"
local skewVerdict, skewReason = Handshake.checkCompat(helloA, wire(skewed))
eq(skewVerdict, "engine_skew", "same-major release skew is its own verdict")
eq(skewReason, "engine_release_mismatch", "and says why")
check(not Handshake.battleAllowed("engine_skew"), "release skew refuses lockstep")
check(Handshake.tradeAllowed("engine_skew"), "release skew still trades")
check(Handshake.strict("engine_skew"), "release skew negotiates strictly")
local skewLines = Handshake.describe(helloA, wire(skewed), "engine_skew", "battle")
local skewJoined = table.concat(skewLines, " ")
check(skewJoined:find("version", 1, true) ~= nil, "skew notice mentions versions")
check(skewJoined:find("999", 1, true) ~= nil, "skew notice names the peer release")
for _, line in ipairs(skewLines) do
check(#line <= 20, "skew line fits the screen: " .. line)
end
local lines = Handshake.describe(helloA, wire(helloMod), "subset", "battle")
check(#lines > 0, "the incompatibility screen has something to say")
local joined = table.concat(lines, " ")