-- Standalone Lua 5.1+; deterministic loaded-world fixture, no engine required. local target = assert(arg[1], "succession.lua path required") local original_time, passed = os.time, 0 local now, world, fields, protected, claims, missing, light, humidity, temperature, season local api, hearth, sim, probes, writes, reads, players, lbm, real_positions local p = {x=0,y=1,z=0} local function key(q) return q.x .. ":" .. q.y .. ":" .. q.z end local function pos(x,y,z) return {x=x or 0,y=y or 1,z=z or 0} end local function metadata(q) local k=key(q); fields[k]=fields[k] or {} local f=fields[k] return {get_string=function(_,name) return f[name] or "" end, set_string=function(_,name,value) f[name]=tostring(value) end, to_table=function() return {fields=f} end} end local function put(q,k,natural) world[key(q)]={name=k=="air" and k or "hearth:"..k} if natural then metadata(q):set_string("succ_natural","1") end end local function name(q) return api.get_node_or_nil(q).name end local function load() probes=0 sim=dofile(target)(hearth,api) end local function reset() p=pos(0,1,0) now,world,fields,protected,claims,missing=1000000,{},{},{},{},{} light,humidity,temperature,season=15,60,15,"Summer" writes,reads,players,real_positions=0,0,true,false api={} function api.get_meta(q) return metadata(q) end function api.get_node_or_nil(q) reads=reads+1 if missing[key(q)] then return nil end return world[key(q)] or {name=q.y==0 and "hearth:grass" or "air"} end function api.swap_node(q,n) writes=writes+1; world[key(q)]=n end function api.get_node_light() return light end function api.get_biome_data() return {humidity=humidity} end function api.register_globalstep(fn) api.step=fn end function api.register_lbm(def) lbm=def end function api.get_connected_players() if not players then return {} end return {{get_pos=function() -- Follow the deterministic scan so its first column is our test plot. -- Subsequent probes of the same node at the same timestamp earn no time. local index=probes%625; probes=probes+1 if real_positions then return pos(0,1,0) end return pos(p.x+12-index%25,p.y,p.z+12-math.floor(index/25)) end}} end hearth={claim_owner_at=function(q) return claims[key(q)] and "owner" end, claim_allowed=function(q, who) return who ~= "" and not claims[key(q)] end, external_protected=function(q) return protected[key(q)] end, climate_at=function() return {temperature=temperature,season=season} end} hearth.seasons=dofile(target:gsub("[^/\\]+$","seasons.lua"))(hearth,api) hearth.lifecycle=dofile(target:gsub("[^/\\]+$","lifecycle.lua")) os.time=function() return now end load() end local function advance(dt) now=now+(dt or 60) assert(sim.step(1)==sim.BUDGET) end local function baseline(k,age) if k then put(p,k,true) end sim.step(1) if age then metadata(p):set_string("succ_age",age) end end local function field(q,f) return tonumber(metadata(q):get_string(f)) or 0 end local DAY,YEAR=86400,365.2425*86400 local function aged(seconds,q) q=q or p metadata(q):set_string("succ_initial_age_seconds",tostring(seconds)) metadata(q):set_string("succ_birth_at",tostring(now)) metadata(q):set_string("succ_last_observed_at",tostring(now)) 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("existing natural ground recruits native grass without regeneration",function() baseline() advance(60); advance(60) assert(name(p)=="air") advance(60) assert(name(p)=="hearth:meadow_grass") assert(metadata(p):get_string("succ_natural")=="1") end) test("nutrients accelerate establishment and are consumed rather than copied",function() metadata(pos(0,0,0)):set_string("succ_nutrients","4") baseline(); advance(100) assert(name(p)=="hearth:meadow_grass") assert(field(pos(0,0,0),"succ_nutrients")==3) end) test("sunny mixed meadow recruits wildflowers, dense grass excludes them",function() put(pos(1),"meadow_grass",true); put(pos(-1),"meadow_grass",true) baseline(); advance(100); advance(100) assert(name(p)=="hearth:wildflowers") for x=-2,2 do for z=-2,2 do if x~=0 or z~=0 then put(pos(x,1,z),"meadow_grass",true) end end end for _=1,6 do advance(100) end assert(name(p)=="hearth:litter") end) test("mature canopy shades out grasses and favors moist ferns",function() put(pos(1),"mature_tree",true); put(pos(-1),"mature_tree",true); put(pos(0,1,1),"mature_tree",true) baseline("meadow_grass") assert(sim.habitat(p).light==6) for _=1,6 do advance(100) end assert(name(p)=="hearth:litter") put(p,"air"); metadata(p):set_string("succ_age","180") advance(100) assert(name(p)=="hearth:fern") end) test("dry exposed fern dies, while river moisture changes its niche",function() light,humidity=14,20 baseline("fern") for _=1,6 do advance(100) end assert(name(p)=="hearth:litter") put(pos(1,0,0),"water_source") assert(sim.habitat(p).water==1 and sim.habitat(p).moisture>0.2) end) test("generic tree needs a real year to establish and ten to mature, never dies at a short deadline",function() baseline("tree_seedling",21500); advance(100) assert(name(p)=="hearth:tree_seedling" and field(p,"succ_age")==21500) aged(YEAR); advance(100) assert(name(p)=="hearth:young_tree" and field(p,"succ_age")==21500) aged(10*YEAR); advance(100) assert(name(p)=="hearth:mature_tree") aged(10000*YEAR); advance(100) assert(name(p)=="hearth:mature_tree" and field(pos(0,0,0),"succ_nutrients")==0) end) test("established sparse meadow eventually recruits a native tree",function() baseline("meadow_grass",7100); advance(100) assert(name(p)=="hearth:tree_seedling") end) test("dense meadow delays rather than permanently preventing woodland recruitment",function() for x=-2,2 do for z=-2,2 do put(pos(x,1,z),"meadow_grass",true) end end baseline("meadow_grass",7100); advance(100) assert(name(p)=="hearth:meadow_grass") metadata(p):set_string("succ_age","14300"); advance(100) assert(name(p)=="hearth:tree_seedling") end) test("mature tree disperses one seed on a durable attempt schedule",function() baseline("mature_tree",2000) metadata(p):set_string("succ_serial","9") advance(100) local seed=pos(-1,1,0) -- serial 10 selects offset 3 assert(name(seed)=="hearth:tree_seedling") local before=writes sim.step(99999) assert(writes==before and field(p,"succ_serial")==10) end) test("grass spreads locally but never replaces occupied flower or crop cells",function() baseline("meadow_grass",1000) metadata(p):set_string("succ_serial","2") put(pos(0,1,-1),"wildflowers",true) -- serial 3 selects offset 4 advance(100) assert(name(pos(0,1,-1))=="hearth:wildflowers") metadata(p):set_string("succ_serial","2") put(pos(0,1,-1),"air"); advance(100) assert(name(pos(0,1,-1))=="hearth:meadow_grass") end) test("moist shaded litter seeds fungi, then returns finite nutrients",function() light=10 baseline("litter",300); metadata(p):set_string("succ_decay_initial","300"); advance(100) assert(name(pos(0,1,1))=="hearth:fungi") -- first attempt selects offset 2 now=now+90*DAY; advance(100); advance(100) assert(name(p)=="air" and field(pos(0,0,0),"succ_nutrients")==1) advance(1) assert(field(pos(0,0,0),"succ_nutrients")==1) end) test("dung decomposes, fertilizes soil and does not instantly sprout a forest",function() assert(sim.deposit(p)) local patch=pos(-1,1,-1) assert(name(patch)=="hearth:dung") -- Move the patch to the scheduler's plot without resetting its natural marker. put(p,"dung",true); baseline(nil,500); advance(100) assert(name(p)=="air" and field(pos(0,0,0),"succ_nutrients")==4) end) test("grazing debits one partial canopy ration, not flowers or planted nodes",function() put(p,"meadow_grass",true); put(pos(1),"fern",true) assert(sim.forage(p)) assert(name(p)=="hearth:meadow_grass" and name(pos(1))=="hearth:fern") assert(field(p,"succ_browse_ready_at")==now+7*DAY) put(pos(1),"wildflowers",true) assert(not sim.forage(p)) put(p,"meadow_grass",false); metadata(p):set_string("succ_natural","") assert(not sim.forage(p)) end) test("grazing retains the perennial and shares seven-day regrowth across animals",function() baseline("meadow_grass") assert(sim.forage(p)) assert(not sim.forage(p)) load(); assert(not sim.forage(p)) now=now+7*DAY assert(sim.forage(p) and name(p)=="hearth:meadow_grass") end) test("source claims and external protection block all simulation writes",function() baseline("mature_tree",259100) protected[key(p)]=true; advance(100) assert(name(p)=="hearth:mature_tree" and writes==0) protected={}; claims[key(p)]=true; advance(100) assert(writes==0 and not sim.deposit(p) and not sim.forage(p)) end) test("support protection and markers block source decay and grazing",function() baseline("meadow_grass",21600) protected[key(pos(0,0,0))]=true; advance(100) assert(writes==0 and not sim.forage(p)) protected={}; metadata(pos(0,0,0)):set_string("placed_by","builder"); advance(100) assert(writes==0 and not sim.forage(p)) end) test("seed dispersal respects destination AND destination-support protection",function() baseline("mature_tree",2000) local seed=pos(-1,1,0) protected[key(seed)]=true metadata(p):set_string("succ_serial","9"); advance(100) assert(name(seed)=="air") protected={}; claims[key(pos(-1,0,0))]=true metadata(p):set_string("succ_serial","9"); advance(100) assert(name(seed)=="air") end) test("any supported placement marker opts the plot out of nature",function() for _,fieldname in ipairs({"owner","planter","placed_by","player_placed","hearth_placed","hearth_player_placed","planted_at","third_party:hand_placed"}) do fields={}; metadata(p):set_string(fieldname,"1") baseline(); advance(100); advance(100) assert(name(p)=="air",fieldname) end end) test("builds, farms, water and nonnatural plants are never overwritten",function() for _,k in ipairs({"wall","crop_1","water_source","sapling","berry_bush"}) do world,fields={},{} put(p,k); baseline(); advance(100); advance(100) assert(name(p)=="hearth:"..k,k) end world,fields={},{}; put(pos(1),"crop_4"); baseline(); advance(100); advance(100) assert(name(p)=="air", "adjacent farm buffer") end) test("unloaded source, support, or destination never cause writes or force loading",function() baseline("mature_tree",2000) missing[key(p)]=true; advance(100); assert(writes==0) missing={}; missing[key(pos(0,0,0))]=true; advance(100); assert(writes==0) missing={}; missing[key(pos(-1,1,0))]=true metadata(p):set_string("succ_serial","9"); advance(100) assert(writes==0) end) test("work and node reads remain bounded after enormous frame stalls",function() real_positions=true reads=0; local work=sim.step(1000000000) assert(work==8 and probes==8) assert(reads<3000,"bounded stencil reads: "..reads) players=false; reads=0 assert(sim.step(999999)==0 and reads==0) end) test("fixed total budget visits every column fairly across multiple players",function() local columns, counts, current, first = {{},{}}, {0,0}, 0, false api.get_connected_players=function() local result={} for i=1,2 do local who=i result[i]={get_pos=function() current=who; first=true; counts[who]=counts[who]+1; return pos(who*100,1,0) end} end return result end api.get_node_or_nil=function(q) if first then columns[current][key(q)]=true; first=false end return nil end for _=1,157 do assert(sim.step(1)==8) end for i=1,2 do local count=0; for _ in pairs(columns[i]) do count=count+1 end assert(count==625 and counts[i]==628) end assert(sim.CACHE_LIMIT>=625*10 and sim.MAX_GAP>=625*10/sim.BUDGET) end) test("vegetation reload alone invalidates age even if its supporting block stayed loaded",function() baseline("mature_tree",259100) lbm.action(p); advance(100) assert(name(p)=="hearth:mature_tree" and field(p,"succ_age")==259100) end) test("calendar anchor survives absence, disconnect, unload and restart; rollback pauses at high water",function() baseline("tree_seedling",21500) local born=field(p,"succ_birth_at") advance(864000); assert(name(p)=="hearth:tree_seedling" and field(p,"succ_age")==21500) players=false; sim.step(1); players=true advance(100); assert(field(p,"succ_age")==21500) lbm.action(pos(0,0,0)); advance(100); assert(field(p,"succ_age")==21500) load(); advance(100); assert(field(p,"succ_age")==21500) advance(-100); assert(field(p,"succ_age")==21500) advance(100); assert(name(p)=="hearth:tree_seedling") assert(field(p,"succ_birth_at")==born and field(p,"succ_last_observed_at")==now) advance(YEAR); advance(100); assert(name(p)=="hearth:young_tree") end) test("failed dispersal attempts survive restart without reroll or advancing age",function() baseline("mature_tree",2000) metadata(p):set_string("succ_serial","9") protected[key(pos(-1,1,0))]=true; advance(100) assert(field(p,"succ_serial")==10) local age,due=field(p,"succ_age"),field(p,"succ_next") protected={}; load(); sim.step(1) assert(field(p,"succ_serial")==10 and field(p,"succ_next")==due) advance(30) assert(field(p,"succ_serial")==10 and name(pos(-1,1,0))=="air") assert(field(p,"succ_age")==age and field(p,"succ_last_observed_at")==now) end) test("cold weather pauses recruitment and stage growth without catchup",function() temperature=-5; baseline(); advance(100); advance(100) assert(name(p)=="air") put(p,"tree_seedling",true); metadata(p):set_string("succ_age","21600"); advance(100) assert(name(p)=="hearth:tree_seedling") end) test("habitat is read only and malformed API coordinates fail closed",function() put(p,"meadow_grass",true); put(pos(1),"wildflowers",true); put(pos(-1),"fern",true) local h=sim.habitat(p) assert(h.grass==1 and h.flowers==1 and h.cover==1 and h.loaded) assert(writes==0 and metadata(p):get_string("succ_age")=="") for _,invalid in ipairs({{}, {x=0/0,y=1,z=0}, {x=math.huge,y=1,z=0}, {x=999999,y=1,z=0}}) do assert(not sim.forage(invalid) and not sim.deposit(invalid) and not sim.habitat(invalid).loaded) end end) test("season swaps preserve natural identity and active age; winter pauses perennial senescence",function() baseline("meadow_grass",1000) season,temperature="Winter",-5 advance(100) assert(name(p)=="hearth:meadow_grass_winter" and field(p,"succ_age")==1000) assert(metadata(p):get_string("succ_natural")=="1" and sim.habitat(p).grass==1) for _=1,7 do advance(100) end assert(name(p)=="hearth:meadow_grass_winter" and field(p,"succ_age")==1000) season,temperature="Spring",15; advance(100) assert(name(p)=="hearth:meadow_grass" and field(p,"succ_age")==1100) end) test("spring greens recruit moist sunny gaps, mushrooms autumn shade; dormant plants are occupied",function() season="Spring"; baseline(); advance(100); advance(100) assert(name(p)=="hearth:spring_greens_patch_spring") metadata(p):set_string("succ_harvest_ready_at",tostring(now+86400)) season="Summer"; advance(100) assert(name(p)=="hearth:spring_greens_patch" and field(p,"succ_harvest_ready_at")>now) assert(not sim.deposit(p) or name(p)=="hearth:spring_greens_patch") reset(); season,light,humidity="Autumn",8,70 baseline(); advance(100); advance(100) assert(name(p)=="hearth:autumn_mushrooms_patch_autumn") reset(); season,light,humidity="Autumn",15,70 baseline(); advance(100); advance(100) assert(name(p)=="hearth:meadow_grass") end) test("forage dispersal respects niches, occupation and durable harvest metadata",function() season="Spring"; baseline("spring_greens_patch_spring",1000) metadata(p):set_string("succ_harvest_ready_at",tostring(now+86400)) metadata(p):set_string("succ_serial","2") advance(100) assert(name(pos(0,1,-1))=="hearth:spring_greens_patch_spring") assert(field(p,"succ_harvest_ready_at")>now) metadata(p):set_string("succ_serial","2"); advance(100) assert(name(pos(0,1,-1))=="hearth:spring_greens_patch_spring") end) test("tree mesh growth denies foreign, marked, claimed and unloaded target volume",function() for _, obstacle in ipairs({"foreign","placed","claimed","unloaded"}) do reset(); baseline("young_tree",86300) local q=pos(2,4,0) if obstacle=="foreign" then put(q,"wall") end if obstacle=="placed" then metadata(q):set_string("player_placed","1") end if obstacle=="claimed" then claims[key(q)]=true end if obstacle=="unloaded" then missing[key(q)]=true end advance(100) assert(name(p)=="hearth:young_tree",obstacle) end end) test("leafless winter canopy admits more light than summer foliage",function() put(p,"mature_tree",true) local summer=sim.habitat(p) season="Winter" local winter=sim.habitat(p) assert(winter.canopysummer.light) assert(math.abs(winter.canopy-0.15)<0.0001) end) test("winter mature canopy still sheds litter and decomposition returns nutrients",function() season,temperature="Winter",-5; baseline("mature_tree",2000) metadata(p):set_string("succ_serial","4"); advance(100) assert(name(pos(-2,1,-1))=="hearth:litter") put(p,"litter",true); metadata(p):set_string("succ_age","1700") advance(100); now=now+90*DAY; advance(100); advance(100) assert(name(p)=="air" and field(pos(0,0,0),"succ_nutrients")==1) end) test("seasonal probes reconcile old canopy and handplaced plants within twelve heights",function() season="Autumn" put(pos(0,5,0),"leaves"); put(p,"wildflowers") metadata(p):set_string("player_placed","1") metadata(p):set_string("succ_age","912") sim.step(1) assert(name(pos(0,5,0))=="hearth:leaves_autumn") assert(name(p)=="hearth:wildflowers_autumn" and field(p,"succ_age")==912) assert(metadata(p):get_string("player_placed")=="1") end) test("season appearance has a fixed 96 swap ceiling after a stalled frame",function() season="Winter"; real_positions=true api.get_node_or_nil=function(q) reads=reads+1 return world[key(q)] or {name="hearth:leaves"} end assert(sim.step(999999)==8) assert(writes==96 and reads<400) end) test("incomplete habitat and protected source write no ecological metadata",function() baseline("tree_seedling",1000) local age,serial,due=field(p,"succ_age"),field(p,"succ_serial"),field(p,"succ_next") missing[key(pos(2,4,2))]=true advance(100) assert(field(p,"succ_age")==age and field(p,"succ_serial")==serial and field(p,"succ_next")==due) assert(writes==0) missing={}; protected[key(p)]=true; advance(100) assert(field(p,"succ_age")==age and field(p,"succ_serial")==serial and field(p,"succ_next")==due) assert(writes==0) end) local function diverse() hearth.diversity=dofile(target:gsub("[^/\\]+$","diversity.lua"))(hearth,api) load() end test("unmarked mapgen deadwood decays after a calendar year with finite capped nutrients",function() diverse(); humidity=20; put(p,"deadwood"); baseline() assert(metadata(p):get_string("succ_natural")=="1" and field(p,"succ_age")==0) for _=1,71 do advance(100) end assert(name(p)=="hearth:deadwood" and field(p,"succ_age")==7100) assert(field(pos(0,0,0),"succ_nutrients")==0) now=now+YEAR; advance(100); advance(100) assert(name(p)=="hearth:litter" and field(p,"succ_age")==0) assert(field(pos(0,0,0),"succ_nutrients")==3) local before=writes; sim.step(999999) assert(writes==before and field(pos(0,0,0),"succ_nutrients")==3) now=now+90*DAY; advance(100); advance(100) assert(name(p)=="air" and field(pos(0,0,0),"succ_nutrients")==4) advance(1); assert(field(pos(0,0,0),"succ_nutrients")==4) reset(); diverse(); humidity=20 metadata(pos(0,0,0)):set_string("succ_nutrients","7") put(p,"deadwood"); baseline(nil,7100); now=now+YEAR; advance(100); advance(100) assert(name(p)=="hearth:litter" and field(pos(0,0,0),"succ_nutrients")==8) end) test("deadwood calendar survives absence, disconnect, reload and restart; rollback pauses",function() diverse(); humidity=20; put(p,"deadwood"); baseline(nil,7100) advance(864000); assert(name(p)=="hearth:deadwood") local born=field(p,"succ_decay_at") players=false; sim.step(1); players=true; advance(100) lbm.action(p); advance(100) lbm.action(pos(0,0,0)); advance(100) load(); advance(100) local high=field(p,"succ_decay_seen_at") advance(-100); assert(field(p,"succ_decay_seen_at")==high) assert(field(p,"succ_decay_at")==born) assert(field(pos(0,0,0),"succ_nutrients")==0) now=now+YEAR; advance(100); advance(100) assert(name(p)=="hearth:litter" and field(pos(0,0,0),"succ_nutrients")==3) end) test("species stages keep one birth anchor, real profile thresholds and never die at lifespan endpoints",function() diverse(); baseline("birch_seedling",21500); aged(5*YEAR); advance(100) assert(name(p)=="hearth:birch_young" and metadata(p):get_string("succ_species")=="birch") local born=field(p,"succ_birth_at") metadata(p):set_string("succ_age","30000"); advance(100) assert(name(p)=="hearth:birch_young","young trees must not take herb senescence") aged(20*YEAR); advance(100) assert(name(p)=="hearth:birch_mature") aged(1000*YEAR); advance(100) assert(name(p)=="hearth:birch_mature" and metadata(p):get_string("succ_species")=="birch") assert(born>0 and metadata(p):get_string("succ_lifecycle_origin")=="legacy_established") end) test("species seeds retain parent identity and reject an incompatible destination soil",function() diverse(); season="Autumn"; baseline("birch_mature",2000) metadata(p):set_string("succ_serial","9"); advance(100) assert(hearth.seasons.kind(name(pos(-1,1,0)))=="birch_seedling") reset(); diverse(); put(pos(0,0,0),"podzol"); put(pos(-1,0,0),"peat") baseline("pine_mature",2000); metadata(p):set_string("succ_serial","9"); advance(100) assert(name(pos(-1,1,0))~="hearth:pine_seedling") -- litter may share the attempt end) test("pine is evergreen while species deciduous canopies lose winter shade",function() diverse(); put(p,"pine_mature",true); put(pos(1,2,0),"pine_leaves") local summer=sim.habitat(p); season="Winter" local winter=sim.habitat(p); assert(winter.canopy==summer.canopy) put(p,"oak_mature",true); put(pos(1,2,0),"oak_leaves_winter") assert(math.abs(sim.habitat(p).canopy-0.1725)<0.0001) end) test("species growth checks the full five-by-five-by-six target volume",function() for _,obstacle in ipairs({"foreign","placed","claimed","unloaded","external"}) do reset(); diverse(); baseline("birch_young",86300) local q=pos(2,6,0) if obstacle=="foreign" then put(q,"wall") end if obstacle=="placed" then metadata(q):set_string("player_placed","1") end if obstacle=="claimed" then claims[key(q)]=true end if obstacle=="unloaded" then missing[key(q)]=true end if obstacle=="external" then protected[key(q)]=true end advance(100); assert(name(p)=="hearth:birch_young",obstacle) end end) test("forest, acidic, alpine and saturated soils recruit live understory without hours of waiting",function() local cases={{"podzol",15,30,"heather"},{"alluvium",15,90,"sedge"}, {"peat",15,90,"sedge"},{"forest_floor",8,70,"moss"},{"alpine_turf",15,30,"heather"}, {"granite",15,10,"lichen"}} for _,c in ipairs(cases) do reset(); diverse(); put(pos(0,0,0),c[1]); light,humidity=c[2],c[3] baseline(); advance(100); advance(100) assert(name(p)=="hearth:"..c[4],c[1].." got "..name(p)) assert(name(pos(0,0,0))=="hearth:"..c[1],"never rewrite supporting terrain") end end) test("wet understory recruits willow and alder instead of silently turning into oak",function() for _,c in ipairs({{"alluvium","willow"},{"peat","alder"},{"podzol","pine"}}) do reset(); diverse(); put(pos(0,0,0),c[1]); humidity=c[1]=="podzol" and 30 or 90 baseline(c[1]=="podzol" and "heather" or "sedge",7100); advance(100) assert(name(p)=="hearth:"..c[2].."_seedling",name(p)) end end) test("scheduler adopts worldgen herbs but never player blocks or placed species trees",function() diverse(); put(p,"clover"); baseline(); advance(100) assert(metadata(p):get_string("succ_natural")=="1" and field(p,"succ_age")==100) for _,k in ipairs({"birch_log","birch_leaves","birch_mature"}) do reset(); diverse(); put(p,k) if k=="birch_mature" then metadata(p):set_string("player_placed","1") end baseline(); advance(100) assert(metadata(p):get_string("succ_natural")=="" and field(p,"succ_age")==0,k) end end) test("clover fixes only two finite nitrogen units per support across restart and recolonization",function() diverse(); baseline("clover",900) for _=1,5 do advance(100) end local support=pos(0,0,0) assert(field(support,"succ_nutrients")==2 and field(support,"succ_fixed_nitrogen")==2) metadata(support):set_string("succ_nutrients","0") load(); sim.step(1); advance(100) assert(field(support,"succ_nutrients")==0) metadata(p):set_string("succ_natural",""); put(p,"clover"); advance(100) assert(field(support,"succ_nutrients")==0 and field(support,"succ_fixed_nitrogen")==2) end) test("natural clover and sedge feed wildlife but dormant variants, flowers and player plants do not",function() diverse(); put(p,"clover",true); assert(sim.forage(p)) put(pos(1,0,0),"alluvium"); put(pos(1),"sedge",true); assert(sim.forage(p)) season="Winter"; put(p,"clover_winter",true); put(pos(1),"sedge_winter",true) assert(not sim.forage(p)) season="Summer"; put(p,"bluebell",true); put(pos(1),"clover") metadata(pos(1)):set_string("succ_natural",""); assert(not sim.forage(p)) end) test("habitat exposes bounded nutrients and pollination without changing metadata",function() diverse(); put(p,"clover",true); put(pos(1),"wildflowers",true) metadata(pos(0,0,0)):set_string("succ_nutrients","999999") local h=sim.habitat(p) assert(h.nutrients==8 and h.pollination==2 and h.edible==1 and h.ground=="grass") assert(field(pos(0,0,0),"succ_nutrients")==999999 and writes==0) end) test("species foliage respects the same total eight-column budget and ninety-six appearance swaps",function() diverse(); season="Autumn"; real_positions=true api.get_node_or_nil=function(q) reads=reads+1; return world[key(q)] or {name="hearth:alder_leaves"} end assert(sim.step(999999)==8 and writes==96) end) test("species seeds respect source, destination and support authority without empty-name claim access",function() for _,mode in ipairs({"source","destination","support","external","marker","ambiguous"}) do reset(); diverse(); baseline("birch_mature",2000) hearth.claim_allowed=function() error("forbidden empty-name player access") end local seed=pos(-1,1,0) if mode=="source" then claims[key(p)]=true end if mode=="destination" then claims[key(seed)]=true end if mode=="support" then claims[key(pos(-1,0,0))]=true end if mode=="external" then protected[key(seed)]=true end if mode=="marker" then metadata(seed):set_string("third_party:placed","1") end if mode=="ambiguous" then hearth.claim_owner_at=function(q) return nil,key(q)==key(seed) end end metadata(p):set_string("succ_serial","9"); advance(100) assert(name(seed)~="hearth:birch_seedling",mode) end end) test("calendar age continues through winter while visible stage growth waits for spring",function() diverse(); baseline("oak_seedling",21500) aged(5*YEAR) season,temperature="Winter",-5; advance(100) assert(name(p)=="hearth:oak_seedling_winter" and field(p,"succ_age")==21500) load(); advance(100); assert(field(p,"succ_age")==21500) lbm.action(p); season,temperature="Spring",15; advance(100) assert(name(p)=="hearth:oak_seedling_spring" and field(p,"succ_age")==21500) advance(100) assert(name(p)=="hearth:oak_young_spring" and metadata(p):get_string("succ_species")=="oak") end) local function catalogue() hearth.diversity=dofile(target:gsub("[^/\\]+$","diversity.lua"))(hearth,api) hearth.botany=dofile(target:gsub("[^/\\]+$","botany.lua"))(hearth,api) hearth.species_effects=dofile(target:gsub("[^/\\]+$","species_effects.lua"))(hearth,api) hearth.biomes=dofile(target:gsub("[^/\\]+$","biomes.lua"))(hearth,api,function() end,{}) -- Exercise one plot per frame; the other seven still consume the global -- budget but have no valid player position. Separate tests cover fairness. api.get_connected_players=function() return {{get_pos=function() local index=probes%625; local visit=probes%8==0; probes=probes+1 if not visit then return nil end return pos(p.x+12-index%25,p.y,p.z+12-math.floor(index/25)) end}} end load() end local function vector(r) local n=r.niche local soil=next(n.substrates) if n.aquatic=="surface" or n.aquatic=="submerged" then soil="alluvium" end return {loaded=true,ground=soil,substrate=r.id=="hornwort" and "water" or soil, light=(n.light[1]+n.light[2])/2,moisture=(n.moisture[1]+n.moisture[2])/2, ph=(n.ph[1]+n.ph[2])/2,water=n.water[1],aquatic=n.aquatic,freshwater=true, nutrients=2,host=n.hosts and next(n.hosts) or nil,canopy=0,grass=0,flowers=0,nursery=0,root_fungi={}} end local function support(q,h) local b=pos(q.x,q.y-1,q.z) if h.water>0 then for i=1,h.water do put(pos(q.x,q.y-i,q.z),"water_source") end put(pos(q.x,q.y-h.water-1,q.z),h.ground) elseif h.substrate=="bark" then put(b,(h.host or "oak").."_young",true) elseif h.substrate=="deadwood" or h.substrate=="litter" then put(b,h.substrate,true); metadata(b):set_string("succ_species",h.host or "oak") else put(b,h.ground) end end local function supplied_habitat(h) -- Controlled niche vectors isolate the integration handoff from the -- independently tested real bounded habitat sampler. Selection is REAL. sim.habitat=function(q) local out={}; for k,v in pairs(h) do out[k]=v end if q.y~=p.y then out.loaded=false end return out end end test("ecology100 recruits every exact candidate with calendar anchors and no automatic death",function() catalogue() local records=hearth.botany.list() assert(#records==100) for index,r in ipairs(records) do reset(); catalogue() local h=vector(r) season=next(r.seasons) -- Exact position-hash start at this record, also meeting the old -- woody-gap gate. No fake choose(), random rerolls or ID aliases. local x=0 while (x*17+7)%100~=index-1 or (x*7)%23~=0 do x=x+1; assert(x<2300) end p=pos(x,1,0); support(p,h); supplied_habitat(h) baseline(nil,7100); advance(100) local candidate=r.group=="trees" and r.id.."_seedling" or r.id assert(hearth.seasons.kind(name(p))==candidate,r.id.." recruit got "..name(p)) assert(metadata(p):get_string("succ_natural")=="1",r.id) assert(metadata(p):get_string("succ_lifecycle_version")=="1" and field(p,"succ_birth_at")==now,r.id) local profile=hearth.lifecycle.plant(r.id) local q=pos(p.x,p.y,p.z-1); support(q,h) metadata(p):set_string("succ_age","1000"); metadata(p):set_string("succ_serial","2") if r.group=="trees" then -- A mature parent disperses the same species seed, not a -- generic oak. The subsequent expansion uses real block plans. put(p,r.id.."_mature",true); aged(profile.maturity_days.max*DAY); metadata(p):set_string("succ_serial","9") season="Autumn" q=pos(p.x-1,p.y,p.z); support(q,h); advance(100) assert(hearth.seasons.kind(name(q))==candidate,r.id.." tree spread") put(q,"air"); metadata(q):set_string("succ_natural","") put(p,candidate,true); aged(profile.juvenile_days.max*DAY); advance(100) assert(metadata(p):get_string("succ_tree_stage")=="young",r.id.." young got "..name(p).." age "..field(p,"succ_age").." stress "..field(p,"succ_stress")) aged(profile.maturity_days.max*DAY); advance(100) assert(metadata(p):get_string("succ_tree_stage")=="mature",r.id.." mature") aged(10000*YEAR); advance(100) assert(hearth.seasons.kind(name(p))==r.id.."_mature",r.id.." must not die") else season=r.seeding.Autumn and "Autumn" or "Summer" if not r.seasons[season] then season=next(r.seasons) end aged(profile.maturity_days and profile.maturity_days.max*DAY or 10000*YEAR) advance(100) if profile.maturity_days and r.seeding[season] then assert(hearth.seasons.kind(name(q))==candidate,r.id.." spread got "..name(q)) else assert(name(q)=="air",r.id.." unknown maturity must not spawn") end aged(10000*YEAR); advance(100) assert(hearth.seasons.kind(name(p))==candidate,r.id.." no guessed senescence") end end end) test("real habitat distinguishes bank depth, source columns, pH, host and bark without writes",function() catalogue() put(pos(1,0,0),"water_source"); put(pos(0,0,0),"alluvium"); humidity=90 local h=sim.habitat(p) assert(h.water==0 and h.near_water==1 and h.aquatic=="bank" and h.ph==6.5) put(pos(0,0,0),"water_source"); put(pos(0,-1,0),"water_source"); put(pos(0,-2,0),"alluvium") h=sim.habitat(p) assert(h.water==2 and h.ground=="alluvium" and h.substrate=="water" and h.freshwater and h.moisture==1) put(p,"hornwort",true); assert(sim.habitat(p).aquatic=="submerged") put(pos(0,0,0),"deadwood",true); metadata(pos(0,0,0)):set_string("succ_species","birch") h=sim.habitat(p); assert(h.substrate=="deadwood" and h.host=="birch") put(pos(0,0,0),"oak_young",true) h=sim.habitat(p); assert(h.substrate=="bark" and h.host=="oak" and h.water==0) metadata(pos(0,0,0)):set_string("player_placed","1") assert(not sim.habitat(p).loaded and writes==0) end) test("real source-water recruitment and very old submerged stands never clear or refill water",function() for _,mode in ipairs({"surface","submerged"}) do reset(); catalogue(); p=pos(mode=="surface" and 0 or 1,1,0) put(pos(p.x,0,0),"water_source"); put(pos(p.x,-1,0),"alluvium") baseline(); advance(100); advance(100) local r=hearth.botany.get(name(p)); assert(r and r.group=="aquatic" and r.niche.aquatic==mode) assert(name(pos(p.x,0,0))=="hearth:water_source") aged(10000*YEAR); advance(100) assert(hearth.botany.kind(name(p))==r.id and name(pos(p.x,0,0))=="hearth:water_source") assert(field(pos(p.x,-1,0),"succ_nutrients")==0) end end) test("water placement denies flowing, too deep, unloaded, marked, claimed and protected columns",function() for _,mode in ipairs({"flowing","deep","unloaded","marked","claimed","external","bottom"}) do reset(); catalogue() put(pos(0,0,0),"water_source"); put(pos(0,-1,0),"alluvium") if mode=="flowing" then put(pos(0,0,0),"water_flowing") end if mode=="deep" then for y=0,-6,-1 do put(pos(0,y,0),"water_source") end end if mode=="unloaded" then missing[key(pos(0,-1,0))]=true end if mode=="marked" then metadata(pos(0,0,0)):set_string("placed_by","builder") end if mode=="claimed" then claims[key(pos(0,0,0))]=true end if mode=="external" then protected[key(pos(0,0,0))]=true end if mode=="bottom" then claims[key(pos(0,-1,0))]=true end baseline(); advance(100); advance(100) assert(name(p)=="air" and writes==0,mode) end end) test("real block plans expand atomically, survive restart and never collapse from old age",function() catalogue(); humidity=45 baseline("birch_seedling",21600); aged(5*YEAR); advance(100) assert(metadata(p):get_string("succ_tree_stage")=="young") local count=0 for _,c in ipairs(hearth.diversity.tree_plan("birch","young")) do local q=pos(p.x+c.x,p.y+c.y,p.z+c.z) assert(metadata(q):get_string("succ_component")==key(p)); count=count+1 end assert(count>10) load(); sim.step(1); aged(20*YEAR); advance(100) assert(metadata(p):get_string("succ_tree_stage")=="mature") local before=writes local q=pos(0,4,0); metadata(q):set_string("owner","builder") metadata(p):set_string("succ_seed_epoch",tostring(now+YEAR)) metadata(p):set_string("succ_litter_epoch",tostring(now+YEAR)) aged(1000*YEAR); advance(100) assert(name(p)=="hearth:birch_mature" and writes==before) metadata(q):set_string("owner",""); advance(100) assert(name(p)=="hearth:birch_mature" and name(pos(0,7,0))~="air") end) test("every tree destination and its support fail closed before any plan write",function() for _,mode in ipairs({"foreign","natural_plant","marker","claim","external","unloaded","support"}) do reset(); catalogue(); humidity=45; baseline("oak_seedling",21600); aged(5*YEAR) local q=pos(0,4,0) if mode=="foreign" then put(q,"wall") end if mode=="natural_plant" then put(q,"yarrow",true) end if mode=="marker" then metadata(q):set_string("player_placed","1") end if mode=="claim" then claims[key(q)]=true end if mode=="external" then protected[key(q)]=true end if mode=="unloaded" then missing[key(q)]=true end if mode=="support" then claims[key(pos(0,3,0))]=true end advance(100) assert(name(p)=="hearth:oak_seedling" and writes==0,mode) end end) test("chopped ecology root cleans matching components but never mapgen or foreign timber",function() catalogue(); humidity=45; baseline("oak_seedling",21600); aged(5*YEAR); advance(100) local old={name=name(p)}; put(p,"air"); now=now+1 assert(sim.tree_removed(p,old)); assert(name(pos(0,3,0))=="air") put(pos(0,3,0),"oak_log"); now=now+1 assert(not sim.tree_removed(p,{name="hearth:oak_young"})) assert(name(pos(0,3,0))=="hearth:oak_log") end) test("catalogue nitrogen caps survive restart and root fungi never accelerate calendar growth",function() catalogue(); humidity=45; baseline("clover",1000) for _=1,5 do advance(100) end assert(field(pos(0,0,0),"succ_fixed_nitrogen")==2 and field(pos(0,0,0),"succ_nutrients")==2) load(); sim.step(1); advance(100); assert(field(pos(0,0,0),"succ_nutrients")==2) reset(); catalogue(); humidity=45; put(pos(0,0,0),"forest_floor") put(pos(1),"birch_bolete",true); metadata(pos(0,0,0)):set_string("succ_nutrients","1") baseline("birch_seedling",16100); advance(100) assert(name(p)=="hearth:birch_seedling" and field(pos(0,0,0),"succ_nutrients")==1) aged(5*YEAR); advance(100) assert(name(p)=="hearth:birch_young" and field(pos(0,0,0),"succ_nutrients")==0) reset(); catalogue(); humidity=45; put(pos(1),"birch_bolete",true) metadata(pos(0,0,0)):set_string("succ_nutrients","1") baseline("oak_seedling",16100); advance(100) assert(name(p)=="hearth:oak_seedling" and field(pos(0,0,0),"succ_nutrients")==1) end) test("saprotrophs age existing deadwood without minting nutrients or attacking natural or player trees",function() catalogue(); humidity=70; light=10 put(pos(0,0,0),"deadwood",true); metadata(pos(0,0,0)):set_string("succ_species","birch") baseline("birch_polypore",1000); advance(100) assert(field(pos(0,0,0),"succ_decay_bonus")>0 and field(pos(0,0,0),"succ_nutrients")==0) assert(field(p,"succ_stress")==0) metadata(pos(0,0,0)):set_string("owner","builder"); local age=field(pos(0,0,0),"succ_age") advance(100); assert(field(pos(0,0,0),"succ_age")==age) reset(); catalogue(); humidity=70; light=10 put(pos(0,0,0),"deadwood",true); metadata(pos(0,0,0)):set_string("succ_species","oak") baseline("birch_polypore",1000); advance(100) assert(field(pos(0,0,0),"succ_age")==0,"birch-only fungus must not accelerate oak decay") end) test("flowering, browse and nursery use botanical tags while harvested bushes retain cooldowns",function() catalogue(); humidity=45; put(p,"blackberry",true); put(pos(1),"yarrow",true) local h=sim.habitat(p); assert(h.pollination==2 and h.edible==1 and h.nursery==1) metadata(p):set_string("botany_ready_at",tostring(now+86400)) baseline(nil,1000); metadata(p):set_string("succ_seed_epoch",tostring(now+YEAR)) advance(100); assert(field(p,"succ_age")==1000 and field(p,"botany_ready_at")>now) assert(sim.forage(p)); assert(name(p)=="hearth:blackberry" and field(p,"botany_ready_at")>now) put(p,"foxglove",true); assert(not sim.forage(p)) season="Spring"; put(p,"rowan_mature",true) metadata(p):set_string("succ_component",key(p)); metadata(p):set_string("succ_tree_stage","mature") put(pos(1),"rowan_leaves",true); metadata(pos(1)):set_string("succ_component",key(p)) h=sim.habitat(p) assert(h.pollination==1 and h.nursery==1 and h.edible==1,"one managed root, not a benefit per leaf") end) test("autumn and winter browse debit a shared partial ration without human fruiting",function() catalogue(); season="Winter"; put(p,"blackberry",true) assert(not hearth.botany.edible("blackberry","Winter")) local ready=now+7*86400 assert(sim.forage(p,"winter_browse")) assert(name(p)=="hearth:blackberry" and field(p,"succ_browse_ready_at")==ready) assert(not sim.forage(p,"winter_browse"),"one resident cannot eat the same plant twice during its rest") season="Summer"; now=ready assert(sim.forage(p,"herbivore"),"the same plant becomes browseable after its shared rest window") assert(field(p,"succ_browse_ready_at")==now+7*86400) end) test("animal seasonal browse can never fell a mature tree or feed from toxic plants",function() catalogue(); season="Winter"; put(p,"oak_mature",true) local before=name(p); local age=field(p,"succ_age"); local cooldown=field(p,"succ_browse_ready_at") assert(not sim.forage(p,"winter_browse")) assert(name(p)==before and field(p,"succ_age")==age and field(p,"succ_browse_ready_at")==cooldown) put(p,"foxglove",true); before=name(p) assert(not sim.forage(p,"winter_browse") and name(p)==before) end) test("one global tree-plan reservation bounds eight concurrent roots and malformed geometry writes nothing",function() catalogue(); humidity=45 local positions={pos(0),pos(20),pos(40),pos(60),pos(80),pos(100),pos(120),pos(140)} local i,visits=0,0 api.get_connected_players=function() return {{get_pos=function() i=i%8+1 local index=visits%625; visits=visits+1 local root=positions[i] return pos(root.x+12-index%25,1,root.z+12-math.floor(index/25)) end}} end for _,root in ipairs(positions) do put(root,"oak_seedling",true) end sim.step(1) for _,root in ipairs(positions) do aged(5*YEAR,root) end local calls=0; local original=hearth.diversity.tree_plan hearth.diversity.tree_plan=function(...) calls=calls+1; return original(...) end advance(100); assert(calls==1) local grown=0 for _,root in ipairs(positions) do if metadata(root):get_string("succ_tree_stage")=="young" then grown=grown+1 end end assert(grown==1 and writes<=sim.MAX_TREE_CELLS) for _,mode in ipairs({"missing","oversize","foreign","duplicate","offset"}) do reset(); catalogue(); humidity=45; baseline("oak_seedling",21600); aged(5*YEAR) hearth.diversity.tree_plan=function() if mode=="missing" then return nil end local plan={{x=0,y=0,z=0,name="hearth:oak_young"}} if mode=="oversize" then for _=1,sim.MAX_TREE_CELLS do plan[#plan+1]=plan[1] end end if mode=="foreign" then plan[1].name="foreign:log" end if mode=="duplicate" then plan[2]=plan[1] end if mode=="offset" then plan[1].x=100 end return plan end advance(100); assert(name(p)=="hearth:oak_seedling" and writes==0,mode) end end) test("water stands and botanical understory retain calendar anchors across restart and reload",function() for _,plant in ipairs({"duckweed","yarrow"}) do reset(); catalogue(); humidity=45 if plant=="duckweed" then put(pos(0,0,0),"water_source"); put(pos(0,-1,0),"alluvium") end baseline(plant,1000); advance(864000); assert(field(p,"succ_age")==1000) load(); advance(100); assert(field(p,"succ_age")==1000) lbm.action(p); advance(100); assert(field(p,"succ_age")==1000) advance(100); assert(field(p,"succ_age")==1000 and field(p,"succ_last_observed_at")==now) end end) test("real moss mat sampler changes local moisture and pH once without map or metadata writes",function() catalogue(); humidity=80; light=10 assert(hearth.botany.suitable("bracken",sim.habitat(p),"Summer")) put(pos(1,0,0),"alluvium"); put(pos(1),"sphagnum",true) local before=sim.habitat(p) assert(before.species_counts.sphagnum==1) assert(not hearth.botany.suitable("bracken",before,"Summer"),"retention changes a real neighbour niche") assert(math.abs(before.moisture-before.source_moisture-0.1)<0.000001) assert(math.abs(before.ph-6.3)<0.000001 and before.source_ph==6.5) put(pos(-1,0,0),"peat"); put(pos(-1),"sphagnum",true) put(pos(0,0,1),"peat"); put(pos(0,1,1),"sphagnum",true) local function snapshot() local out={}; for id,f in pairs(fields) do for k,v in pairs(f) do out[#out+1]=id..k..v end end table.sort(out); return table.concat(out,"|") end local h=sim.habitat(p); local saved=snapshot() for _=1,5 do h=sim.habitat(p) assert(h.species_counts.sphagnum==3 and math.abs(h.ph-6.1)<0.000001) assert(math.abs(h.moisture-h.source_moisture-0.2)<0.000001) end assert(snapshot()==saved and writes==0 and name(pos(1,0,0))=="hearth:alluvium") end) test("moss effects exclude planted protected claimed unsupported and unloaded sources",function() for _,mode in ipairs({"planted","protected","claimed","support","unsupported","unloaded"}) do reset(); catalogue(); light=10 local q=pos(1); put(pos(1,0,0),"peat"); put(q,"sphagnum",true) if mode=="planted" then metadata(q):set_string("player_placed","1") end if mode=="protected" then protected[key(q)]=true end if mode=="claimed" then claims[key(q)]=true end if mode=="support" then claims[key(pos(1,0,0))]=true end if mode=="unsupported" then put(pos(1,0,0),"air") end if mode=="unloaded" then missing[key(q)]=true end local h=sim.habitat(p) assert(not h.species_counts.sphagnum and h.ph==6.5 and writes==0,mode) end end) test("real floating duckweed shades submerged neighbours but not itself or land and preserves water",function() catalogue(); light=12 put(pos(0,0,0),"water_source"); put(pos(0,-1,0),"alluvium"); put(p,"hornwort",true) for _,x in ipairs({-1,1,2}) do put(pos(x,0,0),"water_source"); put(pos(x,-1,0),"alluvium"); put(pos(x),"duckweed",true) end local h=sim.habitat(p) assert(h.aquatic=="submerged" and h.species_counts.duckweed==3 and h.light==7.5 and h.source_light==12) assert(sim.habitat(pos(1)).light==12) assert(sim.habitat(pos(0,1,1)).light==12) season="Winter"; assert(sim.habitat(p).light==12) season="Summer"; protected[key(pos(1))]=true assert(sim.habitat(p).light==9) for _,x in ipairs({-1,0,1,2}) do assert(name(pos(x,0,0))=="hearth:water_source") end assert(writes==0) end) test("alder all living stages and dog lichen share the durable finite nitrogen budget",function() for _,id in ipairs({"alder_seedling","alder_young","alder_mature","dog_lichen"}) do reset(); catalogue() local r=hearth.botany.get(id); local h=vector(r) support(p,h); supplied_habitat(h); baseline(id,1000); aged(90*DAY) for _=1,5 do advance(100) end local below=pos(0,0,0) assert(field(below,"succ_fixed_nitrogen")==2 and field(below,"succ_nutrients")==2,id) metadata(below):set_string("succ_nutrients","0") load(); supplied_habitat(h); sim.step(1); advance(100) assert(field(below,"succ_nutrients")==0,id.." reload") put(p,"clover",true); supplied_habitat(vector(hearth.botany.get("clover"))) advance(100); assert(field(below,"succ_nutrients")==0,id.." shared cap") end end) test("nitrogen never exceeds local four ceiling or writes through source or support protection",function() for _,mode in ipairs({"fraction","full","source","support"}) do reset(); catalogue(); humidity=45 local below=pos(0,0,0) metadata(below):set_string("succ_nutrients",mode=="fraction" and "3.75" or (mode=="full" and "8" or "0")) baseline("clover",1000) if mode=="source" then protected[key(p)]=true end if mode=="support" then claims[key(below)]=true end advance(100) local expected=mode=="fraction" and 4 or (mode=="full" and 8 or 0) assert(field(below,"succ_nutrients")==expected,mode) assert(field(below,"succ_fixed_nitrogen")== (mode=="fraction" and 0.25 or 0),mode) end end) test("multiple host fungi cannot copy nutrients or accelerate growth even with a resource budget",function() for _,budget in ipairs({0,1}) do reset(); catalogue(); humidity=45 put(pos(0,0,0),"forest_floor"); metadata(pos(0,0,0)):set_string("succ_nutrients",budget) put(pos(1),"fly_agaric",true); put(pos(-1),"birch_bolete",true) baseline("birch_seedling",16100); advance(100) assert(field(pos(0,0,0),"succ_nutrients")==budget) assert(name(p)=="hearth:birch_seedling") aged(5*YEAR); advance(100) assert(field(pos(0,0,0),"succ_nutrients")==0 and name(p)=="hearth:birch_young") end end) test("birch wood fungus exhausts supporting deadwood then litter with exactly one conserved payout each",function() catalogue(); humidity=70; light=10 put(p,"deadwood",true); metadata(p):set_string("succ_species","birch") put(pos(0,2,0),"birch_polypore",true) baseline(nil,7100); now=now+YEAR; advance(100); advance(100) assert(name(p)=="hearth:litter" and name(pos(0,2,0))=="air") assert(field(pos(0,0,0),"succ_nutrients")==3) now=now+90*DAY; advance(100); advance(100) assert(name(p)=="air" and field(pos(0,0,0),"succ_nutrients")==4) advance(60); assert(field(pos(0,0,0),"succ_nutrients")==4) end) test("new seedlings never reinterpret legacy seconds and stage swaps retain one calendar birthday",function() catalogue(); humidity=45 baseline("crab_apple_seedling",999999) local born=field(p,"succ_birth_at") assert(metadata(p):get_string("succ_lifecycle_origin")=="legacy_established") assert(field(p,"succ_age")==999999 and born==now) aged(5*YEAR); advance(100) local birthday=field(p,"succ_birth_at") assert(hearth.seasons.kind(name(p))=="crab_apple_young") advance(100); assert(field(p,"succ_birth_at")==birthday and field(p,"succ_age")==999999) end) test("seed-age biennials cannot supply flowers before calendar maturity",function() catalogue(); season="Spring"; humidity=60; light=8 baseline("garlic_mustard",1000) aged(100*DAY) assert(sim.habitat(p).pollination==0) aged(730*DAY) assert(sim.habitat(p).pollination==1) season="Winter"; assert(sim.habitat(p).pollination==0) end) test("calendar metadata corruption versions and rollback fail closed without losing the high water",function() for _,fieldname in ipairs({"succ_birth_at","succ_initial_age_seconds","succ_last_observed_at","succ_lifecycle_seed","succ_seed_epoch"}) do reset(); catalogue(); baseline("oak_seedling",30000) metadata(p):set_string(fieldname,"broken") local before=writes; advance(100) assert(writes==before and field(p,"succ_age")==30000,fieldname) end reset(); catalogue(); baseline("oak_seedling",30000) metadata(p):set_string("succ_lifecycle_version","2"); advance(100) assert(metadata(p):get_string("succ_lifecycle_version")=="2" and field(p,"succ_age")==30000) reset(); catalogue(); baseline("oak_seedling",30000); advance(100) local high=field(p,"succ_last_observed_at"); advance(-50) assert(field(p,"succ_last_observed_at")==high) advance(100); assert(field(p,"succ_last_observed_at")==now) end) test("worldgen botanical flora and full block roots adopt established provenance without replacing canopies",function() for _,id in ipairs({"dandelion","garlic_mustard","holly_fern","crab_apple_log","wild_cherry_log","field_maple_log","log"}) do reset(); catalogue(); humidity=45; put(p,id) baseline(); advance(100) assert(name(p)=="hearth:"..id,id) assert(metadata(p):get_string("succ_lifecycle_origin")=="worldgen_established",id) assert(field(p,"succ_birth_at")==now-100 and metadata(p):get_string("succ_lifecycle_version")=="1",id) assert(field(p,"succ_initial_age_seconds")>0,id) end end) local function snapshot() local out={} for id,f in pairs(fields) do for k,v in pairs(f) do out[#out+1]=id..":"..k.."="..v end end table.sort(out); return table.concat(out,"|").."/writes="..writes end test("nectar is a shared hourly debit and pollen is one expiring boolean, never a seed or fruit grant",function() catalogue(); put(p,"blackberry",true) assert(sim.pollinate(p)) assert(field(p,"succ_nectar_ready_at")==now+3600 and field(p,"succ_pollinated_until")==now+DAY) local saved=snapshot() for _=1,20 do assert(not sim.pollinate(p)) end assert(snapshot()==saved and field(p,"botany_ready_at")==0 and writes==0) load(); assert(not sim.pollinate(p)) now=now+3600; assert(sim.pollinate(p)) assert(field(p,"succ_pollinated_until")==now-3600+DAY,"no additive/extended pollen credit") now=now+DAY; assert(sim.pollinate(p) and field(p,"succ_pollinated_until")==now+DAY) assert(not sim.disperse_seed(pos(1),"oak")) end) test("nectar and fruit deny immature plants, out-of-season food, and malformed shared timestamps",function() catalogue(); baseline("blackberry"); aged(DAY) local saved=snapshot() assert(not sim.pollinate(p) and not sim.frugivore(p) and snapshot()==saved) aged(10*YEAR); season="Winter"; saved=snapshot() assert(not sim.pollinate(p) and not sim.frugivore(p) and snapshot()==saved) season="Summer" for _,bad in ipairs({"broken","-1","inf","nan"}) do metadata(p):set_string("succ_nectar_ready_at",bad) metadata(p):set_string("botany_ready_at",bad) saved=snapshot(); assert(not sim.pollinate(p) and not sim.frugivore(p) and snapshot()==saved,bad) end end) test("frugivore spends one whole existing botanical fruit batch and shares its human lock across restart",function() catalogue(); put(p,"blackberry",true) assert(not sim.frugivore(p,"raspberry")) assert(sim.frugivore(p,"blackberry") and field(p,"botany_ready_at")==now+YEAR) assert(name(p)=="hearth:blackberry" and writes==0) local saved=snapshot() for _=1,20 do assert(not sim.frugivore(p)) end load(); assert(not sim.frugivore(p) and snapshot()==saved) now=now+DAY; assert(not sim.frugivore(p),"no daily fruit refill") local B=hearth.botany hearth.construction={allowed=function() return true end} local human={is_player=function() return true end,get_inventory=function() error("spent fruit cannot reach inventory") end} assert(not B.harvest(p,human)) now=now+YEAR; season="Winter"; assert(not sim.frugivore(p)) season="Summer"; assert(sim.frugivore(p)) end) test("all resource hooks reject source and support claims protection markers and missing nodes without metadata writes",function() for _,hook in ipairs({"pollinate","frugivore","granivore","detritivore","forage"}) do for _,mode in ipairs({"source_claim","support_claim","source_protect","support_protect","source_mark","support_mark","source_missing","support_missing","ambiguous"}) do reset(); catalogue() put(p,hook=="granivore" and "oak_mature" or (hook=="detritivore" and "litter" or "blackberry"),true) if hook=="granivore" then season="Autumn" end local q=mode:match("^support") and pos(0,0,0) or p if mode:find("claim") then claims[key(q)]=true end if mode:find("protect") then protected[key(q)]=true end if mode:find("mark") then metadata(q):set_string("third_party:placed","1") end if mode:find("missing") then missing[key(q)]=true end if mode=="ambiguous" then hearth.claim_owner_at=function(q) return nil,key(q)==key(p) end end local saved=snapshot(); assert(not sim[hook](p) and snapshot()==saved,hook.." "..mode) end end end) test("two annual nuts fund one meal and a separate one-use local cache; species IDs alone authorize nothing",function() catalogue(); season="Autumn"; put(p,"oak_mature",true) assert(not sim.disperse_seed(pos(1),"oak")) local fed,cache=sim.granivore(p,"oak") assert(fed and cache=="oak" and field(p,"succ_nuts_remaining")==0) assert(sim.disperse_seed(pos(1),cache)) assert(hearth.seasons.kind(name(pos(1)))=="oak_seedling") assert(metadata(pos(1)):get_string("succ_lifecycle_origin")=="natural_seed") assert(not sim.disperse_seed(pos(-1),cache) and not sim.granivore(p)) load(); assert(not sim.granivore(p) and not sim.disperse_seed(pos(-1),"oak")) now=now+YEAR; local fed2,cache2=sim.granivore(p); assert(fed2 and cache2=="oak") end) test("one remaining nut grants a meal but no cache and immature or wrong-season roots have no seed budget",function() catalogue(); season="Autumn"; put(p,"hazel_mature",true) metadata(p):set_string("succ_nuts_year",os.date("!*t",now).year) metadata(p):set_string("succ_nuts_remaining","1") local fed,cache=sim.granivore(p); assert(fed and cache==nil and not sim.disperse_seed(pos(1),"hazel")) reset(); catalogue(); season="Autumn"; baseline("oak_seedling"); aged(YEAR) local saved=snapshot(); assert(not sim.granivore(p) and snapshot()==saved) put(p,"oak_mature",true); aged(40*YEAR); season="Summer" saved=snapshot(); assert(not sim.granivore(p) and snapshot()==saved) end) test("failed or delayed caching burns paid authority; donors and destinations cannot be cloned",function() for _,mode in ipairs({"claim","support","marker","occupied","unloaded","donor","delay","wrong","far"}) do reset(); catalogue(); season="Autumn"; put(p,"hazel_mature",true) assert(sim.granivore(p)); local q=pos(1) if mode=="claim" then claims[key(q)]=true end if mode=="support" then claims[key(pos(1,0,0))]=true end if mode=="marker" then metadata(q):set_string("owner","builder") end if mode=="occupied" then put(q,"meadow_grass",true) end if mode=="unloaded" then missing[key(q)]=true end if mode=="donor" then metadata(p):set_string("player_placed","1") end if mode=="delay" then now=now+1 end if mode=="far" then q=pos(200) end local saved=snapshot() assert(not sim.disperse_seed(q,mode=="wrong" and "oak" or "hazel") and snapshot()==saved,mode) assert(not sim.disperse_seed(pos(-1),"hazel") and not sim.granivore(p),mode) end reset(); catalogue(); season="Autumn"; put(p,"oak_mature",true); assert(sim.granivore(p)) load(); assert(not sim.disperse_seed(pos(1),"oak"),"restart discards authority, not debits") end) test("litter transfers ten finite tenths, retains its patch until exhausted and cannot pay again after restart",function() put(p,"litter",true) for i=1,10 do assert(sim.detritivore(p),i) assert(math.abs(field(pos(0,0,0),"succ_nutrients")-i/10)<0.000001) if i<10 then assert(name(p)=="hearth:litter"); load() end end assert(name(p)=="air" and field(p,"succ_litter_remaining")==0) local saved=snapshot(); assert(not sim.detritivore(p) and snapshot()==saved) load(); assert(not sim.detritivore(p) and field(pos(0,0,0),"succ_nutrients")<1.000001) end) test("partial litter feeding and normal calendar decay share exactly one mineral payout",function() put(p,"litter",true); assert(sim.detritivore(p)); baseline() now=now+90*DAY; advance(100); advance(100) assert(name(p)=="air" and math.abs(field(pos(0,0,0),"succ_nutrients")-1)<0.000001) end) test("last litter unit refuses a protected dependent fungus or full soil without a debit",function() catalogue(); put(p,"litter",true); metadata(p):set_string("succ_litter_remaining","0.1") put(pos(0,2,0),"wood_blewit",true); protected[key(pos(0,2,0))]=true local saved=snapshot(); assert(not sim.detritivore(p) and snapshot()==saved) protected={}; metadata(pos(0,0,0)):set_string("succ_nutrients","8") saved=snapshot(); assert(not sim.detritivore(p) and snapshot()==saved) end) test("resource APIs fail closed on malformed positions clocks receipts and nonroot tree components",function() catalogue(); put(p,"blackberry",true) for _,hook in ipairs({"pollinate","frugivore","granivore","detritivore","forage","disperse_seed"}) do for _,bad in ipairs({{}, {x=0/0,y=1,z=0}, {x=math.huge,y=1,z=0}, {x=40000,y=1,z=0}}) do local saved=snapshot(); assert(not sim[hook](bad,"oak") and snapshot()==saved,hook) end end api.now=function() return 0/0 end for _,hook in ipairs({"pollinate","frugivore","granivore","detritivore","forage"}) do local saved=snapshot(); assert(not sim[hook](p) and snapshot()==saved,hook) end api.now=nil; season="Autumn"; baseline("oak_mature"); aged(40*YEAR) put(p,"oak_planks",true) local saved=snapshot(); assert(not sim.granivore(p) and snapshot()==saved) put(p,"oak_mature",true) for _,bad in ipairs({"broken","-1","0.5","3"}) do metadata(p):set_string("succ_nuts_remaining",bad) saved=snapshot(); assert(not sim.granivore(p) and snapshot()==saved,bad) end end) os.time=original_time print("LAST_HEARTH_SUCCESSION_PASS "..passed)