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

388 lines
30 KiB
Lua
Executable File

-- Pure calendar policy, Lua 5.1+. No engine, clock reads, registrations or writes.
-- Ranges describe populations or explicit policy fallbacks, never death deadlines.
local L = {}
local DAY, YEAR, MAX_CLOCK = 86400, 365.2425, 9007199254740991
local function finite(n) return type(n)=="number" and n==n and math.abs(n)<math.huge end
local function clock(n) return finite(n) and n>=0 and n<=MAX_CLOCK end
local function copy(t)
if type(t)~="table" then return t end
local out={}; for k,v in pairs(t) do out[k]=copy(v) end; return out
end
local function range(a,b,url,note,statistic)
return {min=a,max=b,source_url=url or false,confidence=url and "source_reported" or "policy_fallback",
note=note or "Unverified group policy; not a measured species range.",statistic=statistic or "policy_window"}
end
local function years(a,b,url,note,statistic) return range(a*YEAR,b*YEAR,url,note,statistic) end
local WT="https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/"
local WILDLIFE="https://www.wildlifetrusts.org/wildlife-explorer/"
local WIKI="https://en.wikipedia.org/wiki/"
local ADW="https://animaldiversity.org/accounts/"
local RSPB="https://www.rspb.org.uk/birds-and-wildlife/"
local plants, animals = {}, {}
local baseline={
trees={range(14,365),years(1,5),years(10,60),years(60,500)},
shrubs={range(14,365),years(1,2),years(2,10),years(5,80)},
grasses={range(7,90),range(30,90),range(90,365),years(2,10)},
herbs={range(7,90),range(90,365),range(365,730),years(2,10)},
ferns_mosses={range(30,730),years(1,2),years(2,5),false},
fungi={false,false,false,false}, aquatic={range(7,90),range(90,365),range(365,730),false},
lichens={false,years(1,5),false,false},
}
local function plant_record(id,group,latin,pending)
local b=baseline[group]
plants[id]={id=id,kind="plant",group=group,latin=latin or false,pending=pending or false,
germination_days=copy(b[1]),juvenile_days=copy(b[2]),maturity_days=copy(b[3]),lifespan_days=copy(b[4]),
life_cycle=group=="fungi" and "not_applicable" or "perennial",
life_cycle_confidence="policy_fallback",source_url=false,confidence="policy_fallback",
evidence={},seasonal_policy="dormant_not_dead",age_origin="seed_or_propagule"}
end
-- Fixed snapshot of all 100 registered IDs; no catalogue factory is invoked here.
local groups={
trees="oak birch pine willow alder beech aspen rowan hazel spruce",
shrubs="bilberry blackberry raspberry elder blackthorn hawthorn dog_rose holly juniper guelder_rose",
grasses="sedge bulrush meadow_fescue cocksfoot timothy sweet_vernal_grass yorkshire_fog tufted_hairgrass common_reed soft_rush",
herbs="clover nettle heather bluebell yarrow foxglove meadowsweet wild_garlic cowslip wood_anemone",
ferns_mosses="bracken lady_fern male_fern harts_tongue polypody sphagnum haircap_moss moss fork_moss star_moss",
fungi="fly_agaric birch_bolete bay_bolete chanterelle wood_blewit shaggy_inkcap turkey_tail oyster_fungus honey_fungus birch_polypore",
aquatic="duckweed yellow_waterlily white_waterlily water_crowfoot water_mint water_forgetmenot brooklime water_starwort hornwort pondweed",
lichens="reindeer_lichen oakmoss old_mans_beard lichen dog_lichen lungwort_lichen sunburst_lichen hammered_shield cup_lichen pixie_cup",
}
for group,text in pairs(groups) do for id in text:gmatch("%S+") do plant_record(id,group) end end
-- The added twenty are registered by botany, not by this pure module.
local extra_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","shrubs"},
{"ivy","Hedera helix","shrubs"},{"gorse","Ulex europaeus","shrubs"},{"broom","Cytisus scoparius","shrubs"},
{"crab_apple","Malus sylvestris","trees"},{"wild_cherry","Prunus avium","trees"},
{"field_maple","Acer campestre","trees"},{"holly_fern","Polystichum setiferum","ferns_mosses"},
}
for _,r in ipairs(extra_plants) do plant_record(r[1],r[3] or "herbs",r[2],false) end
local function evidence(p,field,url,note)
p.evidence[field]={source_url=url,note=note,reviewed="2026-11-03"}
p.source_url=p.source_url or url; p.confidence="partial_species_evidence"
end
local function field(p,name,value)
p[name]=value; evidence(p,name,value.source_url,value.note)
end
-- All ten trees have distinct profiles. Unsourced timing fields retain their
-- own policy_fallback label even when a different field has species evidence.
local tree_rows={
{"oak","Quercus robur",40,60,500,1000,"https://www.forestryengland.uk/article/english-oak"},
{"birch","Betula pendula",10,20,60,90,"https://treesforlife.org.uk/into-the-forest/trees-plants-animals/trees/birch/birch-facts/"},
{"pine","Pinus sylvestris",15,30,150,300,WIKI.."Pinus_sylvestris"},
{"willow","Salix alba",5,15,60,120,WIKI.."Salix_alba"},
{"alder","Alnus glutinosa",10,20,60,160,WIKI.."Alnus_glutinosa"},
{"beech","Fagus sylvatica",10,30,300,500,WIKI.."Fagus_sylvatica"},
{"aspen","Populus tremula",10,20,80,200,WIKI.."Populus_tremula"},
{"rowan","Sorbus aucuparia",10,10,80,150,WIKI.."Sorbus_aucuparia"},
{"hazel","Corylus avellana",3,10,60,80,WT.."british-trees/a-z-of-british-trees/hazel/"},
{"spruce","Picea abies",20,40,200,532,WIKI.."Picea_abies"},
}
for _,r in ipairs(tree_rows) do
local p=plants[r[1]]; p.latin=r[2]
p.maturity_days=years(r[3],r[4])
p.lifespan_days=years(r[5],r[6])
evidence(p,"natural_history",r[7],"Species reference; only separately attributed fields are verified numerical claims.")
end
field(plants.oak,"maturity_days",years(40,40,tree_rows[1][7],"Acorns usually begin around 40 years; approximate report, not a universal minimum.","approximate_onset"))
field(plants.oak,"lifespan_days",years(500,1000,WIKI.."Quercus_robur","Long-lived survivors often exceed 500 years; some exceed 1000. Not a mortality distribution.","longevity_envelope"))
field(plants.birch,"lifespan_days",years(60,90,tree_rows[2][7],"Typical birch longevity; page covers silver and downy birch and notes exceptional ages above 150.","typical_range"))
field(plants.pine,"lifespan_days",years(150,300,tree_rows[3][7],"Normal reported lifespan; exceptional trees exceed this range.","typical_range"))
field(plants.alder,"lifespan_days",years(60,160,tree_rows[5][7],"Composite envelope: Woodland Trust approximately 60; Wikipedia up to 160. Not typical 60-160 survival.","composite_envelope"))
evidence(plants.alder,"lifespan_context",WT.."british-trees/a-z-of-british-trees/alder/","Approximately 60 years; contrasts with upper longevity reports.")
field(plants.beech,"maturity_days",years(10,30,tree_rows[6][7],"Small seed quantities around 10 years, heavy crops at least 30; different reproductive milestones.","onset_to_heavy_crop"))
field(plants.beech,"lifespan_days",years(300,500,tree_rows[6][7],"Undisturbed lifespan around 300; exceptional individual above 500.","longevity_envelope"))
field(plants.aspen,"lifespan_days",years(80,200,tree_rows[7][7],"Upper endpoint 200 reported; lower endpoint 80 is unverified policy, not a sourced range.","policy_envelope"))
plants.aspen.lifespan_days.confidence="policy_fallback" -- source supports only its upper limit
field(plants.rowan,"maturity_days",years(10,10,tree_rows[8][7],"Reported maturity at about ten years.","approximate_onset"))
field(plants.rowan,"lifespan_days",years(80,150,tree_rows[8][7],"Often not older than 80 in warmer regions; occasional 150.","longevity_envelope"))
field(plants.hazel,"lifespan_days",years(60,80,tree_rows[9][7],"Uncoppiced upper limit 80 reported; lower endpoint 60 is policy. Coppice stools can persist centuries.","policy_envelope"))
plants.hazel.lifespan_days.confidence="policy_fallback"
field(plants.spruce,"lifespan_days",years(200,532,tree_rows[10][7],"532-year ring-dated individual; lower endpoint 200 is policy, not sourced. Never use clonal root ages as stem ages.","policy_envelope"))
plants.spruce.lifespan_days.confidence="policy_fallback"
for _,id in ipairs({"pine","spruce","holly","ivy","gorse","holly_fern","harts_tongue","polypody"}) do
plants[id].seasonal_policy="retain_foliage_policy"
end
for _,id in ipairs({"fungi","lichens","ferns_mosses"}) do
for _,p in pairs(plants) do if p.group==id then p.age_origin="propagule_or_colony" end end
end
local garlic=WILDLIFE.."wildflowers/garlic-mustard"
plants.garlic_mustard.life_cycle="biennial"; plants.garlic_mustard.life_cycle_confidence="source_reported"
evidence(plants.garlic_mustard,"life_cycle",garlic,"First-season leaves retained over winter; flowers in second spring.")
plants.garlic_mustard.maturity_days=range(365,730,false,"Second-season policy window; not an exact source-measured number of days.")
plants.garlic_mustard.lifespan_days=range(365,730,false,"Two-season policy; do not kill at the first frost.")
local foxglove=WILDLIFE.."wildflowers/foxglove"
plants.foxglove.life_cycle="biennial"; plants.foxglove.life_cycle_confidence="source_reported"
evidence(plants.foxglove,"life_cycle",foxglove,"Flower stems in second year; biennial representation, not a guaranteed death date.")
plants.foxglove.maturity_days=copy(plants.garlic_mustard.maturity_days)
-- Registered teasel remains explicitly unverified rather than inventing a citation.
plants.teasel.life_cycle="biennial"
local function animal_record(id,latin,path,diet,movement,life_a,life_b)
local url=WILDLIFE..path
local p={id=id,latin=latin,kind="animal",pending=false,
maturity_days=false,gestation_days=false,incubation_days=false,juvenile_days=false,lifespan_days=false,
breeding_months=false,birth_count=false,birth_count_unit="young_per_litter",reproduction="unknown",
diet=diet,movement=movement,source_url=url,confidence="partial_species_evidence",evidence={},
age_origin="birth_or_hatching",reproduction_enabled=false}
animals[id]=p
evidence(p,"diet",url,diet); evidence(p,"movement",url,movement)
if life_a then field(p,"lifespan_days",years(life_a,life_b,url,"Reported average lifespan, not maximum age or a death deadline.","reported_average")) end
return p
end
animal_record("rabbit","Oryctolagus cuniculus","mammals/rabbit","grass and other vegetation","hopping; underground warrens",3,3)
animal_record("red_squirrel","Sciurus vulgaris","mammals/red-squirrel","nuts and conifer seeds","tree climbing and branch leaping; no hibernation",6,6)
animal_record("bank_vole","Myodes glareolus","mammals/bank-vole","fruit, nuts and small insects","agile; shallow burrows; no hibernation",.5,1.5)
animal_record("wood_mouse","Apodemus sylvaticus","mammals/wood-mouse","berries and seeds","mostly nocturnal; climbing and food caching",1,1)
animal_record("hedgehog","Erinaceus europaeus","mammals/european-hedgehog","invertebrates including beetles and earthworms","nocturnal; winter hibernation",2,3)
animal_record("badger","Meles meles","mammals/european-badger","worms, fruit, plants, eggs and small animals","digging; family groups in setts",5,8)
animal_record("roe_deer","Capreolus capreolus","mammals/roe-deer","tree and shrub buds and leaves; herbs and grasses","solitary in summer; loose winter groups",7,7)
animal_record("stoat","Mustela erminea","mammals/stoat","small rodents and rabbits","bounding gait; active day and night",2,5)
animal_record("weasel","Mustela nivalis","mammals/weasel","voles, mice and small birds","low running gait; enters rodent burrows",2,2)
animal_record("otter","Lutra lutra","mammals/european-otter","fish, amphibians, waterbirds and crustaceans","swimming; sheltered holts",5,10)
local function bird(id,latin,diet,movement,life)
local p=animal_record(id,latin,"unused",diet,movement)
p.source_url=RSPB..id:gsub("_","-"); p.evidence={}
evidence(p,"diet",p.source_url,diet); evidence(p,"movement",p.source_url,movement)
p.reproduction="egg"; p.birth_count_unit="eggs_per_clutch"
if life then field(p,"lifespan_days",years(life,life,p.source_url,"Reported average, not maximum or mandatory death age.","reported_average")) end
end
bird("robin","Erithacus rubecula","worms, insects, other invertebrates, seeds and fruit","hopping; territorial; mostly UK resident")
bird("blackbird","Turdus merula","worms, insects, berries and fruit","ground hopping; some seasonal migration",3)
bird("blue_tit","Cyanistes caeruleus","insects, caterpillars, seeds and nuts","clinging and flight; winter feeding flocks",3)
bird("barn_owl","Tyto alba","mice, voles, shrews and small birds","low silent hunting flight, often dawn or dusk")
bird("mallard","Anas platyrhynchos","seeds, plants, insects and shellfish","swimming and flight; resident or migratory")
animal_record("common_frog","Rana temporaria","amphibians/common-frog","invertebrates and smaller amphibians","hopping and swimming; winter shelter",5,10)
animal_record("smooth_newt","Lissotriton vulgaris","amphibians/smooth-newt","land invertebrates; aquatic crustaceans, molluscs and tadpoles","aquatic breeding; nocturnal land activity; winter shelter",6,6)
animal_record("brown_trout","Salmo trutta","freshwater-fish/brown-trout","insect larvae, small fish and flying insects","river swimming; freshwater resident form",15,20)
animal_record("bumblebee","Bombus terrestris","invertebrates/bees-and-wasps/buff-tailed-bumblebee","floral nectar and pollen","flight; underground colony; some winter-active colonies")
local worm=animal_record("earthworm","Lumbricus terrestris","unused","dead leaf litter and mineral soil","vertical burrows; moist-surface movement")
worm.source_url=WIKI.."Lumbricus_terrestris"; worm.evidence={}
evidence(worm,"diet",worm.source_url,worm.diet); evidence(worm,"movement",worm.source_url,worm.movement)
evidence(worm,"lifespan_context",worm.source_url,"Natural lifespan unknown; six years in captivity is not a wild lifespan range.")
field(worm,"maturity_days",years(1,2,worm.source_url,"At least one year development; reproductive maturity in second year.","development_window"))
worm.reproduction="paired_hermaphrodite_cocoon"
animal_record("hare","Lepus europaeus","mammals/brown-hare","vegetation and young woody bark","bounding; surface forms, not rabbit warrens",2,4)
animal_record("fox","Vulpes vulpes","mammals/red-fox","small mammals, birds, amphibians, worms and fruit","mostly nocturnal; territorial scent marking",2,3)
-- Generic legacy deer is deliberately NOT silently reidentified as roe deer.
animals.deer={id="deer",kind="animal",latin=false,pending=false,confidence="unverified_identity",source_url=false,
evidence={},maturity_days=false,juvenile_days=false,gestation_days=false,incubation_days=false,
lifespan_days=false,breeding_months=false,birth_count=false,reproduction="unknown",reproduction_enabled=false}
local function count(id,a,b)
local p=animals[id]; field(p,"birth_count",range(a,b,p.source_url,"Source-reported litter or clutch count; not guaranteed survivors.","offspring_count"))
end
count("rabbit",3,7); count("bank_vole",3,5); count("wood_mouse",4,8); count("stoat",6,12)
count("weasel",3,6); count("roe_deer",2,3); count("hare",2,4)
count("robin",4,5); count("blackbird",3,4)
local function months(id,list,url,note)
local p=animals[id]; p.breeding_months=list; evidence(p,"breeding_months",url or p.source_url,note)
end
months("roe_deer",{7,8},nil,"Mating July-August; delayed implantation; births May-June, not immediate gestation completion.")
animals.roe_deer.reproduction="delayed_implantation"
animals.stoat.reproduction="delayed_implantation"
evidence(animals.stoat,"reproduction",animals.stoat.source_url,"Summer mating, implantation delayed until following spring; no unsupported numeric gestation.")
months("blackbird",{3,4,5,6,7,8},nil,"UK nesting usually March-August, not a universal physiological limit.")
months("blue_tit",{4,5,6},nil,"UK nesting April-June; normally one brood annually.")
local hare=animals.hare
field(hare,"maturity_days",range(236,365.2425,ADW.."Lepus_europaeus/","Sexual maturity reported from about eight months to one year; conservative runtime uses the upper endpoint.","reported_range"))
field(hare,"gestation_days",range(30,42,ADW.."Lepus_europaeus/","Reported gestation range; several litters can occur during the breeding season.","reported_range"))
field(hare,"juvenile_days",range(30,30,ADW.."Lepus_europaeus/","About one month to weaning/independence; leverets are precocial at birth.","approximate_duration"))
field(hare,"birth_count",range(1,8,ADW.."Lepus_europaeus/","Reported range is 1-8 leverets; runtime uses the conservative lower endpoint.","offspring_count"))
months("hare",{1,2,3,4,5,6,7},ADW.."Lepus_europaeus/","Midwinter (January/February) through midsummer in the cited northern-range account.")
hare.reproduction="live_birth"; hare.reproduction_enabled=true
hare.breeding_seasons={Winter=true,Spring=true,Summer=true}; hare.season_policy="sourced_midwinter_to_midsummer_coarse_seasons"
hare.postpartum_days=30; hare.care_confidence="source_reported"
local fox=animals.fox
field(fox,"maturity_days",range(304,304,ADW.."Vulpes_vulpes/","ADW reports maturity at about ten months (304-day entry); both sexes are reported similarly.","reported_average"))
field(fox,"gestation_days",range(51,53,ADW.."Vulpes_vulpes/","Typical gestation is 51-53 days; the account also notes a broader 49-56-day span.","typical_range"))
field(fox,"juvenile_days",range(56,70,ADW.."Vulpes_vulpes/","Nursing/weaning lasts about 56-70 days; leaving the den at 4-5 weeks is not independence.","weaning"))
field(fox,"birth_count",range(1,9,ADW.."Vulpes_vulpes/","ADW's summary table gives 1-9 pups, while its prose gives 1-13; use the narrower table range and lower endpoint.","source_disagreement_envelope"))
months("fox",{12,1,2},ADW.."Vulpes_vulpes/","Central-range mating is reported in January-February; winter is the conservative coarse runtime window.")
fox.reproduction="live_birth"; fox.reproduction_enabled=true
fox.breeding_seasons={Winter=true}; fox.season_policy="central_range_winter_policy"
fox.postpartum_days=300; fox.care_confidence="policy_fallback"
-- Winter dormancy is a coarse loaded-world behavior, distinct from death or
-- a measured metabolic rate. These taxa retain their saved residents.
animals.hedgehog.winter_dormant=true
animals.common_frog.winter_dormant=true
animals.smooth_newt.winter_dormant=true
local rabbit=animals.rabbit
field(rabbit,"gestation_days",range(30,37,ADW.."Oryctolagus_cuniculus/","ADW reproduction reports 30-37 days; approximately 30 typical.","reported_range"))
field(rabbit,"juvenile_days",range(22,31,ADW.."Oryctolagus_cuniculus/","Weaning age, not sexual maturity.","weaning"))
-- ADW rabbit maturity prose (8 months) conflicts with an embedded 730-day
-- value. Keep maturity unknown rather than laundering that discrepancy.
evidence(rabbit,"maturity_context",ADW.."Oryctolagus_cuniculus/","Conflicting maturity values on this page: not accepted as a verified threshold.")
months("rabbit",{1,2,3,4,5,6,7,8,9,10,11,12},ADW.."Oryctolagus_cuniculus/","Can breed year-round, mostly first half of year; habitat/health still required.")
local squirrel=animals.red_squirrel
field(squirrel,"gestation_days",range(38,39,ADW.."Sciurus_vulgaris/","Gestation 38-39 days.","reported_range"))
field(squirrel,"juvenile_days",range(56,70,ADW.."Sciurus_vulgaris/","Independence eight to ten weeks, not sexual maturity.","independence"))
field(squirrel,"maturity_days",range(296,365.2425,ADW.."Sciurus_vulgaris/","Composite envelope of female 296-day entry, male 320-day entry and approximately one-year narrative.","composite_envelope"))
-- Source gives birth/litter months, not conception months: no month override.
local vole=animals.bank_vole
field(vole,"maturity_days",range(42,56,WT.."animals/mammals/bank-vole/","Females six weeks, males eight weeks; use sex-specific values if available.","sex_envelope"))
field(vole,"juvenile_days",range(25,25,WT.."animals/mammals/bank-vole/","Weaning about 25 days.","weaning"))
local mouse=animals.wood_mouse
field(mouse,"gestation_days",range(21,26,ADW.."Apodemus_sylvaticus/","Gestation range 21-26 days.","reported_range"))
field(mouse,"juvenile_days",range(21,21,ADW.."Apodemus_sylvaticus/","Weaning at three weeks.","weaning"))
field(mouse,"maturity_days",range(65,71,ADW.."Apodemus_sylvaticus/","Male 65-day and female 71-day entries; narrative approximately two months.","sex_envelope"))
field(animals.common_frog,"larval_days",range(98,98,animals.common_frog.source_url,"About 14 weeks AFTER hatching to froglet; not maturity or egg incubation.","metamorphosis"))
animals.common_frog.reproduction="external_eggs"; animals.smooth_newt.reproduction="eggs"
animals.brown_trout.reproduction="external_eggs"; animals.bumblebee.reproduction="colony_caste_dependent"
-- Additional retrieved reproductive accounts, reviewed 2026-11-03. Registration
-- status is independent of evidence. A lifespan is NOT a breeding prerequisite.
field(vole,"gestation_days",range(17,24,ADW.."Myodes_glareolus/","17-24 days, mean 21; upper endpoint includes lactation delay.","reported_range"))
months("bank_vole",{4,5,6,7,8,9},ADW.."Myodes_glareolus/","Late April-September; runtime uses conservative Spring/Summer season policy.")
months("wood_mouse",{2,3,4,5,6,7,8,9,10},WIKI.."Wood_mouse","February-October in this account; ADW instead says March-early winter.")
local blackbird=animals.blackbird
field(blackbird,"maturity_days",years(1,1,ADW.."Turdus_merula/","Usually starts mating at one year.","approximate_onset"))
field(blackbird,"incubation_days",range(13,14,ADW.."Turdus_merula/","13-day prose and 14-day summary; conservative composite.","composite_envelope"))
field(blackbird,"juvenile_days",range(13,14,ADW.."Turdus_merula/","Leaving nest at 13-14 days; NOT full independence.","fledging"))
local mallard=animals.mallard
field(mallard,"maturity_days",range(365,365,ADW.."Anas_platyrhynchos/","Both sexes reported at 365 days; yearling breeding.","reported_average"))
field(mallard,"incubation_days",range(26,28,ADW.."Anas_platyrhynchos/","Time to hatching 26-28 days.","reported_range"))
field(mallard,"birth_count",range(9,13,ADW.."Anas_platyrhynchos/","Typical hen lays 9-13 eggs; not guaranteed survivors.","clutch_count"))
field(mallard,"juvenile_days",range(50,60,WIKI.."Mallard","50-60 days to flight; not reproductive maturity.","fledging"))
local blue=animals.blue_tit
local diary="https://www.bto.org/understanding-birds/articles/blue-tit-diary"
field(blue,"incubation_days",range(14,14,diary,"About two weeks; approximate, not an exact physiological constant.","approximate_duration"))
field(blue,"birth_count",range(8,10,diary,"Majority of clutches contain 8-10 eggs; larger clutches exist.","typical_clutch"))
field(blue,"juvenile_days",range(18,21,diary,"Fledging 18-21 days; parents feed for further weeks.","fledging"))
evidence(blue,"maturity_context",diary,"Usually breeds in spring after hatching; no exact day threshold accepted. Caterpillar provisioning absent, so disabled.")
field(animals.common_frog,"maturity_days",years(3,3,WIKI.."Common_frog","Sexual maturity after three years in this account; local variation exists.","approximate_onset"))
field(animals.common_frog,"incubation_days",range(14,21,WIKI.."Common_frog","Eggs hatch after 2-3 weeks; temperature-dependent, NOT froglet age.","reported_range"))
evidence(animals.brown_trout,"reproduction_context",WIKI.."Brown_trout","About 2000 eggs/kg; requires female mass, gravel redd and temperature-dependent development, not a fixed litter.")
evidence(animals.bumblebee,"reproduction_context",WIKI.."Bombus_terrestris","Queen-founded colonies, diploid females/haploid males, larvae and pupae; caste-specific care cannot use a mammalian litter timer.")
-- Explicit seasonal/care policies, NOT source-reported month precision or a
-- claim to model nests/physiology. Upper dependency endpoints avoid postpartum
-- immediate breeding; bird care lock is deliberately longer than fledging.
for _,id in ipairs({"bank_vole","wood_mouse","blackbird","mallard"}) do
local p=animals[id]
p.reproduction_enabled=true
p.reproduction=(id=="bank_vole" or id=="wood_mouse") and "live_birth" or "egg"
p.breeding_seasons={Spring=true,Summer=true}
p.season_policy="conservative_temperate_spring_summer"
p.postpartum_days=math.max(21,p.juvenile_days.max)+(p.reproduction=="egg" and 21 or 0)
p.care_confidence="policy_fallback"
end
local function id_ok(id) return type(id)=="string" and #id<=64 and id:match("^[a-z][a-z_]*$")~=nil end
function L.plant(id,group)
if not id_ok(id) then return nil,"id" end
if plants[id] then
if group~=nil and group~=plants[id].group then return nil,"group" end
return copy(plants[id])
end
return nil,"unknown_species" -- never fabricate an identity from a group hint
end
function L.animal(id)
if not id_ok(id) then return nil,"id" end
if not animals[id] then return nil,"unknown_species" end
return copy(animals[id])
end
function L.ids(kind)
local out={}; local src=kind=="plant" and plants or kind=="animal" and animals
if not src then return nil,"kind" end
for id in pairs(src) do out[#out+1]=id end; table.sort(out); return out
end
local function valid_range(r)
return type(r)=="table" and finite(r.min) and finite(r.max) and r.min>=0 and r.max>=r.min and r.max<=1000000
end
-- Stable numeric seed, field salt <=64 bytes, bounded floating-point arithmetic.
-- Reproducible selection only; NOT cryptography and never math.randomseed().
function L.duration(r,seed,salt)
if not valid_range(r) then return nil,"range" end
if not finite(seed) or seed<0 or seed>2147483646 or seed%1~=0 then return nil,"seed" end
if type(salt)~="string" or #salt>64 then return nil,"salt" end
local h=seed+1.0
for i=1,#salt do h=(h*131.0+salt:byte(i))%2147483647.0 end
h=(h*48271.0)%2147483647.0
return r.min+(r.max-r.min)*(h/2147483647.0)
end
local durations={"germination_days","juvenile_days","maturity_days","lifespan_days","gestation_days","incubation_days","larval_days"}
function L.resolve(profile,seed)
if type(profile)~="table" or not id_ok(profile.id) then return nil,"profile" end
if not finite(seed) or seed<0 or seed>2147483646 or seed%1~=0 then return nil,"seed" end
local out=copy(profile); out.selected_days={}
for _,name in ipairs(durations) do
if profile[name] then
local n,err=L.duration(profile[name],seed,profile.id..":"..name)
if not n then return nil,err end
out.selected_days[name]=n
end
end
-- Overlapping stage ranges must not produce maturity before dependency or
-- germination. Clamp to the preceding selected endpoint, never outside a range.
local previous=0
for _,name in ipairs({"germination_days","juvenile_days","maturity_days","lifespan_days"}) do
local n=out.selected_days[name]
if n then
n=math.max(previous,n)
if n>profile[name].max then return nil,"profile_order" end
out.selected_days[name]=n; previous=n
end
end
out.selection_seed=seed; return out
end
function L.elapsed(previous,now,work_cap_seconds)
if not clock(previous) or not clock(now) then return nil,"clock" end
if now<previous then return nil,"clock_reversed" end
if not clock(work_cap_seconds) then return nil,"cap" end
local age_delta=now-previous; local work=math.min(age_delta,work_cap_seconds)
return {elapsed_seconds=age_delta,work_seconds=work,deferred_seconds=age_delta-work}
end
local function threshold(profile,name)
local r=profile[name]
if r==false or r==nil then return false end
if not valid_range(r) then return nil end
if profile.selected_days~=nil and type(profile.selected_days)~="table" then return nil end
local selected=profile.selected_days and profile.selected_days[name]
if selected~=nil then
if not finite(selected) or selected<r.min or selected>r.max then return nil end
return selected
end
return r.max -- conservative unsampled policy, not a random draw on each read
end
function L.phase(profile,born_at,now)
if type(profile)~="table" or (profile.kind~="plant" and profile.kind~="animal") then return nil,"profile" end
if not clock(born_at) or not clock(now) then return nil,"clock" end
if now<born_at then return nil,"clock_reversed" end
local age=(now-born_at)/DAY
local g,j,m,life=threshold(profile,"germination_days"),threshold(profile,"juvenile_days"),
threshold(profile,"maturity_days"),threshold(profile,"lifespan_days")
if g==nil or j==nil or m==nil or life==nil then return nil,"profile" end
if (g and j and g>j) or (j and m and j>m) or (m and life and m>life) then return nil,"profile_order" end
local phase="unknown"
if profile.kind=="plant" and g and age<g then phase="seed"
elseif j and age<j then phase=profile.kind=="plant" and "seedling" or "dependent"
elseif m and age<m then phase="juvenile"
elseif m then phase="mature" end
local beyond=life and age>=life or false
return {phase=phase,age_days=age,age_seconds=now-born_at,beyond_lifespan_range=beyond,
-- A population average or record age must NEVER be an automatic death.
death_due=false,reproductive_ready=false,confidence=profile.confidence}
end
function L.seasonal(profile,season)
if type(profile)~="table" or profile.kind~="plant" then return nil,"profile" end
if not ({Spring=true,Summer=true,Autumn=true,Winter=true})[season] then return nil,"season" end
return {dormant=season=="Winter" and profile.seasonal_policy=="dormant_not_dead",death_due=false,
confidence="policy_fallback"} -- presentation only; not a frost or survival model
end
function L.breeding_window(profile,month)
if type(profile)~="table" or profile.kind~="animal" then return nil,"profile" end
if not finite(month) or month%1~=0 or month<1 or month>12 then return nil,"month" end
local list=profile.breeding_months
if list==false or list==nil then return {known=false,within_window=false,eligible=false} end
if type(list)~="table" or #list>12 then return nil,"profile" end
local allowed=false
for _,m in ipairs(list) do
if not finite(m) or m%1~=0 or m<1 or m>12 then return nil,"profile" end
if m==month then allowed=true end
end
return {known=true,within_window=allowed,eligible=false} -- window alone never authorizes reproduction
end
L.VERSION=1; L.SECONDS_PER_DAY=DAY; L.DAYS_PER_YEAR=YEAR; L.MAX_CLOCK=MAX_CLOCK
return L