151 lines
7.9 KiB
Lua
Executable File
151 lines
7.9 KiB
Lua
Executable File
-- Standalone, mocked engine only. arg[1] = construction.lua.
|
|
local factory = dofile(assert(arg[1], "construction.lua path required"))
|
|
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 harness()
|
|
local h = {world = {}, violations = 0, writes = 0, placements = 0, notices = 0, sounds = 0}
|
|
local function key(p) return p.x .. ":" .. p.y .. ":" .. p.z end
|
|
function h.put(x, y, z, name) h.world[key({x = x, y = y, z = z})] = name end
|
|
local api = {registered_nodes = {
|
|
air = {walkable = false, buildable_to = true}, ignore = {},
|
|
stone = {}, ["hearth:wall"] = {}, ["hearth:roof"] = {}, ["hearth:planks"] = {},
|
|
["hearth:foundation"] = {}, ["hearth:frame"] = {}, ["hearth:stairs"] = {},
|
|
["hearth:door"] = {}, ["hearth:door_open"] = {walkable = false},
|
|
["hearth:window"] = {}, ["hearth:vent"] = {},
|
|
water = {walkable = false, buildable_to = true, liquidtype = "source"},
|
|
leaves = {groups = {leaves = 1}},
|
|
}}
|
|
function api.get_node_or_nil(p)
|
|
local name = h.world[key(p)]
|
|
if name == false then return nil end
|
|
return {name = name or "air"}
|
|
end
|
|
function api.is_protected() return h.protected == true end
|
|
function api.record_protection_violation() h.violations = h.violations + 1 end
|
|
function api.get_meta() h.writes = h.writes + 1; error("Preview wrote metadata") end
|
|
function api.item_place(stack) h.placements = h.placements + 1; return stack, "placed" end
|
|
local hearth = {claim_allowed = function() return not h.denied end,
|
|
claim_owner_at = function(p) return h.owners and h.owners[key(p)] end,
|
|
notify = function() h.notices = h.notices + 1 end}
|
|
local player = {pos = {x = 0, y = 0, z = 0}}
|
|
function player:is_player() return true end
|
|
function player:get_pos() return self.pos end
|
|
function player:get_player_name() return "tester" end
|
|
function player:get_player_control() return {sneak = false} end
|
|
h.api, h.hearth, h.player = api, hearth, player
|
|
h.key = key
|
|
h.module = factory(hearth, api)
|
|
function h.preview(item) return h.module.preview({x = 0, y = 0, z = 0}, player, item or "hearth:wall") end
|
|
return h
|
|
end
|
|
test("supported previews do not mutate inventory, metadata, notifications or placement", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone")
|
|
local stack = {get_name = function() return "hearth:wall" end,
|
|
take_item = function() error("Consumed preview") end}
|
|
for _ = 1, 20 do local ok, reason = h.preview(stack); assert(ok and reason == "ok") end
|
|
assert(h.violations == 0 and h.writes == 0 and h.placements == 0 and h.notices == 0)
|
|
end)
|
|
test("distance is six blocks inclusive", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone")
|
|
h.player.pos.x = 6; assert(h.preview())
|
|
h.player.pos.x = 6.01; local ok, reason = h.preview(); assert(not ok and reason == "distance")
|
|
end)
|
|
test("protected and claim-denied preview never records a violation", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone")
|
|
h.protected = true; local ok, reason = h.preview(); assert(not ok and reason == "protected")
|
|
h.protected, h.denied = false, true
|
|
ok, reason = h.preview(); assert(not ok and reason == "protected" and h.violations == 0)
|
|
assert(not h.module.allowed({x = 0, y = 0, z = 0}, h.player) and h.violations == 1)
|
|
end)
|
|
test("nil, ignore and unregistered target nodes fail unloaded", function()
|
|
for _, value in ipairs({false, "ignore", "missing"}) do
|
|
local h = harness(); h.put(0, 0, 0, value)
|
|
local ok, reason = h.preview(); assert(not ok and reason == "unloaded")
|
|
end
|
|
end)
|
|
test("occupied target is not buildable", function()
|
|
local h = harness(); h.put(0, 0, 0, "stone")
|
|
local ok, reason = h.preview(); assert(not ok and reason == "occupied")
|
|
end)
|
|
test("water replacement follows ordinary placement, but water is not an anchor", function()
|
|
local h = harness(); h.put(0, 0, 0, "water"); h.put(0, -1, 0, "water")
|
|
assert(h.preview("hearth:planks"))
|
|
local ok, reason = h.preview(); assert(not ok and reason == "support")
|
|
end)
|
|
test("unloaded support is distinguished from unsupported air or leaves", function()
|
|
local h = harness(); h.put(0, -1, 0, false)
|
|
local ok, reason = h.preview(); assert(not ok and reason == "unloaded")
|
|
h.put(0, -1, 0, "ignore"); ok, reason = h.preview(); assert(not ok and reason == "unloaded")
|
|
h.put(0, -1, 0, "leaves"); ok, reason = h.preview(); assert(not ok and reason == "support")
|
|
end)
|
|
test("two horizontal roof links allowed, third rejected", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone"); h.put(0, 0, 0, "hearth:wall")
|
|
for x = 1, 3 do
|
|
local ok, reason = h.module.preview({x = x, y = 0, z = 0}, h.player, "hearth:roof")
|
|
assert(ok == (x <= 2)); if x == 3 then assert(reason == "support") end
|
|
h.put(x, 0, 0, "hearth:roof")
|
|
end
|
|
end)
|
|
test("sixteen-block vertical limit is identical in preview", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone")
|
|
for y = 0, 16 do
|
|
h.player.pos.y = y
|
|
local ok, reason = h.module.preview({x = 0, y = y, z = 0}, h.player, "hearth:wall")
|
|
assert(ok == (y < 16)); if y == 16 then assert(reason == "support") end
|
|
h.put(0, y, 0, "hearth:wall")
|
|
end
|
|
end)
|
|
test("invalid coordinate, player and item inputs fail closed", function()
|
|
local h = harness()
|
|
for _, p in ipairs({{}, {x = 0/0, y = 0, z = 0}, {x = 0.1, y = 0, z = 0}}) do
|
|
local ok, reason = h.module.preview(p, h.player, "hearth:wall"); assert(not ok and reason == "invalid")
|
|
end
|
|
assert(not h.module.preview({x = 0, y = 0, z = 0}, nil, "hearth:wall"))
|
|
for _, item in ipairs({{}, 123, "missing"}) do
|
|
local ok, reason = h.preview(item); assert(not ok and reason == "item")
|
|
end
|
|
end)
|
|
test("legacy unsupported placement still notifies without feedback", function()
|
|
local h = harness(); local stack = {}
|
|
local p = {type = "node", under = {x = 0, y = 0, z = 0}, above = {x = 0, y = 1, z = 0}}
|
|
assert(h.module.place_building(stack, h.player, p) == stack)
|
|
assert(h.notices == 1 and h.placements == 0)
|
|
end)
|
|
test("optional feedback and denial sound replace only chat presentation", function()
|
|
local h = harness(); local messages = 0
|
|
h.hearth.feedback = function(_, _, kind) assert(kind == "warning"); messages = messages + 1 end
|
|
h.hearth.feedback_sound = function(name) assert(name == "hearth_denial"); h.sounds = h.sounds + 1 end
|
|
local p = {type = "node", under = {x = 0, y = 0, z = 0}, above = {x = 0, y = 1, z = 0}}
|
|
h.module.place_building({}, h.player, p)
|
|
assert(messages == 1 and h.sounds == 1 and h.notices == 0 and h.placements == 0)
|
|
end)
|
|
test("support removal refuses foundations that would leave dependent walls or roofs floating", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone"); h.put(0, 0, 0, "hearth:wall")
|
|
assert(not h.module.can_dig({x = 0, y = -1, z = 0}, h.player))
|
|
assert(h.module.can_dig({x = 0, y = 0, z = 0}, h.player))
|
|
h.put(1, -1, 0, "stone"); h.put(1, 0, 0, "hearth:wall")
|
|
assert(h.module.can_dig({x = 0, y = -1, z = 0}, h.player), "alternate natural support should preserve the wall")
|
|
end)
|
|
test("support removal checks dependent structures and fails closed at unloaded edges", function()
|
|
local h = harness(); h.put(0, -1, 0, "stone"); h.put(0, 0, 0, "hearth:wall")
|
|
h.put(0, 1, 0, "hearth:roof")
|
|
assert(not h.module.can_dig({x = 0, y = 0, z = 0}, h.player))
|
|
h.put(2, 0, 0, false)
|
|
assert(not h.module.can_dig({x = 0, y = -1, z = 0}, h.player))
|
|
end)
|
|
test("placement and removal cannot create structural dependencies across homesteads", function()
|
|
local h = harness(); h.owners = {}
|
|
h.put(0, -1, 0, "stone"); h.owners[h.key({x=0,y=-1,z=0})] = "neighbor"
|
|
h.owners[h.key({x=0,y=0,z=0})] = "builder"
|
|
local ok, reason = h.preview("hearth:wall")
|
|
assert(not ok and reason == "support")
|
|
h.put(0, -1, 0, "stone"); h.put(0, 0, 0, "hearth:wall")
|
|
h.owners[h.key({x=0,y=0,z=0})] = "neighbor"
|
|
assert(not h.module.can_dig({x=0,y=-1,z=0}, h.player))
|
|
end)
|
|
print("PASS " .. total .. " construction checks")
|