-- Standalone Lua 5.1+; policy only, never starts Luanti. local target=assert(arg[1],"lifecycle.lua path required") local L=dofile(target) local passed=0 local function test(label,fn) local ok,err=pcall(fn); assert(ok,label..": "..tostring(err)) passed=passed+1; print("ok "..passed.." - "..label) end local function eq(a,b) assert(a==b,tostring(a).." ~= "..tostring(b)) end local function url(s) return type(s)=="string" and s:match("^https://[^/]+/.+") end local DAY,YEAR=86400,365.2425 local function at(p,days) return assert(L.phase(p,0,days*DAY)) end local new_plants={ dandelion="Taraxacum officinale",daisy="Bellis perennis",red_clover="Trifolium pratense", birdsfoot_trefoil="Lotus corniculatus",ribwort_plantain="Plantago lanceolata",selfheal="Prunella vulgaris", knapweed="Centaurea nigra",oxeye_daisy="Leucanthemum vulgare",field_scabious="Knautia arvensis", teasel="Dipsacus fullonum",cow_parsley="Anthriscus sylvestris",garlic_mustard="Alliaria petiolata", honeysuckle="Lonicera periclymenum",ivy="Hedera helix",gorse="Ulex europaeus",broom="Cytisus scoparius", crab_apple="Malus sylvestris",wild_cherry="Prunus avium",field_maple="Acer campestre",holly_fern="Polystichum setiferum", } local new_animals="rabbit red_squirrel bank_vole wood_mouse hedgehog badger roe_deer stoat weasel otter robin blackbird blue_tit barn_owl mallard common_frog smooth_newt brown_trout bumblebee earthworm" test("module constants retain unaccelerated real seconds",function() eq(L.VERSION,1); eq(L.SECONDS_PER_DAY,DAY); eq(L.DAYS_PER_YEAR,YEAR) eq(at(L.plant("oak"),1).age_seconds,86400) end) test("all hundred actual catalogue records receive group baselines",function() local path=target:gsub("[^/\\]+$","botany.lua") local B=dofile(path)({},{}); local records=B.list(); eq(#records,100) for _,r in ipairs(records) do local p=assert(L.plant(r.id,r.group)); eq(p.group,r.group); eq(p.pending,false) assert(p.germination_days~=nil and p.juvenile_days~=nil and p.maturity_days~=nil and p.lifespan_days~=nil) assert(L.phase(p,0,0),r.id); assert(L.resolve(p,731104),r.id) end end) test("twenty registered plant IDs preserve exact botanical contract",function() local count=0 for id,latin in pairs(new_plants) do local p=assert(L.plant(id)); eq(p.latin,latin); eq(p.pending,false); count=count+1 end eq(count,20); eq(#L.ids("plant"),100) end) test("twenty registered animals plus three legacy identity slots",function() local count=0 for id in new_animals:gmatch("%S+") do local p=assert(L.animal(id)); eq(p.id,id); eq(p.pending,false) assert(type(p.latin)=="string" and url(p.source_url)); count=count+1 end eq(count,20); eq(#L.ids("animal"),23) for _,id in ipairs({"hare","fox","deer"}) do eq(L.animal(id).pending,false) end end) test("ten trees expose species evidence without laundering fallback timings",function() local count=0 for id in ("oak birch pine willow alder beech aspen rowan hazel spruce"):gmatch("%S+") do local p=L.plant(id,"trees"); assert(p.latin and url(p.source_url)) assert(p.germination_days.confidence=="policy_fallback") assert(p.juvenile_days.confidence=="policy_fallback") assert(p.maturity_days.min>=3*YEAR); assert(p.lifespan_days.min>=60*YEAR) count=count+1 end eq(count,10); eq(L.plant("birch").maturity_days.confidence,"policy_fallback") eq(L.plant("alder").maturity_days.confidence,"policy_fallback") end) test("verified oak onset is forty real years not forty seconds",function() local p=L.plant("oak"); eq(p.maturity_days.min,40*YEAR) assert(url(p.maturity_days.source_url)); eq(p.maturity_days.confidence,"source_reported") eq(at(p,30).phase,"seed"); eq(at(p,40*YEAR-1).phase,"juvenile") eq(at(p,40*YEAR).phase,"mature"); eq(at(p,40*YEAR).reproductive_ready,false) end) test("verified rowan and beech distinguish onset and heavy crops",function() eq(L.plant("rowan").maturity_days.max,10*YEAR) local p=L.plant("beech"); eq(p.maturity_days.min,10*YEAR); eq(p.maturity_days.max,30*YEAR) eq(p.maturity_days.statistic,"onset_to_heavy_crop") end) test("alder composite longevity is not a universal hundred-year constant",function() local p=L.plant("alder"); eq(p.lifespan_days.min,60*YEAR); eq(p.lifespan_days.max,160*YEAR) eq(p.lifespan_days.statistic,"composite_envelope"); assert(url(p.evidence.lifespan_context.source_url)) end) test("all source-reported numerical claims carry field-level references",function() local fields={"germination_days","juvenile_days","maturity_days","lifespan_days","gestation_days","incubation_days","larval_days","birth_count"} local claims=0 for _,kind in ipairs({"plant","animal"}) do for _,id in ipairs(L.ids(kind)) do local p=kind=="plant" and L.plant(id) or L.animal(id) for _,name in ipairs(fields) do local r=p[name] if r then assert(r.min>=0 and r.max>=r.min and r.max0) if r.confidence=="source_reported" then assert(url(r.source_url)); assert(url(p.evidence[name].source_url)); claims=claims+1 else eq(r.confidence,"policy_fallback") end end end for _,e in pairs(p.evidence) do assert(url(e.source_url) and #e.note>0); eq(e.reviewed,"2026-11-03") end end end assert(claims>=35) end) test("verified thirty-day rabbit value is gestation not maturity",function() local p=L.animal("rabbit"); eq(p.gestation_days.min,30); eq(p.gestation_days.max,37) eq(p.maturity_days,false); eq(at(p,30).phase,"dependent") assert(p.evidence.maturity_context.note:find("Conflicting",1,true)) eq(at(p,30).reproductive_ready,false) end) test("bank vole maturity weeks differ from weaning days",function() local p=L.animal("bank_vole"); eq(p.juvenile_days.max,25) eq(p.maturity_days.min,42); eq(p.maturity_days.max,56) eq(at(p,24).phase,"dependent"); eq(at(p,30).phase,"juvenile"); eq(at(p,56).phase,"mature") end) test("wood mouse and squirrel retain independently verified gestation",function() eq(L.animal("wood_mouse").gestation_days.min,21); eq(L.animal("wood_mouse").gestation_days.max,26) eq(L.animal("red_squirrel").gestation_days.min,38); eq(L.animal("red_squirrel").gestation_days.max,39) eq(at(L.animal("wood_mouse"),30).phase,"juvenile") end) test("frogs are not sexually mature at metamorphosis",function() local p=L.animal("common_frog"); eq(p.larval_days.max,98); eq(p.maturity_days.max,3*YEAR) eq(p.incubation_days.min,14); eq(at(p,98).phase,"juvenile"); eq(at(p,98).reproductive_ready,false) end) test("earthworm natural lifespan stays unknown and maturity takes years",function() local p=L.animal("earthworm"); eq(p.lifespan_days,false) eq(p.maturity_days.min,YEAR); eq(p.maturity_days.max,2*YEAR) eq(at(p,30).phase,"juvenile"); eq(p.reproduction,"paired_hermaphrodite_cocoon") end) test("generic deer cannot inherit a roe deer life history",function() local p=L.animal("deer"); eq(p.latin,false); eq(p.source_url,false); eq(p.maturity_days,false) eq(p.confidence,"unverified_identity"); assert(L.animal("roe_deer").latin) end) test("winter appearance never declares death including annual representation",function() for _,id in ipairs({"dandelion","daisy","garlic_mustard","foxglove"}) do local p=L.plant(id); local s=L.seasonal(p,"Winter"); eq(s.dormant,true); eq(s.death_due,false) end local p=L.plant("daisy"); p.life_cycle="annual" eq(L.seasonal(p,"Winter").death_due,false) eq(L.seasonal(L.plant("pine"),"Winter").dormant,false) eq(L.seasonal(L.plant("daisy"),"Spring").dormant,false) end) test("biennial leaves survive their first winter",function() local p=L.plant("garlic_mustard"); eq(p.life_cycle,"biennial") eq(p.life_cycle_confidence,"source_reported"); eq(at(p,200).death_due,false) eq(L.seasonal(p,"Winter").death_due,false) eq(L.plant("teasel").life_cycle_confidence,"policy_fallback") end) test("averages and record ages are never mandatory death deadlines",function() for _,p in ipairs({L.animal("rabbit"),L.plant("oak"),L.animal("brown_trout")}) do local s=at(p,p.lifespan_days.max+1); eq(s.beyond_lifespan_range,true); eq(s.death_due,false) end end) test("age retains unloaded calendar gaps while work is bounded separately",function() local gap=40*YEAR*DAY; local e=L.elapsed(100,100+gap,8) eq(e.elapsed_seconds,gap); eq(e.work_seconds,8); eq(e.deferred_seconds,gap-8) eq(L.phase(L.plant("oak"),100,100+gap).phase,"mature") eq(L.elapsed(100,100,0).elapsed_seconds,0) end) test("clock reversal is explicit and never silently rebaselines age",function() local p=L.plant("oak"); local value,reason=L.phase(p,100,99) eq(value,nil); eq(reason,"clock_reversed") value,reason=L.elapsed(100,99,8); eq(value,nil); eq(reason,"clock_reversed") eq(L.phase(p,100,100).age_seconds,0) end) test("NaN infinities negatives strings and beyond-safe clocks fail closed",function() local p=L.plant("oak") for _,v in ipairs({0/0,math.huge,-math.huge,-1,"100",L.MAX_CLOCK+2,1e300}) do eq(L.phase(p,v,100),nil); eq(L.phase(p,0,v),nil) eq(L.elapsed(0,v,8),nil); eq(L.elapsed(0,100,v),nil) end eq(L.phase(p,nil,100),nil); eq(L.elapsed(0,100,nil),nil) end) test("largest valid age is constant-work and is not truncated",function() local s=assert(L.phase(L.plant("oak"),0,L.MAX_CLOCK)) eq(s.age_seconds,L.MAX_CLOCK); assert(s.age_days>1000000000); eq(s.death_due,false) local e=assert(L.elapsed(0,L.MAX_CLOCK,1)); eq(e.work_seconds,1); eq(e.elapsed_seconds,L.MAX_CLOCK) end) test("duration selection is deterministic bounded and salt dependent",function() local r={min=10,max=40} local a=L.duration(r,731104,"oak:maturity_days"); eq(a,L.duration(r,731104,"oak:maturity_days")) assert(a>=10 and a<=40); assert(a~=L.duration(r,731105,"oak:maturity_days")) assert(a~=L.duration(r,731104,"birch:maturity_days")); eq(L.duration({min=30,max=30},0,""),30) end) test("invalid duration ranges and seeds are rejected",function() for _,r in ipairs({{min=-1,max=2},{min=3,max=2},{min=0,max=math.huge},{min=0,max=1e300},{min=0/0,max=2},{}}) do eq(L.duration(r,0,"x"),nil) end for _,seed in ipairs({-1,.5,2147483647,math.huge,0/0,"1"}) do eq(L.duration({min=1,max=2},seed,"x"),nil); eq(L.resolve(L.animal("deer"),seed),nil) end eq(L.duration({min=1,max=2},0,string.rep("x",65)),nil) end) test("resolved profiles preserve range provenance and ordered stages",function() for _,kind in ipairs({"plant","animal"}) do for _,id in ipairs(L.ids(kind)) do local p=kind=="plant" and L.plant(id) or L.animal(id) for _,seed in ipairs({0,1,731104,2147483646}) do local r=assert(L.resolve(p,seed)); eq(r.selection_seed,seed) local previous=0 for _,name in ipairs({"germination_days","juvenile_days","maturity_days","lifespan_days"}) do local n=r.selected_days[name] if n then assert(n>=p[name].min and n<=p[name].max and n>=previous); previous=n end end assert(L.phase(r,0,100)); eq(p.selected_days,nil) end end end end) test("profiles nested evidence lists and IDs are fresh defensive copies",function() local p=L.plant("oak"); p.maturity_days.min=1; p.evidence.maturity_days.note="changed" eq(L.plant("oak").maturity_days.min,40*YEAR) assert(L.plant("oak").evidence.maturity_days.note~="changed") local a=L.animal("roe_deer"); a.breeding_months[1]=1; eq(L.animal("roe_deer").breeding_months[1],7) local ids=L.ids("plant"); ids[1]="changed"; assert(L.ids("plant")[1]~="changed") end) test("identity and group validation never invent profiles",function() for _,id in ipairs({"hearth:oak","../oak","Oak","oak_mature","unknown",string.rep("a",65),true,1}) do eq(L.plant(id,"trees"),nil); eq(L.animal(id),nil) end eq(L.plant("oak","herbs"),nil); eq(L.ids("other"),nil) end) test("malformed profile values and stage ordering fail safely",function() local p=L.plant("oak"); p.maturity_days={min=1,max=2}; eq(L.phase(p,0,0),nil) p=L.plant("oak"); p.selected_days=true; eq(L.phase(p,0,0),nil) p=L.plant("oak"); p.selected_days={maturity_days=0/0}; eq(L.phase(p,0,0),nil) p=L.plant("oak"); p.maturity_days={min=1,max=math.huge}; eq(L.phase(p,0,0),nil) eq(L.phase({},0,0),nil); eq(L.seasonal(L.animal("rabbit"),"Winter"),nil) eq(L.seasonal(L.plant("oak"),"winter"),nil) end) test("breeding windows describe mating or nesting not immediate births",function() local p=L.animal("roe_deer"); eq(p.reproduction,"delayed_implantation"); eq(p.gestation_days,false) eq(L.breeding_window(p,7).within_window,true); eq(L.breeding_window(p,1).within_window,false) eq(L.breeding_window(p,7).eligible,false) eq(L.breeding_window(L.animal("blue_tit"),4).within_window,true) eq(L.breeding_window(L.animal("blue_tit"),12).within_window,false) eq(L.breeding_window(L.animal("barn_owl"),4).known,false) eq(L.breeding_window(p,0),nil); eq(L.breeding_window(p,13),nil); eq(L.breeding_window(p,math.huge),nil) end) test("six verified basic profiles enable reproduction without claiming all taxa complete",function() local enabled={bank_vole=true,wood_mouse=true,blackbird=true,mallard=true,hare=true,fox=true}; local count=0 for _,id in ipairs(L.ids("animal")) do local p=L.animal(id); eq(p.reproduction_enabled,enabled[id] or false) if enabled[id] then count=count+1; assert(p.maturity_days and p.birth_count) assert(p.gestation_days or p.incubation_days); assert(p.postpartum_days>=21) end eq(p.feeding_interval_seconds,nil); eq(p.movement_speed,nil) end eq(count,6); eq(L.animal("bank_vole").gestation_days.max,24) eq(L.animal("blue_tit").incubation_days.max,14); eq(L.animal("blue_tit").maturity_days,false) eq(L.animal("bumblebee").reproduction,"colony_caste_dependent") local hare,fox=L.animal("hare"),L.animal("fox") eq(hare.maturity_days.min,236); eq(hare.gestation_days.max,42); eq(hare.juvenile_days.max,30) eq(hare.birth_count.max,8); eq(hare.reproduction_enabled,true); eq(hare.postpartum_days,30) eq(fox.maturity_days.min,304); eq(fox.gestation_days.max,53); eq(fox.juvenile_days.min,56) eq(fox.birth_count.max,9); eq(fox.reproduction_enabled,true); eq(fox.breeding_seasons.Winter,true) end) test("hedgehog and amphibian winter dormancy is explicit and never a death rule",function() for _,id in ipairs({"hedgehog","common_frog","smooth_newt"}) do local p=L.animal(id); eq(p.winter_dormant,true) assert(L.phase(p,0,L.MAX_CLOCK).death_due==false) end eq(L.animal("hare").winter_dormant,nil) end) test("loading and calling policy does not pollute globals or read the clock",function() local before={}; for k,v in pairs(_G) do before[k]=v end local saved_time,saved_random=os.time,math.random os.time=function() error("unexpected clock read") end; math.random=function() error("unexpected random call") end local ok,err=pcall(function() local fresh=dofile(target); fresh.resolve(fresh.plant("oak"),123) fresh.phase(fresh.animal("wood_mouse"),0,100); fresh.elapsed(0,100,8) end) os.time=saved_time; math.random=saved_random; assert(ok,err) for k,v in pairs(_G) do eq(v,before[k]) end for k,v in pairs(before) do eq(_G[k],v) end end) eq(passed,32) print("PASS "..passed.." lifecycle checks")