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

92 lines
4.5 KiB
Lua
Executable File

local M = dofile(assert(arg[1], "movement.lua path required"))
local total = 0
local function test(name, fn)
local ok, err = pcall(fn)
assert(ok, name .. ": " .. tostring(err)); total = total + 1
end
local function fixture(stamina)
return {}, {stamina = stamina or 100, food = 80, water = 80, wet = 0},
{aux1 = true, up = true}, {grounded = true, velocity = {x = 3, y = 0, z = 0}}
end
local function near(a, b) assert(math.abs(a - b) < 0.00001, tostring(a) .. " ~= " .. tostring(b)) end
test("zero negative and invalid dt cannot change speed or stamina", function()
local s, state, c, e = fixture()
for _, dt in ipairs({0, -1, 0 / 0}) do
local r = M.step(s, state, c, dt, e); near(r.speed, 1); near(state.stamina, 100)
end
end)
test("stall clamps speed and stamina without replay", function()
local s, state, c, e = fixture()
local r = M.step(s, state, c, 100, e)
near(r.speed, 1.3); near(state.stamina, 98.7)
end)
test("speed smoothly reaches target without overshoot", function()
local s, state, c, e = fixture()
local r = M.step(s, state, c, 0.05, e); near(r.speed, 1.15)
for _ = 1, 8 do r = M.step(s, state, c, 0.05, e); assert(r.speed <= 1.45) end
near(r.speed, 1.45)
c.aux1 = false; r = M.step(s, state, c, 0.05, e); near(r.speed, 1.3)
end)
test("exhaustion cannot stutter while aux remains held", function()
local s, state, c, e = fixture(8.5)
assert(not M.step(s, state, c, 0.1, e).sprinting and s.exhausted)
for _ = 1, 90 do assert(not M.step(s, state, c, 0.1, e).sprinting) end
assert(state.stamina >= 30)
c.aux1 = false; M.step(s, state, c, 0.1, e)
c.aux1 = true; assert(M.step(s, state, c, 0.1, e).sprinting)
end)
test("release remembers intent but still requires thirty reserve", function()
local s, state, c, e = fixture(8)
M.step(s, state, c, 0, e); c.aux1 = false; M.step(s, state, c, 0.1, e)
c.aux1 = true; state.stamina = 29.99
assert(not M.step(s, state, c, 0, e).sprinting)
state.stamina = 30; assert(M.step(s, state, c, 0, e).sprinting)
-- A release at zero must survive subsequent held frames below the threshold.
s, state, c, e = fixture(0); c.aux1 = false; M.step(s, state, c, 0.1, e)
c.aux1 = true
for _ = 1, 34 do M.step(s, state, c, 0.1, e) end
assert(M.step(s, state, c, 0, e).sprinting)
end)
test("backward diagonal backwards and sneak never sprint", function()
local s, state, c, e = fixture()
c.down = true; assert(not M.step(s, state, c, 0.1, e).sprinting)
c.down, c.up = false, false; assert(not M.step(s, state, c, 0.1, e).sprinting)
c.up, c.sneak = true, true; assert(not M.step(s, state, c, 0.1, e).sprinting)
end)
test("spear brace requires wield and takes priority over sprint", function()
local s, state, c, e = fixture(); c.RMB = true; e.weapon = "hearth:spear"
local r = M.step(s, state, c, 0.1, e)
assert(r.blocking and not r.sprinting); near(r.speed, 0.7)
r = M.step(s, state, c, 0.1, e); near(r.speed, 0.6)
e.weapon = "hearth:knife"; r = M.step(s, state, c, 0.1, e)
assert(not r.blocking and r.sprinting)
end)
test("real vertical velocity and support determine grounded", function()
local s, state, c, e = fixture(); e.velocity.y = 4
assert(not M.step(s, state, c, 0.1, e).grounded)
e.velocity.y = -4; assert(not M.step(s, state, c, 0.1, e).sprinting)
e.velocity.y, e.grounded = 0, false; assert(not M.step(s, state, c, 0.1, e).sprinting)
end)
test("wet speed does not change wetness or other survival values", function()
local s, state, c, e = fixture(); state.wet = 75; c.aux1 = false
local r = M.step(s, state, c, 0.1, e); near(r.speed, 0.95)
assert(state.wet == 75 and state.food == 80 and state.water == 80)
end)
test("swimming forbids sprint brace and uses swim pace", function()
local s, state, c, e = fixture(); e.swimming, e.weapon, c.RMB = true, "hearth:spear", true
local r = M.step(s, state, c, 0.1, e)
assert(r.swimming and not r.sprinting and not r.blocking); near(r.speed, 0.75)
end)
test("distance motion is velocity derived not a held direction", function()
local s, state, c, e = fixture(); e.velocity.x = 0
assert(not M.step(s, state, c, 0.1, e).moving)
e.velocity.z = 1; c.up = false; assert(M.step(s, state, c, 0.1, e).moving)
end)
test("recovery stays bounded and respects depleted needs", function()
local s, state, c, e = fixture(50); c.aux1 = false; state.food = 0
M.step(s, state, c, 0.1, e); near(state.stamina, 50.2)
state.stamina, state.food = 99.9, 80; M.step(s, state, c, 0.1, e); near(state.stamina, 100)
end)
assert(total == 12, "Unexpected movement regression count")
print("PASS " .. total .. " movement tests")