Files
last-hearth/tests/seasons_spec.lua
T
2026-11-05 09:57:11 -05:00

247 lines
13 KiB
Lua
Executable File

-- Standalone Lua 5.1+: controlled names, actual-epoch climate and lossless swaps.
local target = assert(arg[1], "seasons.lua path required")
local real_time, now, passed = os.time, 1782043200, 0
os.time = function() return now end
local season, world, data, missing, protected, swaps, meta_writes, lbm
local p = {x=0,y=1,z=0}
local hearth, api, seasons
local function key(q) return q.x..":"..q.y..":"..q.z end
local function reset()
season, world, data, missing, protected, swaps, meta_writes = "Summer", {}, {}, false, false, 0, 0
api = {registered_nodes={}}
api.get_node_or_nil = function(q) return not missing and world[key(q)] or nil end
api.swap_node = function(q,n) swaps=swaps+1; world[key(q)]=n end
api.get_meta = function() error("appearance must not access metadata") end
api.register_node = function(name, def) assert(not api.registered_nodes[name]); api.registered_nodes[name]=def end
api.register_lbm = function(def) lbm=def end
hearth = {climate_at=function(epoch) assert(epoch==now); return {season=season} end,
external_protected=function() return protected end,
claim_owner_at=function() return "builder" end}
seasons = dofile(target)(hearth,api)
hearth.seasons = seasons
for _, name in ipairs(seasons.names()) do
local kind=seasons.kind(name)
local base="hearth:"..kind
api.registered_nodes[base] = api.registered_nodes[base] or {
drawtype=(kind=="young_tree" or kind=="mature_tree" or kind=="tree_seedling") and "mesh" or "plantlike",
groups={flora=1}, drop="hearth:sticks", tiles={"base.png"},
can_dig=function() return false end, on_rightclick=function() end,
after_place_node=function() end, collision_box={type="fixed"},
}
end
seasons.register()
end
local function put(name)
world[key(p)]={name="hearth:"..name,param1=7,param2=3}
end
local function name() return world[key(p)].name end
local function test(label,fn)
reset()
local ok,err=pcall(fn); assert(ok,label..": "..tostring(err))
passed=passed+1; print("ok "..passed.." - "..label)
end
test("all seasons use explicit variants and summer restores the canonical tree", function()
put("mature_tree")
for _, s in ipairs({"Spring","Autumn","Winter","Summer"}) do
season=s; assert(seasons.reconcile(p))
assert(name()=="hearth:mature_tree"..(s=="Summer" and "" or "_"..s:lower()))
assert(world[key(p)].param1==7 and world[key(p)].param2==3)
assert(not seasons.reconcile(p), "same-season appearance is idempotent")
end
assert(swaps==4)
end)
test("winter bare mesh returns to the base mesh in spring", function()
for _, kind in ipairs({"young_tree","mature_tree"}) do
local winter=api.registered_nodes["hearth:"..kind.."_winter"]
local spring=api.registered_nodes["hearth:"..kind.."_spring"]
assert(winter.mesh=="hearth_"..kind.."_winter.obj")
assert(spring.mesh=="hearth_"..kind..".obj")
assert(winter.tiles[1]=="hearth_tree_atlas_winter.png")
end
end)
test("legacy mapgen leaves recolor without changing timber or destroying canopy", function()
put("leaves"); season="Autumn"; lbm.action(p)
assert(name()=="hearth:leaves_autumn")
season="Winter"; lbm.action(p); assert(name()=="hearth:leaves_winter")
put("log"); assert(not seasons.reconcile(p) and name()=="hearth:log")
assert(not seasons.kind("hearth:foreign_tree_winter"):match("^mature_tree$"))
end)
test("perennials and flowers rest in the correct seasons without adding needless variants", function()
for _, kind in ipairs({"meadow_grass","fern","wildflowers"}) do
put(kind); season="Winter"; assert(seasons.reconcile(p))
assert(api.registered_nodes[name()].tiles[1]=="hearth_"..kind.."_dormant.png")
season="Spring"; assert(seasons.reconcile(p) and name()=="hearth:"..kind)
end
put("wildflowers"); season="Autumn"; assert(seasons.reconcile(p))
assert(name()=="hearth:wildflowers_autumn" and not seasons.available("wildflowers"))
assert(not api.registered_nodes["hearth:fern_spring"])
assert(seasons.available("litter") and seasons.available("fungi"))
end)
test("food patches only bloom in their season and stay occupied when dormant", function()
for _, s in ipairs({"Spring","Summer","Autumn","Winter"}) do
season=s
assert(seasons.available("spring_greens_patch")== (s=="Spring"))
assert(seasons.available("autumn_mushrooms_patch")== (s=="Autumn"))
assert(seasons.name_for("spring_greens_patch")=="hearth:spring_greens_patch"..(s=="Spring" and "_spring" or ""))
end
end)
test("callbacks, authorization, drops and geometry survive every variant", function()
for _, name in ipairs(seasons.names()) do
local kind=seasons.kind(name)
local base,def=api.registered_nodes["hearth:"..kind],api.registered_nodes[name]
assert(def.can_dig==base.can_dig and def.on_rightclick==base.on_rightclick)
assert(def.after_place_node==base.after_place_node and def.collision_box==base.collision_box)
assert(def.drop==base.drop and def.groups.flora==1)
end
end)
test("claimed handplaced flora can recolor with no metadata access or harvest refill", function()
data={succ_age="8412",succ_natural="1",succ_birth_at="1700000000",succ_lifecycle_version="1",
succ_seed_epoch=tostring(now+86400),succ_harvest_ready_at=tostring(now+86400),player_placed="1",owner="builder"}
local reference=data
put("spring_greens_patch"); season="Spring"; assert(seasons.reconcile(p))
season="Winter"; assert(seasons.reconcile(p))
assert(data==reference and data.succ_age=="8412" and data.player_placed=="1")
assert(data.succ_birth_at=="1700000000" and data.succ_lifecycle_version=="1" and data.succ_seed_epoch==tostring(now+86400))
assert(data.succ_harvest_ready_at==tostring(now+86400) and meta_writes==0)
end)
test("unloaded, ignore, protected and uncontrolled names cause no writes", function()
season="Winter"; put("leaves"); missing=true
assert(not seasons.reconcile(p)); missing=false; protected=true
assert(not seasons.reconcile(p)); protected=false
world[key(p)]={name="ignore"}; assert(not seasons.reconcile(p))
put("foreign_tree"); assert(not seasons.reconcile(p))
assert(swaps==0)
end)
test("hemisphere seasons come from the existing solar model, not the host month", function()
local model=dofile(target:gsub("[^/\\]+$","model.lua"))
local south=model.solar(now,-45,0)
local north=model.solar(now,45,0)
assert(north.season=="Summer" and south.season=="Winter")
hearth.climate_at=function(epoch) assert(epoch==now); return model.solar(epoch,-45,0) end
assert(seasons.season()=="Winter")
put("young_tree"); assert(seasons.reconcile(p))
assert(name()=="hearth:young_tree_winter")
now=1797854400 -- December, southern summer.
assert(seasons.reconcile(p) and name()=="hearth:young_tree")
end)
test("extension is available before registration and missing optional bases are safe", function()
seasons.add_variants("optional_plant",{Winter={texture="optional_dormant.png"}})
seasons.register()
assert(not api.registered_nodes["hearth:optional_plant_winter"])
api.registered_nodes["hearth:optional_plant"]={groups={flora=1},drop="",tiles={"optional.png"}}
seasons.register()
assert(api.registered_nodes["hearth:optional_plant_winter"].tiles[1]=="optional_dormant.png")
assert(seasons.kind("hearth:optional_plant_winter")=="optional_plant")
end)
test("full block stage variants retain bark faces and never acquire sculpted meshes", function()
local stage={drawtype="normal",tiles={"end.png","end.png","bark.png"},paramtype2="facedir",groups={tree=1},drop="hearth:log"}
for _,kind in ipairs({"young_tree","mature_tree","oak_young","oak_mature"}) do
api.registered_nodes["hearth:"..kind]=stage
seasons.add_variants(kind,{Winter={texture="canopy_atlas.png",mesh="obsolete_tree.obj"}})
api.registered_nodes["hearth:"..kind.."_winter"]=nil
end
seasons.register()
for _,kind in ipairs({"young_tree","mature_tree","oak_young","oak_mature"}) do
local d=api.registered_nodes["hearth:"..kind.."_winter"]
assert(d.drawtype=="normal" and d.mesh==nil and d.tiles==stage.tiles)
assert(d.tiles[3]=="bark.png" and d.paramtype2=="facedir" and d.drop=="hearth:log")
end
end)
test("all twelve original calendar months retain hemisphere-aware seasons", function()
local model=dofile(target:gsub("[^/\\]+$","model.lua"))
local north={"Winter","Winter","Spring","Spring","Spring","Summer","Summer","Summer","Autumn","Autumn","Autumn","Winter"}
local opposite={Winter="Summer",Spring="Autumn",Summer="Winter",Autumn="Spring"}
for month=1,12 do
now=real_time({year=2026,month=month,day=15,hour=12,min=0,sec=0})
for _,latitude in ipairs({45,-45}) do
hearth.climate_at=function(epoch) assert(epoch==now); return model.solar(epoch,latitude,0) end
local expected=latitude>0 and north[month] or opposite[north[month]]
assert(seasons.season()==expected)
assert(seasons.available("wildflowers")== (expected=="Spring" or expected=="Summer"))
end
end
end)
local function install_botany()
local botany=dofile(target:gsub("[^/\\]+$","botany.lua"))(hearth,api)
botany.register_nodes(function(id,def) api.register_node("hearth:"..id,def) end)
seasons.register()
return botany
end
test("actual botany factory installs all new policies before seasonal registration", function()
local b=install_botany(); local count=0
for _,r in ipairs(b.list()) do
if not r.existing and r.group~="trees" then
count=count+1
for _,s in ipairs({"Spring","Summer","Autumn","Winter"}) do
season=s; local active=b.active(r.id,s)
local expected=r.node..(active and "" or "_"..s:lower())
assert(seasons.name_for(r.id,now)==expected,r.id..s)
assert(seasons.available(r.id,now)==active,r.id..s)
local def=assert(api.registered_nodes[expected])
assert(def.description==r.common.." ("..r.latin..")")
assert(def.tiles[1]=="hearth_botany_"..r.id..(active and "" or "_dormant")..".png")
assert(seasons.kind(expected)==r.id and b.kind(expected)==r.id)
end
end
end
assert(count==79)
end)
test("botany leaves diversity-owned taxa and block tree policies untouched", function()
local diversity=dofile(target:gsub("[^/\\]+$","diversity.lua"))(hearth,api)
-- Production shares exactly this seasonal service with both factories.
assert(diversity.plants and #diversity.plants==8)
local before={}
for _,id in ipairs(diversity.plants) do
before[id]={}
for _,s in ipairs({"Spring","Summer","Autumn","Winter"}) do
season=s; before[id][s]={seasons.name_for(id,now),seasons.available(id,now)}
end
end
install_botany()
for id,states in pairs(before) do for s,state in pairs(states) do
season=s; assert(seasons.name_for(id,now)==state[1] and seasons.available(id,now)==state[2])
end end
for _,id in ipairs({"holly","juniper","harts_tongue","polypody","turkey_tail","oakmoss"}) do
season="Winter"; assert(seasons.available(id,now) and seasons.name_for(id,now)=="hearth:"..id)
assert(not api.registered_nodes["hearth:"..id.."_winter"])
end
end)
test("dormant aquatic variants retain geometry callbacks and separate real water", function()
local b=install_botany()
for _,r in ipairs(b.list("aquatic")) do
local base=api.registered_nodes[r.node]
world[key(p)]={name=r.node,param1=7,param2=3}
world["0:0:0"]={name="hearth:water",param2=0}
local water=world["0:0:0"]
season="Winter"; assert(seasons.reconcile(p))
local def=api.registered_nodes[name()]
assert(def.tiles[1]=="hearth_botany_"..r.id.."_dormant.png")
assert(def.inventory_image==def.tiles[1] and def.wield_image==def.tiles[1])
assert(def.drawtype==base.drawtype and def.node_box==base.node_box and def.selection_box==base.selection_box)
assert(def.on_place==base.on_place and def.after_place_node==base.after_place_node)
assert(def.drop==base.drop and not def.walkable and not def.buildable_to)
assert(not def.liquidtype and not def.groups.water)
assert(def.groups.hearth_submerged==base.groups.hearth_submerged)
if def.on_place then local stack={}; assert(def.on_place(stack,nil,{})==stack) end
assert(world["0:0:0"]==water and water.name=="hearth:water")
assert(world[key(p)].param1==7 and world[key(p)].param2==3)
season="Summer"; assert(seasons.reconcile(p) and name()==r.node)
end
end)
test("berry seasonal swaps preserve cooldown provenance and harvest authorization", function()
install_botany()
local base=api.registered_nodes["hearth:bilberry"]
data={botany_ready_at=tostring(now+86400),player_placed="1",owner="builder"}
local reference=data
put("bilberry"); season="Winter"; assert(seasons.reconcile(p))
local def=api.registered_nodes[name()]
assert(def.drop=="hearth:sticks" and def.on_rightclick==base.on_rightclick)
assert(def.after_place_node==base.after_place_node and def.groups.not_in_creative_inventory==1)
assert(not seasons.available(name(),now))
season="Summer"; assert(seasons.reconcile(p) and name()=="hearth:bilberry")
assert(data==reference and data.botany_ready_at==tostring(now+86400) and data.owner=="builder")
assert(data.player_placed=="1" and meta_writes==0)
end)
os.time=real_time
print("LAST_HEARTH_SEASONS_PASS "..passed)