package.path = "./?.lua;./?/init.lua;" .. package.path love = love or require("tests.love_stub") if jit then jit.off() end local checks = 0 local function check(cond, msg) checks = checks + 1 if not cond then error("FAIL " .. msg, 2) end end local function allocated(n, fn) collectgarbage("collect") collectgarbage("stop") local before = collectgarbage("count") for i = 1, n do fn(i) end local kb = collectgarbage("count") - before collectgarbage("restart") return kb end local Loader = require("src.mods.Loader") local Runtime = require("src.mods.Runtime") local function memfs(files) return { read = function(path) return files[path] end, getInfo = function(path) return files[path] and { type = "file" } or nil end, load = function(path) if not files[path] then return nil, "no file: " .. path end return load(files[path], path) end, getDirectoryItems = function() return {} end, } end local loader = Loader.new({ fs = memfs({}), generation = 3, version = "firered" }) loader:_installDevShim() Runtime.currentMod, Runtime.modRequire = nil, nil local enginePrefix = (debug.getinfo(Loader.new, "S").source or "") :gsub("^@", ""):gsub("mods[/\\]Loader%.lua$", "") local engineChunk = assert(loadstring([[ local name = ... local module = require(name) return module ]], "@" .. enginePrefix .. "core/game3/garbage_probe.lua")) check(engineChunk("src.core.game3.layout_native") == package.loaded["src.core.game3.layout_native"], "engine require still returns the loaded module") local kb = allocated(2000, function() engineChunk("src.core.game3.layout_native") end) check(kb < 16, ("2000 engine requires allocate nothing (got %.1f KB)"):format(kb)) check(engineChunk("io") == io, "engine code may still require io") local modChunk = assert(loadstring([[ return pcall(function(name) local module = require(name) return module end, ...) ]], "@mods/nosy/main.lua")) local ok, err = modChunk("io") check(not ok and tostring(err):find("is not available to mods", 1, true), "a mod chunk requiring io is denied with no owner on record") local okOs = modChunk("os") check(not okOs, "a mod chunk requiring os is denied with no owner on record") check(select(1, modChunk("src.core.game3.layout_native")), "a mod chunk may still require an ordinary engine module") Runtime.currentMod = "nosy" local okOwned, errOwned = pcall(engineChunk, "io") check(not okOwned and tostring(errOwned):find("[nosy]", 1, true), "a require made while a mod is current is attributed and denied") Runtime.currentMod = nil local LayoutNative = require("src.core.game3.layout_native") local cells = {} for i = 1, 4 * 3 do cells[i] = { mid = 100 + i, coll = i % 2, elev = i % 5 } end cells[6] = nil local layout = LayoutNative.fromDecoded({ width = 4, height = 3, cells = cells, borderMids = { 7, 8, 9, 10 }, borderWidth = 2, borderHeight = 2, }, "TEST_MAP", "test_pair") layout.overrides[1 * 1024 + 2] = { mid = 555, coll = 3, elev = 4 } for y = -5, 7 do for x = -5, 8 do local cell = layout:cellAt(x, y) check(layout:midAt(x, y) == cell.mid and layout:collAt(x, y) == cell.coll and layout:elevAt(x, y) == cell.elev, ("field reads match cellAt at %d,%d"):format(x, y)) end end kb = allocated(20000, function(i) layout:midAt(-3 - i % 7, 9); layout:collAt(40, -2); layout:elevAt(-1, -1) end) check(kb < 16, ("border field reads allocate nothing (got %.1f KB)"):format(kb)) local patched = LayoutNative.fromDecoded({ width = 1, height = 1, cells = { { mid = 1 } } }, "P", "p") patched.cellAt = function() return { mid = 42, coll = 1, elev = 2 } end check(patched:midAt(0, 0) == 42 and patched:collAt(0, 0) == 1 and patched:elevAt(0, 0) == 2, "a replaced cellAt still answers the field reads") local Map = require("src.core.game3.map") local savedNeighbors, savedWorld = Map.neighborList, Map.world local east = LayoutNative.fromDecoded({ width = 2, height = 3, cells = { { mid = 1 }, { mid = 2 }, { mid = 3 }, { mid = 4 }, { mid = 5 }, { mid = 6 } } }, "EAST", "east_pair") local primary = { midLayout = layout, pair = "test_pair" } Map.neighborList = { { dir = "east", def = { midLayout = east }, offset = 0 } } Map.world = {} local mid, pair = Map.worldMidAt(4, 1, primary) check(mid == 3 and pair == "east_pair", "east neighbor cell resolves through the connection") mid, pair = Map.worldMidAt(1, 0, primary) check(mid == 102 and pair == "test_pair", "in-map cell resolves from the primary layout") local _, _, isVoid = Map.worldMidAt(-3, -3, primary) check(isVoid == true, "a cell with no neighbor falls back to the border") kb = allocated(20000, function(i) Map.worldMidAt(4 + i % 2, i % 3, primary); Map.worldMidAt(-2, -2, primary) end) check(kb < 16, ("worldMidAt allocates nothing (got %.1f KB)"):format(kb)) Map.neighborList, Map.world = savedNeighbors, savedWorld local Q = require("src.core.game3.quest_log") local s = { name = "RED", map = "FR_A", x = 1, y = 2 } Q.record(s, "ArrivedInLocation", { [1] = "A" }, { x = 16, y = 32, actors = {} }) local tiles = {} for y = 0, 12 do for x = 0, 16 do tiles[x .. "," .. y] = { 100 + x, "pair" } end end Q.addTiles(s, tiles) local scene = s.questLog.scenes[#s.questLog.scenes] local kept = scene.tiles["3,4"] check(kept ~= tiles["3,4"] and kept[1] == 103, "recorded tiles are copies") kb = allocated(200, function() Q.addTiles(s, tiles) end) check(kb < 16, ("re-sampling unchanged tiles allocates nothing (got %.1f KB)"):format(kb)) check(scene.tiles["3,4"] == kept, "an unchanged tile keeps its recorded copy") tiles["3,4"] = { 999, "other" } Q.addTiles(s, tiles) check(scene.tiles["3,4"] ~= kept and scene.tiles["3,4"][1] == 999 and scene.tiles["3,4"][2] == "other" and scene.tiles["3,4"] ~= tiles["3,4"], "a changed tile is re-recorded as a copy") local R = require("src.core.game3.quest_log_recorder") local savedNeighbors2 = Map.neighborList Map.neighborList = {} local fakeGame = { data = { maps = { FR_A = primary } } } local frame = { x = 32, y = 16 } local filled = R.fillTiles(fakeGame, s, frame, {}) check(filled["2,1"] and filled["2,1"][1] == 555 and filled["2,1"][2] == "test_pair", "fillTiles samples the override cell") local first = filled["0,0"] kb = allocated(200, function() R.fillTiles(fakeGame, s, frame, filled) end) check(kb < 16, ("re-filling unchanged tiles allocates nothing (got %.1f KB)"):format(kb)) check(filled["0,0"] == first, "an unchanged filled tile keeps its table") layout.overrides[0] = { mid = 777, coll = 0, elev = 0 } R.fillTiles(fakeGame, s, frame, filled) check(filled["0,0"] ~= first and filled["0,0"][1] == 777, "a changed cell gets a fresh tile") layout.overrides[0] = nil Map.neighborList = savedNeighbors2 print(("PASS field garbage: %d checks"):format(checks))