mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
182 lines
6.8 KiB
Lua
182 lines
6.8 KiB
Lua
-- Navigation probe: can the bot WALK from A to B?
|
|
--
|
|
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_SPEED=200 \
|
|
-- POKEPORT_GOLD_RESUME=05 \
|
|
-- POKEPORT_GOLD_PROBE="CHERRYGROVE_CITY>ILEX_FOREST,VIOLET_CITY>AZALEA_TOWN" \
|
|
-- POKEPORT_DRIVER=tests/drivers/gold_travel_probe.lua love .
|
|
--
|
|
-- Every teleport the route bot logs is a map the planner could not reach, and
|
|
-- each one used to cost a ten-minute run to reproduce. This puts the player at
|
|
-- the start map with the checkpoint's badges and HMs, asks Bot:travelTo for the
|
|
-- destination, and reports hops and frames -- so a planner change is a
|
|
-- one-minute experiment instead of a full replay.
|
|
--
|
|
-- The placement itself is A.teleport, the same harness shortcut the bot falls
|
|
-- back on; it is the SETUP here, never the measurement. What is measured is
|
|
-- only the walk that follows.
|
|
|
|
local Bot = dofile("tests/drivers/gold/bot.lua")
|
|
local A = Bot.adapter
|
|
|
|
local DEFAULT = "CHERRYGROVE_CITY>ILEX_FOREST"
|
|
|
|
return function(game)
|
|
local bot = Bot.new(game)
|
|
|
|
for _ = 1, 3000 do
|
|
if A.ready(game) then break end
|
|
bot:wait(1)
|
|
end
|
|
if not A.ready(game) then
|
|
print("[probe] the world never came up")
|
|
return
|
|
end
|
|
|
|
local resume = os.getenv("POKEPORT_GOLD_RESUME")
|
|
if resume then
|
|
local ok, err = A.loadCheckpoint(game, resume)
|
|
if not ok then
|
|
print(("[probe] cannot resume %s: %s"):format(resume, tostring(err)))
|
|
return
|
|
end
|
|
for _ = 1, 3000 do
|
|
if A.ready(game) then break end
|
|
bot:wait(1)
|
|
end
|
|
end
|
|
|
|
-- Single-map walk probe: "ROUTE_32@18,6>6,79" places the player and asks for
|
|
-- one local walk. Travel failures usually bottom out in one map's local
|
|
-- pathfinding, and isolating that is the difference between a 60k-frame
|
|
-- reproduction and a 400-frame one.
|
|
local walkSpec = os.getenv("POKEPORT_GOLD_WALK")
|
|
if walkSpec then
|
|
for spec in walkSpec:gmatch("[^;]+") do
|
|
local map, sx, sy, gx, gy =
|
|
spec:match("^%s*([%w_]+)@(%-?%d+),(%-?%d+)>(%-?%d+),(%-?%d+)%s*$")
|
|
if not map then
|
|
print(("[probe] cannot parse walk %q"):format(spec))
|
|
else
|
|
if not A.teleport(game, map, tonumber(sx), tonumber(sy)) then
|
|
print(("[probe] cannot place the player on %s"):format(map))
|
|
else
|
|
bot:wait(30)
|
|
bot:clearDialogue()
|
|
bot:progress()
|
|
local start = bot:frames()
|
|
local ok, res = pcall(function()
|
|
return bot:walkTo(tonumber(gx), tonumber(gy))
|
|
end)
|
|
local px, py = A.pos(game)
|
|
print(("[probe] walk %s: %s in %d frames, ended at (%s,%s) on %s")
|
|
:format(spec, (ok and res) and "ok" or "FAIL",
|
|
bot:frames() - start, tostring(px), tostring(py),
|
|
tostring(A.mapId(game))))
|
|
-- Who is standing where, now. An NPC blocks a step exactly like a
|
|
-- wall but appears nowhere in the extracted map, so a corridor that
|
|
-- looks two cells wide on paper can be one cell wide in play -- which
|
|
-- is the difference between "the planner is wrong" and "there is no
|
|
-- way round".
|
|
local map = A.map(game)
|
|
if map and px then
|
|
local near = {}
|
|
for cy = math.max(0, py - 6), math.min(map.heightCells - 1, py + 6) do
|
|
for cx = 0, map.widthCells - 1 do
|
|
if A.npcAt(game, cx, cy) then
|
|
near[#near + 1] = ("(%d,%d)"):format(cx, cy)
|
|
end
|
|
end
|
|
end
|
|
print(("[probe] npcs within 6 rows: %s")
|
|
:format(#near > 0 and table.concat(near, " ") or "none"))
|
|
end
|
|
bot:progress()
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
local pairsSpec = os.getenv("POKEPORT_GOLD_PROBE") or DEFAULT
|
|
local budget = tonumber(os.getenv("POKEPORT_GOLD_PROBE_BUDGET")) or 60000
|
|
|
|
local results = {}
|
|
for spec in pairsSpec:gmatch("[^,]+") do
|
|
-- "A>B" or "A>B#N", where N pins which REGION of B counts as arriving.
|
|
-- Split maps are the whole reason travelTo grew a region argument
|
|
-- (TEAM_ROCKET_BASE_B3F), so the probe has to be able to ask for one or it
|
|
-- cannot test the thing that matters.
|
|
local from, to, region = spec:match("^%s*([%w_]+)%s*>%s*([%w_]+)%s*#(%d+)%s*$")
|
|
if not from then
|
|
from, to = spec:match("^%s*([%w_]+)%s*>%s*([%w_]+)%s*$")
|
|
end
|
|
region = tonumber(region)
|
|
if not from then
|
|
print(("[probe] cannot parse %q"):format(spec))
|
|
else
|
|
-- Place the player at `from` (setup, not navigation), then walk.
|
|
local defs = bot:mapDefs()
|
|
local def = defs[from]
|
|
local landing = def and (def.warps or {})[1]
|
|
local placed
|
|
if landing then
|
|
placed = A.teleport(game, from, landing.x, landing.y)
|
|
else
|
|
placed = A.teleport(game, from, 0, 0)
|
|
end
|
|
if not placed then
|
|
print(("[probe] cannot place the player on %s"):format(from))
|
|
results[#results + 1] = { spec = spec, ok = false, why = "no placement" }
|
|
else
|
|
bot:wait(30)
|
|
bot:clearDialogue()
|
|
bot:progress()
|
|
local start = bot:frames()
|
|
local hops = 0
|
|
local origSay = bot.say
|
|
-- Count hops without threading a counter through the core.
|
|
bot.say = function(self, ...)
|
|
local line = table.concat({ ... }, " ")
|
|
if type((...)) == "string" and (...):match("^hop ") then
|
|
hops = hops + 1
|
|
end
|
|
return origSay(self, ...)
|
|
end
|
|
local ok, res = pcall(function() return bot:travelTo(to, region) end)
|
|
bot.say = origSay
|
|
local spent = bot:frames() - start
|
|
if not ok then
|
|
results[#results + 1] = { spec = spec, ok = false, frames = spent,
|
|
hops = hops,
|
|
why = (type(res) == "table" and res.why)
|
|
or tostring(res) }
|
|
else
|
|
results[#results + 1] = { spec = spec, ok = res, frames = spent,
|
|
hops = hops,
|
|
why = res and "" or "no route",
|
|
landed = tostring(A.mapId(game)) }
|
|
end
|
|
bot:progress()
|
|
if spent > budget then
|
|
print("[probe] budget spent, stopping")
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
print("")
|
|
print("================ travel probe ================")
|
|
local passed = 0
|
|
for _, r in ipairs(results) do
|
|
if r.ok then passed = passed + 1 end
|
|
print(("%-4s %-46s %7s frames %2s hops %s")
|
|
:format(r.ok and "ok" or "FAIL", r.spec, tostring(r.frames or "-"),
|
|
tostring(r.hops or "-"),
|
|
r.ok and "" or ("%s (on %s)"):format(tostring(r.why),
|
|
tostring(r.landed))))
|
|
end
|
|
print(("%d/%d reachable by walking"):format(passed, #results))
|
|
print("==============================================")
|
|
end
|