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

634 lines
46 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Living ecosystems
The Last Hearth's original succession model takes ecological inspiration from
[Dynamic Life](https://www.planetminecraft.com/data-pack/dynamic-life-1-21-5-and-above/).
No datapack code, species implementation, textures or other assets were copied.
This is a runtime simulation, not extra random map decorations or world regeneration.
## What changes
Unclaimed, unmodified ecological ground automatically recruits niche-appropriate
groundcover, including the eight understory plants described below.
Sunny, moderately grassy sites support wildflowers; dense grass competes with
flowers. Moist shaded sites support ferns. Trees reduce local light and retain
moisture; prolonged shade kills sun-loving ground cover. Rivers and biome humidity
also influence moisture. Established sparse meadow can recruit a tree seedling;
seedlings become young trees, then mature seed-bearing trees. Lifespan endpoints
never automatically kill calendar plants or turn them into deadwood.
Living plants disperse to one adjacent suitable gap per eligible attempt. Mature
trees shed litter and disperse seedlings. Competition and occupation reject seeds;
seedlings never replace flowers, crops, another tree, or even existing meadow grass.
Wildlife can consume active natural grass, ferns, clover, sedge and nettles and
leave dung. Dormant winter plants cannot supply browse. Grazing debits one partial
canopy ration without removing the perennial; the shared per-root browse receipt
rests seven real days. No stubble rendering is installed by this change. Dung, litter and deadwood
decompose; moist, shaded organic patches seed fungi. Decomposition releases finite
nutrients into the supporting soil (capped at eight). Fertile soil recruits ground
cover sooner and successful establishment consumes one nutrient. Fungi are
not edible forage; generic `fungi` decomposes, while botanical fungi have
host-specific root associations or saprotrophic substrate effects below.
These feedbacks permit shifting meadow/flower/
woodland patches rather than a single guaranteed final biome.
Fallen logs (`hearth:deadwood`) are **ordinary full wood cubes laid sideways**, not
irregular branch meshes. Their label is **Fallen log (axe; yields timber)**. Each
requires an axe and drops **one `hearth:log`**, exactly like standing timber: `tree=1`,
`choppy=2`, no hand-diggable `snappy` group. Default full-cube collision and selection
apply. Normal reach, claim/external protection and construction support-removal
checks remain; there is no right-click bypass. Loose `hearth:sticks` remain
hand-gatherable, preserving the first-tool path. Stone still needs a mining tool.
New woodland mapgen places sparse three-cube fallen logs, never force-placing them.
`paramtype2="facedir"`, `param2=12` points end grain along +X; a random schematic
rotation can align the line along Z (`param2=4` is +Z). The name `hearth:deadwood`
is retained for decomposition and species metadata. The `hearth:block_deadwood`
load callback rotates legacy vertical deadwood once, marks `succ_block_deadwood=1`,
and leaves already-horizontal orientation intact. It rechecks exact loaded node
identity, respects external protection, and uses `swap_node` to retain owner/player
markers, natural flags, age, metadata inventories and timers. It does not convert
other timber, rewrite surrounding nodes, or regenerate existing terrain.
### Calendar biology versus bounded loaded work
| Process | Observed active time |
| --- | --- |
| Bare soil recruits ground cover | 3 minutes; 90 seconds with nutrients |
| Grass/flower/fern dispersal eligibility | 15 minutes; attempt every third evaluation |
| Severe niche stress to litter/deadwood | 10 minutes |
| Old meadow recruits a seedling | 2 hours; 4 in dense unfertilized grass, only suitable spatial niches |
| Named seedling to young tree | Absolute juvenile-age endpoint from its lifecycle profile (default 5 real years) |
| Named young to mature tree | Absolute species maturity endpoint, not an additional stage-local duration |
| Generic unidentified tree | Explicit fallback: young after 1 real year, mature after 10; lifespan unknown |
| Living calendar plant death | Never inferred from lifespan endpoints or unknown age |
| Mature tree seed opportunity | At most one present eligible attempt per mean real year; no missed-event replay |
| Named understory seed opportunity | Requires known maturity and seed season; at most one attempt per 30 days |
| Dung / generic fungi decay | Existing game policies: 10 / 40 loaded minutes |
| Litter / deadwood decay | Conservative game fallbacks: 90 calendar days / one mean real year |
New tree generations take actual calendar years, not compressed sessions. One
day is 86,400 seconds, a mean year 365.2425 days. Every named plant uses
[the lifecycle policy](lifecycles.md), including explicitly unverified group
fallbacks; sourced fields are not proof that all timing fields were researched.
Stage swaps preserve one root age. Cold below 3°C/dormancy suppresses visible
stage transitions and seeding, **not calendar aging**. No exact historical
growing-season integration or mortality model is claimed. Young/mature identities are
ordinary full-cube log roots; **a mature tree needs an actual multi-node block trunk
and leaf canopy**, not one bare root cube. The read-only `diversity.tree_plan`
interface below shares the same geometry used by mapgen. Applying it, validating
every occupied destination and support, and tracking generated components belong
to the ecological growth owner. This content change installs no geometry-writing
scheduler or automatic load-time expansion. Do not retain the old 5×5×6 mesh-only
clearance assumption: mature profiles reach 9×10×9 and must be preflighted in full.
Unknown/player plants, water, unloaded nodes, claims and markers must block growth;
the plan's omitted air cells are never permission to clear or replace anything.
Existing mapgen log/leaves trees contribute shade but are not aged or destroyed.
Player-planted generic saplings now require one real year before their bounded
young block-tree establishment, using an explicitly unidentified family fallback,
not an assumed birch. Generic trees have a ten-year maturity policy and no
automatic death. Player provenance is retained on every newly written timber cell.
Existing mature player/mapgen timber is not retroactively made killable.
Generic game grain has an explicit **120 warm-day approximate parameter**, not a
species/cultivar research claim. Stages require 24/72/120 equivalent warm days;
temperature below 3°C contributes zero, 3–10°C half, warmer one. At most 366
midpoint samples cover the entire planting-to-now interval, with no silent
30-day age cutoff. Long gaps coarsen quadrature, not discard age. This is bounded
climate estimation, not exact historic degree-day integration; stage high-water
prevents display regression. Invalid/future planting clocks and rollback pause,
never reset birthdays. Berry regrowth and food-patch harvest cooldowns remain
game resource policies, not organism lifespans. Legacy unidentified berry bushes
retain their initial standing crop, but picking locks them for one mean real year;
bare bushes then return only in an eligible Summer. This is a conservative annual
game policy, not researched species phenology. Rejected inventory transfer
restores original readiness; partial transfer spends the batch. Node swaps
preserve natural/ownership metadata. Seasonal greens/mushrooms retain their daily
game harvest policy, with reservation before inventory callbacks.
#### Calendar root persistence and migration (version 1)
See [the full metadata contract](botany.md#root-persistence-and-migration).
`succ_birth_at` is the UTC recruitment/adoption epoch, `succ_initial_age_seconds`
is a separate documented initial-stage assumption, and
`succ_last_observed_at` is a durable clock high-water mark.
`succ_lifecycle_version=1`, exact `succ_lifecycle_profile`, position-derived
`succ_lifecycle_seed`, `succ_lifecycle_origin` and policy label persist on roots.
New recruits use `natural_seed` and zero initial age. Existing natural legacy
plants migrate once at NOW with `legacy_established`, preserving `succ_age`
unchanged; it remains old active seconds, never millennia or a birthday.
Generated known botanical stands/soil-supported block log roots adopt
`worldgen_established` without resetting mature canopies into seeds or claiming
historically observed ages. Initial stage ages come from known profile endpoints;
unknown maturity is zero, not invented history. No negative/pre-epoch birthdays.
Malformed metadata, unsupported versions and clock rollback fail closed. No
offline mortality, forced terrain loads, whole-world scans or queued seed replay.
## Species diversity and understory
Shared tree identities are exactly **oak, birch, pine, willow, alder, beech, aspen,
rowan, hazel, spruce, crab_apple, wild_cherry, field_maple**, matching the thirteen trees in [botany.md](botany.md). Each has
canonical `hearth:<species>_seedling`, `hearth:<species>_young`, and
`hearth:<species>_mature` nodes. Growth and seed dispersal preserve the parent's
species and calendar anchor; old accelerated stage/death durations are removed.
Each young/mature root drops **one** of its species logs, never three
logs from one cube. Additional generated trunk cubes are separately harvestable.
Roots are hidden from creative inventory, use species log top/end and bark side
textures, `drawtype="normal"`, `tree=1`, `choppy=2`, and default cube collision.
Seedlings are conventional `plantlike` sprites. Mapgen full-block species
logs/leaves/planks belong to the biome module;
logs/leaves contribute canopy without being adopted as killable succession trees.
Recruitment is deterministic, not an all-oak fallback:
- Pine favors cold podzol and alpine turf. Warm podzol permits pioneer birch.
- Birch pioneers dry/open grass, earth and woodland gaps.
- Oak favors stable forest floor with an existing canopy.
- Beech has a broad domed crown and avoids saturation; aspen is tall and columnar;
rowan has an open crown; hazel is low coppice; spruce has a narrow evergreen spire.
Crab apple has a broad round crown, wild cherry has horizontal branch tiers,
and field maple has a broad lobed crown. All thirteen have distinct mature block
silhouettes, species bark/end-grain/leaves/planks and deterministic recruitment.
- Willow favors moist alluvium/floodplain; alder favors peat and wet woodland.
Riparian recruitment requires moisture ≥0.65. Species soil support is delegated
to `hearth.biomes.supports_species(species, surface)` when present; its decision
is authoritative. A standalone soil policy works without the biome module.
- Ecological soils are grass, soil, forest floor, podzol, alluvium, peat and alpine
turf. Biome `surface(node)` moisture hints blend with humidity/water/canopy.
This is **not** an expansion of the separate crop-planting soil policy.
| Understory | Niche and feedback |
| --- | --- |
| Clover | Sunny, moderately moist gaps; edible; flowering pollinator habitat; fixes bounded nitrogen |
| Nettle | Moist, already fertile ground; edible natural browse, not an inventory food item |
| Heather | Dry, open acidic podzol/alpine turf; autumn pollinator habitat |
| Bluebell | Moist shade; blooms only in Spring, occupied and dormant otherwise |
| Sedge | Saturated, lit riparian soil; edible when active |
| Bulrush | Saturated, lit riparian soil; tall wetland cover, not edible |
| Moss | Moist shade; persistent through Winter, not edible |
| Lichen | Dry exposed rock/open ground; persistent through Winter, not edible |
These are active succession species: they establish, compete, spread into **air**,
undergo stress, and senesce. They do not merely decorate a fixed map. Suitable
understory appears on the first ordinary 90/180-second establishment cycle, rather
than waiting for tree maturation. Wet herbs can recruit riparian trees after two
hours; heather can recruit pine. Saturated sites cannot fall back to meadow grass,
and meadow grass cannot disperse into them. Exposed rock admits lichen only;
neither recruitment nor decay replaces rock with soil or fertilizes it.
Clover can add at most **two nitrogen units per supporting soil node**, recorded
as `succ_fixed_nitrogen` across restart, grazing and recolonization; additions stop
at local fertility four. The universal nutrient cap remains eight, and successful
establishment consumes one. Clover has no harvest/item drop or timer, and cannot
create an endless fertilizer-item loop. Habitat reads clamp corrupt/excessive
nutrient metadata to eight without rewriting it.
Natural blooming clover, wildflowers, spring bluebells and autumn heather increase
the read-only `habitat(pos).pollination` count. This exposes a bounded habitat input
for consumers; habitat reads never change berry timers or grant extra berries.
Transactional nectar debit below is separate from this observation.
`edible` is a separate active-natural-forage count. Pine and spruce
are evergreen in the catalogue and seasonal node policy; deciduous trees
retain only 15% canopy influence.
Known unmarked worldgen diversity herbs, species stage roots and generic deadwood are naturalized by
`diversity.bootstrap(pos, node?)` from a load callback or the existing column probe.
Bootstrap checks the plant and support for ownership, ambiguous claims, external
protection and placement/unknown metadata. Hand placement marks `player_placed=1`.
It never adopts species/generic logs, leaves, planks, crops or berry bushes. There
is no general adoption of arbitrary flora, generic litter or fungi, and no new
scanning scheduler. Adopted deadwood needs loaded natural ecological soil and
decays to litter after **one mean calendar year**, releasing three nutrients
(soil cap eight); subsequent litter decay releases one more, once. Adoption is
idempotent and grants no invented age. Organic decay uses durable
`succ_decay_at`, `succ_decay_initial`, `succ_decay_seen_at` high-water and bounded
`succ_decay_bonus`; elapsed absence changes present eligibility, never queues
missed decomposition or payouts. Litter returns only its unconsumed mineral
budget, not another full unit after earthworm feeding. As with
legacy unmarked soil, provenance cannot distinguish third-party placements that
deliberately omit every marker; those integrations must mark or claim their flora.
## Seasonal flora and food
Appearance and availability use `hearth.climate_at(os.time()).season`: the same
actual-epoch/latitude-aware climate as ecology, **not the machine's local month**
and not a replay of an optional preview clock. Southern latitude reverses the
calendar through the existing solar model. Explicit test climates can preview it.
| Flora | Spring | Summer | Autumn | Winter |
| --- | --- | --- | --- | --- |
| Deciduous block leaves | Spring foliage | Original foliage | Autumn colours | Twig canopy texture |
| Young/mature block trunks | Bark/end grain | Bark/end grain | Bark/end grain | Same full cubes; no mesh swap |
| Pine/spruce leaves | Evergreen | Evergreen | Evergreen | Evergreen |
| Clover / nettle / heather / sedge / bulrush | Active | Active | Active | Dormant |
| Bluebell | Bloom | Dormant | Dormant | Dormant |
| Moss / lichen | Active | Active | Active | Active |
| Meadow grass / fern | Living | Living | Living | Dormant perennial |
| Wildflowers | Bloom | Bloom | Dormant | Dormant |
| Spring greens patch | Pickable | Dormant | Dormant | Dormant |
| Autumn mushroom patch | Dormant | Dormant | Pickable | Dormant |
Frost and seasonal dormancy pause visible growth, loaded stress and reproduction,
not calendar age; they do not kill perennials at first frost. Spring observes the
same root anchor plus the full elapsed calendar interval. Deadwood,
litter, dung and decomposer fungi continue returning nutrients in winter; mature
trees can continue shedding litter. Initial legacy berry crops remain available;
spent crops no longer refill every real day.
Spring greens recruit and spread into moist (≥0.55), light ≥9 gaps in Spring.
Autumn mushrooms recruit and spread into moist (≥0.6), shaded (light ≤10) gaps in
Autumn. Deterministic sparse recruitment niches prevent every gap becoming food.
Dormant patches remain occupied: neither seeds nor normal placement replace them.
The existing decomposer `fungi` is still not edible.
Right-click a patch (or dig to pick) within six nodes with normal construction
`use` authorization. One picked greens item restores **12 food** when eaten.
One picked mushroom can be crafted shapelessly into existing `raw_food`, then
cooked at a lit, fuelled hearth. Raw mushrooms have no eat callback. These are
original game foods, not advice about identifying or eating real wild plants.
Picking never removes a patch. An empty inventory slot is required; one successful
item transfer writes `succ_harvest_ready_at = now + 86400` (one real day). Rejected
delivery grants nothing and writes no cooldown. The durable deadline survives
restart, unload and every seasonal toggle; returning to Spring/Autumn cannot refill
it. Harvest writes no age, owner or placement metadata and bypasses no claim.
`hearth.seasons.kind(node_or_name)` canonicalizes only registered controlled
variants; succession, grazing and habitat see the original kind and natural flag.
`name_for(kind, epoch)` selects appearance. `reconcile(pos, node?, epoch?)` swaps
only a loaded controlled node, preserves param1/param2 and all metadata, and does
not call construction/destruction callbacks or reset timers. Appearance-only
swaps may recolour claimed/hand-placed plants without changing permission or
provenance; external protection still blocks them. Ecological mutations retain
the stricter claim/placement exclusions below. No save schema/version change.
## Reviewed species effects and evidence
[species_effects.lua](../mods/hearth/species_effects.lua) is an offline policy
factory, `effects(hearth, api)`. It never fetches Wikipedia, interprets article
prose, registers a scheduler, or writes nodes/metadata. Succession uses an injected
`hearth.species_effects`, or loads this fixed sibling in the engine before its
existing globalstep is registered. Minimal standalone fixtures may omit it.
Content/bootstrap integration can initialize it before succession.
`E.describe(record_or_id)` returns an ordered list of
`{title, status, game, source_species, source_url, ...}`. Status is exactly
`simulated` or `descriptive`: simulated means the named bounded game mechanism
exists, **not** that all real-world consequences or field rates are simulated.
`E.source(id)` and `E.sources()` expose identity references for all 80 catalogue
IDs. Bundled Wikipedia page URLs/revision IDs are used when available; without
the bundle, Latin-title links are only identity pointers, not verified evidence.
Unreviewed records explicitly say that no species-effect evidence was reviewed
here. `identity_revision` / `identity_revision_url` refer to the bundled
introduction snapshot, **not** a claim that its excerpt contains the ecological
body section reviewed below. Effect review date: **2026-11-03**. No invented
revision IDs, biological rates, or undisclosed effect generation from tags.
### Implemented mechanics, not just catalogue descriptions
Calendar-v1 update: nitrogen eligibility below is now **90 calendar days**, not
the historical 900 loaded seconds, retaining the finite two-unit support cap.
New red clover, birdsfoot trefoil, gorse and broom share it. Their tags are not
independently reviewed species-rate measurements. Separately owned read-only
effect descriptions may still describe old acceleration; runtime policy here
and field-level lifecycle confidence labels take precedence.
| Species / relationship | Exact game effect | Evidence and limits |
| --- | --- | --- |
| Common alder (*Alnus glutinosa*), white clover (*Trifolium repens*) | After 900 warm active seconds, add at most one nutrient per eligible evaluation, never above local fertility four. **Two units total per supporting soil node**, recorded in `succ_fixed_nitrogen`, shared across species, recolonization and restart; soil's universal cap remains eight. Alder seedling, young and mature roots resolve through the same species ID; generated logs/leaves do not earn another contribution. | [Alder, Ecology](https://en.wikipedia.org/wiki/Alnus_glutinosa#Ecology) describes *Frankia* nodules and fertility; [white clover, Forage](https://en.wikipedia.org/wiki/Trifolium_repens#Forage) describes symbiotic fixation. The finite lifetime cap is game balance, not an annual nitrogen flux or physiological exhaustion claim. |
| Dog lichen (*Peltigera canina*) | Uses the same existing finite nitrogen budget, never a fertilizer item or independent annual allowance. The pre-existing lungwort-lichen nitrogen tag retains that shared policy; its species-effect evidence is not independently reviewed here. | [Dog lichen, Description](https://en.wikipedia.org/wiki/Peltigera_canina#Description) identifies *Nostoc*; [Peltigera, Description](https://en.wikipedia.org/wiki/Peltigera#Description) explains nitrogen fixation. Explicit species identity plus genus-level mechanism, not a measured species rate. |
| Blunt-leaved bog-moss (*Sphagnum palustre*) | A natural, active mat on loaded, permitted peat/alluvium at the sample's ground level adds **0.10 moisture** and subtracts **0.20 pH** from the local **land/bank habitat estimate**, capped at **two mats** (+0.20 / -0.40); moisture clamps to 0..1, pH to 3.5..8.5. This changes actual recruitment/stress niche tests. | [S. palustre](https://en.wikipedia.org/wiki/Sphagnum_palustre) describes water absorption; [Sphagnum](https://en.wikipedia.org/wiki/Sphagnum) explains water retention and acidification by cation exchange. Acidification evidence is genus-level. Coefficients are game approximations, not measured pH changes per plant. No permanent chemistry, peat creation, terrain conversion or global hydrology. |
| Common duckweed (*Lemna minor*) | Each natural active mat immediately above conserved source water at the sample's stand level reduces nearby **submerged freshwater** habitat light by **1.5**, capped at four mats / six light units. It never brightens a dark site, and never lowers light below two. Surface plants and terrestrial sites are not shaded by this rule. | [L. minor, Cultivation](https://en.wikipedia.org/wiki/Lemna_minor#Cultivation) describes floating carpets; [Lemnoideae, Duckweed in natural environments](https://en.wikipedia.org/wiki/Lemnoideae#Duckweed_in_natural_environments) describes shade. This is a local group-level shading approximation, not an algae, oxygen, contaminant-removal or nutrient-purification simulation. |
| Host-associated fungi, including fly agaric (*Amanita muscaria*) | A compatible natural fungus can transfer/debit **at most one existing soil nutrient** when a tree independently reaches its calendar juvenile threshold. Multiple fungi select a maximum allowance, not a sum. There is **no time reduction**; the former 16,200/21,600-second boost is removed. | [Fly agaric ecology](https://en.wikipedia.org/wiki/Amanita_muscaria#Distribution_and_ecology) describes associations, not a measured growth acceleration. This is not nutrient creation or a full carbon/water ledger. |
| Saprotrophic wood fungi, including birch polypore (*Fomitopsis betulina*) | On suitable natural deadwood/litter only, advance its calendar decay bonus by at most **60 seconds per eligible evaluation**, capped at the one-year / 90-day substrate lifetime. The ordinary transition consumes deadwood to litter to air, paying **three then the unconsumed portion of one** soil nutrient once, subject to cap eight. Removing the support removes its natural fungal occupant. The fungus itself has no extra payout. Soil-growing saprotrophs receive no special nutrient effect. | [Birch polypore, Distribution, habitat and ecology](https://en.wikipedia.org/wiki/Fomitopsis_betulina) describes birch-specific brown rot. Host suitability remains authoritative. Natural parasitism on weakened living birches is **not simulated**. No attacks on living/player trees. |
The sampler collects `species_counts` (including canonical alder roots, moss and
duckweed) within its existing **5x5x5** stencil. Mat feedback uses only the
sample's stand level, with natural/protection/placement checks on the mat and its
immediate support; claimed, planted, protected, unsupported or unloaded sources
do not contribute. A partially unloaded sample grants no derived mat effects.
`E.habitat(base, observations)` returns a copy and exposes `source_moisture`,
`source_ph`, and `source_light`. Succession calls it **once after deriving fresh
base moisture, support-class pH and aquatic classification**. Repeated habitat
reads cannot accumulate acidification or retention and never change metadata.
Consumers must not feed an already-derived habitat back as a new base sample.
All aquatic stands still occupy air above water: no rule clears, replaces,
refills or converts a water-source node. These habitat estimates drive botanical
niches, not the renderer's actual node light or a global pH map.
Existing season-gated browse, flowering/pollination and nursery counts are also
reported, as is canopy shade/moisture (pine/spruce evergreen; deciduous Winter
influence 15%). Nursery cover reduces stress accrual to 75%; pollination is an
input, not a new berry-yield promise. Engineer tags on other species remain
**descriptive / not simulated**: no automatic bank stabilization, erosion or rock
weathering. Resin, allelopathy, toxicity attacks and living-tree parasitism are
explicitly not simulated by this module. Article food/medicinal statements never
grant food permission, poison attacks, healing or recipes.
Focused evidence is in [species_effects_spec.lua](../tests/species_effects_spec.lua)
and [succession_spec.lua](../tests/succession_spec.lua): all-80 source mapping,
descriptor honesty, quantitative mat caps and changed real niche eligibility,
read-only/repeated sampling, source/support protection, shared durable nitrogen,
no duplicate fungal nutrients, and source-water preservation. These are standalone
Lua tests, not a claim of full-engine or multiplayer validation.
## Safety and execution contract
- One scheduler runs once per second, doing **at most eight column probes total**,
shared round-robin across connected players (not eight per player). Columns scan
a 25×25 square and twelve vertical nodes (+6..−5) around a player, seeking existing natural
ground. No connected players means no ecological work.
- Each probe checks at most twelve heights and one site. Encountered foliage,
including old unmarked mapgen leaves, is reconciled on that same probe: **at most
twelve appearance swaps per column, 96 per second total**, not a second player
radius scan. An LBM reconciles each controlled flora node on load without
emergence or a world registry; that load-driven work is one swap per callback,
separate from the scheduler's per-second budget. Habitat uses a fixed
5×5×5 stencil; site safety uses a fixed 3×3×3 stencil. Mutations per evaluated
site are at most two (mature seed plus litter); other stages write at most one.
Wildlife calls scan at most nine candidate sites and mutate at most one.
- Only `get_node_or_nil` reads are used. Missing/ignore nodes fail closed. There
is no emerge, force-load, mapgen edit, ABM, per-node timer or offline replay.
- Legacy active counters accrue from consecutive observations. Calendar plant
ages and organic decay use durable absolute anchors, not these counters.
First observation after restart, load, cache eviction, disconnect, clock rewind,
or a gap longer than 1,200 seconds only establishes a baseline. The load callback
invalidates observations when vegetation or its natural support reloads. A stalled
server step gets one fixed budget, never a backlog of simulation steps.
- A bounded 8,192-entry in-memory observation cache is disposable (enough for ten
disjoint stationary-player scan squares). Age, stress,
next-attempt timestamp, natural-origin flag and attempt serial live in node
metadata; soil stores only its nutrient count. There is no global save schema.
Durable attempt timestamps/serials prevent repeated evaluation or rerolling
a blocked seed by reconnecting/restarting. Restarting cannot accelerate or
reset birthdays, organic clocks or resource receipts. Calendar eligibility
survives absence; only bounded present work occurs on return. Legacy active
processes remain sampled approximations, not per-tick accounting.
- Source, destination and natural supports must all be unclaimed, externally
unprotected and free of placement metadata. All normal content placement marks
`player_placed=1`, including hand-placed soil/turf and soil left by crop harvest.
Recognized legacy markers are
`owner`, `planter`, `placed_by`, `hearth_placed`, `hearth_player_placed` and
`planted_at`. Unknown third-party non-succession metadata also opts out
conservatively. Any nonempty/nonzero marker opts out. Nearby markers/builds/farms
also block succession conservatively. Only explicitly natural simulation plants
may be consumed, replaced or decayed. Never replace water or arbitrary
`buildable_to` nodes. Old unmarked player earth cannot be distinguished from
mapgen earth: claim it to keep nature out.
## Wildlife interface
`hearth.succession` is installed by the content registrar:
- `forage(pos) -> boolean`: debit one partial canopy ration from one nearby
active natural browse plant on safe support. The plant remains rooted;
`succ_browse_ready_at = now + 7 days` is shared across animals and restarts.
Seven days is an unverified conservative game fallback, not measured biomass
recovery. No dormant flora, nonbrowse flowers, crops or player plants are eaten.
- `pollinate(pos) -> boolean`: exact source, not a new scan. Require botanical
active flowering (`B.pollinator`), known calendar maturity, natural root and
permitted loaded source/support. Atomically reserve
`succ_nectar_ready_at = now + 3600` before any effects. Competing bees share
this receipt. One non-additive boolean `succ_pollinated_until = now + 86400`
is written only when the old credit has expired; repeated visits do not stack
yield or extend the existing credit. One hour replenishment and one day credit
are game approximations, not measured species rates.
- `frugivore(pos, species_id?) -> boolean`: verify exact identity if supplied,
known mature shrub and actual botanical fruiting season. Consume one feeding
unit from the existing batch by spending the **whole two-fruit patch**; discard
the remainder rather than duplicate it. Reserve the human harvest field
`botany_ready_at = now + one mean year`; no items or animal-only fruit account.
- `granivore(pos, species_id?) -> boolean, optional_cache_id`: only mature
oak/hazel rooted trees (including adopted soil-level block roots), in Autumn
and their botanical seeding window. `succ_nuts_year` (UTC calendar year) and
`succ_nuts_remaining` persist a maximum two-nut current crop per root/year.
One nut funds a meal. If a second exists it is **separately debited** and the
second return is the plain species ID expected by wildlife. One remaining
nut returns `true, nil`; no old-year crop replay, reconnect refill or immature
root production. Two nuts is a coarse budget, not a measured yield.
- `disperse_seed(pos, species_id) -> boolean`: plain IDs alone confer no
authority. One in-memory handoff slot records the immediately preceding paid
granivore cache nut, with its source/species/UTC second. Any cache attempt
consumes the slot **before** destination checks; other mutating wildlife
hooks or another granivore call supersede it. Restart loses it, never refunds
nuts. Source identity/protection is rechecked; destination must be nearby
(within two horizontal nodes, same root height), loaded, permitted air on
viable natural support, with valid cooldown metadata. Existing establishment
preflight/niche rules apply. Success creates exactly one calendar-zero
same-species seedling and a one-day destination receipt. This matches the
synchronous squirrel caller; no opaque token or saved animal seed is required.
It is an internal trusted-call contract, not authentication against hostile
mods that can call hooks between them.
- `detritivore(pos) -> boolean`: exact natural litter source; consume 0.1 of
its normalized one-unit `succ_litter_remaining` mass and transfer exactly
0.1 existing mineral budget to permitted soil. At most ten meals per patch,
shared across restart. Keep the patch until the last unit; final removal
preflights any dependent fungus. Full soil refuses without spending material.
Ordinary decay returns only the remainder. No extra fertilizer aura, items
or renewal timer. Normalized units are game accounting, not measured digestion.
- `deposit(pos) -> boolean`: place exactly one dung nutrient patch in nearby
**air** over safe natural support. Never replaces existing plants or water.
Nutrients arrive through active decomposition, not immediately on deposit.
- `habitat(pos) -> table`: read-only counts `grass`, `flowers`, `cover`, `water`,
`edible`, `pollination`, plus `ground`, `nutrients` (0..8), biome `heat`, `canopy`,
estimated `light`, `moisture` and `loaded`. Counts use the fixed
local stencil; wildlife should reject an incomplete (`loaded=false`) sample.
- `step(dtime) -> number`: registered scheduler; returns columns processed.
Exposed constants document the scheduler's fixed production limits, not settings.
Positions must be finite near-world-range vectors; APIs round to node coordinates.
Callers supply animal ground-cover height (the air/plant cell above soil), not the
supporting soil or the animal's eye height. Calls return false when no safe target
exists. Habitat has no writes, timers or protection-violation reports.
### Integration handoff: botanical fruit effect remains separately owned
Current [botany.lua](../mods/hearth/botany.lua) human harvest still has a fixed
two-berry output and a one-day refill. This owned change does **not** pretend
that `succ_pollinated_until` already increases botanical fruit yield. The botany
owner must apply at most **one extra berry to an existing eligible fruit batch**
when its unexpired boolean credit is present, spend that credit with the shared
harvest transaction, and never give fruit merely for a pollen visit. It must also
replace daily human botanical refill with the next annual fruiting-season policy
and coordinate the same `botany_ready_at` lock used here. Animal fruit consumption
already uses a conservative one-year lock, but a human-first harvest still uses
the separately owned old daily policy until that integration lands. Flowering
may precede fruiting by months: the requested one-day credit is explicitly a
limited game approximation, not an annual pollination/fruit-set simulation.
Runtime signatures were checked against [wildlife.lua](../mods/hearth/wildlife.lua)
and [fauna.md](fauna.md): it consumes literal `true`, calls fruit/nut hooks with
the plain species ID, compares the optional cache return to that ID, and calls
dispersal synchronously only for oak/hazel squirrels. The older fauna contract's
one-day fruit deadline is superseded here by the conservative annual animal
receipt; its owner should align that documentation. No wildlife edits were made.
All resource failures leave node/resource metadata unchanged; paid cache authority
is the intentional exception, lost even on failed caching. No secrets, network
requests, serialization evaluation or cryptographic authorization were introduced.
## Assets and verification
### Exact registered node / art integration contract
All names below have the `hearth:` namespace. Existing canonical nodes retain their
names and callbacks/groups/drops. Summer always uses the existing canonical tree
and leaves node; no redundant summer variant is registered.
- Legacy `tree_seedling`, `young_tree`, `mature_tree`, `leaves` retain `_spring`,
`_autumn`, `_winter` IDs for save compatibility (12 variants). The content
registrar removes old mesh/atlas references from every legacy stage variant:
seedlings use `hearth_sapling.png`; young/mature trees use log top/side textures
and remain normal cubes. Seasonal appearance belongs to their separate leaves.
- Deadwood uses `hearth_log_top.png` on both ends and `hearth_log_side.png` on
the remaining faces, with no mesh, custom collision/selection or seasonal variant.
- Mapgen leaf variants use `hearth_leaves_spring.png`, `hearth_leaves_autumn.png`,
`hearth_leaves_winter.png`; canonical Summer uses existing `hearth_leaves.png`.
- `meadow_grass_winter`, `fern_winter`, `wildflowers_autumn`, `wildflowers_winter`
use `hearth_meadow_grass_dormant.png`, `hearth_fern_dormant.png` and
`hearth_wildflowers_dormant.png`. Other seasons use existing canonical nodes/art.
- `spring_greens_patch` and `autumn_mushrooms_patch` are dormant canonical nodes;
`spring_greens_patch_spring`, `autumn_mushrooms_patch_autumn` are active variants.
Textures: `hearth_spring_greens.png`, `hearth_spring_greens_dormant.png`,
`hearth_autumn_mushrooms.png`, `hearth_autumn_mushrooms_dormant.png`.
- Picked craftitems (not nodes): `hearth:spring_greens`, `hearth:autumn_mushrooms`;
icons `hearth_item_spring_greens.png`, `hearth_item_autumn_mushrooms.png`.
The legacy seasonal set adds **20 nodes total**, including both canonical food patches. All variants
are hidden from creative inventory, retain base callbacks and drop definitions,
and cannot become duplicate harvest sources by toggling. The registrar honours
explicit mesh tiles directly; it does not require `override_item` in engine mocks.
### Main-agent wiring and exact diversity interface
The calendar expansion owns only the six requested flora/ecology modules, their
six focused tests and the two botany/ecosystem documents. Bootstrap, lifecycle
policy, content, schema and generated assets remain other owners' responsibility.
Exact integrated order (main owns wiring):
0. Assign `hearth.lifecycle = dofile(modpath .. "/lifecycle.lua")` (plain namespace,
not a factory) **before** botany/content.
1. Create/install `hearth.seasons = dofile(modpath .. "/seasons.lua")(hearth, api)`.
2. Create/install `hearth.botany = dofile(modpath .. "/botany.lua")(hearth, api)`.
3. **Before succession setup**, create/install
`hearth.diversity = dofile(modpath .. "/diversity.lua")(hearth, api)`.
This factory immediately extends `seasons.add_variants` with species policy.
4. Instantiate succession using its factory and assign `hearth.succession`.
5. After normal content, register/install biomes (thirteen species' logs/leaves/planks).
6. Call `hearth.diversity.register_nodes(register_node)` with the bare-name wrapper:
39 species stage nodes, eight existing understory nodes and the bootstrap LBM.
7. Call `hearth.botany.register_nodes(register_node)`: exactly 79 new non-tree taxa,
with all explicit tiles and inventory/wield images preserved. The thirteen trees
and eight existing understory nodes are not registered twice.
8. Call `seasons.register()` after every canonical node, then instantiate wildlife.
The returned diversity table exposes:
- `species_order`, `plants`: ordered thirteen/eight canonical short names (the
latter are legacy registrar-owned understory, not the whole botanical catalogue).
- `grounds`, `rocks`: exact short-name membership sets; `is_ground(node_or_name)`,
`is_rock(node_or_name)` and `supports_ground(kind, support)` are boolean policies.
- `tree_info(kind_or_node) -> species, stage`: two Lua return values, or nil;
stages are `seedling`, `young`, `mature`. Seasonal names canonicalize first.
- `family(kind_or_node) -> canonical_family`: species stages map to legacy
`tree_seedling`, `young_tree`, `mature_tree`; species leaves/logs map to
`leaves`/`log`; herbs retain their own canonical kind.
- `supports_species(species, surface)`, `suitable(kind, habitat, season?)`:
support/niche policy with optional biome integration.
- `choose_recruit(pos, habitat, season) -> understory_kind_or_nil`;
`choose_tree(pos, habitat) -> species_seedling_kind_or_nil`. Habitats supply
`ground` as a short name and normalized `moisture`, `light`, `loaded`,
`nutrients`, `canopy` and optionally biome `heat`.
- `edible(kind, epoch?)`, `pollinator(kind, season)`;
`is_worldgen_flora(kind)`, `bootstrap(pos, node?) -> boolean`;
`register_nodes(register_node)`, idempotent node registration.
### Geometry handoff to ecological growth
`diversity.tree_plan(species, stage?) -> plan | nil` is read-only. Species is one
of the thirteen IDs or `"generic"` for legacy trees; stage is `"young"` or `"mature"`
(default mature). Invalid IDs/stages or missing biome integration return nil.
`plan` is a fresh dense array of `{x, y, z, name, param2}` records: integer offsets
relative to the stage root at `(0,0,0)`, fully qualified node names, and facedir.
`plan.size={x,y,z}` gives the bounding schematic dimensions. There are **no air
records**. The root is `hearth:<species>_<stage>`; remaining trunk/branch and canopy
records use `hearth:<species>_log` and `hearth:<species>_leaves`. Generic plans use
oak geometry with `young_tree`/`mature_tree` roots and canonical `log`/`leaves`.
Every non-root occupied cell exactly matches
`biomes.tree_schematic(species, canonical?, stage?)` (x-fastest, then y, then z).
Young plans are at most 3×5×3; mature plans at most 9×10×9. Mutating a returned
plan cannot change later plans or mapgen geometry.
**Ecology owner:** preflight the complete bounded plan, loaded supports and all
destination claims/protection/provenance before writing. Only the exact existing
natural root and verifiably owned tree components may be reused on stage growth;
never overwrite water, player nodes, farms or unrelated flora. Track component
ownership/root association and stale callbacks; reject partial growth, coordinate
overflow, unloaded cells and blocked transitions. Preserve species identity and
handle component removal/death within the existing scheduler budget. This API
neither applies geometry nor migrates/expands old roots on load. Mature roots
must not be treated as finished trees without their real block canopies.
When ecological death creates fresh `hearth:deadwood`, supply `param2=12` (or `4`
for a Z-oriented log) immediately; the load migration is for legacy saves, not
a substitute for orienting newly created fallen timber.
`seasons.add_variants(kind, definitions, active_seasons?)` is available immediately
after factory creation and before registration. Definitions map `Spring`, `Summer`,
`Autumn`, `Winter` to `{texture=..., mesh=...}`. An optional active-season boolean
set controls availability. Missing base nodes in mocks/optional integrations are
skipped without an assertion; a later registration call can register their variants.
No original 12-month climate calculation or preview-clock semantics changed.
### Exact species/understory art contract
For **all thirteen** species in `species_order`:
- Seedlings use `hearth_botany_<species>.png` on a plantlike sprite. Young/mature
roots and species timber use `hearth_<species>_log_top.png` on both ends and
`hearth_<species>_log_side.png` elsewhere. Planks use `hearth_<species>_planks.png`.
No ecological tree definition references a mesh or atlas.
- Biome-owned full-block leaves use `hearth_<species>_leaves.png`. Deciduous
seasonal variants use `hearth_<species>_leaves_spring.png`,
`hearth_<species>_leaves_autumn.png`, `hearth_<species>_leaves_winter.png`.
Pine **and spruce** leaves retain the base texture throughout the year.
- Deciduous stage/leaf variants append `_spring`, `_autumn`, `_winter`; Summer
restores the canonical node. Seedling variants keep the botany tile. Young/mature
seasonal IDs are retained for existing saves but have the **same bark cubes**,
never foliage atlases. Their availability still pauses in Winter for deciduous
species. Diversity registers those multi-face cube variants before the seasonal
foliage copier; seasons remains responsible for name selection and swapping.
Understory base textures are `hearth_clover.png`, `hearth_nettle.png`,
`hearth_heather.png`, `hearth_bluebell.png`, `hearth_sedge.png`,
`hearth_bulrush.png`, `hearth_moss.png`, `hearth_lichen.png`. Clover, nettle,
heather, bluebell, sedge and bulrush additionally use
`hearth_<plant>_dormant.png` in Winter (nodes append `_winter`). Bluebell also
has `_summer` and `_autumn` node variants using its same dormant texture.
Moss and lichen have no dormant art. No art is generated or modified here.
With all biome leaves installed, diversity adds **187 nodes**: 47 canonical nodes,
99 deciduous stage variants, 33 deciduous full-block leaf variants and eight
understory variants. Pine and spruce add no seasonal variants. Botany independently
adds 79 nodes. Species provenance on generic deadwood remains metadata.
The standalone succession fixture tests growth, niches, shade, competition,
seedlings, seeds, nutrient feedback, dung/fungi/decay, grazing, source/destination/
support protection, placement markers, unloaded nodes, work/read budgets, cold,
restart cooldowns and no-catchup behavior. Content tests check node registration,
named textures, wildlife API wiring and placement marking. Seasonal fixtures add
all four seasons, northern/southern calendars, seasonal foliage/restoration,
metadata/age conservation, forage cooldowns/restart, niches/occupation, protected
and unloaded no-write cases, target-volume clearance and the 96-swap bound.
The blockshape-focused standalone suites are diversity, biomes and content;
botany has its separate catalogue suite. They check exact thirteen-species ordering,
distinct mature schematics, bounded young canopies, shared rooted geometry,
metadata-safe deadwood migration, axe-only harvesting, unique material recipes,
79 additional botany nodes and preserved aquatic icons/placement guards.
Engine visuals,
multiplayer ecology balance and long-session woodland density still require
in-engine playtesting; unit fixtures are not a performance benchmark.
Security review: no credentials, cryptography, certificates, network requests,
deserialization or unbounded external inputs were introduced. Mutation authority
is least-privilege nature (no player identity); loaded/protection/marker checks
fail closed before writes.