This commit is contained in:
2026-11-05 09:57:11 -05:00
parent d34388bf2c
commit 89e392cc2e
81 changed files with 12410 additions and 1135 deletions
+89 -16
View File
@@ -33,9 +33,9 @@ local function reset()
auth=auth+1; assert(q==p and player:is_player() and action=="use"); return allowed
end}
end
local inv={room_for_item=function(_,list,item) assert(list=="main" and item=="hearth:berries 2"); return room end,
local inv={room_for_item=function(_,list,item) assert(list=="main" and (item=="hearth:berries 2" or item=="hearth:berries 3")); return room end,
add_item=function(_,list,item)
assert(list=="main" and item=="hearth:berries 2"); added=added+2-leftover
assert(list=="main" and (item=="hearth:berries 2" or item=="hearth:berries 3")); added=added+tonumber(item:match("(%d+)$"))-leftover
return {get_count=function() return leftover end}
end}
local player={is_player=function() return true end,get_inventory=function() return inv end}
@@ -56,12 +56,12 @@ local function register()
B.register_nodes(function(id,def) assert(not defs["hearth:"..id],id); defs["hearth:"..id]=def end)
end
local function bush(id) world[key(p)]={name="hearth:"..(id or "bilberry")} end
test("exactly eighty disjoint real taxa in eight ordered groups of ten",function()
assert(#B.groups==8 and #B.catalogue==80 and #B.list()==80)
test("exactly one hundred disjoint real taxa in eight uneven groups",function()
assert(#B.groups==8 and #B.catalogue==100 and #B.list()==100)
assert(table.concat(B.groups,",")=="trees,shrubs,grasses,herbs,ferns_mosses,fungi,aquatic,lichens")
local ids,latin={},{},{}
for _,g in ipairs(B.groups) do
assert(#B.list(g)==10,g)
assert(#B.list(g)==({trees=13,shrubs=14,grasses=10,herbs=22,ferns_mosses=11,fungi=10,aquatic=10,lichens=10})[g],g)
for _,r in ipairs(B.list(g)) do
assert(not ids[r.id] and not latin[r.latin]); ids[r.id]=true; latin[r.latin]=true
assert(r.common~="" and r.latin:match("^[A-Z][a-z]+ [a-z%-]+$"),r.latin)
@@ -69,6 +69,21 @@ test("exactly eighty disjoint real taxa in eight ordered groups of ten",function
end
end
end)
test("twenty expansion identities match the lifecycle contract with nitrogen and flower seasons",function()
local L=dofile(target:gsub("[^/\\]+$","lifecycle.lua"))
local ids={"dandelion","daisy","red_clover","birdsfoot_trefoil","ribwort_plantain","selfheal","knapweed",
"oxeye_daisy","field_scabious","teasel","cow_parsley","garlic_mustard","honeysuckle","ivy","gorse","broom",
"crab_apple","wild_cherry","field_maple","holly_fern"}
assert(#ids==20)
for _,id in ipairs(ids) do
local r=B.get(id); local p=L.plant(id)
assert(r and p and r.latin==p.latin and r.group==p.group and not r.food,id)
end
for _,id in ipairs({"red_clover","birdsfoot_trefoil","gorse","broom"}) do assert(B.nitrogen(id,0)==1) end
assert(B.pollinator("ivy","Autumn") and not B.pollinator("ivy","Spring"))
assert(B.pollinator("gorse","Winter") and not B.pollinator("ribwort_plantain","Summer"))
assert(B.seeding("garlic_mustard","Summer") and not B.seeding("garlic_mustard","Spring"))
end)
test("legacy IDs map exactly without counting generic bushes or patches",function()
local expected={oak="Quercus robur",birch="Betula pendula",pine="Pinus sylvestris",willow="Salix alba",
alder="Alnus glutinosa",clover="Trifolium repens",nettle="Urtica dioica",heather="Calluna vulgaris",
@@ -94,7 +109,7 @@ test("catalogue snapshots and getters cannot alter internal policy",function()
assert(B.get("oak").latin=="Quercus robur" and not B.has_tag("oak","pollinator"))
assert(B.get("oak").niche.light[1]==4)
end)
test("all eighty species have a passing season niche vector",function()
test("all one hundred species have a passing season niche vector",function()
for _,r in ipairs(B.list()) do assert(B.suitable(r.id,habitat(r.id),next(r.seasons)),r.id) end
end)
test("each niche dimension rejects out-of-window values for all species",function()
@@ -213,7 +228,7 @@ test("nitrogen is capped and fungi transfer only an existing positive budget",fu
assert(B.resin("spruce") and B.resin("pine_log") and not B.resin("oak"))
assert(writes==0)
end)
test("register exactly sixty-two missing understory nodes, never legacy or trees",function()
test("register exactly seventy-nine missing understory nodes, never legacy or trees",function()
register(); local count=0
for name,def in pairs(defs) do
count=count+1; local r=B.get(name)
@@ -222,7 +237,7 @@ test("register exactly sixty-two missing understory nodes, never legacy or trees
assert(def.description==r.common.." ("..r.latin..")")
assert(not def.walkable and not def.buildable_to and def.groups.hearth_ecosystem==1)
end
assert(count==62); register(); local again=0; for _ in pairs(defs) do again=again+1 end; assert(again==count)
assert(count==79); register(); local again=0; for _ in pairs(defs) do again=again+1 end; assert(again==count)
end)
test("existing registered definitions are preserved and construction stays late-bound",function()
local marker={description="external owner"}; defs["hearth:bilberry"]=marker
@@ -253,17 +268,19 @@ test("harvest checks permission before metadata or inventory and returns at most
bush(); allowed=false; local ok,reason=B.harvest(p,player)
assert(not ok and reason=="denied" and auth==1 and writes==0 and added==0)
allowed=true; ok,reason=B.harvest(p,player)
assert(ok and reason==2 and added==2 and tonumber(fields[key(p)].botany_ready_at)==time+86400)
assert(ok and reason==2 and added==2 and tonumber(fields[key(p)].botany_ready_at)==time+365.2425*86400)
end)
test("cooldown has no catchup and survives a new module instance",function()
bush(); assert(B.harvest(p,player)); assert(not B.harvest(p,player) and added==2)
policies={} -- a fresh factory may reinstall the same seasonal policy
B=factory(H,A); assert(not B.harvest(p,player)); time=time+86400*30
assert(not B.harvest(p,player) and added==2)
time=time+86400*400
assert(B.harvest(p,player) and added==4); assert(not B.harvest(p,player) and added==4)
end)
test("full inventory does not consume stock or start cooldown",function()
bush(); room=false; local ok,reason=B.harvest(p,player)
assert(not ok and reason=="inventory" and writes==0 and added==0)
assert(not ok and reason=="inventory" and fields[key(p)].botany_ready_at=="" and added==0)
room=true; leftover=2; assert(not B.harvest(p,player) and fields[key(p)].botany_ready_at=="")
leftover=1; ok,reason=B.harvest(p,player); assert(ok and reason==1 and added==1)
assert(not B.harvest(p,player))
@@ -296,11 +313,11 @@ test("rightclick returns item stack and reentrant inventory cannot duplicate",fu
local stack={}; assert(defs["hearth:bilberry"].on_rightclick(p,{},player,stack)==stack)
inv.add_item=original; assert(added==2)
end)
test("the actual position selector can return each of all eighty exact node IDs",function()
test("the actual position selector can return each of all hundred exact node IDs",function()
for index,r in ipairs(B.list()) do
local h=habitat(r.id); h.include_trees=true
local x=0
while (x*17+7)%80~=index-1 do x=x+1; assert(x<80) end
while (x*17+7)%100~=index-1 do x=x+1; assert(x<100) end
local k,selected=B.choose({x=x,y=1,z=0},h,next(r.seasons))
assert(k==r.node:sub(8) and selected.id==r.id,r.id)
assert(B.suitable(k,h,next(r.seasons)))
@@ -317,7 +334,7 @@ test("submerged candidates work as separate air-cell stands, never as liquid nod
assert(not def.buildable_to and not def.walkable and def.drawtype=="plantlike")
end
end)
test("all sixty-two new understory policies match actual activity and dormant art",function()
test("all seventy-nine new understory policies match actual activity and dormant art",function()
local count=0
for _,r in ipairs(B.list()) do
local policy=policies[r.id]
@@ -335,7 +352,7 @@ test("all sixty-two new understory policies match actual activity and dormant ar
end
end
end
assert(count==62)
assert(count==79)
policies.holly.active.Winter=false; assert(B.active("holly","Winter"))
assert(next(policies.juniper.definitions)==nil and next(policies.holly.definitions)==nil)
end)
@@ -354,7 +371,7 @@ test("harvest samples one validated epoch before climate and uses the same coold
return {season="Summer"}
end
assert(B.harvest(p,player) and clock_reads==1 and climate_calls==1)
assert(tonumber(fields[key(p)].botany_ready_at)==1782043200+86400)
assert(tonumber(fields[key(p)].botany_ready_at)==1782043200+365.2425*86400)
end)
test("only exact owned seasonal aliases harvest in an active fruit season",function()
bush("bilberry_winter"); season="Winter"
@@ -375,6 +392,62 @@ test("southern berry seasons use the solar model at the validated epoch",functio
bush(); time=1782043200
local ok,reason=B.harvest(p,player); assert(not ok and reason=="season" and writes==0)
time=1797854400; assert(B.harvest(p,player) and added==2)
assert(tonumber(fields[key(p)].botany_ready_at)==time+86400)
assert(tonumber(fields[key(p)].botany_ready_at)==time+365.2425*86400)
end)
test("shared annual fruit readiness rejects an animal debit and has no daily refill",function()
bush(); local m=A.get_meta(p)
m:set_string("botany_ready_at",tostring(time+365.2425*86400))
assert(not B.harvest(p,player) and added==0)
time=time+86400; assert(not B.harvest(p,player))
time=time+(365.2425-1)*86400; assert(B.harvest(p,player) and added==2)
end)
test("one unexpired pollination credit adds only one berry and is spent once",function()
bush(); local m=A.get_meta(p)
m:set_string("succ_pollinated_until",tostring(time+86400))
local ok,n=B.harvest(p,player); assert(ok and n==3 and added==3)
assert(m:get_string("succ_pollinated_until")=="0")
assert(not B.harvest(p,player) and added==3)
time=time+365.2425*86400; ok,n=B.harvest(p,player)
assert(ok and n==2 and added==5)
end)
test("expired pollination is not a bonus and invalid credits fail closed",function()
bush(); local m=A.get_meta(p)
for _,v in ipairs({"broken","-1",tostring(time+86401),"inf"}) do
m:set_string("succ_pollinated_until",v)
local before=writes; assert(not B.harvest(p,player) and added==0 and writes==before)
end
m:set_string("succ_pollinated_until",tostring(time))
local ok,n=B.harvest(p,player); assert(ok and n==2 and added==2)
end)
test("empty fruit cannot be manufactured by fresh pollination",function()
bush(); local m=A.get_meta(p)
m:set_string("botany_ready_at",tostring(time+86400))
m:set_string("succ_pollinated_until",tostring(time+86400))
local before=writes; assert(not B.harvest(p,player) and writes==before and added==0)
assert(tonumber(m:get_string("succ_pollinated_until"))==time+86400)
end)
test("inventory rejection restores fruit and credit; partial delivery spends both",function()
bush(); local m=A.get_meta(p); local pollen=tostring(time+86400)
m:set_string("succ_pollinated_until",pollen)
room=false; assert(not B.harvest(p,player))
assert(m:get_string("botany_ready_at")=="" and m:get_string("succ_pollinated_until")==pollen)
room=true; leftover=3; assert(not B.harvest(p,player))
assert(m:get_string("botany_ready_at")=="" and m:get_string("succ_pollinated_until")==pollen)
leftover=2; local ok,n=B.harvest(p,player); assert(ok and n==1 and added==1)
assert(m:get_string("succ_pollinated_until")=="0" and not B.harvest(p,player))
end)
test("both inventory callbacks observe reserved fruit and pollination credit",function()
bush(); local m=A.get_meta(p); m:set_string("succ_pollinated_until",tostring(time+86400))
local old_room,old_add=inv.room_for_item,inv.add_item
local function check()
assert(not B.harvest(p,player))
assert(m:get_string("succ_pollinated_until")=="0")
end
inv.room_for_item=function(...) check(); return old_room(...) end
inv.add_item=function(...) check(); return old_add(...) end
local ok,n=B.harvest(p,player)
inv.room_for_item,inv.add_item=old_room,old_add
assert(ok and n==3 and added==3)
end)
assert(passed==44,"Unexpected focused botany integration count")
print("BOTANY_SPEC_PASS "..passed)