Files
last-hearth/mods/hearth/species_effects.lua
T
2026-11-03 12:41:09 -05:00

117 lines
7.8 KiB
Lua
Executable File

-- Reviewed policy, not executable Wikipedia prose. No network, scheduler or map writes.
return function(hearth, api)
local E = {}
local B = assert(hearth.botany, "species effects require botany")
local articles=hearth.wiki_articles
if not articles and api.get_modpath then
articles=dofile(api.get_modpath("hearth").."/wiki_articles.lua")
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 finite(n) return type(n)=="number" and n==n and math.abs(n)<math.huge end
local function count(n,cap) return finite(n) and math.min(cap,math.max(0,math.floor(n))) or 0 end
local wiki="https://en.wikipedia.org/wiki/"
-- Original summaries of pages reviewed 2026-11-03. Numbers below are game
-- approximations, NOT measurements reported by these articles.
local reviewed={
alder={article="Alnus_glutinosa",note="Ecology: Frankia root nodules fix nitrogen and improve soil fertility."},
clover={article="Trifolium_repens",note="Cultivation and uses / Forage: symbiotic root-nodule nitrogen fixation."},
dog_lichen={article="Peltigera_canina",related="Peltigera",
note="Species Description identifies Nostoc; genus Description explains nitrogen fixation. Genus-level mechanism, not a measured species rate."},
sphagnum={article="Sphagnum_palustre",related="Sphagnum",
note="Species introduction describes water absorption; genus introduction explains cation exchange and acidification. Acidification is genus-level evidence."},
duckweed={article="Lemna_minor",related="Lemnoideae",
note="Species Cultivation describes floating carpets; subfamily Duckweed in natural environments describes shade. Shade is a group-level mechanism."},
birch_polypore={article="Fomitopsis_betulina",
note="Distribution, habitat and ecology: brown rot of birch wood; living-tree parasitism is not simulated."},
fly_agaric={article="Amanita_muscaria",
note="Distribution and ecology: ectomycorrhizal tree associations. Game host list is deliberately narrower; carbon and water exchange are not simulated."},
}
local function record(value)
return B.get(type(value)=="table" and value.id or value)
end
-- Identity references cover all 80 records. An identity link is not evidence
-- that every tagged trait has been reviewed or implemented.
function E.source(value)
local r=record(value); if not r then return nil end
local evidence=reviewed[r.id]
local snapshot=hearth.wiki_articles or articles
local article=type(snapshot)=="table" and snapshot[r.id]
return {source_species=r.latin,source_url=evidence and wiki..evidence.article
or (article and article.url) or wiki..r.latin:gsub(" ","_"),
reviewed=evidence~=nil,reviewed_on=evidence and "2026-11-03" or nil,
note=evidence and evidence.note or "Identity reference only; no species-effect evidence reviewed here.",
evidence_url=evidence and wiki..(evidence.related or evidence.article) or nil,
-- Snapshot revision is identity provenance; the intro may omit the
-- reviewed body section. Never present it as a pinned effect claim.
identity_revision=article and article.revision or nil,
identity_revision_url=article and article.revision_url or nil}
end
function E.sources()
local out={}; for _,r in ipairs(B.list()) do out[r.id]=E.source(r) end; return out
end
local tags={"nitrogenfixer","browse","pollinator","ectomycorrhiza","saprotroph","parasite","engineer","nursery","allelopathy","resin"}
local nitrogen="After 900 warm active seconds: at most 1 nutrient per eligible evaluation, 2 total per support via succ_fixed_nitrogen shared across species/restarts; stops at fertility 4 (soil cap 8)."
local descriptions={
browse="Active natural plants count as wildlife forage; grazing consumes eligible plants, not mature trees. Not human food permission.",
pollinator="Only natural plants in their catalogue flowering season add to pollination. This input does not itself change berry timers.",
nursery="Each active natural non-component plant adds nursery cover; any cover reduces succession stress accumulation to 75%. No extra animal spawn is promised.",
parasite="Not simulated: no infection or attack on living trees or player plants.",
allelopathy="Not simulated: no chemical suppression or plant killing.",
resin="Not simulated by species effects: no automatic resin production or extraction.",
}
function E.describe(value)
local r=record(value); if not r then return {} end
local out={}
local function add(title,status,game)
local row=E.source(r); row.title,row.status,row.game=title,status,game
out[#out+1]=row
end
for _,tag in ipairs(tags) do if r.tags[tag] then
local status,game="descriptive",descriptions[tag]
if tag=="nitrogenfixer" then status,game="simulated",nitrogen
elseif tag=="browse" or tag=="pollinator" or tag=="nursery" then status="simulated"
elseif tag=="ectomycorrhiza" then
if r.group=="fungi" then
status,game="simulated","Active compatible nearby fungus permits seedling growth at 16200 instead of 21600 active seconds; successful growth debits at most 1 existing soil nutrient once, regardless of fungus count. No nutrient creation or carbon/water ledger."
else game="Not simulated as a tree aura: only an active compatible nearby fungus can enable the finite seedling nutrient debit." end
elseif tag=="saprotroph" then
status,game="simulated","On suitable natural deadwood/litter only: advance its finite decay age by at most 60 seconds per evaluation, capped at 7200/1800. Only substrate decay pays soil nutrients once; the fungus pays none. Soil-growing saprotrophs receive no special nutrient effect."
elseif tag=="engineer" then
if r.id=="sphagnum" then
status,game="simulated","Local land habitat estimate: +0.10 moisture and -0.20 pH per mat, at most 2 mats; moisture clamped 0..1, pH 3.5..8.5. No terrain, water or persistent pH writes."
elseif r.id=="duckweed" then
status,game="simulated","Active floating mats reduce nearby submerged freshwater habitat light by 1.5 each, at most 4 mats / 6 light; never brighten an already-dark site and never lower it below 2. Source-water nodes remain intact."
else game="Not simulated: no species-specific erosion, bank stabilization, rock weathering or hydrology effect for this engineer tag." end
end
add(tag,status,game or "Not simulated: natural-history descriptor only.")
end end
if r.group=="trees" then
add("canopy","simulated","Trunks/crowns reduce local habitat light and contribute moisture through existing canopy sampling; pine/spruce retain full Winter influence, deciduous foliage retains 15%. Not a hydrological solver.")
end
if r.toxic then add("toxicity","descriptive","Not simulated as an attack: this warning never grants a poison aura or human food permission.") end
return out
end
-- Pure derivation from ONE base sample. Callers must supply fresh baseline h
-- on each observation, never feed a derived result back as a new baseline.
function E.habitat(h,observations)
local out=copy(h)
if type(h)~="table" or h.loaded~=true then return out end
local seen=type(observations)=="table" and observations or h.species_counts or {}
out.source_moisture,out.source_ph,out.source_light=h.moisture,h.ph,h.light
if (h.water or 0)==0 and (h.aquatic==nil or h.aquatic=="land" or h.aquatic=="bank") then
local mats=count(seen.sphagnum,2)
if mats>0 then
if finite(h.moisture) then out.moisture=math.max(0,math.min(1,h.moisture+mats*0.1)) end
if finite(h.ph) then out.ph=math.max(3.5,math.min(8.5,h.ph-mats*0.2)) end
end
elseif h.freshwater==true and h.aquatic=="submerged" and finite(h.light) then
out.light=h.light-math.min(count(seen.duckweed,4)*1.5,math.max(0,h.light-2))
end
return out
end
return E
end