Lots of content
This commit is contained in:
Executable
+183
@@ -0,0 +1,183 @@
|
||||
-- Standalone Lua 5.1+; no real core/hearth mutation leaks into engine suites.
|
||||
local target = assert(arg[1], "presentation.lua path required")
|
||||
local total = 0
|
||||
local function test(name, fn)
|
||||
local old_core, old_hearth = core, hearth
|
||||
local ok, message = pcall(fn)
|
||||
core, hearth = old_core, old_hearth
|
||||
assert(ok, name .. ": " .. tostring(message))
|
||||
total = total + 1; print("PASS " .. name)
|
||||
end
|
||||
local function harness()
|
||||
local h = {objects = {}, connected = {}, leaves = {}, deaths = {}, shutdowns = {}}
|
||||
core = {
|
||||
register_entity = function(name, def) h.entity_name, h.def = name, def end,
|
||||
get_player_by_name = function(name) return h.connected[name] end,
|
||||
register_on_leaveplayer = function(fn) h.leaves[#h.leaves + 1] = fn end,
|
||||
register_on_dieplayer = function(fn) h.deaths[#h.deaths + 1] = fn end,
|
||||
register_on_shutdown = function(fn) h.shutdowns[#h.shutdowns + 1] = fn end,
|
||||
}
|
||||
hearth = {players = {}, sessions = {}}
|
||||
function h.player(name)
|
||||
local p = {name = name or "fixture", item = "hearth:knife", hp = 20}
|
||||
function p:get_player_name() return self.name end
|
||||
function p:get_wielded_item() return {get_name = function() return self.item end} end
|
||||
function p:get_pos() return {x = 1, y = 52, z = 3} end
|
||||
function p:get_hp() return self.hp end
|
||||
h.connected[p.name] = p
|
||||
hearth.players[p.name] = {food = 85, stamina = 100}
|
||||
hearth.sessions[p.name] = {}
|
||||
return p
|
||||
end
|
||||
core.add_entity = function(pos, name, staticdata)
|
||||
assert(name == h.entity_name and pos.y == 52)
|
||||
if h.fail then return nil end
|
||||
if h.throw then error("fixture failure") end
|
||||
local obj = {props = {}, writes = 0}
|
||||
local lua = {object = obj}; obj.lua = lua
|
||||
function obj:get_pos() if not self.removed then return pos end end
|
||||
function obj:remove()
|
||||
assert(not self.removed, "duplicate removal")
|
||||
self.removed = true; h.def.on_deactivate(lua)
|
||||
end
|
||||
function obj:set_properties(props)
|
||||
self.writes = self.writes + 1
|
||||
for k, v in pairs(props) do self.props[k] = v end
|
||||
end
|
||||
function obj:set_armor_groups(groups) self.armor = groups end
|
||||
function obj:set_attach(parent, bone, position, rotation, forced)
|
||||
if h.refuse_attach then return end
|
||||
self.parent, self.bone, self.position, self.rotation, self.forced = parent, bone, position, rotation, forced
|
||||
end
|
||||
function obj:get_attach() return self.parent, self.bone, self.position, self.rotation, self.forced end
|
||||
h.objects[#h.objects + 1] = obj
|
||||
h.def.on_activate(lua, staticdata or "", 0)
|
||||
return obj
|
||||
end
|
||||
function h.count()
|
||||
local n = 0; for _, obj in ipairs(h.objects) do if not obj.removed then n = n + 1 end end; return n
|
||||
end
|
||||
function h.tick_objects()
|
||||
for _, obj in ipairs(h.objects) do if not obj.removed then h.def.on_step(obj.lua, 0.1) end end
|
||||
end
|
||||
dofile(target)
|
||||
return h
|
||||
end
|
||||
|
||||
test("fixed whitelist creates all four generated UV meshes with correct bone transform", function()
|
||||
local h = harness(); local p = h.player()
|
||||
for _, tool in ipairs({"knife", "axe", "pick", "spear"}) do
|
||||
p.item = "hearth:" .. tool; hearth.presentation_step(p)
|
||||
local obj = h.objects[#h.objects]
|
||||
assert(h.count() == 1 and obj.props.mesh == "hearth_" .. tool .. ".obj")
|
||||
assert(obj.parent == p and obj.bone == "Hand_Right" and obj.forced == false)
|
||||
assert(obj.position.x == 0 and obj.position.y == 0 and obj.position.z == 0)
|
||||
assert(obj.rotation.x == 90 and obj.rotation.y == 0 and obj.rotation.z == 0)
|
||||
assert(obj.props.is_visible == true and obj.armor.immortal == 1)
|
||||
end
|
||||
assert(h.def.initial_properties.textures[1] == "hearth_person.png")
|
||||
assert(h.def.initial_properties.visual_size.x == 1 and h.def.initial_properties.visual_size.y == 1)
|
||||
end)
|
||||
test("steady owner has exactly one entity and no repeated property traffic", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
local obj = h.objects[1]; local writes = obj.writes
|
||||
for _ = 1, 200 do hearth.presentation_step(p); h.tick_objects() end
|
||||
assert(#h.objects == 1 and h.count() == 1 and obj.writes == writes)
|
||||
end)
|
||||
test("weapon change removes old entity before creating replacement", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
p.item = "hearth:axe"; hearth.presentation_step(p)
|
||||
assert(h.objects[1].removed and h.count() == 1 and #h.objects == 2)
|
||||
h.def.on_deactivate(h.objects[1].lua); hearth.presentation_step(p)
|
||||
assert(h.count() == 1 and #h.objects == 2)
|
||||
end)
|
||||
test("empty unknown and path-like names remove visuals without accepting paths", function()
|
||||
local h = harness(); local p = h.player()
|
||||
for _, name in ipairs({"", "hearth:log", "../hearth_spear.obj", "hearth:knife.obj", "other:spear"}) do
|
||||
p.item = "hearth:knife"; hearth.presentation_step(p)
|
||||
p.item = name; hearth.presentation_step(p); assert(h.count() == 0)
|
||||
end
|
||||
end)
|
||||
test("entity self-cleans on weapon change even without feedback tick", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
p.item = ""; h.tick_objects(); assert(h.count() == 0)
|
||||
end)
|
||||
test("disconnect callback removes immediately and reconnect never inherits owner", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
h.connected[p.name] = nil; h.leaves[1](p); assert(h.count() == 0)
|
||||
local replacement = h.player(); hearth.presentation_step(replacement)
|
||||
h.leaves[1](p); hearth.presentation_step(p)
|
||||
assert(h.count() == 1 and #h.objects == 2 and h.objects[2].parent == replacement)
|
||||
end)
|
||||
test("session replacement invalidates old entity even on same PlayerRef", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
hearth.sessions[p.name] = {}; h.tick_objects(); assert(h.count() == 0)
|
||||
hearth.presentation_step(p); assert(h.count() == 1 and #h.objects == 2)
|
||||
end)
|
||||
test("lost player state session or connected identity self-cleans", function()
|
||||
for _, missing in ipairs({"state", "session", "connection"}) do
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
if missing == "state" then hearth.players[p.name] = nil
|
||||
elseif missing == "session" then hearth.sessions[p.name] = nil
|
||||
else h.connected[p.name] = nil end
|
||||
h.tick_objects(); assert(h.count() == 0); hearth.presentation_step(p); assert(h.count() == 0)
|
||||
end
|
||||
end)
|
||||
test("waiting dead and death callback cleanup are idempotent", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
hearth.sessions[p.name].waiting = true; h.tick_objects(); assert(h.count() == 0)
|
||||
hearth.presentation_step(p); assert(h.count() == 0)
|
||||
hearth.sessions[p.name].waiting = false; hearth.presentation_step(p)
|
||||
p.hp = 0; h.tick_objects(); h.deaths[1](p); h.leaves[1](p); assert(h.count() == 0)
|
||||
hearth.presentation_step(p); assert(h.count() == 0)
|
||||
end)
|
||||
test("foreign activation and static restoration are rejected with no saved payload", function()
|
||||
local h = harness(); h.player()
|
||||
for _, data in ipairs({"", "inventory payload", "{owner=fixture}"}) do
|
||||
local obj = core.add_entity({y = 52}, h.entity_name, data)
|
||||
assert(obj.removed and h.def.get_staticdata(obj.lua) == "")
|
||||
end
|
||||
assert(h.count() == 0)
|
||||
end)
|
||||
test("harmless entity cannot collide select attack or create inventory", function()
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
local def, obj = h.def, h.objects[1]; local props = def.initial_properties
|
||||
assert(props.static_save == false and props.physical == false and props.pointable == false)
|
||||
assert(props.collide_with_objects == false and props.is_visible == false)
|
||||
assert(props.makes_footstep_sound == false and obj.lua.inventory == nil)
|
||||
assert(def.on_punch(obj.lua, p) == true); def.on_rightclick(obj.lua, p)
|
||||
assert(p.hp == 20 and h.count() == 1 and hearth.players[p.name].stamina == 100)
|
||||
end)
|
||||
test("detached redirected and removed objects are replaced without duplicates", function()
|
||||
for _, mode in ipairs({"detach", "redirect", "remove"}) do
|
||||
local h = harness(); local p = h.player(); hearth.presentation_step(p)
|
||||
local obj = h.objects[1]
|
||||
if mode == "remove" then obj:remove()
|
||||
elseif mode == "detach" then obj.parent = nil
|
||||
else obj.bone = "Head" end
|
||||
hearth.presentation_step(p); assert(h.count() == 1 and #h.objects == 2)
|
||||
end
|
||||
end)
|
||||
test("failed creation and silent attach failure leave no orphan", function()
|
||||
for _, failure in ipairs({"fail", "throw", "refuse_attach"}) do
|
||||
local h = harness(); local p = h.player(); h[failure] = true
|
||||
hearth.presentation_step(p); assert(h.count() == 0)
|
||||
h[failure] = false; hearth.presentation_step(p); assert(h.count() == 1)
|
||||
end
|
||||
end)
|
||||
test("multiple owners are bounded independently and shutdown cleans all", function()
|
||||
local h = harness(); local a, b = h.player("a"), h.player("b")
|
||||
for _ = 1, 100 do hearth.presentation_step(a); hearth.presentation_step(b) end
|
||||
assert(h.count() == 2 and #h.objects == 2)
|
||||
h.shutdowns[1](); h.shutdowns[1](); assert(h.count() == 0)
|
||||
end)
|
||||
test("presentation never writes persisted state or session fields", function()
|
||||
local h = harness(); local p = h.player()
|
||||
local state, session = hearth.players[p.name], hearth.sessions[p.name]
|
||||
setmetatable(state, {__newindex = function() error("state write") end})
|
||||
setmetatable(session, {__newindex = function() error("session write") end})
|
||||
for _, item in ipairs({"hearth:knife", "hearth:spear", ""}) do p.item = item; hearth.presentation_step(p) end
|
||||
h.leaves[1](p); assert(next(session) == nil and state.food == 85 and state.stamina == 100)
|
||||
assert(hearth.players[p.name] == state and hearth.sessions[p.name] == session)
|
||||
end)
|
||||
print("PASS " .. total .. " presentation tests")
|
||||
Reference in New Issue
Block a user