388 lines
30 KiB
Lua
Executable File
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
|