-- Bounded room/ventilation tests with an isolated fake node map. local target = assert(arg[1], "environment.lua path required") local function key(p) return p.x .. ":" .. p.y .. ":" .. p.z end local h = {world = {}, reads = 0, clock = 0, fire = nil, fuel_until = 100, roof_center = nil} local function put(x, y, z, name) h.world[key({x = x, y = y, z = z})] = name end local function room() h.world = {} for x = -3, 3 do for z = -3, 3 do put(x, 0, z, "stone") put(x, 4, z, x == 0 and z == 0 and (h.roof_center or "roof") or "roof") end end for y = 1, 3 do for c = -3, 3 do put(-3, y, c, "wall"); put(3, y, c, "wall") put(c, y, -3, "wall"); put(c, y, 3, "wall") end end end core = {registered_nodes = { air = {walkable = false, buildable_to = true}, stone = {walkable = true}, roof = {walkable = true}, wall = {walkable = true}, ["hearth:door_open"] = {walkable = false}, ["hearth:vent"] = {walkable = true, groups = {hearth_vent = 1}}, }} function core.get_node_or_nil(pos) h.reads = h.reads + 1 local name = h.world[key(pos)] if name == false then return nil end return {name = name or "air"} end function core.get_us_time() return h.clock * 1000000 end function core.find_node_near() return h.fire end function core.get_meta() return {get_string = function(_, name) return name == "fuel_until" and tostring(h.fuel_until) or "" end} end function core.line_of_sight() return true end vector = { round = function(p) return {x = math.floor(p.x + 0.5), y = math.floor(p.y + 0.5), z = math.floor(p.z + 0.5)} end, offset = function(p, x, y, z) return {x = p.x + x, y = p.y + y, z = p.z + z} end, distance = function(a, b) return math.sqrt((a.x-b.x)^2 + (a.y-b.y)^2 + (a.z-b.z)^2) end, } hearth = {model = {fuel_remaining = function(deadline) return math.max(0, deadline - 1) end}, weather = "clear", clock = {temperature = 10}} dofile(target) local passed = 0 local function test(name, fn) local ok, err = pcall(fn) assert(ok, name .. ": " .. tostring(err)) passed = passed + 1 print("ok " .. passed .. " - " .. name) end local player_pos = {x = 0, y = 0, z = 0} test("closed room is fully sheltered and flood-fill work is bounded", function() h.roof_center, h.fire = nil, nil; room(); h.reads = 0 local env = hearth.environment(player_pos) assert(env.enclosed and env.roof and env.shelter == 1 and not env.ventilated) assert(h.reads < 2500, "room probe exceeded its fixed-volume budget") end) test("open door marks one exposed side without losing all roof shelter", function() h.roof_center, h.fire = nil, nil; room(); put(0, 1, 3, "hearth:door_open") hearth.invalidate_environment() local env = hearth.environment(player_pos) assert(not env.enclosed and env.roof and env.open_sides == 1) assert(math.abs(env.shelter - 0.8125) < 0.00001 and env.ventilated) end) test("a roof vent preserves weather shelter and safely exhausts hearth smoke", function() h.roof_center, h.fire = "hearth:vent", {x = 0, y = 1, z = 0}; room() hearth.invalidate_environment() local env = hearth.environment(player_pos) assert(env.enclosed and env.shelter == 1 and env.ventilated and env.heat > 0 and not env.smoke) h.roof_center, h.fire = nil, {x = 0, y = 1, z = 0}; room(); hearth.invalidate_environment() env = hearth.environment(player_pos) assert(env.enclosed and env.heat > 0 and env.smoke) end) test("changed structure invalidates cached room geometry", function() h.roof_center, h.fire = nil, nil; room(); hearth.invalidate_environment() assert(hearth.environment(player_pos).enclosed) put(0, 1, 3, "hearth:door_open") assert(hearth.environment(player_pos).enclosed, "cache should hold until an edit invalidates it") hearth.invalidate_environment() assert(not hearth.environment(player_pos).enclosed) end) test("unloaded room probes fail closed instead of granting shelter", function() h.roof_center, h.fire = nil, nil; room(); h.world["3:1:0"] = false hearth.invalidate_environment() local env = hearth.environment(player_pos) assert(env.shelter == 0 and not env.enclosed) end) print("PASS " .. passed .. " room and ventilation tests")