122 lines
8.9 KiB
Lua
Executable File
122 lines
8.9 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 100 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 90 real calendar days of root age, in warm active conditions: 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). Timing is game policy, not a sourced fixation rate."
|
|
local descriptions={
|
|
browse="Active natural plants count as wildlife forage; grazing debits a shared partial-canopy ration with a seven-real-day rest, not the perennial root. Not human food permission.",
|
|
pollinator="Mature natural plants in their flowering season support pollination. Nectar visits reserve a shared one-hour nectar deadline and at most one pollination credit expiring after one real day. Human harvesting spends that credit for at most one extra berry from an existing fruit crop; it never resets annual fruit readiness or creates seeds.",
|
|
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 a finite nutrient exchange: successful calendar-eligible seedling growth debits at most 1 existing soil nutrient once, regardless of fungus count. No accelerated growth, nutrient creation or carbon/water ledger; lifecycle age thresholds are unchanged."
|
|
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: add at most 60 seconds per evaluation to its finite decay bonus, capped at one mean real year for deadwood or 90 real days for litter. Only substrate decay pays its remaining 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
|
|
local profile=hearth.lifecycle and hearth.lifecycle.plant(r.id)
|
|
add("lifecycle","simulated", "Calendar ages use real elapsed days, including unloaded time; reconciliation is bounded and loaded-only, with no missed-event replay. "
|
|
.. (profile and "Profile: "..profile.confidence..". Each timing field is separately source_reported, policy_fallback or UNKNOWN; an identity URL is not timing evidence."
|
|
or "Lifecycle profile UNKNOWN in this runtime; no invented maturity or lifespan.")
|
|
.. " Lifespan endpoints never authorize automatic death; fungal/lichen unknown maturity is not propagation permission.")
|
|
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
|