mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
96 lines
3.7 KiB
Lua
96 lines
3.7 KiB
Lua
-- Regression: a plan must never be blocked by another map's NPCs.
|
|
--
|
|
-- The route driver's BFS treats every entity as a wall, keyed by a folded
|
|
-- cell id (`y * width + x`). A warp swaps the map id before the entity list
|
|
-- is rebuilt, so a plan made in that window sees the PREVIOUS map's NPCs --
|
|
-- and folding hides how wrong that is. On an 8-wide gate, a forest NPC at
|
|
-- (16,43) folds to 43*8+16 = 360, which is a perfectly ordinary cell of the
|
|
-- gate; the wall lands somewhere innocent and nothing looks amiss.
|
|
--
|
|
-- Observed as: "goto (4,1) unreachable on VIRIDIAN_FOREST_NORTH_GATE; from
|
|
-- (2,1); npcs: SPRITE_YOUNGSTER@(16,43) SPRITE_YOUNGSTER@(30,33)
|
|
-- SPRITE_POKE_BALL@(12,29)" -- Viridian Forest coordinates listed against a
|
|
-- gate the size of a room. Every following segment skipped and the attempt
|
|
-- was lost.
|
|
--
|
|
-- The fix is to ignore any entity outside the current map's bounds. This
|
|
-- pins the folding arithmetic that makes the bug invisible, so a future
|
|
-- reader can see why the bounds check is not merely defensive.
|
|
--
|
|
-- Self-contained; run via `luajit tests/parity_stale_npc.lua`.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
local S = require("tests.harness").suite("parity stale npc")
|
|
local check, eq = S.check, S.eq
|
|
|
|
-- the driver's blocking rule, isolated: fold a cell, wall it, but only for
|
|
-- entities that are actually on this map
|
|
local function blockedCells(npcs, w, h)
|
|
local blocked = {}
|
|
for _, npc in ipairs(npcs) do
|
|
if npc.x >= 0 and npc.y >= 0 and npc.x < w and npc.y < h then
|
|
blocked[npc.y * w + npc.x] = true
|
|
end
|
|
end
|
|
return blocked
|
|
end
|
|
|
|
-- VIRIDIAN_FOREST_NORTH_GATE is 8 cells wide; the forest is far larger.
|
|
local GATE_W, GATE_H = 8, 8
|
|
|
|
-- The arithmetic that made this invisible: a foreign NPC folds onto a real
|
|
-- cell of the small map rather than into obvious nonsense.
|
|
eq(43 * GATE_W + 16, 360, "forest (16,43) folds onto a plain integer id")
|
|
eq(1 * GATE_W + 4, 12, "the gate's own (4,1) folds to 12")
|
|
|
|
-- Without the bounds check, foreign NPCs wall off cells of the gate.
|
|
do
|
|
local naive = {}
|
|
for _, n in ipairs({ { x = 16, y = 43 }, { x = 30, y = 33 }, { x = 12, y = 29 } }) do
|
|
naive[n.y * GATE_W + n.x] = true
|
|
end
|
|
check(next(naive) ~= nil,
|
|
"unguarded, off-map NPCs still produce blocked cell ids")
|
|
end
|
|
|
|
-- With it, they are ignored entirely.
|
|
do
|
|
local blocked = blockedCells({
|
|
{ x = 16, y = 43 }, { x = 30, y = 33 }, { x = 12, y = 29 },
|
|
{ x = 2, y = 18 }, { x = 27, y = 40 },
|
|
}, GATE_W, GATE_H)
|
|
check(next(blocked) == nil,
|
|
"every off-map NPC is ignored on the gate")
|
|
end
|
|
|
|
-- ...while the map's own NPCs still block, which is the whole point.
|
|
do
|
|
local blocked = blockedCells({
|
|
{ x = 3, y = 2 }, -- SPRITE_SUPER_NERD, really in the gate
|
|
{ x = 2, y = 5 }, -- SPRITE_GRAMPS
|
|
{ x = 16, y = 43 }, -- stale, from the forest
|
|
}, GATE_W, GATE_H)
|
|
check(blocked[2 * GATE_W + 3], "an NPC inside the gate still blocks its cell")
|
|
check(blocked[5 * GATE_W + 2], "and so does the second one")
|
|
local n = 0
|
|
for _ in pairs(blocked) do n = n + 1 end
|
|
eq(n, 2, "exactly the two real NPCs block -- the stale one does not")
|
|
end
|
|
|
|
-- Edge cases of the bound itself.
|
|
do
|
|
local blocked = blockedCells({
|
|
{ x = GATE_W - 1, y = GATE_H - 1 }, -- last legal cell
|
|
{ x = GATE_W, y = 0 }, -- one past the right edge
|
|
{ x = 0, y = GATE_H }, -- one past the bottom
|
|
{ x = -1, y = 0 }, -- negative
|
|
}, GATE_W, GATE_H)
|
|
check(blocked[(GATE_H - 1) * GATE_W + (GATE_W - 1)],
|
|
"the far corner is in bounds and blocks")
|
|
local n = 0
|
|
for _ in pairs(blocked) do n = n + 1 end
|
|
eq(n, 1, "off-by-one and negative coordinates are all rejected")
|
|
end
|
|
|
|
S.finish()
|