# 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:_seedling`, `hearth:_young`, and `hearth:_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:_`; remaining trunk/branch and canopy records use `hearth:_log` and `hearth:_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_.png` on a plantlike sprite. Young/mature roots and species timber use `hearth__log_top.png` on both ends and `hearth__log_side.png` elsewhere. Planks use `hearth__planks.png`. No ecological tree definition references a mesh or atlas. - Biome-owned full-block leaves use `hearth__leaves.png`. Deciduous seasonal variants use `hearth__leaves_spring.png`, `hearth__leaves_autumn.png`, `hearth__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__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.