247 lines
13 KiB
Lua
Executable File
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)
|