Files
2026-11-03 12:41:09 -05:00

119 lines
5.5 KiB
Lua
Executable File

-- M2 bedroll interaction and scheduler tests; isolated engine interface.
local target = assert(arg[1], "runtime.lua path required")
local total = 0
local function test(name, fn)
local old_core, old_hearth, old_vector = core, hearth, vector
local ok, err = pcall(fn)
core, hearth, vector = old_core, old_hearth, old_vector
assert(ok, name .. ": " .. tostring(err)); total = total + 1
end
local function harness()
local h = {steps = {}, connected = {}, saved = 0, messages = {}, clock = 0,
nodes = {ground = {walkable = true}, air = {walkable = false},
["hearth:bedroll"] = {walkable = false, groups = {snappy = 3}}}}
core = {
settings = {get = function(_, k) if k == "mapgen_limit" then return "512" end end},
registered_nodes = h.nodes,
get_node = function(p) return {name = p.x == 0 and p.y == 3 and p.z == 0
and "hearth:bedroll" or (p.y < 4 and "ground" or "air")} end,
get_node_or_nil = function(p) return {name = p.x == 0 and p.y == 3 and p.z == 0
and "hearth:bedroll" or (p.y < 4 and "ground" or "air")} end,
get_connected_players = function() return h.connected end,
register_globalstep = function(fn) h.steps[#h.steps + 1] = fn end,
get_us_time = function() return h.clock * 1000000 end,
find_node_near = function() return nil end,
add_particlespawner = function() end,
}
vector = {offset = function(p, x, y, z) return {x=p.x+x,y=p.y+y,z=p.z+z} end}
hearth = {players = {}, sessions = {}, weather = "clear"}
hearth.movement = {delta = function(dt) return math.max(0, math.min(dt, .1)) end,
step = function(s, state, controls, dt)
s.sprinting = false; s.blocking = false
return {speed = 1, moving = false, grounded = true, swimming = false}
end}
hearth.clock_update = function() end
hearth.model = {needs = function(state)
local copy = {}; for k,v in pairs(state) do copy[k] = v end
return copy, 0
end}
hearth.environment = function()
return {temperature=15,shelter=h.shelter or .75,heat=0,smoke=false,rain=h.rain or false,roof=true}
end
hearth.construction = {allowed = function(pos, p)
return not h.denied and p:get_player_name() == "resting-player"
and math.abs(pos.x-p:get_pos().x) <= 6
end}
hearth.feedback = function(_, msg, kind) h.messages[#h.messages+1]={msg=msg,kind=kind} end
hearth.feedback_step = function() end
hearth.update_hud = function() end
hearth.player_save = function() h.saved=h.saved+1; return true end
hearth.safe_arrival = function() end
hearth.texture = function(c) return c end
function h.player()
local meta = {values={}}
function meta:set_string(k,v) self.values[k]=v end
function meta:set_int(k,v) self.values[k]=v end
function meta:get_int(k) return self.values[k] or 0 end
local p = {pos={x=0,y=4,z=0}, velocity={x=0,y=0,z=0}, hp=20, controls={}}
function p:is_player() return true end
function p:get_player_name() return "resting-player" end
function p:get_pos() return self.pos end
function p:get_velocity() return self.velocity end
function p:get_hp() return self.hp end
function p:get_player_control() return self.controls end
function p:get_wielded_item() return {get_name=function() return "" end} end
function p:get_meta() return meta end
function p:set_physics_override() end
function p:set_clouds() end
h.connected={p}; hearth.players[p:get_player_name()]={food=80,water=80,warmth=50,wet=0,stamina=20}
hearth.sessions[p:get_player_name()]={save=0,damage=0,waiting=false}
return p, meta
end
dofile(target)
function h.tick(dt)
h.clock=h.clock+dt
for _,step in ipairs(h.steps) do step(dt) end
end
return h
end
test("rest requires a living joined player, permitted placed bedroll and roof", function()
local h=harness(); local p=h.player()
assert(hearth.start_rest(p,{x=0,y=3,z=0}))
hearth.sessions["resting-player"].resting=nil; h.denied=true
assert(not hearth.start_rest(p,{x=0,y=3,z=0}))
h.denied=false; h.shelter=0
assert(not hearth.start_rest(p,{x=0,y=3,z=0}))
h.shelter=.5; h.rain=true
assert(not hearth.start_rest(p,{x=0,y=3,z=0}))
end)
test("five uninterrupted sheltered seconds grant warmth without directly changing stamina", function()
local h=harness(); local p=h.player(); local s=hearth.players[p:get_player_name()]
assert(hearth.start_rest(p,{x=0,y=3,z=0}))
for _=1,49 do h.tick(.1) end
assert(hearth.sessions[p:get_player_name()].resting and s.stamina==20)
h.tick(.1)
assert(not hearth.sessions[p:get_player_name()].resting)
for _=1,10 do h.tick(.1) end -- warmth is applied on the bounded needs tick
s = hearth.players[p:get_player_name()]
assert(s.stamina==20 and s.warmth==58)
assert(h.saved==1 and h.messages[#h.messages].kind=="success")
end)
test("movement, unsafe weather and damage interrupt without granting rest", function()
for _,mode in ipairs({"move","rain","damage"}) do
local h=harness(); local p=h.player(); local s=hearth.players[p:get_player_name()]
assert(hearth.start_rest(p,{x=0,y=3,z=0}))
for _=1,10 do h.tick(.1) end
if mode=="move" then p.pos.x=1 elseif mode=="rain" then h.shelter=.25; h.rain=true else p.hp=19 end
for _=1,3 do h.tick(.1) end -- spatial shelter is revalidated at most every 250ms
assert(not hearth.sessions[p:get_player_name()].resting and s.stamina<38 and s.warmth<58)
end
end)
test("rest cooldown and completion cannot be used as free spam", function()
local h=harness(); local p=h.player()
assert(hearth.start_rest(p,{x=0,y=3,z=0}))
for _=1,50 do h.tick(.1) end
assert(not hearth.start_rest(p,{x=0,y=3,z=0}))
end)
print("PASS "..total.." M2 bedroll rest tests")