local M = dofile(assert(arg[1], "model.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 -- Explicit UTC fixtures; os.time(table) would depend on the machine timezone. local june_noon, december_noon = 1782043200, 1797854400 test("reject malformed locations", function() assert(not M.location("bad", 0)); assert(not M.location(91, 0)) assert(not M.location(0, 181)); assert(not M.location(0 / 0, 0)) assert(not M.location(math.huge, 0)); assert(M.location(0, 0)) end) test("latitude changes solstice elevation", function() local summer = M.solar(june_noon, 45, 0) local winter = M.solar(december_noon, 45, 0) assert(summer.elevation > winter.elevation + 40) assert(summer.temperature > winter.temperature) end) test("opposite hemispheres reverse seasons", function() assert(M.solar(june_noon, 45, 0).season == "Summer") assert(M.solar(june_noon, -45, 0).season == "Winter") end) test("longitude changes solar time", function() local a, b = M.solar(june_noon, 0, 0), M.solar(june_noon, 0, 90) assert(math.abs((b.hour - a.hour) % 24 - 6) < 0.01) end) test("polar night and polar day stay finite", function() assert(M.solar(june_noon, 90, 0).daylight) assert(not M.solar(december_noon, 90, 0).daylight) assert(M.solar(june_noon, -90, 0).timeofday >= 0) end) test("deterministic weather across restarts", function() assert(M.weather(june_noon, 30, -70) == M.weather(june_noon, 30, -70)) end) local initial = {food = 80, water = 80, warmth = 80, wet = 20, stamina = 70} test("shelter dries while exposed rain wets", function() local dry = M.needs(initial, {temperature = 5, shelter = 1, rain = true}, 1) local wet = M.needs(initial, {temperature = 5, shelter = 0, rain = true}, 1) assert(dry.wet < initial.wet and wet.wet > initial.wet) assert(initial.wet == 20) -- no hidden mutation end) test("rain cloak slows wetting, adds warmth, and a rack accelerates sheltered drying", function() local cold = {food = 80, water = 80, warmth = 68, wet = 20, stamina = 70} local bare = M.needs(cold, {temperature = 5, shelter = 0, rain = true}, 1) local coated = M.needs(cold, {temperature = 5, shelter = 0, rain = true, clothing = true}, 1) assert(coated.wet < bare.wet and coated.warmth > bare.warmth) local rack = M.needs(initial, {temperature = 5, shelter = 1, rain = true, drying = true}, 1) assert(rack.wet < M.needs(initial, {temperature = 5, shelter = 1, rain = true}, 1).wet) end) test("rest recovers sprint reserve", function() local rest = M.needs(initial, {temperature = 20}, 1) local run = M.needs(initial, {temperature = 20, sprinting = true}, 1) assert(rest.stamina > initial.stamina and run.stamina < initial.stamina) end) test("large pauses never replay offline starvation", function() local state = M.needs(initial, {temperature = -20}, 100000) assert(state.food > 79 and state.water > 79) end) test("negative elapsed does not reverse needs", function() local state = M.needs(initial, {temperature = -20}, -1) assert(state.food == initial.food) end) test("fuel expires and is bounded", function() assert(M.fuel_remaining(100, 101) == 0) assert(M.fuel_remaining(10000000, 0) == 600) end) print("PASS " .. total .. " model tests")