-- 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")