Files
gen1recomp/tests/engine/route22_facing_phase_bug2644.lua
T
2026-10-03 06:57:16 -04:00

173 lines
9.1 KiB
Lua

package.path = './?.lua;./?/init.lua;' .. package.path
love = require('tests.love_stub')
local T = require('tests.harness').suite('Route22 facing phases #2644')
local Version = require('src.core.GameVersion')
local Game = require('src.core.Game')
local OW = require('src.world.OverworldController')
local Runner = require('src.script.ScriptRunner')
local Map = require('src.world.Map')
local Player = require('src.world.Player')
local NPC = require('src.world.NPC')
local MapScripts = require('src.script.MapScripts')
local Music = require('src.core.Music')
local TextBox = require('src.render.TextBox')
local Font = require('src.render.Font')
local Data = require('src.core.Data')
local previous={version=Version.get(),data=Game.data,save=Game.save,stack=Game.stack,map_scripts=Data.map_scripts}
local bindings, fontBindings = {},{}
local resetFontCaches
for i=1,100 do
local name,value=debug.getupvalue(OW.onStepComplete,i)
if not name then break end
if name=='Game' or name=='mapScripts' then
bindings[#bindings+1]={index=i,value=value}
debug.setupvalue(OW.onStepComplete,i,name=='Game' and Game or MapScripts)
end
end
for i=1,100 do
local name,value=debug.getupvalue(Font.load,i)
if not name then break end
if name=='state' or name=='loadedFrom' then fontBindings[#fontBindings+1]={index=i,value=value} end
if name=='resetTextCaches' then resetFontCaches=value end
end
local base
for i=1,100 do
local name,value=debug.getupvalue(MapScripts.attachBase,i)
if not name then break end
if name=='base' then base=value;break end
end
local previousBase=assert(base).ROUTE_22
base.ROUTE_22=nil
Data.map_scripts=nil
local story = require('data.scripts.story5')
local oldMusic,oldMapMusic = Music.play,Music.playMap
Music.play = function() end;Music.playMap=Music.play
local observed = {}
local success,problem=xpcall(function()
for _, version in ipairs({'red','blue','yellow'}) do
Version.set(version)
local root = os.getenv('ROUTE22_' .. version:upper() .. '_CACHE')
local cartridgeRoot=os.getenv('GEN1_' .. version:upper() .. '_CACHE')
if not root and cartridgeRoot then root=cartridgeRoot .. '/data/generated' end
if root then
local data = {}
for _, key in ipairs({'maps','tilesets','text','sprites','field','constants','font'}) do
data[key] = assert(loadfile(root .. '/' .. key .. '.lua'))()
end
Game.data = data
Font.load(data)
local map = Map.new(data.maps.ROUTE_22, data.tilesets[data.maps.ROUTE_22.tileset])
T.eq(map.id, 'ROUTE_22', version .. ' actual imported map ID')
MapScripts.attachBase('ROUTE_22', story.ROUTE_22)
for _, n in ipairs({1,2}) do for _, y in ipairs({4,5}) do for _,outcome in ipairs({'win','lose'}) do
local tag = version .. ' rival' .. n .. ' y' .. y .. ' ' .. outcome
Game.save = {version=version,generation=1,player={name='RED',rival='BLUE'},flags=n==1 and
{EVENT_GOT_POKEDEX=true} or {EVENT_BEAT_BROCK=true,EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE=true,EVENT_BEAT_GIOVANNI=true}}
local pushed = {}
Game.stack = {push=function(_, box) pushed[#pushed+1]=box end}
local player = Player.new(data,29,y,'left')
local ow = setmetatable({map=map,player=player,npcs={},entities={player},scriptMoves={}}, {__index=OW})
local finished=0
ow.afterBattle=function(_,result) T.eq(result,outcome,tag .. ' battle completion callback result');finished=finished+1 end
ow.runner = Runner.new(Game,ow)
ow:onStepComplete()
T.check(ow.runner:isRunning(),tag .. ' actual completed-step dispatch triggers base hook')
local npc = assert(ow:npcByIndex(n))
T.eq(npc.def.x,25,tag .. ' actual ROM spawn x')
T.eq(npc.def.y,5,tag .. ' actual ROM spawn y')
T.eq(npc.cellX,25,tag .. ' movement is pending on trigger')
T.eq(ow.runner.pc,2,tag .. ' actual runner waits at approach')
T.eq(#pushed,0,tag .. ' no text before movement')
T.eq(player.facing,'left',tag .. ' source trigger facing LEFT')
local frames, wrong, beforeEnd = 0,0,0
local trace = {}
while #pushed==0 and frames<160 do
ow:updateScriptMoves()
if #pushed==0 then
if player.facing~='left' then wrong=wrong+1 end
T.eq(npc.facing,'right',tag .. ' approach NPC faces RIGHT frame' .. frames)
end
if npc.moving then NPC.update(npc,map,ow.entities) end
frames=frames+1
if #pushed==0 and npc.cellX~=(y==4 and 29 or 28) then beforeEnd=beforeEnd+1 end
trace[#trace+1]={frame=frames,x=npc.cellX,y=npc.cellY,progress=npc.progress,moving=npc.moving,
playerFacing=player.facing,rivalFacing=npc.facing,text=#pushed>0}
end
T.check(frames<160,tag .. ' real movement reaches dialogue under bound')
T.check(beforeEnd>0,tag .. ' tested while rival not at destination')
T.eq(wrong,0,tag .. ' source LEFT throughout approach')
T.eq(npc.cellX,y==4 and 29 or 28,tag .. ' actual movement target x')
T.eq(npc.cellY,5,tag .. ' actual movement target y')
T.eq(npc.moving,false,tag .. ' no moving NPC when text opens')
T.eq(npc.facing,y==4 and 'up' or 'right',tag .. ' rival final direction')
T.eq(player.facing,y==4 and 'down' or 'left',tag .. ' player final direction')
T.eq(getmetatable(pushed[1]),TextBox,tag .. ' actual TextBox owns prebattle dialogue')
local label='_Route22RivalBeforeBattleText' .. n
T.check(type(data.text[label])=='string' and #data.text[label]>0,tag .. ' imported required dialogue exists')
T.eq(ow.runner.script[ow.runner.pc][1],'show_text',tag .. ' yielded before battle')
T.eq(ow.runner.script[ow.runner.pc][2],label,tag .. ' exact prebattle dialogue')
T.eq(Game.save.flags['EVENT_BEAT_ROUTE22_RIVAL_' .. (n==1 and '1ST' or '2ND') .. '_BATTLE'],nil,
tag .. ' no battle progression prematurely committed')
observed[#observed+1]={version=version,n=n,y=y,frames=frames,wrongFacingFrames=wrong,trace=trace}
ow.runner.ctx.resumeBattle={result=outcome,battle={}}
pushed[1].onDone()
local beat='EVENT_BEAT_ROUTE22_RIVAL_' .. (n==1 and '1ST' or '2ND') .. '_BATTLE'
if outcome=='win' then
T.eq(Game.save.flags[beat],true,tag .. ' victory commits only matching story flag')
T.eq(#pushed,2,tag .. ' actual resumed battle success opens postbattle text')
T.eq(finished,0,tag .. ' afterBattle waits for script walkoff')
pushed[2].onDone()
local exitFrames=0
while ow.runner:isRunning() and exitFrames<400 do
ow:updateScriptMoves()
T.check(map:isWalkableCell(npc.cellX,npc.cellY),tag .. ' exit remains on actual walkable ground')
T.check(npc.cellX~=29 or npc.cellY~=y,tag .. ' exit avoids parked player')
if npc.moving then NPC.update(npc,map,ow.entities) end
exitFrames=exitFrames+1
end
T.check(exitFrames<400,tag .. ' real exit finishes under bound')
T.eq(npc.cellX,n==1 and 31 or 25,tag .. ' source exit end x')
T.eq(npc.cellY,n==1 and 10 or 5,tag .. ' source exit end y')
else
T.eq(Game.save.flags[beat],nil,tag .. ' loss does not commit story flag')
T.eq(#pushed,1,tag .. ' loss skips victory text')
T.eq(#ow.scriptMoves,0,tag .. ' loss skips walkoff')
end
T.eq(finished,1,tag .. ' actual completion continuation runs once')
T.eq(ow.runner:isRunning(),false,tag .. ' script finishes')
T.eq(ow:npcByIndex(n),nil,tag .. ' hide_object removes live rival')
T.eq(Game.save.objectToggles.ROUTE_22['ROUTE22_RIVAL'..n],false,tag .. ' final toggle preserved')
end end end
local function gate(flags,x,y,busy)
local ran=0
local ow={player={facing='right'},runner={isRunning=function()return busy end,run=function()ran=ran+1 end}}
local game={data=data,save={flags=flags}}
T.eq(story.ROUTE_22.onStep(game,ow,x,y),false,version .. ' negative trigger')
T.eq(ran,0,version .. ' negative trigger queues nothing')
T.eq(ow.player.facing,'right',version .. ' negative trigger preserves direction')
end
gate({},29,4,false)
gate({EVENT_GOT_POKEDEX=true,EVENT_BEAT_BROCK=true},29,4,false)
gate({EVENT_BEAT_GIOVANNI=true,EVENT_BEAT_ROUTE22_RIVAL_2ND_BATTLE=true},29,4,false)
gate({EVENT_BEAT_GIOVANNI=true},28,4,false)
gate({EVENT_BEAT_GIOVANNI=true},29,4,true)
local ow={player={facing='left'},runner={isRunning=function()return false end,run=function()end}}
T.eq(story.CERULEAN_CITY.onStep({data=data,save={flags={}}},ow,20,6),true,version .. ' Cerulean still triggers')
T.eq(ow.player.facing,'up',version .. ' generic ambush retains immediate UP control')
else print('SKIP #2644 actual '..version..' phase/movement/continuation gate: set ROUTE22_'..version:upper()..'_CACHE or GEN1_'..version:upper()..'_CACHE') end
end
end,debug.traceback)
Music.play,Music.playMap=oldMusic,oldMapMusic
Game.data,Game.save,Game.stack=previous.data,previous.save,previous.stack
Data.map_scripts=previous.map_scripts;base.ROUTE_22=previousBase
for _,binding in ipairs(bindings) do debug.setupvalue(OW.onStepComplete,binding.index,binding.value) end
for _,binding in ipairs(fontBindings) do debug.setupvalue(Font.load,binding.index,binding.value) end
if resetFontCaches then resetFontCaches() end
Version.set(previous.version)
assert(success,problem)
if os.getenv('ROUTE22_OBSERVATIONS') then
local f=assert(io.open(os.getenv('ROUTE22_OBSERVATIONS'),'wb'))
f:write(require('src.link.Json').encode(observed));f:close()
end
T.finish()