-- Isolated tests for real claim commands, persistence, and wrapper protection. local target = assert(arg[1], "init.lua path required") local saved = {claims = {}, homes = {}, access = {}} local writes, commands, particles = {}, {}, {} local players = {} local function copy(value) if type(value) ~= "table" then return value end local result = {}; for k, v in pairs(value) do result[k] = copy(v) end; return result end for _, name in ipairs({"owner", "housemate", "keeper", "outsider", "another-owner"}) do local player = {name = name, pos = {x = 0, y = 10, z = 0}} function player:get_player_name() return self.name end function player:get_pos() return self.pos end players[name] = player end core = { registered_nodes = {}, get_modpath = function() return "/hearth" end, get_mod_storage = function() return {} end, get_player_by_name = function(name) return players[name] end, register_chatcommand = function(name, def) commands[name] = def end, is_protected = function() return false end, record_protection_violation = function() end, chat_send_player = function() end, add_particle = function(spec) particles[#particles + 1] = spec 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} local persistence = { load_world = function(kind) return copy(saved[kind]) end, save_world = function(kind, value) saved[kind] = copy(value); writes[#writes + 1] = kind end, } local original_dofile = dofile local loaded, character_marker = {}, {} dofile = function(path) local name = path:match("([^/\\]+%.lua)$") loaded[#loaded + 1] = name if name == "character.lua" then return function(h, api) assert(h == hearth and api == core); return character_marker end end if name == "interface.lua" or name == "players.lua" then assert(hearth.character == character_marker, "Character factory must precede UI and players") end if name == "model.lua" then return {location = function() return true end} end if name == "schema.lua" then return {} end if name == "persistence.lua" then return function() return persistence end end if name == "access.lua" then local source = path:match("[/\\]init%.lua$") and path:gsub("init%.lua$", "access.lua") or "/hearth/access.lua" return original_dofile(source) end if name == "movement.lua" or name == "combat.lua" then return {} end return true -- Remaining bootstrap modules are intentionally inert in this suite. end local total = 0 local function test(name, fn) local ok, err = pcall(fn) assert(ok, name .. ": " .. tostring(err)) total = total + 1 print("ok " .. total .. " - " .. name) end local function command(param, name) return commands.claim.func(name or "owner", param or "") end original_dofile(target) test("character factory loads once before interface and player initialization", function() local positions = {} for i, name in ipairs(loaded) do assert(not positions[name], "Duplicate module load: " .. name); positions[name] = i end assert(hearth.character == character_marker) assert(positions["character.lua"] < positions["interface.lua"] and positions["character.lua"] < positions["players.lua"]) assert(positions["presentation.lua"] < positions["players.lua"]) end) test("claim creation, boundaries, and world persistence are owner-scoped", function() local ok = command("") assert(ok and saved.claims.owner.x == 0 and saved.claims.owner.y == 10) assert(hearth.claim_role_at({x = 0, y = 10, z = 0}, "owner") == "owner") assert(hearth.claim_allowed({x = 12, y = 34, z = 12}, "owner", "build")) assert(not hearth.claim_allowed({x = 12, y = 10, z = 0}, "outsider", "build")) local shown, message = command("show") assert(shown and message:find("X %-12%.%.12") and #particles >= 40) local listed = command("list") assert(listed and #writes == 1) end) test("resident and storekeeper roles are least-privilege and immediately persisted", function() assert(command("share housemate resident")) assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "build")) assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "dig")) assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "use")) assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "combat")) assert(command("share keeper storekeeper")) assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "keeper", "storage")) for _, action in ipairs({"build", "dig", "use", "combat"}) do assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "keeper", action), action) end local ok, members = command("list") assert(ok and members:find("housemate %(resident%)") and members:find("keeper %(storekeeper%)")) end) test("invalid grants, outsider mutations, and revoked permissions fail closed", function() players["another-owner"].pos = {x = 100, y = 10, z = 0} assert(command("", "another-owner")) for _, param in ipairs({"share bad/name resident", "share outsider admin", "share owner resident", "share another-owner resident", "unshare bad/name"}) do assert(not command(param), param) end assert(not command("share outsider resident", "outsider")) assert(command("unshare housemate")) assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "build")) assert(not command("share " .. string.rep("a", 33) .. " resident")) for i = 1, 15 do assert(command("share crew" .. i .. " resident")) end assert(not command("share overflow resident")) end) test("claim release revokes grants and removes the owner's saved home", function() assert(hearth.in_own_claim({x = 0, y = 10, z = 0}, "owner")) hearth.store_home("owner", {x = 0, y = 10, z = 0}) assert(hearth.home_for("owner")) assert(command("remove")) assert(not saved.claims.owner and not saved.access.owner and not hearth.home_for("owner")) assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "outsider", "build")) end) print("PASS " .. total .. " claim service tests")