Files
2026-11-05 09:57:11 -05:00

1015 lines
57 KiB
Lua
Executable File

-- Standalone focused runtime contracts. No engine or external dependencies.
local target=assert(arg[1],"wildlife.lua path required")
local root=target:gsub("[^/\\]+$","")
local L=dofile(root.."lifecycle.lua")
local factory=dofile(root.."fauna.lua")
local wildlife=dofile(target)
local passed=0
local only=arg and arg[2]
local function test(name,fn)
if only and name~=only then return end
local ok,err=pcall(fn); assert(ok,name..": "..tostring(err))
passed=passed+1; print("ok "..passed.." - "..name)
end
local function copy(t) if type(t)~="table" then return t end; local o={}; for k,v in pairs(t) do o[k]=copy(v) end; return o end
-- Small flat JSON codec ONLY for controlled mock records; never evaluates Lua.
local function json(t)
local out={}; for k,v in pairs(t) do
local text=type(v)=="string" and ('"'..v..'"') or tostring(v)
out[#out+1]='"'..k..'":'..text
end; table.sort(out); return "{"..table.concat(out,",").."}"
end
local function parse(text)
if text:sub(1,1)~="{" or text:sub(-1)~="}" then return nil end
local out={}; for k,v in text:gmatch('"([%w_%-]+)":([^,}]+)') do
if v:sub(1,1)=='"' then out[k]=v:sub(2,-2)
elseif v=="true" then out[k]=true elseif v=="false" then out[k]=false
else out[k]=tonumber(v) end
end; return out
end
local function fixture(injected,saved_storage,creatura_api)
local h={defs={},objects={},players={},steps={},metas={},time=1782072000,season="Summer",plants="meadow_grass",
natural=true,forages=0,deposits=0,habitat=true,water=false,reads=0,blocked=function() return false end}
local api={registered_nodes={}}
h.storage_data=saved_storage or {}; h.logs={}
local storage={get_string=function(_,key)
if h.storage_read_error then error("storage unavailable") end
return h.storage_data[key] or ""
end,set_string=function(_,key,value)
if h.storage_write_error then error("storage unavailable") end
if not h.storage_drop_write then h.storage_data[key]=value end
end}
function api.get_mod_storage() return storage end
function api.log(level,message) h.logs[#h.logs+1]=level..":"..message end
for _,id in ipairs({"meadow_grass","fern","clover","blackberry","hazel","litter"}) do api.registered_nodes["hearth:"..id]={walkable=false} end
function api.now() return h.time end
function api.get_modpath() return root:gsub("[/\\]$","") end
function api.register_entity(n,d) assert(not h.defs[n]); h.defs[n]=d end
function api.register_globalstep(fn) h.steps[#h.steps+1]=fn end
function api.get_connected_players() return h.players end
function api.is_protected(p) return h.blocked(p) end
function api.find_nodes_in_area_under_air(a,b,names)
h.scans=(h.scans or 0)+1
assert((b.x-a.x+1)*(b.y-a.y+1)*(b.z-a.z+1)==648)
if not h.search then return {} end
local accept={}; for _,name in ipairs(names) do accept[name]=true end
local out={}
for x=a.x,b.x do for z=a.z,b.z do for y=a.y,b.y do
local p={x=x,y=y,z=z}; local n=api.get_node_or_nil(p)
local above=api.get_node_or_nil({x=x,y=y+1,z=z})
if n and accept[n.name] and above and above.name=="air" then out[#out+1]=p end
end end end
return out
end
function api.get_node_or_nil(p)
h.reads=h.reads+1
if h.unloaded and h.unloaded(p) then return nil end
if h.terrain then return {name=h.terrain(p)} end
return {name=p.y<=3 and "hearth:grass" or (h.water and p.y<=6 and "hearth:water_source")
or (p.y==4 and h.plants and "hearth:"..h.plants or "air")}
end
function api.get_meta(p)
local k=p.x..":"..p.y..":"..p.z; h.metas[k]=h.metas[k] or {}; local m=h.metas[k]
return {get_string=function(_,key)
if key=="succ_natural" and not h.real_metadata then return h.natural and "1" or "" end
return m[key] or ""
end,set_string=function(_,key,v) m[key]=v end,to_table=function() return {fields=m} end}
end
api.parse_json=parse; api.write_json=json
function api.add_item() error("no item source") end
function h.player(x,name)
local p={name=name or ("observer_"..tostring(#h.players+1)),pos={x=x or 0,y=4,z=0}}
function p:get_player_name() return self.name end
function p:get_pos() return self.pos end
function p:punch() error("no player attacks") end
h.players[#h.players+1]=p; return p
end
function h.add(id,x,z,payload,dtime,y)
local def=assert(h.defs["hearth:"..id]); local e={name="hearth:"..id}
local o={entity=e,pos={x=x or 12,y=y or (h.water and 4 or 3.5),z=z or 0},animations={}}
e.object=o
function o:get_pos() return not self.removed and self.pos or nil end
function o:get_luaentity() return self.entity end
function o:set_pos() error("no teleport movement") end
function o:set_velocity(v) self.velocity=copy(v) end
function o:set_acceleration(v) self.acceleration=v end
function o:set_armor_groups(v) self.armor=v end
function o:set_yaw(v) self.yaw=v end
function o:set_animation(frames,fps,blend,loop)
self.animations[#self.animations+1]={frames=frames,fps=fps,blend=blend,loop=loop}
end
function o:remove() self.removed=true; def.on_deactivate(e) end
h.objects[#h.objects+1]=o; def.on_activate(e,payload or "",dtime or 0)
return e,o
end
function api.add_entity(p,name,data)
local _,o=h.add(name:match(":(.+)$"),p.x,p.z,data,0,p.y); return o
end
local hearth={lifecycle=L,sessions={},climate_at=function(time)
local hour=h.solar_hour or (time%86400)/3600
local elevation=h.solar_elevation or (hour>=6 and hour<18 and 30 or -20)
return {season=h.season,hour=hour,elevation=elevation,daylight=elevation>-.833}
end,
claim_owner_at=function(p)
return h.claim and h.claim(p) and "owner" or nil,h.ambiguous and h.ambiguous(p) or false
end}
hearth.creatura=creatura_api
hearth.fauna=factory(hearth,api)
hearth.succession={habitat=function()
return {loaded=h.habitat,grass=8,cover=8,canopy=1,moisture=.8,near_water=h.water and 8 or 0,water=h.water and 3 or 0}
end,forage=function(p)
assert(not h.blocked(p)); h.forages=h.forages+1; return h.feed_ok~=false
end,deposit=function(p) assert(not h.blocked(p)); h.deposits=h.deposits+1; return true end}
if not injected then hearth.fauna=nil end -- exercise self-contained fixed dofile path
h.hearth=hearth; h.api=api; h.result=wildlife(hearth,api); h.F=h.result.fauna
function h.stats() return h.result.status() end
function h.ai(n)
for _=1,(n or 1) do h.time=h.time+1; h.steps[1](1) end
end
function h.simulate(seconds)
for _=1,seconds do
h.time=h.time+1; h.steps[1](1)
for _=1,5 do for _,o in ipairs(h.objects) do if not o.removed then
local def=h.defs[o.entity.name]; def.on_step(o.entity,.2)
if not o.removed then
o.pos.x=o.pos.x+o.velocity.x*.2; o.pos.y=o.pos.y+o.velocity.y*.2; o.pos.z=o.pos.z+o.velocity.z*.2
end
end end end
end
end
function h.tick(n,dt)
for _=1,n do
h.time=h.time+1; h.steps[1](dt or 1)
for _,o in ipairs(h.objects) do if not o.removed then h.defs[o.entity.name].on_step(o.entity,dt or 1) end end
end
end
function h.saved(e) return h.defs[e.name].get_staticdata(e) end
function h.restore(id,x,changes,y)
local s={version=1,species=id,born_at=h.time-86400,origin="established",sex="female",foodreserve=.5,
nextfeed=h.time,pregnancy_until=0,last_seen=h.time,seed=12,last_meal=h.time,dung_pending=false}
if id=="earthworm" then s.sex="hermaphrodite" end
for k,v in pairs(changes or {}) do s[k]=v end
return h.add(id,x,0,json(s),60,y)
end
function h.botany()
hearth.botany={kind=function(n) return (type(n)=="table" and n.name or n):gsub("^hearth:","") end,
get=function(n) return {group="grasses",toxic=false,existing=false} end,
pollinator=function(n,s) return n.name=="hearth:clover" and s=="Summer" end,
edible=function(n,s) return n.name=="hearth:blackberry" and s=="Summer" end,
browse=function(n,s) return n.name=="hearth:meadow_grass" and s=="Summer" end,
has_tag=function(_,tag) return tag=="browse" end,
seeding=function(n,s) return n.name=="hearth:hazel" and s=="Autumn" end}
end
return h
end
test("registers exactly 23 persistent articulated meshes and animation calls",function()
local h=fixture(); local n=0
for name,d in pairs(h.defs) do
n=n+1; local id=name:sub(8); local p=d.initial_properties
assert(p.mesh=="hearth_fauna_"..id..".b3d" and p.textures[1]=="hearth_fauna_"..id..".png")
assert(p.static_save and p.physical and p.pointable and p.eye_height>=.15)
local bounds=h.F.get(id).bounds
assert(p.collisionbox[1]==bounds.min[1] and p.collisionbox[2]==0 and p.collisionbox[3]==bounds.min[3]
and p.collisionbox[4]==bounds.max[1] and p.collisionbox[5]==bounds.max[2] and p.collisionbox[6]==bounds.max[3])
end
assert(n==23); local e,o=h.restore("hare",12)
assert(not o.removed and o.armor.immortal==1 and h.defs[e.name].on_punch(e)==true)
assert(o.animations[1].frames.x==0 and o.animations[1].frames.y==19 and o.animations[1].fps==24)
assert(fixture(true).stats().species_count==23)
end)
test("pointable right-click dispatches only an online session a detached exact-species snapshot",function()
local h=fixture(); local player=h.player(40,"alice"); h.hearth.sessions.alice={}
local e=h.restore("hare",12,{foodreserve=.63}); local before=h.stats(); local calls=0
h.hearth.inspect_fauna=function(clicker,id,snapshot)
calls=calls+1; assert(clicker==player and id=="hare")
assert(snapshot.origin=="established" and snapshot.resident==true and snapshot.foodreserve==.63)
assert(snapshot.observation=="awake" and snapshot.born_at==e.state.born_at)
snapshot.foodreserve=0; snapshot.origin="born"; snapshot.resident=false
end
h.defs[e.name].on_rightclick(e,player)
assert(calls==1 and e.state.foodreserve==.63 and e.state.origin=="established")
assert(h.stats().total==before.total and h.stats().actions==before.actions and h.stats().deaths==0)
assert(h.forages==0 and h.deposits==0 and h.stats().meals==0,"inspection has no ecological transaction")
local outsider=h.player(40,"outsider"); h.defs[e.name].on_rightclick(e,outsider)
h.defs[e.name].on_rightclick(e,{})
local impostor={get_player_name=function() return "alice" end}
h.defs[e.name].on_rightclick(e,impostor)
assert(calls==1,"missing session, non-player, and unconnected impostor are ignored")
h.defs[e.name].on_deactivate(e); h.defs[e.name].on_rightclick(e,player)
assert(calls==1,"stale entity callbacks are ignored")
end)
test("punching triggers flee motion without damage, death, drops, or state loss",function()
local h=fixture(); local puncher=h.player(14,"puncher"); local e,o=h.restore("hare",12,{foodreserve=.4})
local before=h.stats(); h.defs[e.name].on_punch(e,puncher)
assert(not o.removed and e.target.x<0 and e.state.foodreserve==.4)
assert(h.stats().total==before.total and h.stats().deaths==0 and h.stats().births==0 and h.stats().meals==0)
assert(#h.objects==1 and o.armor.immortal==1)
end)
test("songbirds loosely gather by species and keep separation without global flock state",function()
local h=fixture(); h.solar_hour=12; h.player()
local first,one=h.restore("robin",12)
local second,two=h.restore("robin",20)
h.ai(2)
assert(first.target.x>0 and second.target.x<0,"nearby robins should steer toward one another")
assert(h.saved(first):find('"resident_id":',1,true) and h.saved(second):find('"resident_id":',1,true))
local mixed=fixture(); mixed.solar_hour=12; mixed.player()
local songbird=mixed.restore("robin",12)
local owl=mixed.restore("barn_owl",20)
mixed.ai(2)
assert(not (songbird.target.x>0 and owl.target.x<0),"different species must not be assigned to one flock")
end)
test("Creatura API registers bounded visible fauna groups and accepts its integer ground position",function()
local registrations={}
local C={register_abm_spawn=function(name,def) registrations[name]=copy(def) end,
get_boid_dir=function(self) return {x=self.kind=="robin" and .5 or 0,y=0,z=0} end}
local h=fixture(true,nil,C)
local registration_count=0; for _ in pairs(registrations) do registration_count=registration_count+1 end
assert(registration_count==10)
for name,def in pairs(registrations) do
assert(name:match("^hearth:") and def.interval==45 and def.chance==1800)
assert(def.spawn_cap==h.F.get(name).cap and def.min_group==1 and def.max_group==2)
end
local e,o=h.restore("robin",12,nil,4)
assert(not o.removed and e.object:get_pos().y==4,"Creatura's integer feet coordinate is supported above node y=3")
end)
test("persistent roundtrip before first player joins, unload is not death",function()
local h=fixture(); local e,o=h.restore("wood_mouse",12)
assert(not o.removed and h.stats().total==1)
local text=h.saved(e); assert(#text<2048 and text:sub(1,1)=="{")
h.defs[e.name].on_deactivate(e); h.defs[e.name].on_deactivate(e)
assert(h.stats().total==0 and h.stats().deaths==0)
h.time=h.time+86400; local b,obj=h.add("wood_mouse",12,0,text,86400)
assert(not obj.removed and b.state.born_at==e.state.born_at and b.state.sex==e.state.sex)
assert(b.state.foodreserve==e.state.foodreserve and h.stats().births==0)
end)
test("hostile static payloads reject types fields clocks ids versions pregnancy and size",function()
local h=fixture()
for _,change in ipairs({{species="unknown"},{species="fox"},{version=2},{born_at=h.time+1},
{last_seen=h.time+1},{last_meal=h.time+1},{seed=-1},{foodreserve=2},{foodreserve="1"},
{nextfeed=h.time+86401},{sex="other"},{mesh="evil.b3d"},{pregnancy_until=h.time+100},{origin="unknown"}}) do
local _,o=h.restore("hare",12,change); assert(o.removed,json(change))
end
for _,payload in ipairs({"return os.execute('bad')","[]",string.rep("x",2049),"{}"}) do
local _,o=h.add("hare",12,0,payload); assert(o.removed)
end
assert(h.stats().total==0)
h.api.parse_json=function() error("malformed") end
local _,o=h.add("hare",12,0,"{}"); assert(o.removed)
end)
test("new activation requires a nearby player and habitat, restores do not require food",function()
local h=fixture(); local _,o=h.add("hare",12); assert(o.removed)
h.player(); h.habitat=false; local _,a=h.add("hare",12); assert(a.removed)
local _,b=h.restore("fox",12); assert(not b.removed)
local j=fixture(); j.player(); j.plants=false
local _,c=j.add("deer",12); assert(c.removed,"cover is not food")
end)
test("seeded established cohort uses sourced maturity days and marks estimated origin",function()
local h=fixture(); h.player(); local e,o=h.add("bank_vole",12)
assert(not o.removed); local days=(h.time-e.state.born_at)/86400
assert(days>=42 and days<=56 and e.state.origin=="established")
local before=e.state.born_at; h.ai(65)
assert(e.state.born_at==before and not o.removed and h.stats().births==0)
end)
test("no 900-second vanish or dt-based calendar age and no unknown old-age death",function()
local h=fixture(); h.player(); local e,o=h.restore("deer",12,{born_at=100})
local born=e.state.born_at
for _,dt in ipairs({-1,0/0,math.huge,"bad",100000}) do h.defs[e.name].on_step(e,dt) end
h.time=h.time+100000; h.ai(2)
assert(not o.removed and e.state.born_at==born and h.stats().births==0)
end)
test("restores exceed species local player and global admission caps without deletion",function()
local h=fixture()
for i=1,20 do h.restore("hare",i*120) end
assert(h.stats().hare==20)
for _,r in ipairs(h.F.list()) do for i=1,r.cap do h.restore(r.id,(#h.objects+1)*120) end end
assert(h.stats().total>64)
h.player(); local _,blocked=h.add("hare",12); assert(blocked.removed)
local j=fixture()
for _,r in ipairs(j.F.list()) do j.restore(r.id,12) end
assert(j.stats().total==23)
local k=fixture(); k.player()
for _,r in ipairs(k.F.list()) do k.restore(r.id,#k.objects%2==0 and -20 or 20) end
assert(k.stats().total==23)
assert(h.stats().deaths+j.stats().deaths+k.stats().deaths==0)
end)
test("finite food web consumes actual registered prey exactly once with hours cooldown",function()
for _,pair in ipairs({{"fox","rabbit"},{"fox","hare"},{"stoat","bank_vole"},{"stoat","rabbit"},
{"weasel","wood_mouse"},{"barn_owl","bank_vole"},{"badger","earthworm"},
{"hedgehog","earthworm"},{"robin","earthworm"},{"blackbird","earthworm"},
{"common_frog","earthworm"},{"smooth_newt","earthworm"}}) do
local h=fixture(); h.player(); local predator=h.restore(pair[1],12)
local prey,o=h.restore(pair[2],12.2); prey.next_ai=9999
-- Diurnal and nocturnal tests use actual UTC, not an accelerated day.
if h.F.get(pair[1]).activity=="day" then h.time=h.time-12*3600; predator.state.last_seen=h.time; predator.state.nextfeed=h.time; predator.state.last_meal=h.time end
h.ai(2); assert(o.removed and h.stats().meals==1,pair[1])
assert(predator.state.foodreserve>.7)
h.defs[prey.name].on_deactivate(prey); assert(h.stats().meals==1)
local prey2,o2=h.restore(pair[2],12.2); prey2.next_ai=9999; h.ai(4)
assert(not o2.removed and h.stats().meals==1,pair[1].." cooldown")
end
end)
test("aquatic otter consumes actual trout, fish never spawns food from absent insects",function()
local h=fixture(); h.water=true; h.plants=false; h.player()
local e,o=h.restore("brown_trout",12); e.next_ai=9999
local otter,obj=h.add("otter",12.2); assert(not obj.removed and not o.removed)
h.ai(2); assert(o.removed and h.stats().meals==1 and otter.state.foodreserve>.7)
local j=fixture(); j.water=true; j.player(); local _,fish=j.add("brown_trout",12)
assert(fish.removed,"no fictional insect biomass recruitment")
end)
test("protected prey and callback stencil cannot mutate claims or ambiguous areas",function()
for _,mode in ipairs({"claim","ambiguous","blocked"}) do
local h=fixture(); h.player(); local e=h.restore("fox",12); local prey,o=h.restore("hare",12.2)
prey.next_ai=9999; h[mode]=function(p) return p.x>=12.1 end
h.ai(2); assert(not o.removed and h.stats().meals==0 and e.state.foodreserve<=.5)
local j=fixture(); j.player(); j.restore("hare",12)
j[mode]=function(p) return p.x==13 end; j.ai(2)
assert(j.forages==0 and j.deposits==0)
end
end)
test("natural grazing and one delayed metadata-only deposit, no inventory source",function()
local h=fixture(); h.player(); local e=h.restore("hare",12); h.ai(2)
assert(h.forages==1 and e.state.dung_pending and h.stats().grazed==1)
e.state.nextfeed=h.time+86400; h.time=h.time+3600; h.ai(2)
assert(h.deposits==1 and not e.state.dung_pending)
h.ai(2); assert(h.deposits==1)
local j=fixture(); j.player(); j.restore("hare",12); j.natural=false; j.ai(2); assert(j.forages==0)
end)
test("missing ecological hooks grant no nectar fruit litter seed or fake effects",function()
for _,row in ipairs({{"bumblebee","clover"},{"blackbird","blackberry"},{"earthworm","litter"},{"red_squirrel","hazel"}}) do
local h=fixture(); h.botany(); h.plants=row[2]; h.season=row[2]=="hazel" and "Autumn" or "Summer"
h.player(); local e=h.restore(row[1],12); h.time=h.time-12*3600
e.state.last_seen=h.time;e.state.nextfeed=h.time;e.state.last_meal=h.time
h.ai(2); assert(e.state.foodreserve<=.5 and h.stats().pollinations==0 and h.stats().detritus==0)
end
end)
test("two bees share active flower nectar lock, false results never earn nutrition",function()
local h=fixture(); h.player(); h.botany(); h.plants=false
h.terrain=function(p) return p.y<=3 and "hearth:grass" or (p.y==4 and p.x==12 and p.z==0 and "hearth:clover" or "air") end
h.time=h.time-12*3600
local visits=0
h.hearth.succession.pollinate=function(p)
visits=visits+1; local m=h.api.get_meta(p); local ready=tonumber(m:get_string("succ_nectar_ready_at")) or 0
if ready>h.time then return false end
m:set_string("succ_nectar_ready_at",tostring(h.time+300)); return true
end
local a=h.restore("bumblebee",12); local b=h.restore("bumblebee",12)
h.ai(2); assert(h.stats().pollinations==1 and visits>=2)
assert((a.state.foodreserve>.7)~=(b.state.foodreserve>.7))
assert(h.stats().dispersals==0)
end)
test("seasonal fruit debits shared human harvest budget; no free winter fruit",function()
local h=fixture(); h.player(); h.botany(); h.plants="blackberry"; h.time=h.time-12*3600
local calls=0
h.hearth.succession.frugivore=function(p,id)
assert(id=="blackberry"); local m=h.api.get_meta(p)
if (tonumber(m:get_string("botany_ready_at")) or 0)>h.time then return false end
m:set_string("botany_ready_at",tostring(h.time+86400)); calls=calls+1; return true
end
local e=h.restore("blackbird",12); h.ai(2); assert(calls==1 and e.state.foodreserve>.7)
e.state.nextfeed=h.time; h.season="Winter"; h.ai(2); assert(calls==1)
end)
test("finite litter consumption and earned hazel caching use distinct hooks",function()
local h=fixture(); h.player(); h.plants="litter"; local supply=1
h.hearth.succession.detritivore=function() if supply==0 then return false end; supply=supply-1; return true end
local e=h.restore("earthworm",12); h.ai(2); assert(supply==0 and h.stats().detritus==1)
e.state.nextfeed=h.time; h.ai(2); assert(h.stats().detritus==1)
local j=fixture(); j.player(); j.botany(); j.plants="hazel"; j.season="Autumn"; j.time=j.time-12*3600
local nuts=2; local cache=0
j.hearth.succession.granivore=function(_,id) assert(id=="hazel"); if nuts==0 then return false end; nuts=0; return true,"hazel" end
j.hearth.succession.disperse_seed=function(_,id) assert(id=="hazel" and nuts==0); cache=cache+1; return true end
local squirrel=j.restore("red_squirrel",12); j.ai(2); assert(cache==1 and j.stats().dispersals==1)
squirrel.state.nextfeed=j.time; j.ai(2); assert(cache==1)
-- Eating a single remaining nut does not authorize planting that same nut.
j.hearth.succession.granivore=function() return true end
squirrel.state.nextfeed=j.time; j.ai(2); assert(cache==1)
end)
test("smooth bounded velocity, animation transitions and correct authored forward yaw",function()
local h=fixture(); h.player(10); local e,o=h.restore("hare",12)
h.ai(1); assert(e.clip=="locomotion" and o.pos.x==12)
h.defs[e.name].on_step(e,.1)
assert(o.velocity.x>0 and o.velocity.x<=.2 and o.pos.x==12)
assert(math.abs(o.yaw+math.pi/2)<.001)
assert(o.animations[#o.animations].frames.x==20)
h.defs[e.name].on_step(e,1000); assert(math.abs(o.velocity.x)<=1.3 and e.lease>1)
h.defs[e.name].on_step(e,1); assert(o.velocity.x>0 and e.lease>0)
h.defs[e.name].on_step(e,1); h.defs[e.name].on_step(e,.1); assert(o.velocity.x==0)
end)
test("animals keep moving across the two-second AI interval and traverse toward safe food",function()
-- Keep the observer outside the eight-node flight radius so this fixture
-- isolates food-seeking instead of correctly prioritizing predator flight.
local h=fixture(); h.player(0); h.botany(); h.plants=false
h.terrain=function(p)
if p.y<=3 then return "hearth:grass" end
if p.y==4 and p.x==14 and p.z==0 then return "hearth:meadow_grass" end
return "air"
end
local e,o=h.restore("hare",12)
h.ai(1); assert(e.target.x>0 and e.lease>2)
h.simulate(3)
assert(o.pos.x>12.5,"physics steps must carry the animal toward the food node")
assert(h.forages==1 and e.state.foodreserve>.5,"reached browse is debited once")
end)
test("collision sweep catches a fractional-cell obstruction without teleporting",function()
local h=fixture(); h.player(10); local e,o=h.restore("hare",12)
h.plants=false
h.terrain=function(p)
if p.x>=13 and p.y>=4 then return "hearth:wall" end
return p.y<=3 and "hearth:grass" or "air"
end
h.ai(1); assert(o.velocity.x==0 and o.pos.x==12 and not o.removed)
end)
test("land water cliff building unloaded and protected-source crossings stop safely",function()
for _,mode in ipairs({"water","cliff","building","unloaded","claimed"}) do
local h=fixture(); h.player(10); local e,o=h.restore("hare",12)
h.terrain=function(p)
if p.x>=13 then
if mode=="water" then return "hearth:water_source" end
if mode=="cliff" then return "air" end
if mode=="building" and p.y>=4 then return "hearth:wall" end
end
return p.y<=3 and "hearth:grass" or "air"
end
if mode=="unloaded" then h.unloaded=function(p) return p.x>=13 end end
if mode=="claimed" then h.claim=function() return true end end
h.tick(2,.1); assert(o.velocity.x==0 and not o.removed and o.pos.x==12,mode)
end
end)
test("flight swim feed rest clips are selected by actual runtime behaviors",function()
local h=fixture(); h.player(); h.time=h.time-12*3600
local e=h.restore("robin",12); h.ai(1); assert(e.clip=="flight_swim")
h.time=h.time+12*3600; h.ai(2); assert(e.clip=="rest")
local j=fixture(); j.water=true; j.player(); local f=j.restore("brown_trout",12); j.ai(1)
assert(f.clip=="flight_swim")
local k=fixture(); k.player(); local a=k.restore("hare",12); k.ai(1); assert(a.clip=="feed")
end)
test("solar activity schedules rest at night, twilight and winter dormancy",function()
local h=fixture(); h.player()
local robin,ro=h.restore("robin",12); h.ai(1)
assert(robin.clip=="rest" and not ro.removed,"diurnal robin rests after sunset")
local n=fixture(); n.player(); local hare,ho=n.restore("hare",12); n.ai(1)
assert(hare.clip~="rest" and not ho.removed,"nocturnal hare remains active at night")
local d=fixture(); d.player(); d.solar_hour=18; d.solar_elevation=0
local deer,do_=d.restore("deer",12); d.ai(1)
assert(deer.clip~="rest" and not do_.removed,"crepuscular deer uses dusk light")
local w=fixture(); w.player(); w.season="Winter"
local hedgehog,wo=w.restore("hedgehog",12); w.ai(1)
assert(hedgehog.clip=="rest" and not wo.removed and w.stats().deaths==0)
assert(w.F.get("hedgehog").winter_dormant and w.F.get("common_frog").winter_dormant
and w.F.get("smooth_newt").winter_dormant)
end)
test("sleep does not freeze loaded hunger",function()
local h=fixture(); h.player(100); local e,o=h.restore("robin",12)
local _,prey=h.restore("earthworm",12.2); prey.next_ai=9999
local loaded_at=h.time
e.state.last_meal=loaded_at-3600; e.state.hunger_seconds=100
local hunger_before=e.state.hunger_seconds
-- A daytime species rests at this controlled night elevation. Advance one
-- loaded minute, then let its scheduled think observe that interval; hunger
-- accounting is capped at 60 seconds per production think.
h.solar_hour=2; h.solar_elevation=-20; h.time=loaded_at+60
h.ai(1)
assert(not o.removed and e.clip=="rest","sleeping robin remains alive and resting")
assert(not prey.removed and h.stats().meals==0 and h.forages==0,"resting wildlife does not feed")
assert(e.state.hunger_seconds==hunger_before+60,"loaded sleeping time accrues the capped 60-second hunger interval")
assert(e.state.last_seen==h.time,"the loaded interval snapshot advances to the observed time")
end)
test("winter woody browse requires a botanical browse tag and shared winter transaction mode",function()
local h=fixture(); h.player(); h.botany(); h.season="Winter"; h.solar_hour=2; h.solar_elevation=-20; h.plants="oak_winter"
h.api.registered_nodes["hearth:oak_winter"]={walkable=false}
local B=h.hearth.botany
B.kind=function(n) local name=type(n)=="table" and n.name or n; return name:find("oak",1,true) and "oak" or nil end
B.has_tag=function(_,tag) return tag=="browse" end
B.browse=function() return false end
B.get=function() return {group="trees",toxic=false} end
local mode
h.hearth.succession.forage=function(_,browse_mode) mode=browse_mode; return browse_mode=="winter_browse" end
local e=h.restore("hare",12); h.ai(1)
assert(mode=="winter_browse" and e.state.foodreserve>.5 and h.stats().grazed==1)
local denied=fixture(); denied.player(); denied.botany(); denied.season="Winter"; denied.solar_hour=2; denied.solar_elevation=-20; denied.plants="oak_winter"
denied.api.registered_nodes["hearth:oak_winter"]={walkable=false}
local D=denied.hearth.botany
D.kind=B.kind; D.has_tag=B.has_tag; D.browse=B.browse; D.get=function() return {group="trees",toxic=true} end
local x=denied.restore("hare",12); denied.ai(1)
assert(x.state.foodreserve<=.5 and denied.stats().grazed==0,"toxic woody plants are not winter browse")
end)
test("legacy perennial grass remains seasonal wildlife browse without human edibility",function()
local h=fixture(); h.player(0); h.botany(); h.season="Winter"; h.solar_hour=2; h.solar_elevation=-20
-- Mirror the production season adapter: climate_at alone does not provide
-- the `available()` plant-phenology query used by botany browse.
h.hearth.seasons={kind=function(n)
local value=type(n)=="table" and n.name or n
return type(value)=="string" and value:gsub("^hearth:","") or nil
end,season=function() return h.season end,
available=function(id) return id~="meadow_grass" or h.season~="Winter" end}
local _,o=h.restore("hare",12); local mode
local botanical=h.hearth.botany.kind
h.hearth.botany.kind=function(n)
local id=botanical(n)
if id=="meadow_grass" then return nil end -- legacy grass is outside the 100-taxon catalogue
return id
end
h.hearth.botany.browse=function() return false end
h.hearth.succession.forage=function(_,browse_mode)
mode=browse_mode; h.forages=h.forages+1; return true
end
h.ai(3) -- first think schedules its next turn two seconds later
assert(mode=="winter_browse" and h.forages==1 and not o.removed)
end)
test("unmarked registered botanical ground cover does not block swept terrestrial movement",function()
local h=fixture(); h.player(0); h.botany(); h.plants=false
h.api.registered_nodes["hearth:dandelion"]={walkable=false}
h.terrain=function(p)
if p.y<=3 then return "hearth:grass" end
if p.x==13 and p.y==4 and p.z==0 then return "hearth:dandelion" end
if p.x==14 and p.y==4 and p.z==0 then return "hearth:meadow_grass" end
return "air"
end
h.hearth.botany.get=function(n)
local value=type(n)=="table" and n.name or n
if value=="hearth:dandelion" then return {group="herbs",toxic=false,existing=false} end
if value=="hearth:meadow_grass" then return {group="grasses",toxic=false,existing=true} end
end
h.hearth.botany.browse=function(n,s)
return n.name=="hearth:meadow_grass" and (s=="Spring" or s=="Summer")
end
local e,o=h.restore("hare",12)
h.ai(1); h.simulate(3)
assert(not o.removed,"animal should remain present while crossing native botanical cover")
assert(o.pos.x>12.5,"animal should pass through the unmarked dandelion cell before feeding")
assert(h.forages==1 and e.state.foodreserve>.5,"reached browse is debited once after crossing plant cover")
end)
test("shared scheduler caps 24 actions per second including recruitment and no dt catchup",function()
local h=fixture(); h.player(-1000)
for _,r in ipairs(h.F.list()) do for _=1,r.cap do h.restore(r.id,(#h.objects+1)*120) end end
assert(h.stats().total>64)
for _=1,4 do local before=h.stats().actions; h.ai(1); assert(h.stats().actions-before<=24) end
local before=h.stats().actions
for _,dt in ipairs({-1,0/0,math.huge,"bad"}) do h.steps[1](dt) end
assert(h.stats().actions==before); h.steps[1](100000); assert(h.stats().actions-before<=24)
assert(h.stats().actions>20)
end)
test("no unloaded or player-absent recruitment, no all-species burst or offline birth queue",function()
local h=fixture(); h.ai(60); assert(h.stats().total==0)
h.player(); h.ai(4); assert(h.stats().total==0 and not h.scans)
h.ai(1); assert(h.stats().total<=1 and h.scans==1)
local j=fixture(); j.player(); j.unloaded=function() return true end; j.ai(60); assert(j.stats().total==0)
local k=fixture(); k.player(); local e=k.restore("deer",12)
k.time=k.time+365*86400; k.ai(2); assert(k.stats().births==0 and k.stats().deer==1)
assert(e.state.pregnancy_until==0)
end)
test("unknown predator starvation never invents deadlines, rabbit uses loaded hungry days",function()
local h=fixture(); h.player(); h.plants=false
local fox,o=h.restore("fox",12,{last_meal=h.time-86400,born_at=h.time-2*86400})
h.time=h.time+10*86400; h.ai(2); assert(not o.removed and fox.state.foodreserve<=.5)
o:remove() -- isolate the starvation probe from legitimate rabbit predation
local paused,alive=h.restore("rabbit",12,{last_meal=h.time-8*86400,born_at=h.time-9*86400})
h.ai(2); assert(not alive.removed and paused.state.hunger_seconds<=2,"offline gap is not starvation")
local e,obj=h.restore("rabbit",12,{last_meal=h.time-8*86400,born_at=h.time-9*86400,hunger_seconds=8*86400})
h.ai(2); assert(obj.removed and h.stats().deaths==1 and e.state.last_meal<h.time)
end)
local DAY=86400
local function pair(id)
local h=fixture(true); h.player()
if h.F.get(id).activity=="day" then h.time=h.time-12*3600 end
if id=="wood_mouse" or id=="blackbird" then
h.botany(); h.plants="blackberry"; h.hearth.succession.frugivore=function() return true end
end
local a=h.restore(id,12,{born_at=h.time-400*DAY,foodreserve=.9,sex="female"})
local b=h.restore(id,12.5,{born_at=h.time-400*DAY,foodreserve=.9,sex="male",seed=13})
if id=="mallard" then
-- A loaded vegetated bank beside water, not an empty water column
-- falsely treated as represented food.
h.water=true
h.terrain=function(p) return p.y<=3 and "hearth:grass" or (p.y==4 and "hearth:meadow_grass" or "air") end
end
assert(a.state and b.state); return h,a,b
end
test("paired rodents schedule real UTC gestation and never instant adult births",function()
for _,id in ipairs({"bank_vole","wood_mouse"}) do
local h,a=pair(id); h.ai(1)
local due=a.state.pregnancy_until; local days=h.F.get(id).profile.gestation_days.max
assert(due==a.state.conception_at+days*DAY and h.stats().births==0)
h.time=due-2; h.ai(1); assert(h.stats().births==0)
h.ai(2); assert(h.stats().births>=1)
for _,o in ipairs(h.objects) do if o.entity.state and o.entity.state.origin=="born" then
assert(h.time-o.entity.state.born_at<60 and h.F.phase(id,o.entity.state.born_at,h.time).phase=="dependent")
end end
end
end)
test("sourced hare and fox profiles breed only after maturity with bounded real-time litters",function()
for _,row in ipairs({{"hare",42,"gestation_days"},{"fox",53,"gestation_days"}}) do
local id,days,field=row[1],row[2],row[3]
local h,a,mate=pair(id)
if id=="fox" then h.season="Winter"; h.plants="blackberry"; h.botany() end
if id=="fox" then
-- Keep the mating male from consuming the fixture's only hare before
-- the due-date habitat check; this test is about the litter schedule.
mate.next_ai=99999
local prey=h.restore("hare",13,{sex="male",foodreserve=1}); prey.next_ai=99999
end
h.ai(1)
local profile=h.F.get(id).profile
assert(a.state.pregnancy_until==a.state.conception_at+profile[field].max*DAY)
assert(profile.birth_count.min<=a.state.litter_total and a.state.litter_total<=profile.birth_count.max)
assert(h.stats().births==0 and a.state.pregnancy_until>h.time+30*DAY)
h.time=a.state.pregnancy_until; h.ai(2)
assert(h.stats().births>=1 and h.stats().births<=profile.birth_count.min)
end
end)
test("pairing requires two mature healthy nearby compatible animals and seasonal habitat",function()
for _,mode in ipairs({"missing","same_sex","young","hungry","winter","habitat","far","species"}) do
local h,a,b=pair("bank_vole")
if mode=="missing" then b.object:remove()
elseif mode=="same_sex" then b.state.sex="female"
elseif mode=="young" then b.state.born_at=h.time-60
elseif mode=="hungry" then b.state.foodreserve=.1
elseif mode=="winter" then h.season="Winter"
elseif mode=="habitat" then h.habitat=false
elseif mode=="far" then b.object.pos.x=16
elseif mode=="species" then b.object:remove(); h.restore("hare",12.5,{sex="male",foodreserve=1}) end
h.ai(1); assert(a.state.pregnancy_until==0,mode)
end
end)
test("actual successful forage debit funds reproduction but false forage cannot",function()
for _,success in ipairs({true,false}) do
local h,a,b=pair("bank_vole"); a.state.foodreserve=.5; b.state.foodreserve=.5
local supply=2
h.hearth.succession.forage=function()
if not success or supply==0 then return false end; supply=supply-1; return true
end
h.ai(4)
assert((a.state.pregnancy_until>0)==success)
assert(supply==(success and 0 or 2))
end
end)
test("overdue pregnancy restores once with one bounded litter not offline catchup",function()
local h,a,b=pair("bank_vole"); h.ai(1)
local due=a.state.pregnancy_until; local count=a.state.litter_total
local text=h.saved(a); a.object:remove(); b.object:remove(); h.players={}
h.time=due+365*DAY; local restored,o=h.add("bank_vole",12,0,text,365*DAY)
assert(not o.removed and restored.state.pregnancy_until==due)
h.ai(2); assert(h.stats().births==0)
h.player(); h.ai(8)
assert(h.stats().births==count and count<=h.F.get("bank_vole").profile.birth_count.max)
assert(restored.state.pregnancy_until==0 and restored.state.litter_remaining==0)
local saved=h.saved(restored); restored.object:remove()
local again,obj=h.add("bank_vole",12,0,saved,60); assert(not obj.removed)
h.ai(4); assert(h.stats().births==count and again.state.nextbreed>h.time)
end)
test("partial birth caps retain original due and remaining across unload and restart",function()
local h,a=pair("bank_vole"); h.ai(1); local due=a.state.pregnancy_until
local fillers={}; for i=1,3 do fillers[i]=h.restore("bank_vole",12+i*.1,{sex="male",foodreserve=.1}) end
h.time=due; h.ai(2)
assert(h.stats().births==1 and a.state.litter_remaining==2 and a.state.litter_delivered==1)
local saved=h.saved(a); assert(saved~="invalid"); a.object:remove()
local restored,o=h.add("bank_vole",12,0,saved,60); assert(not o.removed)
h.ai(4); assert(h.stats().births==1 and restored.state.pregnancy_until==due)
for _,e in ipairs(fillers) do e.object:remove() end
h.ai(8); assert(h.stats().births==3 and restored.state.pregnancy_until==0)
end)
test("blocked unloaded foodless and absent-player births defer without redating",function()
for _,mode in ipairs({"claimed","unloaded","foodless","players"}) do
local h,a=pair("bank_vole"); h.ai(1); local due=a.state.pregnancy_until
h.time=due
if mode=="claimed" then h.claim=function() return true end
elseif mode=="unloaded" then h.unloaded=function() return true end
elseif mode=="foodless" then h.plants=false; h.habitat=false
else h.players={} end
h.ai(4); assert(h.stats().births==0 and a.state.pregnancy_until==due)
h.claim=nil; h.unloaded=nil; h.plants="meadow_grass"; h.habitat=true
if #h.players==0 then h.player() end
h.ai(2); assert(h.stats().births>=1 and (a.state.pregnancy_until==due or a.state.pregnancy_until==0))
end
end)
test("pregnancy schema rejects giant counts forged deadlines males and incomplete authority",function()
local h,a=pair("bank_vole"); h.ai(1); local valid=parse(h.saved(a)); a.object:remove()
for _,change in ipairs({{litter_total=1000000,litter_remaining=1000000},{litter_remaining=-1},
{pregnancy_until=h.time+1000*DAY},{pregnancy_until=h.time+1},{conception_at=h.time+1},
{sex="male"},{born_at=h.time-60},{litter_delivered=999},{litter_remaining=1.5},
{nextbreed=h.time+1000*DAY},{parent_seed=-1},{extra=true}}) do
local s=copy(valid); for k,v in pairs(change) do s[k]=v end
local _,o=h.add("bank_vole",12,0,json(s),60); assert(o.removed,json(change))
end
local _,obj=h.add("bank_vole",12,0,json(valid),60); assert(not obj.removed)
local _,nested=h.add("bank_vole",12,0,'{"version":'..string.rep('[',1000)); assert(nested.removed)
end)
test("blackbird and mallard use sourced incubation independent of missing lifespan",function()
for _,id in ipairs({"blackbird","mallard"}) do
local h,a=pair(id); h.ai(1)
local p=h.F.get(id).profile; local due=a.state.pregnancy_until
assert(due==a.state.conception_at+p.incubation_days.max*DAY and due>h.time)
h.time=due-2; h.ai(1); assert(h.stats().births==0)
h.ai(2); assert(h.stats().births>0 and h.stats().counts[id]<=h.F.get(id).cap)
assert(a.state.nextbreed>=h.time-2+p.postpartum_days*DAY)
end
end)
test("failed spawn retries same child and reentrant callbacks cannot duplicate delivery",function()
local h,a=pair("bank_vole"); h.ai(1); h.time=a.state.pregnancy_until
local add=h.api.add_entity; local remaining=a.state.litter_remaining
h.api.add_entity=function() return nil end
h.ai(2); assert(h.stats().births==0 and a.state.litter_remaining==remaining)
h.api.add_entity=function(p,id,data)
local o=add(p,id,data); h.steps[1](1); return o
end
h.ai(2); assert(h.stats().births==1 and a.state.litter_remaining==remaining-1)
assert(h.stats().actions<=24*4)
end)
test("postpartum lock and dependent care consume reserve without juvenile hunting",function()
local h,a=pair("bank_vole"); h.ai(1); h.time=a.state.pregnancy_until; h.ai(8)
assert(h.stats().births==3 and a.state.nextbreed>=h.time-8+25*DAY)
local child
for _,o in ipairs(h.objects) do if o.entity.state and o.entity.state.origin=="born" then child=o.entity; break end end
assert(child and child.state.sex and child.state.parent_seed==a.state.seed)
local feeds=h.forages; local reserve=a.state.foodreserve
a.next_ai=99999; child.state.nextfeed=h.time; h.ai(2)
assert(h.forages==feeds and a.state.foodreserve<reserve and child.clip=="rest")
a.next_ai=0; a.state.foodreserve=1; h.ai(2)
assert(a.state.pregnancy_until==0 and h.stats().births==3)
end)
-- Real modules, untouched mapgen flora, no succession scheduler/adoption pass.
-- This deliberately lacks the old always-natural metadata and fake habitat.
local function landscape(saved_storage)
local h=fixture(true,saved_storage); h.real_metadata=true; h.search=true; h.time=h.time-8*3600
h.plants=false
h.height=function(x,z) return 3+math.floor((x+z)/12) end
h.terrain=function(p)
local top=h.height(p.x,p.z)
if p.y<=top then return "hearth:grass" end
if p.y==top+1 then
return ({"hearth:meadow_grass","hearth:clover","hearth:fern"})[(p.x+p.z)%3+1]
end
return "air"
end
h.hearth.seasons=dofile(root.."seasons.lua")(h.hearth,h.api)
h.hearth.botany=dofile(root.."botany.lua")(h.hearth,h.api)
h.hearth.diversity=dofile(root.."diversity.lua")(h.hearth,h.api)
h.api.register_lbm=function() end
h.api.get_biome_data=function() return {humidity=70,heat=50} end
h.api.swap_node=function() error("wildlife must not replace map nodes") end
h.hearth.succession=dofile(root.."succession.lua")(h.hearth,h.api)
h.player()
return h
end
test("quiet new daylight mixed-flora slope recruits within 30 seconds using actual succession",function()
local h=landscape(); assert(h.stats().total==0)
h.ai(30)
local s=h.stats(); assert(s.total>=1 and s.total<=6,json(s.counts))
assert(s.recruitment.attempts==6 and s.recruitment.scans==6 and s.recruitment.successes>=1)
assert(s.counts.hare==0 and s.counts.fox==0 and s.births==0)
assert(s.counts.bank_vole+s.counts.bumblebee>0,"represented daylight herbivores/pollinators")
local fed=false
for _,o in ipairs(h.objects) do if not o.removed then
local p=o:get_pos(); assert(p.y==h.height(p.x,p.z)+.5,"actual surface, not player Y")
assert(p.x*p.x+p.z*p.z>=144 and p.x*p.x+p.z*p.z<=576)
assert(o.entity.state.origin=="established")
end end
-- The final admission at second 30 is not in that tick's prebuilt AI list.
h.ai(2)
for _,o in ipairs(h.objects) do if not o.removed and o.entity.state.foodreserve>.5 then fed=true end end
assert(fed and h.stats().grazed+h.stats().pollinations>0,"actual finite succession transaction must feed")
end)
test("worldgen passability is independent of botanical adoption but never consumes player plants",function()
local h=landscape(); h.height=function() return 3 end
local e,o=h.restore("rabbit",12)
assert(not o.removed,"unadopted worldgen sprite must not reject safe persistent activation")
assert(h.api.get_meta({x=12,y=4,z=0}):get_string("succ_natural")=="")
local j=landscape(); j.height=function() return 3 end
for x=-30,30 do for z=-30,30 do j.api.get_meta({x=x,y=4,z=z}):set_string("planter","gardener") end end
j.ai(30); assert(j.stats().total==0 and j.stats().grazed==0)
for _,m in pairs(j.metas) do assert(m.succ_natural==nil and m.succ_browse_ready_at==nil) end
end)
test("natural worldgen adoption respects unknown metadata support claims and protection",function()
for _,mode in ipairs({"unknown","support","claim","ambiguous","protected","unloaded"}) do
local h=landscape(); h.height=function() return 3 end
if mode=="unknown" then
for x=-30,30 do for z=-30,30 do h.api.get_meta({x=x,y=4,z=z}):set_string("thirdparty_owner","someone") end end
elseif mode=="support" then
for x=-30,30 do for z=-30,30 do h.api.get_meta({x=x,y=3,z=z}):set_string("player_placed","1") end end
elseif mode=="claim" then h.claim=function() return true end
elseif mode=="ambiguous" then h.ambiguous=function() return true end
elseif mode=="protected" then h.blocked=function() return true end
else h.unloaded=function() return true end end
h.ai(30); assert(h.stats().total==0,mode)
for _,m in pairs(h.metas) do assert(m.succ_natural==nil,mode) end
end
end)
test("habitat matching skips unsupported species without a 23 interval delay",function()
local h=landscape(); h.height=function() return 3 end
h.terrain=function(p) return p.y<=3 and "hearth:grass" or (p.y==4 and "hearth:yarrow" or "air") end
h.api.registered_nodes["hearth:yarrow"]={walkable=false}
h.ai(5)
assert(h.stats().total==1 and h.stats().counts.bumblebee==1)
local s=h.stats().recruitment; assert(s.attempts==1 and s.scans==1 and s.last_reason=="admitted")
s.reasons.admitted=999; assert(h.stats().recruitment.reasons.admitted==1,"defensive status copy")
end)
test("hare immigration joins as a sourced mature resident and still needs a mate",function()
local h=landscape(); h.height=function() return 3 end
-- Hares are nocturnal; exercise immigration during the configured solar night
-- instead of expecting a sleeping species to appear in daylight.
h.time=math.floor(h.time/86400)*86400+2*3600
h.terrain=function(p) return p.y<=3 and "hearth:grass" or (p.y==4 and "hearth:meadow_grass" or "air") end
h.ai(5); assert(h.stats().counts.hare==1)
local e=h.objects[1].entity
local profile=h.F.get("hare").profile
assert(e.state.origin=="established")
assert(e.state.born_at==h.time-math.ceil(profile.maturity_days.max*DAY),"wild immigrant gets an estimated mature, not newborn, calendar age")
local phase=h.F.phase("hare",e.state.born_at,h.time)
assert(phase and phase.phase=="mature" and h.F.can_breed("hare",e.state.born_at,h.time,"female","Summer"))
h.ai(2); assert(h.stats().births==0,"one mature resident cannot breed without a mate")
end)
test("eight-node observer danger flees but 12-node recruits do not flee immediately",function()
local h=fixture(); h.player(5); local e=h.restore("hare",12); h.ai(1)
assert(e.clip=="locomotion" and e.target.x>0)
local j=fixture(); j.player(); local a=j.restore("hare",12); j.ai(1)
assert(a.clip=="feed" and j.forages==1)
end)
test("candidate search refuses empty water foodless and dormant surfaces",function()
for _,mode in ipairs({"empty","water","winter"}) do
local h=landscape(); h.height=function() return 3 end
if mode=="winter" then h.season="Winter" end
h.terrain=function(p)
if p.y<=3 then return "hearth:grass" end
if p.y==4 then return mode=="water" and "hearth:water_source"
or (mode=="winter" and "hearth:clover_winter" or "air") end
return "air"
end
h.api.registered_nodes["hearth:clover_winter"]={walkable=false}
h.ai(30); assert(h.stats().total==0,mode)
end
end)
test("recruitment queries bounded terrain once per five seconds and shares AI credit",function()
local h=landscape(); h.height=function() return 3 end
for _=1,60 do local before=h.stats().actions; h.ai(1); assert(h.stats().actions-before<=24) end
assert(h.scans<=12 and h.stats().recruitment.attempts==12)
assert(h.stats().total<=12 and h.stats().total<=h.stats().player_cap)
local scans=h.scans; h.players={}; h.ai(100); assert(h.scans==scans)
h.player(); h.ai(4); assert(h.scans==scans)
h.ai(1); assert(h.scans==scans+1)
end)
test("seeded region unloads ten times and walking away back keeps IDs without immigrants",function()
local h=landscape(); h.height=function() return 3 end
local observer=h.players[1]; observer.pos={x=24,y=4,z=24}
h.ai(80); assert(h.stats().total==4 and h.stats().recruitment.successes==4)
local residents={}; local ids={}
for _,o in ipairs(h.objects) do if not o.removed then
local e=o.entity; assert(e.state.resident_id and e.state.founder_region=="r_0_0")
assert(not ids[e.state.resident_id]); ids[e.state.resident_id]=true
residents[#residents+1]=e
end end
for cycle=1,10 do
local saved={}
for _,e in ipairs(residents) do
saved[#saved+1]={id=e.kind,pos=copy(e.object.pos),text=h.saved(e)}
h.defs[e.name].on_deactivate(e); e.object.removed=true
end
observer.pos={x=2400,y=4,z=2400}; h.unloaded=function() return true end
h.ai(10); assert(h.stats().total==0)
observer.pos={x=24,y=4,z=24}; h.unloaded=nil; residents={}
for _,s in ipairs(saved) do
local e,o=h.add(s.id,s.pos.x,s.pos.z,s.text,600,s.pos.y)
assert(not o.removed and ids[e.state.resident_id]); residents[#residents+1]=e
end
h.ai(10); assert(h.stats().total==4 and h.stats().recruitment.successes==4)
end
assert(h.stats().deaths==0 and h.stats().births==0)
end)
test("full founder journal survives restart even with zero active residents",function()
local h=landscape(); h.height=function() return 3 end; h.players[1].pos={x=24,y=4,z=24}
h.ai(80); assert(h.stats().recruitment.successes==4)
local saved={}; for _,o in ipairs(h.objects) do if not o.removed then
saved[#saved+1]={id=o.entity.kind,p=copy(o.pos),text=h.saved(o.entity),resident_id=o.entity.state.resident_id}
end end
local j=landscape(copy(h.storage_data)); j.height=function() return 3 end
j.players[1].pos={x=24,y=4,z=24}; j.ai(80)
assert(j.stats().total==0 and j.stats().recruitment.successes==0)
j.api.get_connected_players=nil
for _,s in ipairs(saved) do
local e,o=j.add(s.id,s.p.x,s.p.z,s.text,86400,s.p.y)
assert(not o.removed and e.state.resident_id==s.resident_id)
end
j.ai(2); assert(j.stats().total==4 and j.stats().births==0)
end)
test("invalid candidate unloaded and foodless searches retain retryable quota",function()
local h=landscape(); h.height=function() return 3 end; h.players[1].pos={x=24,y=4,z=24}
h.unloaded=function() return true end; h.ai(20)
assert(h.stats().total==0 and h.stats().residents.regions==1)
local journal=parse(h.storage_data.wildlife_residents_v1)
assert(journal.r_0_0:match("^4/%d+/$"),"failed attempts have no founder membership")
h.unloaded=nil; h.ai(80); assert(h.stats().total==4)
local j=landscape(copy(h.storage_data)); j.height=function() return 3 end
j.players[1].pos={x=24,y=4,z=24}; j.ai(80); assert(j.stats().total==0)
end)
test("predation spends a real resident but never refunds its founder slot",function()
local h=fixture(); h.player()
for i=1,4 do local e,o=h.add("hare",12+i*.1); assert(not o.removed); e.next_ai=99999 end
local predator=h.restore("fox",12); h.ai(2)
assert(h.stats().deaths==1 and h.stats().meals==1 and h.stats().hare==3)
local _,replacement=h.add("hare",13); assert(replacement.removed)
assert(predator.state.foodreserve>.7)
local journal=parse(h.storage_data.wildlife_residents_v1)
local n=0; for _ in journal.r_0_0:gmatch("=hare;") do n=n+1 end; assert(n==4)
end)
test("changed claims habitat and nil ignore terrain freeze residents without erasing saves",function()
for _,mode in ipairs({"claim","ambiguous","protected","nil","ignore","building","foodless"}) do
local h=fixture(); h.player(); local e,o=h.restore("hare",12); local id=e.state.resident_id
local text=h.saved(e); h.defs[e.name].on_deactivate(e); o.removed=true
if mode=="claim" then h.claim=function() return true end
elseif mode=="ambiguous" then h.ambiguous=function() return true end
elseif mode=="protected" then h.blocked=function() return true end
elseif mode=="nil" then h.unloaded=function() return true end
elseif mode=="ignore" then h.terrain=function() return "ignore" end
elseif mode=="building" then h.terrain=function() return "hearth:wall" end
else h.plants=false; h.habitat=false end
local restored,obj=h.add("hare",12,0,text,86400)
assert(not obj.removed and restored.state.resident_id==id,mode)
h.tick(10); assert(not obj.removed and h.stats().deaths==0 and h.forages==0 and h.deposits==0,mode)
assert(parse(h.saved(restored)).resident_id==id)
if mode=="foodless" then assert(restored.state.foodreserve<.5,"food loss causes hunger, not deletion")
else assert(obj.velocity.x==0 and obj.velocity.z==0,mode) end
end
end)
test("corrupt version size nesting and membership registry fail closed without deleting restores",function()
for _,text in ipairs({"broken","[]",string.rep("x",1048577),json({version=2,counter=0}),
json({version=1,counter=-1}),'{"version":1,"counter":0,"bad":{}}',
json({version=1,counter=1,r_0_0="0/0/1=unknown;"}),
json({version=1,counter=1,r_0_0="0/0/1=hare;1=hare;"}),
json({version=1,counter=1,r_0_0="0/0/1=hare;garbage"})}) do
local h=fixture(true,{wildlife_residents_v1=text}); h.player(); h.search=true
local e,o=h.restore("hare",12); assert(not o.removed)
local _,fresh=h.add("hare",14); assert(fresh.removed)
h.ai(10); assert(h.stats().total==1 and not o.removed and not h.stats().residents.storage_ok)
assert(h.saved(e)~="invalid" and h.storage_data.wildlife_residents_v1==text and #h.logs>0)
end
end)
test("storage read write and silent-write faults stop admissions but not existing residents",function()
for _,fault in ipairs({"storage_read_error","storage_write_error","storage_drop_write"}) do
local h=fixture(); h.player(); local e,o=h.restore("hare",12); local id=e.state.resident_id
h[fault]=true; local _,fresh=h.add("hare",14)
assert(fresh.removed and not h.stats().residents.storage_ok)
local restored,obj=h.add("hare",12,0,h.saved(e),60)
assert(not o.removed and not obj.removed and restored.state.resident_id==id)
end
end)
test("registry budget refuses new regions without eviction truncation or reseeding",function()
local journal={version=1,counter=0}; local n=0
for x=-32,31 do for z=-32,31 do journal["r_"..x.."_"..z]="0/0/"; n=n+1 end end
assert(n==4096)
local h=fixture(true,{wildlife_residents_v1=json(journal)}); h.player(4000)
assert(h.stats().residents.storage_ok and h.stats().residents.regions==4096)
local _,fresh=h.add("hare",4012); assert(fresh.removed)
local _,restored=h.restore("hare",4012); assert(not restored.removed)
assert(h.stats().residents.regions==4096 and parse(h.storage_data.wildlife_residents_v1).r_0_0=="0/0/")
journal.r_32_32="0/0/"
local j=fixture(true,{wildlife_residents_v1=json(journal)})
assert(not j.stats().residents.storage_ok,"oversize record count rejects whole ledger")
end)
test("stable individual IDs and food origin age brood state serialize publicly across restarts",function()
local h,a=pair("bank_vole"); h.ai(1)
local state=copy(a.state); local text=h.saved(a); local public=parse(text)
assert(public.resident_id==state.resident_id and public.origin=="established" and math.abs(public.foodreserve-state.foodreserve)<1e-12)
local j=fixture(true,copy(h.storage_data)); j.time=h.time+86400; j.players={}
local e,o=j.add("bank_vole",12,0,text,86400)
assert(not o.removed and e.state.resident_id==state.resident_id and e.state.born_at==state.born_at)
for _,k in ipairs({"sex","origin","foodreserve","seed","pregnancy_until","conception_at","litter_remaining","nextbreed"}) do
assert(e.state[k]==state[k] or (k=="foodreserve" and math.abs(e.state[k]-state[k])<1e-12),k)
end
local before=e.state.resident_id; j.player(); local fresh,obj=j.add("bank_vole",14)
assert(not obj.removed and fresh.state.resident_id>before)
end)
test("born children obtain durable distinct IDs without spending founder quota",function()
local h,a=pair("bank_vole"); h.ai(1); h.time=a.state.pregnancy_until; h.ai(8)
assert(h.stats().births==3)
local seen={[a.state.resident_id]=true}; local child
for _,o in ipairs(h.objects) do if o.entity.state and o.entity.state.origin=="born" then
child=o.entity; assert(child.state.founder_region=="" and not seen[child.state.resident_id]); seen[child.state.resident_id]=true
end end
assert(child)
local id=child.state.resident_id; local text=h.saved(child); child.object:remove()
local restored,obj=h.add(child.kind,12,0,text,60); assert(not obj.removed and restored.state.resident_id==id)
local journal=parse(h.storage_data.wildlife_residents_v1)
assert(not journal.r_0_0 or not journal.r_0_0:find("="),"attempt-only journal has no founder membership")
end)
test("unsafe static fields still reject while valid native dtime restoration is retained",function()
local h=fixture(); local e=h.restore("hare",12); local s=parse(h.saved(e))
for _,changes in ipairs({{resident_id=0},{resident_id="1"},{founder_region="r_9999_0"},
{founder_region="../../world"},{hunger_seconds=-1},{hunger_seconds="8"}}) do
local bad=copy(s); for k,v in pairs(changes) do bad[k]=v end
local _,o=h.add("hare",12,0,json(bad),86400); assert(o.removed)
end
local _,o=h.add("hare",12,0,json(s),86400); assert(not o.removed)
assert(#h.logs>=6,"strict snapshot rejection is logged without dumping raw input")
end)
test("missing rolled back registry never reuses saved IDs or erases those residents",function()
local h=fixture(); local e=h.restore("hare",12); local text=h.saved(e)
local j=fixture(); local restored,o=j.add("hare",12,0,text,60)
assert(not o.removed and restored.state.resident_id==e.state.resident_id and not j.stats().residents.storage_ok)
j.player(); local _,fresh=j.add("hare",14); assert(fresh.removed)
end)
if only then
assert(passed==1,"selected wildlife test was not found: "..only)
print("PASS 1 selected wildlife test: "..only)
else
assert(passed==62)
print("PASS "..passed.." persistent wildlife tests")
end