This commit is contained in:
2026-11-05 09:57:11 -05:00
parent d34388bf2c
commit 89e392cc2e
81 changed files with 12410 additions and 1135 deletions
+196
View File
@@ -1,7 +1,203 @@
# The Last Hearth — woodland travelers and botanical pixel catalogue
## Authoritative fauna-23 / catalogue-100 art contract (2026-11-03)
This section supersedes earlier catalogue counts and package totals, not the
character-layer or legacy OBJ contracts. Original MIT procedural drawings and
meshes only: no downloaded assets, fonts, libraries or engine launch. Runtime
wildlife integration remains the Lua owner's responsibility; these generators
do not register monsters, change spawning, collision boxes or gameplay.
### Exact animated fauna handoff
[The fauna generator](../scripts/fauna-art.mjs) exports frozen `FAUNA_NAMES`,
`FAUNA_ANIMATIONS`, `FAUNA_ART_MANIFEST`, `FAUNA_MATERIALS` and pure methods
`faunaB3D(id)`, `faunaTexture(id)`, `faunaBounds(id)`, `faunaPortraitSheet(frame=9)`.
Unknown IDs and frames fail closed. Every result is deterministic and fresh.
Exactly these **23** IDs, in manifest/portrait row-major order:
hare, deer, fox, rabbit, red_squirrel, bank_vole, wood_mouse, hedgehog, badger,
roe_deer, stoat, weasel, otter, robin, blackbird, blue_tit, barn_owl, mallard,
common_frog, smooth_newt, brown_trout, bumblebee, earthworm.
Each has **hearth_fauna_<id>.b3d** and
**hearth_fauna_<id>.png**; the latter is an opaque **64×64** nearest-neighbor
semantic atlas. These 23 PNGs are deliberately **not** worldArt tiles. Old
hearth_hare.obj, hearth_fox.obj, hearth_deer.obj and their textures remain byte
compatible and generated; runtime can migrate to the new names independently.
Every model has one root, one textured mesh, outward-wound flat-normal solids,
half-texel inset UVs, normalized rigid bone weights, no scale animation and
**100 real skeletal frames at 24 FPS**. B3D key indices 1–100 map to Luanti
frames 0–99. `visual_size={x=1,y=1}`, **10 model units/node**, feet/lowest bind
surface at **Y=0**, forward +Z. `faunaBounds(id).min/max` are authoritative
**bind-mesh node bounds**, not guessed collision or conservative animated bounds.
They are also serialized per animal; do not infer scale from portrait cells.
Bind bounds in nodes below are rounded to five decimals for review; the exported
values and generated manifest retain full precision. All minimum Y values are 0.
| ID | Minimum X, Y, Z | Maximum X, Y, Z |
| --- | --- | --- |
| hare | -0.13750, 0, -0.34150 | 0.13750, 0.81400, 0.37004 |
| deer | -0.31625, 0, -0.68300 | 0.31625, 1.59470, 0.65081 |
| fox | -0.15000, 0, -0.86240 | 0.15000, 0.68560, 0.50280 |
| rabbit | -0.15400, 0, -0.25954 | 0.15400, 0.55090, 0.31371 |
| red_squirrel | -0.07500, 0, -0.46744 | 0.07500, 0.59240, 0.24266 |
| bank_vole | -0.07000, 0, -0.17039 | 0.07000, 0.19600, 0.17507 |
| wood_mouse | -0.06000, 0, -0.31045 | 0.06000, 0.23860, 0.16250 |
| hedgehog | -0.11000, 0, -0.21173 | 0.11000, 0.25000, 0.23755 |
| badger | -0.19000, 0, -0.45078 | 0.19000, 0.49240, 0.47589 |
| roe_deer | -0.22375, 0, -0.49176 | 0.22375, 1.22850, 0.48443 |
| stoat | -0.06000, 0, -0.45146 | 0.06000, 0.24620, 0.26954 |
| weasel | -0.04250, 0, -0.26846 | 0.04250, 0.18080, 0.19111 |
| otter | -0.12500, 0, -0.70711 | 0.12500, 0.38840, 0.39778 |
| robin | -0.16460, 0, -0.21895 | 0.16460, 0.37210, 0.17388 |
| blackbird | -0.18440, 0, -0.30653 | 0.18440, 0.41750, 0.20140 |
| blue_tit | -0.12500, 0, -0.17516 | 0.12500, 0.31670, 0.13705 |
| barn_owl | -0.28340, 0, -0.27369 | 0.28340, 0.64340, 0.27500 |
| mallard | -0.27350, 0, -0.43790 | 0.27350, 0.47040, 0.25550 |
| common_frog | -0.09900, 0, -0.15026 | 0.09900, 0.14750, 0.22272 |
| smooth_newt | -0.04000, 0, -0.34231 | 0.04000, 0.10140, 0.16295 |
| brown_trout | -0.10450, 0, -0.35520 | 0.10450, 0.30450, 0.24000 |
| bumblebee | -0.14400, 0, -0.06500 | 0.14400, 0.12250, 0.10640 |
| earthworm | -0.02000, 0, -0.19127 | 0.02000, 0.04000, 0.19127 |
| Clip | Inclusive engine frames | Authored action |
| --- | --- | --- |
| idle | 0–19 | breathing, looking, ear/tail attention; segmented worm motion |
| locomotion | 20–39 | linked upper/lower limb strides, tail follow-through; worm joint wave |
| feed | 40–59 | head/neck dip and bird peck; small head/segment sampling motion |
| flight_swim | 60–79 | mirrored wing flaps with secondary flight feathers; aquatic tail beats/fins |
| rest | 80–99 | settled head/limbs, low-amplitude breathing and tail/segment movement |
All five ranges loop with matching endpoint poses. No engine-side fake motion
is needed. The shared `flight_swim` range on non-flying terrestrial mammals
is an **alert looking reserve**, not invented flight/swimming. The rest range
is a settled standing/perched posture, **not a speculative lying-down or curled
skeletal deformation**. Frog/newt limbs paddle; fish have a hinged caudal fan,
paired pectorals and dorsal fin; bees have six legs and thoracic wings; the
earthworm has twelve serially parented body joints. No bones are driven merely
because another species has similarly named appendages.
Hare ears and hind feet, compact rabbit, squirrel's raised brush, vole's short
tail versus mouse's long tail, hedgehog spine blocks, broad badger, tall antlered
deer versus short roe antlers, slender stoat/weasel, low otter with cream muzzle,
robin breast, blackbird bill, blue tit cap, owl facial discs, duck neck/webbed
feet/speculum and amphibian low profiles are authored species landmarks, not a
single body recoloured. The restrained coat/cream/earth/mineral palette matches
the Minecraft/Vintage Story-like block world without copying either game's art.
The sixteen **16×16** material patches occupy four row-major columns:
| Row | 0 | 1 | 2 | 3 |
| --- | --- | --- | --- | --- |
| 0 | coat | belly | face | ear |
| 1 | foot | tail | wing | eye |
| 2 | nose | stripe | feather | beak |
| 3 | fin | spot | horn | membrane |
Badger face stripes, cream otter underside, stoat black tail tip, fox cream tip,
bee bands, trout spots, wing bars and feather veins are semantic markings, not
whole-model tints. All atlas texels have alpha 255 (solid low-poly appendages).
`generateAssets()` writes **fauna-art.json** at package root: format 1,
`animations`, `animals` (ID, exact filenames, dimensions, units, visual size,
bind bounds, posture, clips, MIT provenance), and `portraitSheet` metadata.
Every output uses the existing guarded staging writer and package hash inventory.
The portrait path inside staging is
**mods/hearth/textures/hearth_fauna_contact_sheet.png**: **512×768**, four
columns of 128px cells, 23 animals plus one paper cell, frame 9. It is an actual
CPU triangle rasterization of skinned geometry with atlas sampling and flat
lighting, **not a texture swatch, font illustration or engine screenshot**.
`faunaPortraitSheet(65)` previews the same geometry's flight/swim pose. Each cell
fits its own posed bounds, so cells are an identity review, not relative scale.
### Catalogue 100 and three additional cube-tree material families
The JavaScript art catalogue now matches all **100 exact IDs and Latin names**
in the read-only Lua BOTANY catalogue. Every taxon has active and dormant 32px
sprites: **200** provenance records in **botany-art.json**, each including
`botanicalLatin`, group, morphology, state, filename, dimensions and MIT author.
Group counts: trees 13, shrubs 14, grasses 10, herbs 22, ferns_mosses 11,
fungi 10, aquatic 10, lichens 10. The twenty additions are:
- crab_apple — Malus sylvestris; wild_cherry — Prunus avium;
field_maple — Acer campestre.
- honeysuckle — Lonicera periclymenum; ivy — Hedera helix;
gorse — Ulex europaeus; broom — Cytisus scoparius.
- dandelion — Taraxacum officinale; daisy — Bellis perennis;
red_clover — Trifolium pratense; birdsfoot_trefoil — Lotus corniculatus;
ribwort_plantain — Plantago lanceolata; selfheal — Prunella vulgaris;
knapweed — Centaurea nigra; oxeye_daisy — Leucanthemum vulgare;
field_scabious — Knautia arvensis; teasel — Dipsacus fullonum;
cow_parsley — Anthriscus sylvestris; garlic_mustard — Alliaria petiolata.
- holly_fern — Polystichum setiferum (catalogue contract ID; soft shield-fern).
Original 80 drawings and approved leaf PNG bytes are retained. New silhouettes
have distinct flowerheads, twining honey tubes, climbing ivy rootlets, gorse
spines, broom switches, ligulate dandelion rays, trifoliate clovers, plantain's
anther ring, teasel cones, compound parsley umbels, brassica siliques and forked
fern pinnae. Dormant specimens lose flowers and fruit, keeping species-specific
dry structure. Existing runtime pixel-wind plant waving is unchanged.
All thirteen cube-tree IDs: oak, birch, pine, willow, alder, beech, aspen, rowan,
hazel, spruce, crab_apple, wild_cherry, field_maple. Each has log_top, log_side,
leaves and planks. Deciduous species also have leaves_spring, leaves_autumn and
leaves_winter. Cherry has horizontal lenticels; crab apple fine fissures; maple
interrupted cork diamonds. **46** compatibility bark/leaf atlases cover all
species/seasons, including underscore IDs parsed by the anchored suffix contract
`/_atlas(?:_(spring|autumn|winter))?$/`, never positional underscore splitting.
No new unused irregular-tree OBJ meshes are generated; the 29 legacy flora OBJ
exports and the 23 old species-stage meshes remain frozen.
The world contact sheet now has **421 tiles**, **512×864**, **16 columns**,
sorted filenames as the row-major legend, height **ceil(count/16)*32**. The
bounded **1–512** tile guard remains intact. Its 61 additions are 40 botanical
sprites plus 21 cube-tree materials/seasonal leaves. Fauna never consumes those
cells. The two-swatch minimal fixture closes at **675 PNGs and 63 models**:
**37 OBJ + 26 B3D** (3 players and 23 fauna), with exact manifest-derived
inventories and package-integrity verification, not loose minimum-count checks.
Validation is Node-only: independent B3D chunk parsing, winding, normals, UVs,
weights, every keyed pose, forward-kinematic displacement for all five clips,
loop seams, species landmarks, encoded texture pixels, botanical catalogue
equality, unique silhouettes, approved old leaf hashes, package bytes and output
link refusal. No engine render, performance, live animation or spawn claim.
Final focused run: **60 passed, 0 failed, 0 cancelled, 0 skipped**, Node
**24.22.0**, **160.80 seconds**, exit 0 and empty stderr. Suites: fauna art,
world art, flora art, existing assets and character art. Both actual skinned
mesh sheets (idle frame 9 and flight/swim frame 65) and the 421-tile world sheet
were visually inspected outside the source tree. No manual binary files added.
CodeGuard boundaries: no credentials or certificates introduced; SHA256 is used
only for deterministic integrity checks; positive ID/frame validation, bounded
allocations and existing symlink/junction-safe staging writes are retained.
Optional audio codec decode/regeneration was not configured; existing Ogg
framing/CRC/hash checks passed. Directory-junction refusal passed; the existing
file-symlink subcase lacked Windows symlink privilege.
## Authoritative character art contract (2026-11-03)
**Revision note (2026-11-04):** the 2026-11-03 option counts and rig/mesh-size
figures in this section are superseded by the expanded character catalogue below.
### Expanded woodland traveler update (2026-11-04)
The current public body allowlist is `compact`, `lean`, `balanced`, `broad`,
`sturdy`; all remain cosmetic, equal-height silhouettes using the same rig and
collision. Added original face linework (`lined`), hair styles (`curly`,
`topknot`, `undercut`), facial hair (`goatee`, `braided_beard`), garments
(`hooded_cloak`, `travel_coat`, `wool_mantle`), accents (`satchel`, `tool_belt`,
`pauldrons`, `hood`), and a `birthmark`. The current generated manifest is 258
distinct validated filenames (including transparent `none` variants), and the
expanded player B3D contains 384 vertices and 192 triangles. The model adds an
original draped shoulder mantle, belt, back satchel, and projecting nose/ears;
no player mechanics or tool attachment names are changed. Current Lua and art
builders must use the exact texture order implemented by `C.preview()` and
`characterLayers()`; hood accents intentionally overlay hair. See
[character-creator.md](character-creator.md) for the current option list.
This section supersedes earlier **player** appearance and package-total statements
below; botanical art itself is unchanged. Character work owns only
[scripts/media.mjs](../scripts/media.mjs),
+63 -15
View File
@@ -1,8 +1,8 @@
# Botany: 80 temperate taxa and integration contract
# Botany: 100 temperate taxa and calendar integration
## Scope and evidence
The catalogue in [mods/hearth/botany.lua](../mods/hearth/botany.lua) has **80 distinct real species**, partitioned into **eight ordered groups of ten**. It represents a north-west/central European temperate landscape mosaic: deciduous woods, conifer woods, hedgerows, acid heath, wet meadow, bog and freshwater margins. It does **not** imply that all species grow together in one patch, or that all are native to every British locality. Norway spruce is native to continental northern/central Europe and widely planted in Britain. Hazel is counted only among trees; heather only among herbs; rushes and reedmace only among grasses/sedges. Lichens are lichen-forming fungi but have their own disjoint gameplay category, not a second count in fungi.
The catalogue in [mods/hearth/botany.lua](../mods/hearth/botany.lua) has **100 distinct real taxa** in eight disjoint groups: **13 trees, 14 shrubs, 10 grasses, 22 herbs, 11 ferns/mosses, 10 fungi, 10 aquatic taxa and 10 lichens**. It represents a north-west/central European temperate landscape mosaic, not one plant community or a claim that every species is native everywhere. Hazel is counted among trees, heather among herbs, and lichens separately from other fungi. Existing generic gathering patches are not part of this count.
Taxonomic/reference authorities for maintaining these names and broad habitats:
@@ -19,11 +19,38 @@ The numeric light/moisture/pH/depth windows below are **explicit game approximat
## Exact ID manifest (art, trees and ecology handoff)
`id` is the canonical catalogue key. Every non-tree node is exactly `hearth:<id>`. Every tree's initial node is `hearth:<id>_seedling`; tree stages/resources belong to the diversity/resources owner. **Do not create a second generic berry bush or alias a generic patch into this count.** Existing generic `berry_bush`, `fern`, `wildflowers` and `meadow_grass` remain outside the 80.
`id` is the canonical catalogue key. Every non-tree node is exactly `hearth:<id>`. Every tree's initial node is `hearth:<id>_seedling`; diversity/resources own tree stages and materials. Generic `berry_bush`, `fern`, `wildflowers` and `meadow_grass` remain outside the 100.
Owner flags: **E** = existing node, never registered here; **D** = tree diversity owner, never registered here; **B** = new botany-owned node. The 13 existing mappings are retained verbatim: five trees plus clover, nettle, heather, bluebell, sedge, bulrush, moss and lichen. “Sedge”, “moss” and “lichen” receive a specific catalogue interpretation; their old IDs do not change.
Art contract for **all 80**: `hearth_botany_<id>.png`. This module uses that exact tile for each of its **62 new understory nodes**. Trees use species bark cubes and full-block leaf crowns supplied by diversity/terrain; no seasonal tree mesh is introduced on a cube. Seasonal copying preserves bark/end-grain faces, `facedir`, callbacks and drops; foliage gets its own species-season texture. Assets and real-engine visual validation remain the art/main integration owners' responsibility.
Art contract for **all 100**: `hearth_botany_<id>.png`. Botany registers **79** non-tree nodes (87 non-trees minus eight legacy understory IDs). Diversity/terrain own all thirteen tree stages, bark/end-grain cubes, leaves and planks. No random tint or seasonal mesh substitutes for species materials. Seasonal copying preserves bark faces, `facedir`, callbacks and drops. Assets and native-engine visual validation remain the art/main integration owners' responsibility.
### Exact twenty additions (counts above include these)
| ID | Latin identity from lifecycle contract | Group / ecological emphasis |
| --- | --- | --- |
| dandelion | Taraxacum officinale | herbs; open meadow, spring–autumn flowers |
| daisy | Bellis perennis | herbs; short open grassland flowers |
| red_clover | Trifolium pratense | herbs; summer/autumn flowers, bounded nitrogen fixation |
| birdsfoot_trefoil | Lotus corniculatus | herbs; drier meadow, flowers, bounded nitrogen fixation |
| ribwort_plantain | Plantago lanceolata | herbs; meadow browse, wind-pollinated, no nectar aura |
| selfheal | Prunella vulgaris | herbs; meadow summer/autumn flowers |
| knapweed | Centaurea nigra | herbs; meadow late flowers and cover |
| oxeye_daisy | Leucanthemum vulgare | herbs; summer meadow flowers |
| field_scabious | Knautia arvensis | herbs; drier base-rich meadow flowers |
| teasel | Dipsacus fullonum | herbs; summer flowers and seedhead cover; biennial fallback |
| cow_parsley | Anthriscus sylvestris | herbs; hedge margins, spring/summer flowers |
| garlic_mustard | Alliaria petiolata | herbs; shaded edges, second-season spring flowering policy |
| honeysuckle | Lonicera periclymenum | shrubs; hedge/woodland edge flowers; no food |
| ivy | Hedera helix | shrubs; evergreen cover, autumn flowers, spring seed window; no food |
| gorse | Ulex europaeus | shrubs; open acid scrub, flowers including winter, bounded nitrogen fixation |
| broom | Cytisus scoparius | shrubs; open acid scrub, spring/summer flowers, bounded nitrogen fixation |
| crab_apple | Malus sylvestris | trees; spring flowers, broad round crown |
| wild_cherry | Prunus avium | trees; spring flowers, taller horizontal branch tiers |
| field_maple | Acer campestre | trees; spring flowers, broad lobed crown |
| holly_fern | Polystichum setiferum | ferns_mosses; evergreen damp shade, commonly soft shield-fern |
The historical 80-entry manifest below lists the retained baseline; append these twenty, not twenty per group. No addition grants human food or a new berry output. Nitrogen-fixing *Lotus corniculatus* is birdsfoot trefoil, not an aquatic lotus. Climbing ivy/honeysuckle use stand sprites rather than simulated climbing geometry.
### trees (10; P/T)
@@ -152,7 +179,7 @@ Loading the file returns **`factory(hearth, api) -> B`**. The factory has no reg
### Catalogue/query APIs
- `B.groups`, `B.group_order`: ordered arrays `trees, shrubs, grasses, herbs, ferns_mosses, fungi, aquatic, lichens`.
- `B.catalogue`: ordered 80-record snapshot, group order then manifest row order.
- `B.catalogue`: ordered 100-record snapshot, group order then retained and added row order.
- `B.get(value) -> record|nil`; `B.list(group_or_nil) -> ordered records`; `B.group_of(value) -> group|nil`; `B.kind(value) -> id|nil`.
- `value` may be a bare ID, full `hearth:` node name or `{name=...}`. Trees also resolve canonical `_seedling`, `_young`, `_mature`, `_leaves`, `_log`, `_planks`. Seasonal names resolve through `hearth.seasons.kind` when available. Generic patches and foreign namespaces do not resolve.
- Every returned record is a detached copy. Editing a snapshot cannot change internal policy.
@@ -178,7 +205,7 @@ Loading the file returns **`factory(hearth, api) -> B`**. The factory has no reg
Surface depth ranges/anchors are intentionally coarse: even floating duckweed uses a valid freshwater bottom observation rather than an unbounded “water somewhere” fallback. Succession checks up to six consecutive loaded `hearth:water_source` cells and an accepted bottom, with authority/markers checked on every cell. Flowing water, unknown bottoms, over-depth or unloaded columns fail closed. Surface/submerged mode for a new stand is position-deterministic; established stands retain their species mode. Thus all ten aquatic taxa have establishment/spread/age pathways, but **hornwort/starwort use above-water stand markers, not scientifically exact submerged geometry**. `register_nodes` keeps direct source-water item placement disabled. Bank flora occupies dry cells beside actual water.
`choose` considers all 80 records in a deterministic position-hashed order, skipping trees unless `h.include_trees=true`. It returns an unprefixed actual node kind, e.g. `bluebell`, `water_mint` or `beech_seedling`, plus a detached descriptor. It never writes the map, chooses an occupied cell, checks claim ownership itself, consumes nutrients or rescans the world. The caller must independently verify loaded, empty, unmodified destination/support and authorization **immediately before any write**, using its existing bounded scheduler. No plant replaces another, spreads water or creates nutrients here.
`choose` considers all 100 records in a deterministic position-hashed order, skipping trees unless `h.include_trees=true`. It returns the actual bare node kind plus a detached descriptor. The caller independently verifies loaded empty unmodified destination/support and authorization immediately before any write. No plant replaces another, spreads water or creates nutrients through this query.
### Interactions and finite effects
@@ -186,6 +213,7 @@ Surface depth ranges/anchors are intentionally coarse: even floating duckweed us
- `B.interactions(value) -> tag-keyed descriptor copies` with a `description`, and where applicable `cap` or `enabled=false`.
- `B.active(value, season) -> boolean`: establishment/visible-season gate, **not** an instruction to destroy winter-dormant perennial plants. Four spring woodland herbs gate to Spring; evergreen trees, mosses/lichens and persistent bracket fungi have broader windows.
- `B.pollinator(value, season) -> boolean`: actual flowering tag **and** flowering season. Oak, wind-pollinated grasses, nettle, mosses and fungi do not become nectar sources. Meadowsweet and dog rose represent pollen as well as nectar relations.
- `B.seeding(value, season) -> boolean`: qualitative seed-season window; succession additionally requires calendar maturity. This is not a physiological reproduction authorization or a replay queue.
- `B.edible(value, season) -> boolean`: **game berry permission only**, independent of wildlife browse; only bilberry/raspberry in Summer and blackberry in Summer/Autumn return true.
- `B.browse(value, season) -> boolean`: active wildlife relationship; not a universal diet rule. Wildlife owner must still choose an appropriate animal diet.
- `B.resin(value) -> boolean`: only the catalogue's pine/spruce/juniper resin tag. It grants no item or extraction callback.
@@ -196,7 +224,7 @@ Surface depth ranges/anchors are intentionally coarse: even floating duckweed us
### Registration and harvest
`B.register_nodes(register_node)` accepts the content registrar **taking bare names**. It is idempotent and skips known existing IDs, all ten trees and any already present `api.registered_nodes[full_name]`. In an otherwise empty registry it registers **62** nodes (70 non-tree taxa minus eight existing understory IDs). It registers no recipes, aliases, items or tree stages/resources. Existing flora definitions are never overridden. Main should load this factory after seasons and call registration after legacy diversity registration.
`B.register_nodes(register_node)` accepts the content registrar taking **bare names**. It is idempotent and skips existing IDs, all thirteen trees and already registered definitions. It registers **79** nodes in an empty registry, no recipes/aliases/items/tree resources. Main loads the factory after lifecycle/seasons and calls registration after legacy diversity registration.
New terrestrial plants/fungi/shrubs are conventional crossed `plantlike` sprites with no meshes/sculptures. Surface aquatics are thin nodeboxes wholly inside ±0.5 of the node. They are not water; submerged sprites are not given liquid behavior or water groups. All are non-walkable and `buildable_to=false`. Ordinary herbs, mosses, fungi and lichens dig to their own node item. **All new shrubs dig to `hearth:sticks`**, so digging/replanting cannot erase a berry cooldown and mint a fresh fruit-bearing bush. Placement marks `player_placed=1`; normal construction/protection handling is supplied by the existing registrar. There is no new growth timer.
@@ -212,30 +240,50 @@ The factory may precede construction publication, but harvest cannot. Time uses
Reasons: `invalid`, `denied`, `species`, `season`, `clock`, `metadata`, `cooldown`, `inventory`; success returns delivered count 1 or 2. No player health changes, medicinal effects, wild-consumption guidance or automatic fertilizer is added.
## Ecology80 integration and bounds
## Calendar integration and bounds (version 1)
The single succession loop owns recruitment, local spread, dormancy, senescence and decomposition for all 80 catalogue entries. It uses the **actual node kind returned by `B.choose`**, including species seedling suffixes, and rechecks `B.suitable` against destination facts before writes. Trees enter older gaps only after 7,200 active seconds; other recruits establish after 180 active seconds (90 with existing nutrients). No second scheduler, unbounded radius, map emergence or offline replay is introduced. Existing eight column probes/second, radius 12, twelve sampled heights, 8,192-entry age cache, 60-second durable attempt cooldown and 1,200-second maximum continuity gap remain.
Main must assign the pure namespace `hearth.lifecycle` **before** botany/content. This change does not edit bootstrap files owned by main. See [the exact lifecycle API and evidence](lifecycles.md). Succession consumes `plant(id)` and `phase(profile, 0, age_seconds)` with conservative upper-endpoint thresholds, retaining field-level evidence/fallback labels. Its root anchors, not the loaded activity cache, determine age. One real day is 86,400 seconds; one mean year is 365.2425 days. No ten-minute years, accelerated stage counters or nutrient age multipliers remain for catalogue plants/trees.
The existing scheduler still performs eight column probes/second, radius 12, twelve sampled heights, at most 96 seasonal swaps and one globally reserved full tree-plan operation per wall-clock second. The 8,192-entry continuity cache and 1,200-second gap still bound **loaded work**, stress and material decomposition; they no longer truncate biological age. Empty-gap colonization remains a coarse 90/180-active-second recruitment opportunity (woody gaps after 7,200), not a claim that a real seed germinates in minutes. New recruits start calendar age zero and must wait their full profile thresholds before young/mature transitions and propagation. Reconciliation never emerges terrain, scans the whole world or replays missed seasons.
### Root persistence and migration
- `succ_birth_at`: validated UTC epoch anchor; for a natural recruit this is actual recruitment time. `succ_lifecycle_version=1` and `succ_lifecycle_policy=calendar_v1_conservative_max` label this format and threshold policy.
- `succ_initial_age_seconds`: separately recorded **inferred establishment age**, zero for `natural_seed`. It avoids inventing negative/pre-epoch birthdays for ancient-looking generated vegetation. Biological age is initial age plus the entire difference between current epoch and birth anchor.
- `succ_lifecycle_origin`: `natural_seed`, `legacy_established` or `worldgen_established`. Legacy `succ_age` is retained unchanged, never multiplied or subtracted into a birthday. Migration anchors once at NOW; stage inference is a documented assumption, not historical observation. Seedlings infer their profile emergence threshold, young roots the juvenile threshold, mature roots and un-staged botanical stands their known maturity threshold. Unknown maturity infers zero, not a fabricated age.
- `succ_lifecycle_profile`, `succ_lifecycle_seed`: exact identity and persistent deterministic position seed. Version 1 intentionally uses profile maxima, not sampled thresholds; the stored seed reserves reproducibility without claiming a measured age distribution.
- `succ_last_observed_at`: durable high-water timestamp. Clock rollback pauses reconciliation until the clock catches up; it never resets birth or subtracts age. Malformed timestamps, seeds, profile identity or unknown versions fail closed without migration/reroll. Stage swaps preserve all root anchors. Components reference the root and do not age separately.
- `succ_seed_epoch`: next allowed present-time seeding opportunity, advanced **before** one attempt (trees one mean year, understory 30 days). Missed opportunities are not queued. Current seed season, maturity, habitat, loaded air and protection still apply. Unknown maturity does not authorize propagation. `succ_litter_epoch` similarly bounds mature litter attempts to 30 days.
Known unmodified botanical stands and soil-supported worldgen log roots adopt `worldgen_established`; they are not converted to seed sprites, assigned fake historical birthdays, expanded or killed on load. Existing mapgen canopies remain unmanaged timber. Species/generic log roots retain their node and mature-stage provenance. Every adoption checks source/support ownership, protection and markers. Load callbacks only mark/invalidate continuity; first bounded root observations initialize calendar anchors.
Winter/cold pauses visible growth transitions, seeding and loaded stress, **not calendar age**. On a later favorable visit an overdue plant may advance one stage; no historical season integration is claimed. Biennial root/rosette policy persists across winter, not instant flowering or death. All lifespan endpoints are informational; there is **no automatic age-based plant death**, including unknown-lived fungi/lichens. Historical growing seasons, seed banks, annual stem death and fungal fruitbody turnover need future species-specific models. Unfavorable habitat records stress but does not kill a calendar-managed organism using the former ten-minute threshold.
`S.habitat(pos)` is read-only. Its 5×5×5 stencil now exposes exact catalogue dimensions: finite `light`, `moisture`, `ph`, `nutrients`, depth-valued `water`, separate `near_water`, `ground`, semantic `substrate`, `freshwater`, `aquatic`, `host`, active browse count (`edible`, retained for wildlife compatibility), seasonal `pollination`, `nursery` and host-fungus IDs. pH is an explicit **game support-class approximation**: peat 4.5; podzol/granite 5; alpine turf/slate/sandstone/scree/stone/forest floor/bark/deadwood/litter 5.5; grass/soil/alluvium/sand/basalt 6.5; limestone 7.5. Missing support observations fail closed. Nursery-tagged natural cover slows accumulated habitat stress by 25%; it creates neither water nor nutrients. Fungal host observations use only nearby identified natural trees or recorded natural wood species, never player timber.
Non-tree active lifetimes (seconds): shrubs/lichens 86,400; grasses/aquatic 21,600; herbs 14,400; ferns/mosses 43,200; fungal fruit bodies 2,400. Dormant perennials occupy their cells and pause, not disappear at first frost. Soil-supported vegetation becomes litter; rock/bark/wood/litter/water stands disappear without replacing their support. Fungal fruit bodies **do not independently mint nutrients through litter**. Saprotrophs advance only an existing, natural, authorized litter/deadwood clock by at most 60 seconds per attempt. The substrate's eventual consumed decomposition pays once using the existing capped nutrient path.
Material decomposition (generic dung/litter/deadwood/fungi) retains bounded loaded accounting; these are resource-cycle approximations, not living-organism years. Botanical fungi do not independently mint litter nutrients. Saprotrophs advance only an existing natural authorized substrate clock by at most 60 seconds per attempt; consumed material pays once through the capped nutrient path.
All nitrogen-fixer tags use a persisted two-unit **soil support** budget (`succ_fixed_nitrogen`), shared across death/recolonization/restart; a bark-dwelling fixer without a soil support gets no fertilizer effect. Matching ectomycorrhizal fungi reduce seedling establishment age from 21,600 to 16,200 only with a positive existing soil resource budget; successful growth debits at most one from that same budget. Failed growth creates/debits nothing. `parasite` and `allelopathy` remain deliberately **disabled descriptors**; no tree infection or neighboring-plant damage. `engineer`/`resin` stay habitat/resource descriptors, not autonomous terraforming/item generation. Pollination is exposed for wildlife/harvest integration, **not a berry-yield multiplier**: the unchanged botany harvest policy still returns at most two berries per real-day cooldown. Succession preserves a valid `botany_ready_at`; other non-succession provenance metadata still opts plants out.
All nitrogen-fixer tags use a persisted two-unit **soil support** budget (`succ_fixed_nitrogen`) shared across restart/recolonization; additions require at least 90 calendar days and stop at local fertility four. New gorse, broom, red clover and birdsfoot trefoil use the same cap. Root fungi may transfer/debit at most one existing soil unit when an independently age-eligible seedling grows; they **never reduce calendar thresholds**. Failed growth debits nothing. Parasite/allelopathy remain disabled descriptors. Pollination is habitat, not a berry multiplier; harvest remains at most two berries per real-day cooldown.
### Owned full-block tree lifecycle
`D.tree_plan(species, stage)` supplies fresh full-block plans for all ten species. Succession validates integer offsets within ±4 horizontally and 0–9 vertically, at most 810 entries, no duplicate/foreign names, and a root. **One global tree-plan reservation per wall-clock second**, including denied attempts, covers plan construction/preflight/application regardless of player count. Old/new plan union work is bounded (at most two plans); there are no dynamic per-frame world tree registries.
`D.tree_plan(species, stage)` supplies fresh full-block plans for all thirteen species. Succession validates integer offsets within ±4 horizontally and 0–9 vertically, at most 810 entries, no duplicate/foreign names, and a root. **One global tree-plan reservation per wall-clock second**, including denied attempts, covers plan construction/preflight/application regardless of player count. Old/new plan union work is bounded (at most two plans); there are no dynamic per-frame world tree registries.
Every destination and its immediate support must be loaded, unclaimed, externally unprotected and unmarked. New components require air; growth never silently replaces even natural groundcover. Existing components must match the root-key `succ_component`, species/stage expected node (season-canonicalized) and rotation. `succ_tree_stage` and `succ_species` persist on the root; components carry its plain coordinate key. Components never independently age or pay nutrients. A tree's own owned leaves do not shade its own root into automatic starvation; neighbouring crowns still shade recruits. Pine **and spruce** retain winter shade.
Senescence atomically clears only the root's matching components and leaves **one ground-supported full-cube fallen log**, `param2=12`; existing content supplies the axe requirement and one-log drop. No floating standing log pile, sculpted tree, or chain of extra resource logs is created. Any protected/marked/foreign component blocks collapse rather than being destroyed. Natural epiphytes losing their owned substrate are removed in the same bounded preflight, while foreign attachments block it.
Automatic senescence/collapse from age is disabled. The owned-component cleanup mechanism remains for explicit root removal; it cannot clear unowned mapgen/player timber. Any protected/marked/foreign component blocks cleanup. Natural epiphytes losing an owned substrate participate in the same bounded preflight; foreign attachments block it.
`S.tree_removed(pos, oldnode) -> boolean` is the optional after-dig handoff. A standard `register_on_dignode` hook calls it when available. It validates an empty loaded root, old stage identity and every remaining matching owned component; it cannot clear unrelated mapgen/player wood. External removals that do not invoke this hook must explicitly call the handoff; a denied/unloaded cleanup is not queued/replayed. Unmanaged pre-existing mapgen tree canopies are not retroactively adopted as managed trees. The independently registered generic deadwood adoption remains intact.
## Focused validation and integration boundary
[tests/botany_spec.lua](../tests/botany_spec.lua) is a standalone Lua 5.1+ mocked-API suite. It checks all 80 positive niche vectors and each light/moisture/pH/substrate rejection; 8×10 disjoint counts; unique scientific names; exact legacy mappings; seasonal/host/water distinctions; texture and registration ownership; bounded interactions; toxic/food exclusion; denied/full/partial/reentrant harvest; reload/cooldown and invalid clocks/metadata.
[tests/botany_spec.lua](../tests/botany_spec.lua) checks all 100 positive niche vectors and dimension rejections, exact uneven group counts, lifecycle Latin identities, legacy mappings, seasonal/host/water distinctions, registration ownership, finite interactions and denied/full/partial/reentrant harvest. Calendar-managed seedlings/juveniles do not supply mature flower habitat: biennial second-season policy needs both maturity and the current flower season.
Standalone focused suites: [tests/botany_spec.lua](../tests/botany_spec.lua), [tests/succession_spec.lua](../tests/succession_spec.lua) and [tests/seasons_spec.lua](../tests/seasons_spec.lua). They include all 80 real selector outputs and recruitment/spread/lifecycle pathways with supplied species niche vectors, separately tested **actual** water/wood/pH habitat sampling, water preservation/authority, atomic real tree plans, owned cleanup/restart, malformed plans, concurrency bounds, finite fungal/nitrogen effects and cube-safe seasonal registration. Verified markers: `BOTANY_SPEC_PASS 32`, `LAST_HEARTH_SUCCESSION_PASS 63`, `LAST_HEARTH_SEASONS_PASS 12` (107 named cases; the all-80 case additionally loops every taxon). Controlled niche-vector coverage is not evidence that all species spawn in one real-world patch. Require each final marker, all named passes and **empty stderr**; Fengari exit zero alone can hide assertion failures. On the network workspace, module source was read from disk once and executed in-memory to avoid repeated SMB reads timing out; tests/production modules were not rewritten for staging. No full engine or persistent world is run for this integration. Visual correctness, real-engine harvest/dig callback wiring and empirical gameplay/performance acceptance remain to be verified by main.
Focused suites cover all 100 real selector outputs with controlled niche vectors, actual water/wood/pH sampling, calendar migration and rollback, no guessed mortality, bounded material effects, atomic block plans and cube-safe seasons. Require all named successes, final suite markers and empty stderr; Fengari exit zero alone can hide failures. Source is read from disk once into an in-memory loader to avoid SMB repeated-read overhead. Native-engine/asset/bootstrap integration and empirical gameplay remain main-owner verification work; controlled habitat vectors are not real-world distribution evidence.
Calendar expansion validation: **178 named passes**, empty stderr, all six final
markers: botany 38, diversity 15, biomes 26, seasons 16, ecology 9, succession 74.
Only the requested owned files were edited. The separately owned lifecycle suite
and schema/content/art/bootstrap integration are not included in this result.
Security review followed CodeGuard: centralized runtime authorization, deny-by-default missing policy, finite validated positions/time, persistent bounded harvest, inventory preflight and reentrancy lock. No credentials, certificates, cryptography, network operations or dynamic executable inputs are introduced.
+12 -9
View File
@@ -8,18 +8,19 @@ forms retain priority.
## A quiet traveler, not a costume catalogue
All choices are inclusive and independent, with no gender, identity or stat
fields. Compact, balanced and broad silhouettes have identical mechanics,
height, camera, collision, reach, movement and attachments. Natural hair, muted
mineral cloth, undyed linen and worn leather fit the woodland world.
fields. Compact, lean, balanced, broad and sturdy silhouettes have identical
mechanics, height, camera, collision, reach, movement and held-tool anchors.
Hair, layered field clothes, cloaks, belt gear and packs use original low-poly
geometry with hand-drawn, muted woodland pixel art.
Four tabs keep the 14 × 11 form manageable:
| Tab | Choices |
| --- | --- |
| Face | 3 body shapes, 6 skin tones, 4 face-line patterns, 5 eye colors |
| Hair | 7 styles, 6 shared hair/facial-hair colors, 5 facial-hair styles |
| Clothing | 4 garments, 8 cloth colors, 4 trousers, 3 boot leathers |
| Finishing | 5 accents, 8 accent colors, 4 face-detail patterns |
| Face | 5 body silhouettes, 6 skin tones, 5 face-line patterns, 5 eye colors |
| Hair | 10 styles, 6 shared hair/facial-hair colors, 7 facial-hair styles |
| Clothing | 7 garments, 8 cloth colors, 4 trousers, 3 boot leathers |
| Finishing | 9 accents, 8 accent colors, 5 face-detail patterns |
Dropdowns use readable names, not gender categories or free-form colors. Cloth
and accent palettes are sage, teal, slate, mauve, ochre, rust, linen and charcoal.
@@ -64,8 +65,10 @@ Encoding and decoding use `core.write_json` / `core.parse_json(raw,nil,true)`;
parser errors do not log player payloads. There is no executable deserialization,
custom path, uploaded texture or free-form hexadecimal color. Every mesh and
layer filename is built solely from fixed IDs. Compositing order is skin, face,
eyes, garment, trousers, boots, accent, hair, beard, detail: clothing must stay
below long hair. Face overlays are skin-neutral; there is no global color tint.
eyes, garment, trousers, boots, hair, beard, accent, detail; accents layer above
hair so a selected hood frames and covers it. The model includes an original
draped mantle, belt, pack, projecting nose and ears. Face overlays are
skin-neutral; there is no global color tint.
The held-tool material remains the existing compatibility texture.
Apply serializes and checks the roundtrip, then writes metadata **before**
+188 -53
View File
@@ -13,14 +13,16 @@ 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 and finally deadwood.
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 food or breeding habitat. Grazing opens gaps
but imposes a five-minute recolonization cooldown. Dung, litter and deadwood
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
@@ -47,7 +49,7 @@ identity, respects external protection, and uses `swap_node` to retain owner/pla
markers, natural flags, age, metadata inventories and timers. It does not convert
other timber, rewrite surrounding nodes, or regenerate existing terrain.
### Active-time scales (minimums, not wall-clock promises)
### Calendar biology versus bounded loaded work
| Process | Observed active time |
| --- | --- |
@@ -55,17 +57,22 @@ other timber, rewrite surrounding nodes, or regenerate existing terrain.
| 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 |
| Seedling to young tree | 6 hours |
| Young to mature tree | 24 additional hours |
| Mature tree death | 72 additional hours |
| Mature seed dispersal | After 30 minutes; every tenth evaluation |
| Wildflower / grass and fern senescence | 4 / 6 hours |
| Dung / litter / fungi / deadwood decay | 10 / 30 / 40 / 120 minutes |
| 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 therefore take sessions, not seconds. Each stage resets its
own age. Cold weather below 3°C suppresses establishment and living reproduction/
stage transitions. Decomposition and stress remain possible. This is a practical
niche model, not a botanical or hydrological solver. Young/mature identities are
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
@@ -76,17 +83,54 @@ clearance assumption: mature profiles reach 9×10×9 and must be preflighted in
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 saplings, crops and berry bushes retain their existing time contract.
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**, matching the ten trees in [botany.md](botany.md). Each has
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. The six-hour/24-hour/72-hour stage durations above
remain unchanged. Growth and seed dispersal preserve the parent's species, and
death records `succ_species` on generic deadwood (retained through its litter
transition). Each young/mature root drops **one** of its species logs, never three
`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.
@@ -101,7 +145,9 @@ Recruitment is deterministic, not an all-oak fallback:
- 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.
All ten have distinct mature block silhouettes and deterministic recruitment.
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
@@ -138,8 +184,9 @@ 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 berry-regrowth consumers; this module does **not** change berry timers or grant
extra berries. `edible` is a separate active-natural-forage count. Pine and spruce
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.
@@ -150,9 +197,13 @@ 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 **two observed active hours**, releasing three nutrients
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 age or absence catch-up. As with
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.
@@ -176,10 +227,12 @@ calendar through the existing solar model. Explicit test climates can preview it
| Spring greens patch | Pickable | Dormant | Dormant | Dormant |
| Autumn mushroom patch | Dormant | Dormant | Pickable | Dormant |
Frost and seasonal dormancy pause living age, stress and reproduction; they do not
kill perennials at first frost. Spring resumes the same ecological age. Deadwood,
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. First-night berry availability stays year-round.
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
@@ -231,14 +284,21 @@ 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*) | An active compatible nearby fungus permits a tree seedling's young-stage transition at **16,200 rather than 21,600 active seconds**. Only a successful transition consumes **at most one existing soil nutrient**. Multiple fungi select the maximum allowance, not a sum; zero nutrients means no early growth. | [Fly agaric, Distribution and ecology](https://en.wikipedia.org/wiki/Amanita_muscaria#Distribution_and_ecology) describes ectomycorrhizal associations. Game host lists are narrower than the article. The existing growth debit is not nutrient creation, a full carbon-for-mineral ledger, or simulated water transfer. |
| Saprotrophic wood fungi, including birch polypore (*Fomitopsis betulina*) | On suitable natural deadwood/litter only, advance its existing decay age by at most **60 seconds per eligible evaluation**, capped at 7,200 / 1,800. The ordinary substrate transition consumes deadwood to litter to air, paying **three then one** soil nutrients once, subject to cap eight. Removing the support removes its natural fungal occupant. The fungus itself has no extra nutrient 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. Existing host suitability remains authoritative. Natural parasitism on weakened living birches is **not simulated**. No attacks on living/player trees. |
| 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
@@ -287,7 +347,8 @@ Lua tests, not a claim of full-engine or multiplayer validation.
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.
- Ages accrue from consecutive observations, not from absolute planting dates.
- 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
@@ -297,9 +358,10 @@ Lua tests, not a claim of full-engine or multiplayer validation.
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 pauses growth; it cannot
accelerate it. Ages are sampled approximations between visits, not per-tick
accounting; very busy/many-player worlds may deliberately progress more slowly.
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.
@@ -316,9 +378,52 @@ Lua tests, not a claim of full-engine or multiplayer validation.
`hearth.succession` is installed by the content registrar:
- `forage(pos) -> boolean`: consume exactly one nearby active natural
`meadow_grass`, `fern`, `clover`, `sedge` or `nettle` on safe natural support.
Does not eat dormant flora, flowers, crops or player plants.
- `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.
@@ -334,6 +439,31 @@ Callers supply animal ground-cover height (the air/plant cell above soil), not t
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
@@ -368,9 +498,13 @@ explicit mesh tiles directly; it does not require `override_item` in engine mock
### Main-agent wiring and exact diversity interface
The blockshape change owns only diversity, content, biomes, their three focused
tests and this document. Succession, ecology, seasons, botany and generated assets
remain other owners' responsibility. Exact integrated order:
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)`.
@@ -378,17 +512,18 @@ remain other owners' responsibility. Exact integrated order:
`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 (ten species' logs/leaves/planks).
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:
30 species stage nodes, eight existing understory nodes and the bootstrap LBM.
7. Call `hearth.botany.register_nodes(register_node)`: exactly 62 new non-tree taxa,
with all explicit tiles and inventory/wield images preserved. The ten trees
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 ten/eight canonical short names.
- `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;
@@ -409,7 +544,7 @@ The returned diversity table exposes:
### Geometry handoff to ecological growth
`diversity.tree_plan(species, stage?) -> plan | nil` is read-only. Species is one
of the ten IDs or `"generic"` for legacy trees; stage is `"young"` or `"mature"`
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
@@ -445,7 +580,7 @@ No original 12-month climate calculation or preview-clock semantics changed.
### Exact species/understory art contract
For **all ten** species in `species_order`:
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
@@ -470,10 +605,10 @@ heather, bluebell, sedge and bulrush additionally use
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 **142 nodes**: 38 canonical nodes,
72 deciduous stage variants, 24 deciduous full-block leaf variants and eight
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 62 nodes. Species provenance on generic deadwood remains metadata.
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/
@@ -484,10 +619,10 @@ 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 ten-species ordering,
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, all 78 unique recipes,
62 additional botany nodes and preserved aquatic icons/placement guards.
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.
Executable
+509
View File
@@ -0,0 +1,509 @@
# Fauna: evidence, calendar time and integration contract
Reviewed 2026-11-04. Scope includes the catalogue, wildlife runtime, direct
in-game interaction panel and focused Lua tests. No engine run or visual-quality
acceptance is claimed by this change.
## Public interfaces
The catalogue is a factory: `dofile(modpath.."/fauna.lua")(hearth,api)`.
It returns `VERSION`, `SECONDS_PER_DAY`, `list()` / `catalog()` (ordered record
arrays), `get(id_or_entity)`, `kind(id_or_entity)`, `source(id)` (URL array),
`phase(id,born_at,utc)`, `breeding_window(id,month)` and
`can_breed(id,born_at,utc,sex,season?)`. Unknown IDs return nil or deny breeding.
All catalogue records and lists are defensive copies. Records expose `id`,
`entity`, `common`, `latin`, `source_url`, `wikipedia_url`, `sources`, `confidence`,
`profile` (unaltered lifecycle evidence), `foods`, `prey`, `medium`, `activity`,
`bounds`, `mesh`, `texture`, `clips`, `fps`, `frames`, `eye_height` and policy caps.
Main may construct `hearth.fauna` before wildlife. Otherwise wildlife loads the
fixed sibling factory via `api.get_modpath("hearth")`; no caller-supplied path or
saved asset name is accepted. The catalogue uses `hearth.lifecycle` or loads the
fixed sibling lifecycle module. Main should inject lifecycle so seeded cohort ages
use its `duration()` selection consistently. No network request occurs at runtime.
Wildlife returns `fauna`, `species` (legacy hare entity), `global_cap`,
`player_cap`, `local_cap`, `ai_budget`, `status()`. Status includes legacy
`total`, `hare`, `deer`, `fox`, plus a copied `counts` dictionary containing all
23 IDs, `species_count=23`, `actions`, `births`, `meals`, `grazed`, `dung`,
`deaths`, `pollinations`, `dispersals`, `detritus`, and the three caps.
It also exposes a defensive `recruitment` diagnostics copy: `interval=5`,
`scan_nodes=648`, `attempts`, `successes`, `scans`, `last_reason`, and `reasons`
(reason-to-count dictionary). The `/ecosystem` survey displays all species plus
admission attempts, successes and the latest outcome; it remains observation-only.
Reasons include `waiting`,
`invalid_player`, `global_cap`, `no_surface`, `observer_near`,
`blocked_or_unloaded`, `habitat_unloaded`, `no_active_food_match`,
`population_cap`, `unsafe_body`, `activation_rejected`, `admitted`,
`registry_unavailable`, `region_budget`, and `region_seeded`.
`status().residents` is a fresh diagnostics table containing `storage_ok`,
`regions`, `region_limit=4096`, `region_size=48`, `founders_per_region=4`, and
the persistent individual `counter`. Counts describe loaded residents, not the
entire unloaded population. Diagnostics do not expose mutable journal records.
## Field journal and ambient grouping
Right-clicking a live, pointable animal records that species in the observing
player's metadata under a versioned, bounded, canonical species-ID list. The
species guide marks recorded and not-yet-encountered identities; opening a
catalogue-only profile never unlocks an encounter. This is a private per-player
field-journal convenience, not a local census, achievement reward, or change to
the animal/world state. Invalid saved journal text fails closed and is never
overwritten. Active robins, blackbirds, blue tits and mallards also use a small,
local cohesion/separation bias when choosing wander direction. It is computed
from loaded same-species residents only—there are no persisted flock IDs,
unbounded entity scans, or forced spawn groups.
These are original adaptations of ambient grouping and encounter journaling
ideas visible in Animalia's public [bird behavior](https://github.com/ElCeejo/animalia/blob/main/mobs/song_bird.lua),
[AI](https://github.com/ElCeejo/animalia/blob/main/api/mob_ai.lua), and
[Libri](https://github.com/ElCeejo/animalia/blob/main/api/libri.lua) source.
Last Hearth does not include that mod's code, models, textures or audio.
## Runtime API integration
The managed package pins the MIT-licensed Creatura 0.2.10 release from ContentDB
release 34130; the downloaded archive SHA-256 and upstream commit are recorded
in [mods/creatura/UPSTREAM.md](../mods/creatura/UPSTREAM.md). Creatura supplies
animal-focused pathfinding, boid algorithms and its ABM spawn scheduler.
Last Hearth supplies species identities, original meshes/textures, habitat and
diet policy, caps, protected/provenance-aware admission, persistent resident
IDs, feeding transactions and player interactions. Creatura spawn callbacks do
not bypass Last Hearth's `on_activate` checks. Ambient ABMs are restricted to
common species, small groups, natural soil types and fixed per-species caps; no
drops, taming, combat or livestock systems are enabled. The bounded Last Hearth
recruitment path remains available as a fallback and does not award food when
vegetation serves only as an ambient habitat signal.
Engine API: entity/globalstep registration, connected players, loaded-only
`get_node_or_nil`, node definitions, node metadata, protection, `add_entity`,
`parse_json` / `write_json`, mod storage (`hearth.storage`, otherwise
`api.get_mod_storage()`), and normal ObjectRef position, velocity, yaw,
acceleration, armor and animation methods. Optional `api.now()` is an integer UTC
epoch test seam; otherwise `os.time()` is used. Climate uses
`hearth.climate_at(utc).season`; qualitative habitat seasons may be accelerated
by climate configuration, but animal ages and activity hours never are.
## Direct in-game interaction
Registered animal entities are natively pointable while retaining their
existing physical collision envelope. Luanti's normal object picking dispatches
right-clicks to `hearth.inspect_fauna(clicker, species_id, snapshot)`; this is
not a HUD raycast or a node-placement change. Wildlife verifies that the entity
is currently active and that the exact clicker is an online player with a
session, then passes a detached snapshot of calendar birth time, modeled
reserve, origin, persistence-ID availability and current rest/awake animation
observation. The callback is looked up at interaction time so wildlife does not
own formspec registration. The interface checks the online player and session
again, and escapes/bounds displayed catalogue and snapshot text. Species-guide
selection can also open a species-only profile; it is not a local animal census.
The panel reports Latin identity and source URLs, lifecycle phase and real
calendar age when available, currently modeled diet and prey, activity/sleep
and winter-rest policy, and whether reproduction is enabled for the profile or
disabled for incomplete evidence. Food reserve is explicitly labelled as a
coarse game policy, not biology. Persistent residency and current animation are
observations only; no position is displayed. A direct animal encounter adds one
species ID to the observing player's bounded field journal; catalogue browsing
does not. Neither action changes animal state, ecology, inventory or terrain.
Punching
continues to trigger flee movement; immortal armor and the lack of damage/drop
handlers prevent combat or item rewards. Collision and crafting/node placement
remain unchanged. Native mesh appearance and animation quality were not
visually verified in an engine session.
## Required succession transaction contract
These are **required transactional hooks**; the current succession supplies them.
Wildlife does not replace or bypass their shared budgets. Missing hooks or anything other than
literal boolean true gives no feeding credit and no ecological-success count.
Wildlife performs no direct vegetation/node mutation or inventory operation.
Main must repeat all loaded/protection/provenance checks inside each transaction.
Forage/deposit's entire 3x3 neighbourhood is guarded by wildlife before invocation.
| Hook | Exact required semantics |
| --- | --- |
| `habitat(pos)` | Loaded observation with `loaded`, `grass`, `cover`, `canopy`, `moisture`, `water`, `near_water`. Unknown/unloaded must fail recruitment. |
| `forage(pos) -> boolean` | Existing metadata-only natural browse debit, finite and seasonal, not inventory/crop consumption. |
| `deposit(pos) -> boolean` | Existing bounded metadata-only dung/nutrient receipt. One pending receipt per earned meal; no item or offline queue. |
| `pollinate(pos) -> boolean` | Atomically debit legitimate nectar from a mature, currently flowering natural plant and apply bounded pollination. Persist shared `succ_nectar_ready_at` before any callback; all bees compete for the same budget. Reject invalid timestamps. Use an explicit nectar replenishment policy; wildlife's 300-second inter-meal throttle is NOT evidence for floral replenishment. No seed generation. |
| `frugivore(pos, species_id) -> boolean` | Verify exact botany identity and fruiting season, then atomically debit the **same `botany_ready_at`** used by human `botany.harvest`. Set its existing annual (`365.2425 * 86400` seconds) resource deadline before callbacks. Never maintain a parallel animal-only berry budget; reject invalid/future readiness, nonnatural/planted nodes and immature shrubs. No inventory output. |
| `detritivore(pos) -> boolean` | Consume a finite unit of existing natural litter and transfer only conserved organic/mineral material. Reject empty substrate. Never an infinite soil fertility aura. |
| `granivore(pos, species_id) -> boolean, optional_cache_id` | Debit an existing finite seasonal seed/nut resource. True grants one meal; optional `"hazel"` or `"oak"` grants a **second separately debited viable nut**, not the eaten seed. No token if the second nut was unavailable. |
| `disperse_seed(pos, species_id) -> boolean` | Accept one immediately preceding, separately debited cache nut; check loaded natural viable destination and bounded seed metadata. Only squirrel hazel/oak caches invoke this hook. Consume authority once even if the destination fails (lost nut, never duplicated). No persistent token, offline planting, pollinator seed aura, or arbitrary new species. |
The cache hook must track immediate granivore authority in succession, rather than
trust arbitrary callers. Both source and destination are guarded; successful
consumption and successful caching are different counters. Seed-eating mice,
voles and birds are not automatically awarded dispersal. Fruit transport/gut
passage is not implemented and must not be presented as simulated.
## Time, provenance and deliberately unknown biology
**Six profiles now enable reproduction:** bank vole, wood mouse, blackbird, mallard, European hare and red fox. The sourced hare profile uses 365-day maturity, 42-day gestation, one leveret per litter and a 30-day care lock; its broad reported season is represented as Winter/Spring/Summer. The fox profile uses 304-day maturity, 53-day typical gestation, one pup per litter, and a conservative 300-day postpartum lock; its coarse breeding season is Winter. ADW reports 56-70 days of fox nursing/weaning, but the existing generic dependent-care loop cannot represent paternal provisioning or fox den care, so this species is not a complete family-behavior simulation.
Registration (`pending=false` for all 23) is independent
of evidence completeness. This is not whole-ecosystem true-life certification.
The following pages were retrieved on 2026-11-03; ranges are original factual
summaries, not bundled article text. Runtime uses conservative upper development
and maturity endpoints and the lower sourced litter/clutch count.
| Species | Maturity | Development to birth/hatch | Count | Sources and limitations |
| --- | --- | --- | --- | --- |
| bank vole | 42-56 d sex envelope; runtime 56 d for both | 17-24 d gestation; runtime 24 d | 3-5, runtime 3 | [ADW](https://animaldiversity.org/accounts/Myodes_glareolus/) gives 17-24 d, 20-25 d weaning, late April-September; [Woodland Trust](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/animals/mammals/bank-vole/) supplies conservative 6/8-week maturity; [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/bank-vole) supplies the chosen 3-5 count. ADW reports a wider 1-10, typically 4-8; these are not silently merged. |
| wood mouse | 65-71 d; runtime 71 d | 21-26 d gestation; runtime 26 d | 4-8, runtime 4 | [ADW](https://animaldiversity.org/accounts/Apodemus_sylvaticus/) gives maturity, gestation and 21 d weaning, but 4-7 young; retained 4-8 is from [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/wood-mouse). [Wikipedia](https://en.wikipedia.org/wiki/Wood_mouse) reports February-October season and 23-27 d gestation, differing from ADW. |
| blackbird | about one year | 13-14 d incubation; runtime 14 d | 3-4, runtime 3 | [ADW](https://animaldiversity.org/accounts/Turdus_merula/) gives first breeding at one year, 13-day prose/14-day summary and 13-14 d leaving nest. [RSPB](https://www.rspb.org.uk/birds-and-wildlife/blackbird) supplies clutch/nesting window. Fledging is not full independence. |
| mallard | 365 d both sexes | 26-28 d incubation; runtime 28 d | 9-13, runtime 9 | [ADW](https://animaldiversity.org/accounts/Anas_platyrhynchos/) gives these values; [Wikipedia](https://en.wikipedia.org/wiki/Mallard) gives 50-60 d to flight and spring nesting. Unknown lifespan does not prevent breeding. |
| European hare | 236-365 d; runtime 365 d | 30-42 d gestation; runtime 42 d | 1-8 leverets, runtime 1 | [ADW](https://animaldiversity.org/accounts/Lepus_europaeus/) reports maturity at 8 months-1 year, one-month weaning, 30-42 d gestation and 1-8 per litter; winter through midsummer breeding. One-month care lock is a conservative policy. |
| red fox | about 304 d; runtime 304 d | 51-53 d typical gestation; runtime 53 d | 1-9 in ADW summary, runtime 1 | [ADW](https://animaldiversity.org/accounts/Vulpes_vulpes/) reports 10-month maturity, winter mating in central-range populations, 51-53 d typical gestation and 56-70 d nursing/weaning. Its prose says 1-13 while its summary table says 1-9; the narrower table range is retained. A 300-day postpartum lock approximates its once-yearly seasonal cycle; this is policy. |
Breeding requires same-species female and male within two nodes, both mature,
safe, recently fed (within twice their feeding throttle), reserve at least 0.7,
and loaded food-bearing habitat. Conception spends 0.2 reserve from each parent;
each delivered young spends another 0.1 from the mother and starts with 0.1.
These are explicit normalized energy policies, **not biological calorimetry**.
Only successful succession transactions earn reserve. There is no feeding-count
shortcut to maturity, free fertility command, item reward or player attack.
`hearth.climate_at(utc).season` (or an explicitly supplied season) determines the
hemisphere-aware season gate: Spring/Summer for the four original profiles,
Winter/Spring/Summer for hare, and Winter for fox. Northern reference months remain research
observations, not a simultaneous northern-month veto on southern spring. These
coarse season policies omit some legitimate early-autumn breeding;
it is not exact local phenology. Missing climate denies new conception. Calendar
age and due times always use real epoch seconds, never climate-speed multipliers.
Pregnancy and incubation use a common persisted deadline field; eggs are a
bounded maternal brood state, **not mammalian pregnancy or separate nest nodes**.
Egg laying, nest construction, exact incubation attendance and clutch failure
are not simulated. Dependent young rest rather than hunt/browse as adults;
nearby loaded mothers transfer 0.05 reserve at the ordinary feeding cadence.
This is coarse care accounting, not milk chemistry or a species-specific chick
diet. Maternal postpartum locks are 25 d (vole), 21 d (mouse), 42 d (blackbird),
81 d (mallard), 30 d (hare) and 300 d (fox) after the last admitted young. Bird locks deliberately include an
extra 21 d beyond the conservative dependency/fledging allowance. They do not
claim measured inter-clutch intervals. Assets still use the authored species
mesh, not visually distinct baby models.
**Seventeen profiles stay reproduction-disabled**, including three legacy slots.
Blue tit now has [BTO](https://www.bto.org/understanding-birds/articles/blue-tit-diary)
8-10 typical eggs, about 14 d incubation and 18-21 d fledging; first breeding is
described as the next spring, not an accepted exact day threshold, and represented
caterpillar provisioning is missing. ADW's Cyanistes URL was missing and its
older Parus URL contained taxonomy only; neither validates numerical overrides.
[Common frog](https://en.wikipedia.org/wiki/Common_frog) adds about 3-year maturity
and 14-21 d incubation, but aquatic eggs/tadpoles and their food remain absent.
[Brown trout](https://en.wikipedia.org/wiki/Brown_trout) needs female mass for its
approximately 2000 eggs/kg, a gravel redd and temperature-dependent development.
[Bumblebee](https://en.wikipedia.org/wiki/Bombus_terrestris) requires queen/worker/
male castes and colony care; a single litter timer would be misleading. Rabbit
maturity, squirrel conception season, roe deer/stoat implantation, other mammal
profiles, robin/barn owl care, newt larvae and worm cocoons remain incomplete.
No missing values are filled with guesses. Lifespan averages never trigger death.
One-time regional founders represent existing wild individuals, **not reproduction**.
Where maturity is sourced, the estimated initial age uses its upper endpoint;
`born_at=utc-estimated_age_days*86400`, `origin="established"` explicitly labels
the estimate. Unknown maturity has no invented age offset and still remains an
established individual, not a purported newborn. `origin="born"` marks actual
admitted young with `born_at=utc` at delivery, including delayed admission after
an overdue pregnancy; it never creates an adult-aged catch-up cohort.
`self.id` remains an in-memory scheduling serial. `state.resident_id` is the
separate durable world-local positive integer allocated from mod storage;
`state.founder_region` identifies the founder's geographical cell, or is empty
for births and legacy-ID migration. Sex, selection seed, food reserve, birth
epoch and reproductive state survive normal unloading and restart. Position is
saved by the engine's static-object system, not copied into a second registry.
Feeding throttles (nectar five minutes, predators two hours, other plant food one
hour), movement speeds except earthworm, and normalized two-day energy reserves
are **coarse simulation policies**, not sourced metabolic measurements. Unknown
starvation durations are false: hunger can accumulate over hours/days but cannot
justify an invented death threshold. Rabbit is the sole enabled starvation
case: the fetched European rabbit account reports winter survival without food
of about 2-8 days; the conservative upper eight-day bound is used, counting
only represented loaded hunger (`hunger_seconds`), never an unloaded absence. This is
not a veterinary prediction for an individual. No free food appears because a
predator lacks prey. There are no modeled insect-larva, mayfly, beetle, parasite,
egg or nestling populations to feed on. Thus adult trout currently have no
supported live-prey recruitment resource; saved trout swim and remain otter prey
without pretending an unimplemented invertebrate biomass exists.
## Persistence and bounds
`static_save=true`. Static state is version-1 JSON, at most 2048 bytes, with only
`version`, `species`, `born_at`, `origin`, `sex`, `foodreserve`, `nextfeed`,
`pregnancy_until`, `last_seen`, `seed`, `last_meal`, `dung_pending`, plus
`conception_at`, `litter_total`, `litter_remaining`, `litter_delivered`,
`nextbreed`, `last_birth_at`, `parent_seed`, `parent_born_at`, `resident_id`,
`founder_region`, `hunger_seconds`. No deserialize,
Lua evaluation, arbitrary asset names or paths. Exact entity/species match,
finite numeric types/ranges and coherent nonfuture observation/birth/meal clocks
are mandatory. Flat/nonnested input is checked before silent JSON decoding.
Pregnancies require an enabled female profile, mature conception, exact sourced
maximum development interval, coherent bounded integer litter counts, and a
deadline no further ahead than that interval. Overdue deadlines remain valid.
The old version-1 empty reproductive state migrates to zero counters; old nonzero
pregnancies without the new schema are refused. Next-feed deadlines are bounded to one day ahead; future birth or
observation data is rejected, never used for credit. Failed JSON encoding emits
an invalid sentinel rather than silently making a fresh cohort next load.
Validated **existing residents always restore**, even above species/global/local/
player caps, with no connected-player API, or on newly claimed, blocked,
foodless, `nil`/`ignore` unloaded terrain. Positive native `dtime_s` is expected
for restoration and is accepted. No habitat or collision read may delete a
validated existing resident merely because its surroundings changed. Invalid
snapshots, unknown fields/species, incoherent clocks or out-of-world coordinates
are still rejected, with bounded reason-only warnings (no raw payload logging).
Movement and ecological transactions stop safely inside new claims, ambiguous
claims, external protection or unknown/unsafe collision terrain. An animal on
still-safe but depleted ground can wander and become hungry rather than vanish.
There is no teleport/terrain-repair escape for an animal trapped by construction.
Deactivation forgets **live counts only**, idempotently; it never records a
death or removes founder membership. Nothing deletes an existing animal to
free admission slots. Dormant mapblock animals stay in the engine save and can
return above the new-admission caps. Births and new founders, unlike restores,
still require all current safety, habitat and cap checks. There is no 900-second
lifetime or walk-away despawn/replenishment path.
Offline **calendar ages/deadlines** advance from saved epochs, but unloaded AI
is frozen, not a whole-world ecological simulation. There are no replayed
offline meals, mating cycles, founder admissions or dung/caching queues.
An existing overdue brood is reconciled on budgeted AI after a player returns:
at most one newborn per attempt, no more than its single stored litter total.
Species/global/local/player caps, safe loaded positions, current habitat and
maternal energy still apply. Full caps delay the original due time without
redating; remaining/delivered counts survive normal save/reload. Failed admission
does not spend a child; callback reservations and activation acknowledgement
prevent synchronous reentry duplication. Once complete, the brood clears and
postpartum lock prevents repeated deliveries. This is normal-save idempotence,
**not a cross-mapblock crash-atomic transaction or authenticated save format**.
Server-local static data is not exposed as client/admin fertility input. Schema
validation rejects giant/incoherent payloads but cannot authenticate a coherently
rewritten world save; operators must protect world files. No such bypass API is added.
Energy/hunger work on each safe loaded observation is bounded to 60 seconds;
restoration preserves reserve and accumulated hunger, resets the observation
clock and clears the pending dung attempt. A successful meal or maternal
feeding resets loaded hunger. Legacy snapshots start hunger at zero because
their historical loaded/unloaded intervals cannot be reconstructed. Unsafe or
player-absent AI turns freeze energy/hunger. No force-loading or emergence.
### One-time, geographically bounded founder registry
The separate mod-storage key is **`wildlife_residents_v1`**. This does not change
the game's world-schema version. Its flat JSON envelope has `version=1`,
`counter` (last allocated individual ID), and sparse `r_<x>_<z>` fields.
Cell coordinates are `floor(world_x/48)`, `floor(world_z/48)`, including negatives;
they do not depend on player, session, Y, current population or visit count.
Each region value is a canonical bounded string:
`attempts/last_attempt_utc/resident_id=species;resident_id=species;...`.
Both the decoder and encoder enforce the 1,048,576-byte registry ceiling;
each region value is at most 256 bytes with at most **four founder memberships**.
The decoder requires exact version, known species, valid bounded cell keys,
finite integral clocks/counter/IDs, unique founder IDs and no extra fields.
Nested/deep JSON is rejected before parsing; no code evaluation, assets, paths,
credentials or client-provided allocation authority are stored.
At most **4096 region records** can ever be admitted automatically. Records
are never evicted, reset on return, silently truncated or recycled after death.
New regions beyond this permanent budget are refused; existing records and
saved animals remain intact. Attempts (saturating at one billion) and last UTC
are persisted even for a loaded search that has no surface, unavailable food,
or unloaded/invalid candidates; these attempts do **not** fill founder quota.
An incomplete cell remains retryable on later valid observations, with no
timeout that marks an empty/unloaded area permanently populated. A full cell
never fills a dead, eaten, migrated-away or currently unloaded animal's slot.
Different eligible species may share its four slots; their IDs and types are
retained permanently, not inferred from live counts. This is a one-time founder
quota, not a continuously restored carrying-capacity target. Natural births
remain independently funded by the existing calendar/food/cap rules and do not
spend or refund founder slots.
Membership and monotonic ID allocation are written/read-verified **before**
the new animal is acknowledged. Storage read/write failure, silent dropped
writes, invalid JSON/version, duplicate/unknown membership, or any exceeded
bound disables all new admissions (including births), without deleting validated
existing residents. Missing storage also disables new admissions. A known saved
ID above a missing/rolled-back counter fails closed instead of reusing its ID.
Legacy snapshots without ID fields receive an ID and empty `founder_region`
when storage is healthy; with failed storage they remain valid residents without
an allocated ID, rather than being destroyed. They can migrate on a later restore
after operator repair. Never clear this key to make animals reappear: restore
world/mod-storage backups together. A process/storage crash after reservation
can conservatively spend a founder slot or burn an ID without creating an animal;
there is no cross-mapblock crash-atomic population transaction or automatic refill.
New-admission global cap is 64; player-neighbourhood cap 16 within 48 nodes;
local cap 12 within 16 nodes. Per-species caps range 2-8. Overlapping player
neighbourhoods are all checked for new founders/births only, never movement or
restoration. Loaded counts can exceed these limits after restoration; this
delays further admissions without erasing any resident. AI and founder searches share 24 actions/second,
maximum one second of accrued work; entities are visited in serial round-robin
order. Recruitment spends one action every five active seconds, with no credit
banked while players are absent. It rotates players and eight search patches,
queries one 9x9x8 box through `find_nodes_in_area_under_air` (648 cells maximum),
and checks one round-robin surface candidate per attempt. This is at most 12
queries / 7,776 queried cells per active minute, not a whole-map scan. Habitat,
body-envelope and ordinary AI observations are additional bounded work, not
included in that terrain-query cell count. No query may emerge unloaded blocks.
An integration without this search API reports `no_surface`, not a guessed Y.
Candidates lie 12-24 horizontal nodes from the selected observer; any observer
within eight nodes vetoes admission. Ground/plant surfaces set the body base
half a node above actual support, not at the player's Y. Water surfaces retain
their own water-cell Y and use the stand above water for succession observations.
The fixed vertical search band is player Y minus four through plus three;
distant cliffs outside this band wait for a closer observer.
One plant-food observation and one succession habitat observation are shared
across cheap rotating catalogue matches. Only currently active, habitat/food-fit,
uncapped species receive body-safety/admission checks. There are no 23 separate
terrain scans or a forced 11.5-minute wait for the next eligible species.
Daylight flowering yarrow can admit a bee, mixed understory a bank vole; nocturnal species
remain nocturnal and predators still require represented fruit or actual prey.
Qualitative herb seeding seasons are not an implemented food budget: only
oak/hazel nuts are recognized as seeds, matching succession's `granivore` debit.
Founder admission does not call `can_breed`, require a completed reproductive profile,
or fabricate an age/maturity threshold for unknown biology.
Unadopted, unmarked, supported worldgen understory is collision-passable without
pretending it was already adopted. At a recruitment candidate, at most nine
ground-level cells use guarded `diversity.bootstrap` (when available). The three
legacy meadow-grass/fern/wildflower sprites receive only `succ_natural` metadata
under the same loaded, natural-support and provenance checks. This closes the
gap between succession's 12-node adoption ring and the wildlife search ring.
No botanical birthday/profile, forage ration, inventory item or plant node is
created. Unknown placement metadata, named planter markers, claims, ambiguous
claims and external protection deny adoption. Botany's presence does not hide
legacy grass/fern browse. Every subsequent meal still requires the existing
finite succession transaction; merely discovering or adopting a plant grants no
nutrition. Existing saved animals activate before the first observer joins,
without bootstrap, safe/room rejection or a current-food requirement; unsafe
terrain freezes subsequent movement/transactions rather than destroying them.
Founders are initialized only as loaded habitat is explored near an observer;
unexplored regions have not yet been instantiated. This unavoidable lazy
initialization is not recurring immigration when walking around a seeded region.
There is no startup world-wide spawn, force-loading or offline execution.
Per-frame safety/velocity checks are bounded per loaded entity and do not count
as AI decisions. Restoration may exceed 64 loaded objects; the shared AI action
ceiling remains 24/second, but per-frame callbacks/list sorting are not a measured
constant CPU bound for an arbitrarily large restored population.
### Absent-animal diagnosis and focused regression
Before this correction, a single hardcoded candidate at player Y was tried for
one species every 30 seconds. All 23 species therefore took 690 seconds (11.5
minutes) to revisit. Slopes failed body support, and unadopted sprites both
blocked clearance and failed food recognition outside succession's adoption
ring. With botany loaded, legacy meadow grass/fern also lost the fallback browse
path. These were admission/resource blockers, not a missing adult-age privilege:
this runtime never gated immigration on known maturity or `can_breed`. The old
danger comparison used squared distance 16 (four nodes); it now uses 64 (eight).
The focused wildlife suite now has 57 named checks. It adds repeated physics steps across AI intervals, approach to reachable food, fine-grained collision sweep, solar activity and dormancy, danger/hunger while resting, winter browse-mode negotiation, and sourced hare/fox reproductive timing.
## Movement and art
Every fixed ID uses `hearth_fauna_<id>.b3d` and `hearth_fauna_<id>.png`.
Five actual ObjectRef animation calls select idle 0-19, locomotion 20-39,
feed 40-59, flight/swim 60-79, rest 80-99, at 24 fps with 0.15-second blend.
The authored asset has 100 frames. Bounds were obtained from `faunaBounds()` in
the existing art generator (10 model units per node), including long fox tails,
squirrel tail height, bird wings and antlers. Eye heights have a 0.15 minimum.
Collision is based on those measured bind bounds; safety uses a yaw-independent
envelope and animation margin, checking bottom, middle and top, source,
destination and a swept path sampled at no more than 0.2 nodes (at most 20
intervals for a four-node segment). No `set_pos` teleport movement.
Velocity ramps at at most 2 nodes/second squared up to a 1.3 nodes/second ceiling;
each command has a 2.5-second lease, exceeding the two-second AI interval.
Safety stops may be abrupt. Land,
low flight, submerged swimming and moist-land invertebrate corridors differ;
amphibians/otters/mallards may inhabit either land or water but **shore transitions
are deliberately refused** until swept bank-ramp traversal is implemented.
Flight can move vertically within a three-node natural-ground corridor.
Unloaded, claimed, ambiguous, protected, building, unsupported and wrong-medium
samples stop movement. This is conservative local movement, not full navigation,
arboreal climbing, digging, seasonal migration or fluid dynamics. Gravity is held
at zero inside these validated corridors to avoid uncontrolled cliff falls.
The earthworm surface speed is the reported 20 metres/hour, not a mammal sprint.
Species activity differs: local solar day/night schedules, twilight activity,
winddown/wakeup transitions, short rest windows for flexible activity profiles,
and winter dormancy for hedgehogs, frogs and newts. Danger is processed before
rest and loaded hunger continues during sleep; dormancy never removes an animal.
Resource search checks a fixed 5x5 horizontal area and follows a reachable food
candidate rather than requiring it beside the next AI tick. Winter browse accepts
only a non-toxic tagged tree/shrub and never implies human edibility. Wildlife
passes `winter_browse` to the shared forage hook, but the current succession
implementation does not accept that mode; winter twig/stem debiting is therefore
not active until a separately scoped succession change. Ordinary shared browse
continues to use its existing seven-day plant cooldown. Also represented are
danger flight and hedgehog stillness, alert pauses, nectar visits,
fruit feeding, nuts/caching, litter consumption and actual finite prey pursuit.
The source accounts describe many additional behaviors; descriptions are not
claims that burrows, dreys, antler shedding, caste systems or parenting are coded.
Art quality requires separate native-engine animation/visual acceptance.
## Research register and implemented nutritional web
Fetched Wikipedia identity/diet accounts (first eight plus earthworm/deer), and
Wildlife Trust/RSPB accounts for the remaining rows, on 2026-11-03. All Wikipedia
URLs are explicit identity links; generic deer is **Cervidae**, not a silently
chosen Latin binomial. Bank vole retains the lifecycle identity *Myodes glareolus*
(synonym *Clethrionomys glareolus* on the retrieved Wikipedia page). Prose below
is original summary; no source media or article text is bundled.
| ID | Identity / Wikipedia URL | Reviewed nutrition and represented behavior |
| --- | --- | --- |
| hare | [Lepus europaeus](https://en.wikipedia.org/wiki/Lepus_europaeus) | Grass/herbs, surface forms, nocturnal escape; represented browse and fox prey. |
| deer | [Cervidae, unidentified legacy deer](https://en.wikipedia.org/wiki/Deer) | Family-level foliage browsing only; no species timing asserted. |
| fox | [Vulpes vulpes](https://en.wikipedia.org/wiki/Vulpes_vulpes) | Hare/rabbit/vole/mouse/squirrel/worm/frog prey, seasonal berries; mostly nocturnal. |
| rabbit | [Oryctolagus cuniculus](https://en.wikipedia.org/wiki/Oryctolagus_cuniculus) | Grass/leaves; fox and stoat prey; no fictitious warren reproduction. |
| red_squirrel | [Sciurus vulgaris](https://en.wikipedia.org/wiki/Sciurus_vulgaris) | Seeds/nuts/fruit; diurnal, canopy-dependent establishment; separately funded oak/hazel cache. |
| bank_vole | [Myodes glareolus](https://en.wikipedia.org/wiki/Myodes_glareolus) | Leaves/seeds/fruit, needs cover; fox/stoat/weasel/owl prey. |
| wood_mouse | [Apodemus sylvaticus](https://en.wikipedia.org/wiki/Apodemus_sylvaticus) | Seeds/fruit and nocturnal covered movement; fox/stoat/weasel/owl prey. |
| hedgehog | [Erinaceus europaeus](https://en.wikipedia.org/wiki/Erinaceus_europaeus) | Finite earthworm prey, nocturnal/winter rest; badger prey. |
| badger | [Meles meles](https://en.wikipedia.org/wiki/Meles_meles) | Worms, rodents, hedgehogs, fruit; nocturnal. No adult rabbit edge: account emphasizes kittens. |
| roe_deer | [Capreolus capreolus](https://en.wikipedia.org/wiki/Capreolus_capreolus) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/mammals/roe-deer): buds/leaves/herbs/grass; delayed implantation not fabricated. |
| stoat | [Mustela erminea](https://en.wikipedia.org/wiki/Mustela_erminea) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/mammals/stoat): rabbit and small rodents; day/night hunting. |
| weasel | [Mustela nivalis](https://en.wikipedia.org/wiki/Mustela_nivalis) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/mammals/weasel): vole/mouse/small birds, low running. |
| otter | [Lutra lutra](https://en.wikipedia.org/wiki/Lutra_lutra) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/mammals/european-otter): salmonids/frogs/newts; aquatic pursuit. |
| robin | [Erithacus rubecula](https://en.wikipedia.org/wiki/Erithacus_rubecula) | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/robin): earthworms, seeds, fruit; hopping/flight. |
| blackbird | [Turdus merula](https://en.wikipedia.org/wiki/Turdus_merula) | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/blackbird): earthworms and seasonal fruit; ground feeding. |
| blue_tit | [Cyanistes caeruleus](https://en.wikipedia.org/wiki/Cyanistes_caeruleus) | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/blue-tit): seeds/nuts/berries; caterpillars are described but absent from simulated food supply. |
| barn_owl | [Tyto alba](https://en.wikipedia.org/wiki/Tyto_alba) | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/barn-owl): finite voles/mice, low hunting flight; no adult-rabbit edge. |
| mallard | [Anas platyrhynchos](https://en.wikipedia.org/wiki/Anas_platyrhynchos) | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/mallard): seeds/berries/plants; water-associated. |
| common_frog | [Rana temporaria](https://en.wikipedia.org/wiki/Rana_temporaria) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/amphibians/common-frog): invertebrates; only registered earthworms modeled; moist habitat/winter shelter. |
| smooth_newt | [Lissotriton vulgaris](https://en.wikipedia.org/wiki/Lissotriton_vulgaris) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/amphibians/smooth-newt): land worms, nocturnal moist habitat; aquatic prey stages unimplemented. |
| brown_trout | [Salmo trutta](https://en.wikipedia.org/wiki/Salmo_trutta) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/freshwater-fish/brown-trout): larvae/small fish/flying insects; no phantom biomass, saved trout are finite otter prey. |
| bumblebee | [Bombus terrestris](https://en.wikipedia.org/wiki/Bombus_terrestris) | [Wildlife Trust](https://www.wildlifetrusts.org/wildlife-explorer/invertebrates/bees-and-wasps/buff-tailed-bumblebee): legitimate nectar/pollen at active flowers; shared pollination/nectar transaction, not seed dispersal. |
| earthworm | [Lumbricus terrestris](https://en.wikipedia.org/wiki/Lumbricus_terrestris) | Natural dead leaves/mineral soil; moist nocturnal surface motion, finite litter debit. Wild lifespan unknown; captive six-year record is not a death deadline. |
Lifecycle's separately attributed numerical references also include
[ADW rabbit](https://animaldiversity.org/accounts/Oryctolagus_cuniculus/),
[ADW squirrel](https://animaldiversity.org/accounts/Sciurus_vulgaris/),
[ADW wood mouse](https://animaldiversity.org/accounts/Apodemus_sylvaticus/) and
[Woodland Trust bank vole](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/animals/mammals/bank-vole/).
Those evidence values retain field-level confidence; unsupported fields stay unknown.
## Focused acceptance
Standalone catalogue and wildlife tests use injected clocks and strict expected
named-test totals; no native engine, background game instance or offline event
replay is involved. They cover catalogue identity/evidence/copies, real-day age,
unknown breeding denial, seasons, JSON hostile payloads and roundtrip, all caps,
startup restore before players, all modeled trophic classes, single-use prey,
feeding cooldowns, protected source/destination stencils, shared floral/fruit
budgets, finite litter/nut authority, velocity bounds/no teleport, runtime
animation calls, cliff/water/unloaded refusal, and scheduler work caps.
The wildlife suite now defines **57 named checks**, including the movement,
activity, dormancy, browse-mode and sourced breeding regressions. These changes
were not executed in this pass. No native engine, full-repository test
run, real-world save or art files are changed. These tests validate mocked
runtime contracts, not native mapblock persistence, rendered young animals,
engine crash recovery or biological completeness; native integration remains
the main agent's responsibility.
+228
View File
@@ -0,0 +1,228 @@
# Calendar lifecycle foundation
## Scope and status
[The policy module](../mods/hearth/lifecycle.lua) returns a **plain, pure Lua namespace**. It does not load itself into `hearth`, call an engine API, read the clock, seed a global RNG, register plants/entities/callbacks, or write metadata, inventories, storage or files. It has no dependencies. [Content integration](../mods/hearth/content.lua) explicitly assigns its return to `hearth.lifecycle` **before botany, ecology and succession are constructed**. **No engine was launched for this integration pass.**
This is **not complete biological simulation**. [Current succession](../mods/hearth/succession.lua) now separates persistent calendar root ages from bounded loaded work. [Current wildlife](../mods/hearth/wildlife.lua) registers 23 animated animal identities with persisted epoch ages; it loads the fixed [fauna factory](../mods/hearth/fauna.lua) when needed. Content publishes that **exact returned catalogue** as `hearth.fauna`, not another independent instance. Six evidence-backed profiles enable guarded reproduction; other incomplete profiles remain disabled. There are no accelerated adult births, lifespan-endpoint deaths, force-loaded simulations or replayed offline meals/seeds/births. Movement, nutrition and timing fallbacks remain deliberately conservative game policies, not a true-life certification.
The runtime catalogue has **100 botanical taxa** and **23 animal identity slots**, including unresolved generic `deer`. Botanical groups are intentionally uneven: **13 trees, 14 shrubs, 10 grasses, 22 herbs, 11 ferns/mosses, 10 fungi, 10 aquatic taxa and 10 lichens**. The twenty additions extend actual ecological roles rather than padding every category to ten. **Every profile now has `pending=false`, reflecting actual registration, not evidence confidence.** The original ten trees and twenty additions carry Latin names in lifecycle; remaining identities come from botany, not invented alternative taxonomy. Actual cross-module identities and all tree-stage IDs, including multi-underscore names, are covered by the focused content/botany tests.
## Units, stages and uncertainty
- `SECONDS_PER_DAY = 86400`: one real elapsed day is always 86,400 seconds. No speed multiplier, growing-season compression, nutrient acceleration or session-time substitution exists.
- `DAYS_PER_YEAR = 365.2425` is a mean Gregorian-year conversion convention, **not a biological constant or a calendar-anniversary calculator**. Month gates take an explicit UK/NW-European month from the caller; no timezone or hemisphere is inferred.
- Every duration is either **`false` (unknown/not applicable)** or a range `{min, max, confidence, source_url, note, statistic}` measured in days. `birth_count` has the same range shape but represents counts, not days. Unknown is never represented by zero or infinity.
- `germination_days` is the policy age at emergence from seed/propagule. It includes a broad waiting allowance in fallback profiles, but is not a measured stratification model. Spores, vegetative recruitment, seed banks and dormancy need species-specific treatment later.
- `juvenile_days` is the **absolute age threshold ending early dependency/seedling establishment**, not a duration to add after germination. Animal notes identify weaning or independence, which are not reproductive maturity.
- `maturity_days` is absolute reproductive-age policy, not full height, timber harvest age, fledging, metamorphosis or an entitlement to breed. Endpoints sometimes span different sexes or different milestones; read `statistic` and `note`.
- `lifespan_days` is contextual longevity information: an average, typical range, or upper-record envelope. It is **never a mandatory death deadline**, a survival curve, or proof that an organism of that age is dead. Colony/genet ages are not stem or fruitbody ages.
- `life_cycle` is `annual`, `biennial`, `perennial`, or `not_applicable`. Most group defaults are explicitly **unverified perennial policy**, not verified species habit. Fungi have `not_applicable`; fern/moss/lichen propagule stages are deliberately coarse. Foxglove and garlic-mustard second-season flowering have evidence. Teasel's biennial representation remains a labelled fallback.
- All profiles, nested ranges/evidence, lists and resolved selections are fresh copies. Altering a returned profile never changes the module's catalogue.
`confidence = partial_species_evidence` means **some** fields have evidence, not that every number is researched. Check each range's own confidence and each `evidence[field]`. `source_reported` means the referenced page actually reported the observation reviewed on **2026-11-03**, not scientific consensus, an exact constant, or independent primary-literature validation. `policy_fallback` means an unverified modelling window. A source on a profile does not validate its fallback germination/maturity numbers. `source_url = false` is intentional where no supporting source is claimed.
### Unverified group baselines
These are broad, non-accelerated **policy placeholders**, not sourced species ranges. They should not authorize reproductive events or automatic mortality. `d` = real days; `y` = mean Gregorian years.
| Group | Germination/establishment | Seedling/dependency end | Maturity | Longevity context |
| --- | --- | --- | --- | --- |
| trees | 14–365 d | 1–5 y | 10–60 y | 60–500 y |
| shrubs | 14–365 d | 1–2 y | 2–10 y | 5–80 y |
| grasses | 7–90 d | 30–90 d | 90–365 d | 2–10 y |
| herbs | 7–90 d | 90–365 d | 365–730 d | 2–10 y |
| ferns/mosses | 30–730 d | 1–2 y | 2–5 y | unknown |
| fungi | unknown | unknown | unknown | unknown |
| aquatic | 7–90 d | 90–365 d | 365–730 d | unknown |
| lichens | unknown | 1–5 y | unknown | unknown |
No uniform group window can describe all species, propagules, sites or genets correctly. In particular these windows do not turn water-starwort into a verified perennial, a fungal fruitbody into a seedling, or all lichens into organisms with a known sexual maturity. Species-specific verification is still needed.
### Ten tree profiles with species-reference review, plus three fallback trees
The playable tree order now contains thirteen IDs. `crab_apple`, `wild_cherry` and `field_maple` use the labelled group baseline (14–365 days emergence, 1–5 years seedling/dependency, 10–60 years maturity, 60–500 years longevity context), **not species-researched numeric timings**. The ten profiles below retain their separately reviewed evidence. An identity source does not upgrade a fallback threshold.
All ten inherit the fallback germination and seedling windows above. **Only oak, beech and rowan currently have accepted source-reported reproductive-age fields.** All other maturity windows below are explicitly unverified policy; do not describe the example birch/alder ages as verified facts.
| ID / species | Maturity window, years | Longevity context, years | Actual reviewed evidence and limits |
| --- | --- | --- | --- |
| `oak` / *Quercus robur* | around 40 | 500–1000 envelope | [Forestry England](https://www.forestryengland.uk/article/english-oak) reports acorns usually around 40; [Wikipedia](https://en.wikipedia.org/wiki/Quercus_robur) describes survivors exceeding 500 and some exceeding 1000. Neither is a death schedule. |
| `birch` / *Betula pendula* | **fallback 10–20** | typical 60–90 | [Trees for Life](https://treesforlife.org.uk/into-the-forest/trees-plants-animals/trees/birch/birch-facts/) covers both silver and downy birch and notes exceptional ages of 150 or more. No numeric maturity accepted. |
| `pine` / *Pinus sylvestris* | **fallback 15–30** | typical 150–300 | [Wikipedia](https://en.wikipedia.org/wiki/Pinus_sylvestris) distinguishes normal longevity from much older records. Cone ripening time is not tree reproductive age. |
| `willow` / *Salix alba* | **fallback 5–15** | **fallback 60–120** | [Wikipedia](https://en.wikipedia.org/wiki/Salix_alba) supports fast growth, comparatively short life and dioecy; the numerical windows here are not sourced. |
| `alder` / *Alnus glutinosa* | **fallback 10–20** | composite 60–160 | [Woodland Trust](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/british-trees/a-z-of-british-trees/alder/) says approximately 60; [Wikipedia](https://en.wikipedia.org/wiki/Alnus_glutinosa) reports up to 160. This is a composite envelope, not a measured typical range or a universal 100-year age. |
| `beech` / *Fagus sylvatica* | about 10 onset to 30 heavy crop | 300–500 envelope | [Wikipedia](https://en.wikipedia.org/wiki/Fagus_sylvatica) distinguishes small early seed quantities from heavy crops and undisturbed longevity from exceptional individuals. |
| `aspen` / *Populus tremula* | **fallback 10–20** | **fallback envelope 80–200** | [Wikipedia](https://en.wikipedia.org/wiki/Populus_tremula) supports upper longevity 200, but not the chosen lower endpoint. It also describes suckering and growth-rate changes, not a verified reproductive threshold. |
| `rowan` / *Sorbus aucuparia* | around 10 | 80–150 envelope | [Wikipedia](https://en.wikipedia.org/wiki/Sorbus_aucuparia) reports maturity around ten, often no older than 80 in warmer regions, with occasional 150-year trees. |
| `hazel` / *Corylus avellana* | **fallback 3–10** | **fallback envelope 60–80** | [Woodland Trust](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/british-trees/a-z-of-british-trees/hazel/) supports uncoppiced upper age 80, not lower age 60. Coppice stools can persist centuries; do not apply stem ages to a stool. |
| `spruce` / *Picea abies* | **fallback 20–40** | **fallback envelope 200–532** | [Wikipedia](https://en.wikipedia.org/wiki/Picea_abies) records a ring-dated 532-year individual, not a typical 200–532 lifespan. Millennia-old clonal roots are not ages for a new stem. |
Oak's 40-year approximate onset is represented as `{min=40y,max=40y}` to retain the source's single approximate value, not manufacture a research range. It is still an approximate observation. A real reproduction engine must consider location, weather, health, sex, pollination and seed production separately.
### Twenty added plant identities (historical policy contract)
| ID | Exact requested Latin name | Policy group |
| --- | --- | --- |
| `dandelion` | *Taraxacum officinale* | herbs |
| `daisy` | *Bellis perennis* | herbs |
| `red_clover` | *Trifolium pratense* | herbs |
| `birdsfoot_trefoil` | *Lotus corniculatus* | herbs |
| `ribwort_plantain` | *Plantago lanceolata* | herbs |
| `selfheal` | *Prunella vulgaris* | herbs |
| `knapweed` | *Centaurea nigra* | herbs |
| `oxeye_daisy` | *Leucanthemum vulgare* | herbs |
| `field_scabious` | *Knautia arvensis* | herbs |
| `teasel` | *Dipsacus fullonum* | herbs |
| `cow_parsley` | *Anthriscus sylvestris* | herbs |
| `garlic_mustard` | *Alliaria petiolata* | herbs |
| `honeysuckle` | *Lonicera periclymenum* | shrubs |
| `ivy` | *Hedera helix* | shrubs |
| `gorse` | *Ulex europaeus* | shrubs |
| `broom` | *Cytisus scoparius* | shrubs |
| `crab_apple` | *Malus sylvestris* | trees |
| `wild_cherry` | *Prunus avium* | trees |
| `field_maple` | *Acer campestre* | trees |
| `holly_fern` | *Polystichum setiferum* | ferns_mosses |
The ID `holly_fern` is retained exactly as requested; *Polystichum setiferum* is generally called soft shield-fern, so this ID must not be used to infer a different Latin species. These group assignments are policy categories, not research claims about precise growth rates. The new twenty are **not all fully researched**. [Garlic mustard](https://www.wildlifetrusts.org/wildlife-explorer/wildflowers/garlic-mustard) has a verified two-season habit with retained winter leaves; its numeric 365–730 day window remains policy, since second spring is not exactly day 365. [Existing foxglove](https://www.wildlifetrusts.org/wildlife-explorer/wildflowers/foxglove) likewise has second-year flower stems. [Teasel](https://www.wildlifetrusts.org/wildlife-explorer/wildflowers/teasel) supports seasonal flowers/seedheads, but that page was not accepted as numerical biennial timing evidence.
## Animal research coverage
The following are real species pages retrieved during implementation, not constructed citation placeholders. Each accepted diet/movement or numeric field retains its own URL and original short evidence note. Average lifespans below are **source-reported averages**, including sources that use a range for that label. They are not hard limits.
| ID / species | Reported average years | Accepted evidence / remaining limits |
| --- | --- | --- |
| `rabbit` / *Oryctolagus cuniculus* | 3 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/rabbit): grazing, warrens, 3–7 young. [ADW](https://animaldiversity.org/accounts/Oryctolagus_cuniculus/): gestation 30–37 d; weaning 22–31 d; year-round capability. **Maturity unknown:** eight-month prose conflicts with an embedded 730-day female value. Thirty days is gestation, not maturity. |
| `red_squirrel` / *Sciurus vulgaris* | 6 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/red-squirrel): tree movement, nuts/seeds, no hibernation. [ADW](https://animaldiversity.org/accounts/Sciurus_vulgaris/): 38–39 d gestation, 56–70 d independence, 296–365.2425 d composite maturity envelope; narratives/sex entries differ. Birth/litter months were **not** converted into conception months. |
| `bank_vole` / *Myodes glareolus* | 0.5–1.5 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/bank-vole): 3–5 young, fruit/nuts/insects. [Woodland Trust](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/animals/mammals/bank-vole/): about 25 d weaning; female maturity six weeks, male eight (42–56 d sex envelope). Gestation unknown here. |
| `wood_mouse` / *Apodemus sylvaticus* | 1 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/wood-mouse): nocturnal climbing/caching, 4–8 young. [ADW](https://animaldiversity.org/accounts/Apodemus_sylvaticus/): 21–26 d gestation; 21 d weaning; 65/71 d male/female maturity entries. Breeding-month endpoint “early winter” is not encoded as an invented exact month. |
| `hedgehog` / *Erinaceus europaeus* | 2–3 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/european-hedgehog): nocturnal invertebrate feeding, winter hibernation; reproductive timing unknown. |
| `badger` / *Meles meles* | 5–8 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/european-badger): digging, setts, varied diet. January/February births do not establish conception/gestation timing; those fields remain unknown. |
| `roe_deer` / *Capreolus capreolus* | 7 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/roe-deer): July–August mating, delayed implantation, May–June births, 2–3 kids. Numeric maturity/gestation unknown. |
| `stoat` / *Mustela erminea* | 2–5 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/stoat): bounding, small-animal diet, 6–12 kits, summer mating followed by delayed implantation. No invented fixed gestation or exact summer months. |
| `weasel` / *Mustela nivalis* | 2 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/weasel): low running gait, rodent/bird diet, 3–6 kits. Numeric reproductive ages unknown. |
| `otter` / *Lutra lutra* | 5–10 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/mammals/european-otter): swimming, fish and aquatic prey. Young entering water at ten weeks is **not** reproductive maturity. |
| `robin` / *Erithacus rubecula* | unknown | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/robin): territorial resident behaviour, invertebrates/fruit, about 4–5 eggs. “A few years” and record ages were not turned into invented average bounds. |
| `blackbird` / *Turdus merula* | 3 | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/blackbird): ground feeding, 3–4 eggs, UK nesting March–August. Incubation/maturity not supplied by the reviewed page. |
| `blue_tit` / *Cyanistes caeruleus* | 3 | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/blue-tit): clinging/flight, caterpillars/seeds/nuts, UK nesting April–June. “Up to 16” does not establish a lower clutch bound, so no fabricated clutch range. |
| `barn_owl` / *Tyto alba* | unknown | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/barn-owl): low silent hunting flight and small-mammal prey; no accepted numerical lifecycle fields. |
| `mallard` / *Anas platyrhynchos* | unknown | [RSPB](https://www.rspb.org.uk/birds-and-wildlife/mallard): swimming/flight, varied diet, weather-dependent breeding. A February start and “up to 13” ducklings do not supply a complete breeding window or clutch range. |
| `common_frog` / *Rana temporaria* | 5–10 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/amphibians/common-frog): pond spawning, hopping, winter shelter; about 14 weeks **after hatch** to froglet (`larval_days=98`). Not incubation or sexual maturity. |
| `smooth_newt` / *Lissotriton vulgaris* | 6 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/amphibians/smooth-newt): aquatic breeding, nocturnal land feeding, winter shelter; precise reproductive timing unknown. |
| `brown_trout` / *Salmo trutta* | 15–20 | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/freshwater-fish/brown-trout): river swimming, insect/fish diet, external eggs in gravel. Its January–March statement was **not generalized** into a universal salmonid breeding window; local spawning/degree-day data still needed. |
| `bumblebee` / *Bombus terrestris* | unknown | [Wildlife Trusts](https://www.wildlifetrusts.org/wildlife-explorer/invertebrates/bees-and-wasps/buff-tailed-bumblebee): nectar/pollen, flight, underground colony, possible winter activity. ID explicitly models this species, not every bumblebee. Caste-specific life history is required; no single queen/worker lifespan is invented. |
| `earthworm` / *Lumbricus terrestris* | **wild lifespan unknown** | [Wikipedia](https://en.wikipedia.org/wiki/Lumbricus_terrestris): paired hermaphroditic cocoon reproduction, leaf litter and vertical burrows; at least one-year development with reproductive maturity in second year. Six years in captivity is not a wild lifespan. Generic earthworm ID explicitly represents this species only. |
[Existing `hare`](https://www.wildlifetrusts.org/wildlife-explorer/mammals/brown-hare) provisionally represents *Lepus europaeus*: 2–4 reported average years, grazing/bark feeding, surface forms, 2–4 leverets. [Existing `fox`](https://www.wildlifetrusts.org/wildlife-explorer/mammals/red-fox) represents *Vulpes vulpes*: 2–3 reported average years, omnivorous diet and territorial/nocturnal behaviour. **Existing generic `deer` is not silently mapped to `roe_deer`:** Latin identity and every timing field remain unknown.
The table above records the initial research baseline; **the reproductive additions below supersede its unknown gestation/incubation/maturity statements where explicitly listed**. No precise meal frequency or species metabolic model is claimed. Qualitative diet/movement descriptions do not establish numeric physiology.
### Reproductive implementation update (2026-11-03)
Six profiles now set `reproduction_enabled=true`: **bank vole, wood mouse,
blackbird, mallard, European hare and red fox**. All other 17 remain disabled,
not silently completed.
- [European hare ADW](https://animaldiversity.org/accounts/Lepus_europaeus/): maturity 8 months to one year, 30-42 d gestation, about one-month weaning, 1-8 leverets, and breeding from midwinter through midsummer. Runtime uses the conservative 365 d maturity, 42 d gestation and one young; Winter/Spring/Summer is a coarse northern-season mapping and the 30 d care lock is policy.
- [Red fox ADW](https://animaldiversity.org/accounts/Vulpes_vulpes/): about 304 d maturity, typical 51-53 d gestation, 56-70 d nursing/weaning and winter mating in central-range populations. The account's prose count (1-13) differs from its summary table (1-9); runtime uses the narrower table and one pup. A 300 d postpartum lock approximates once-yearly breeding, as a policy. Paternal provisioning and den care are not simulated by the generic dependent-young model.
The [source and implementation register](fauna.md#time-provenance-and-deliberately-unknown-biology)
details values, source disagreements and policy limits.
- [Bank vole ADW](https://animaldiversity.org/accounts/Myodes_glareolus/): 17-24 d gestation,
20-25 d weaning, late April-September season. Existing 42-56 d maturity and 3-5
litter range retain their own Woodland Trust/Wildlife Trusts citations. Runtime
conservatively uses 56 d maturity, 24 d development, three young, 25 d care lock.
- [Wood mouse Wikipedia](https://en.wikipedia.org/wiki/Wood_mouse): February-October
breeding; this differs from ADW's March-early winter. Existing ADW 21-26 d
gestation, 65-71 d maturity and 21 d dependency remain independently attributed;
Wildlife Trusts 4-8 count is retained. Runtime uses 71 d, 26 d, four young and
a 21 d care lock, not the differing Wikipedia gestation range.
- [Blackbird ADW](https://animaldiversity.org/accounts/Turdus_merula/): first breeding
about one year; 13-day incubation prose versus 14-day summary; nest departure
13-14 d, not full independence. Runtime uses one mean Gregorian year and 14 d
incubation; existing RSPB count yields three young. Bird care lock is a labelled
conservative 42 d policy, not a measured reproductive interval.
- [Mallard ADW](https://animaldiversity.org/accounts/Anas_platyrhynchos/): 365 d maturity
both sexes, 26-28 d incubation, 9-13 eggs. [Wikipedia](https://en.wikipedia.org/wiki/Mallard)
supplies 50-60 d to flight and spring nesting. Runtime uses 365 d, 28 d, nine
young; conservative care lock 81 d. Unknown lifespan is **not** a breeding veto.
- [Blue tit BTO diary](https://www.bto.org/understanding-birds/articles/blue-tit-diary):
majority 8-10 eggs, about 14 d incubation, 18-21 d fledging, first breeding the
following spring. Exact maturity day and caterpillar provisioning remain
unresolved, so reproduction stays disabled. ADW's Cyanistes page was missing;
Parus yielded taxonomy only. BTO BirdFacts extraction redirected to an audio
embed; no numerical claim is attributed to that failed extraction.
- [Common frog Wikipedia](https://en.wikipedia.org/wiki/Common_frog): about three-year
sexual maturity, 14-21 d egg incubation. Its 98 d post-hatch larval observation
remains distinct. Eggs/tadpoles/feeding are not represented: still disabled.
- [Brown trout Wikipedia](https://en.wikipedia.org/wiki/Brown_trout): approximately
2000 eggs/kg, not a fixed litter; female mass, gravel redd and temperature-based
development are absent. [Bumblebee](https://en.wikipedia.org/wiki/Bombus_terrestris)
needs caste-specific colony/larva/pupa care. Both stay disabled.
Every added numeric range has field-level source evidence. Spring/Summer breeding
seasons, conservative upper endpoints, minimum litter counts, reserve costs and
postpartum locks are explicitly **simulation policies**, not new source-reported
physiological precision. `fauna.can_breed` uses `hearth.climate_at(utc).season` or
an explicit season, so southern spring is not vetoed by northern reference months.
Unknown climate denies conception. Reproduction requires two mature, healthy,
recently fed, same-species opposite-sex animals, real loaded food-bearing habitat
and space. Lifespan completeness is intentionally unnecessary.
Wildlife persists one pregnancy/incubation and its partial-delivery counters.
Overdue broods can finish once, after safe loaded habitat/caps/energy permit;
there is no offline replay of missed conceptions or multiple litters. One young
per additional AI action shares the 24-action budget. Newborn age starts at actual
admission, never at an invented adult cohort age. Dependent young rest and can
receive debited maternal reserve. This does not claim complete nest, lactation,
species-specific chick diet, mortality, genetic parentage or crash-atomic saving.
Sources were fetched and read, including failed/empty candidates. USDA FEIS requests returned HTTP 403; several guessed species/older bird subpages were missing or unextractable. **Those failures are not citations and contributed no numerical overrides.** Wikipedia and ADW are secondary educational accounts with their own limitations; no downloaded article text or images are bundled here, only original notes and factual references. ADW's rabbit conflict is explicitly retained as an uncertainty, not resolved by cherry-picking a maturity number.
## Exact API for the main integration owner
Content now loads the fixed sibling with `dofile` and explicitly assigns its returned table to `hearth.lifecycle` before constructing any biological consumer. There is no factory argument and no implicit global publication. Wildlife's returned `fauna` is exposed unchanged. `/botany` displays the 100 taxa over ten pages; `/fauna` is an independent observational catalogue with identities, source URLs, field confidence and UNKNOWN values. The plant-only article reader is unchanged.
| Call | Return / contract |
| --- | --- |
| `plant(id, group?)` | Fresh plant profile or `nil, reason`. Bare exact ID only; optional group must match the fixed snapshot. Unknown ID plus a group hint **does not invent a species**. Canonicalize node/stage/season names through botany before calling. |
| `animal(id)` | Fresh animal profile or `nil, reason`. Exact bare ID; no `deer` → `roe_deer` alias. |
| `ids("plant" or "animal")` | Fresh sorted ID list (100 / 23); invalid kind returns `nil, "kind"`. |
| `duration(range, seed, salt)` | Deterministic number in inclusive range, in the range's units. Seed integer 0–2147483646; salt string at most 64 bytes; finite nonnegative bounds with `max <= 1000000`. Invalid inputs return `nil, reason`. Selection is continuous, not a claim of a measured uniform distribution. |
| `resolve(profile, seed)` | Fresh profile retaining original ranges/provenance, plus `selected_days`, `selection_seed`. Uses species/field salts; overlapping germination/dependency/maturity/lifespan selections are clamped upward to preserve stage order, never outside a range. Invalid/impossible ordering returns `nil, reason`. Does not sample counts or month lists. |
| `elapsed(previous, now, work_cap_seconds)` | `{elapsed_seconds, work_seconds, deferred_seconds}`; **full** calendar delta plus a separate capped work allowance. No age is discarded. Cap zero is valid. Invalid inputs return `nil, "clock"` or `nil, "cap"`; reversal returns `nil, "clock_reversed"`. Does not loop through missed events. |
| `phase(profile, born_at, now)` | Observation `{phase, age_days, age_seconds, beyond_lifespan_range, death_due=false, reproductive_ready=false, confidence}` or `nil, reason`. All times are caller-supplied epoch seconds; no clock reads. |
| `seasonal(profile, season)` | `{dormant, death_due=false, confidence="policy_fallback"}` for explicit `Spring`, `Summer`, `Autumn`, `Winter`. Winter dormancy is presentation policy, not a frost/survival calculation; some evergreen-policy profiles retain foliage. |
| `breeding_window(profile, month)` | `{known, within_window, eligible=false}`. Numeric integer month 1–12 only. Unknown window is **not** all-year permission. Known windows here are rabbit capability, roe-deer mating, blackbird nesting and blue-tit nesting. No births are scheduled. |
Clock inputs must be finite numeric seconds in `[0, 9007199254740991]`; strings, NaN, infinities, negatives and larger values fail closed. Fractional seconds are allowed. Equality has zero elapsed age. `now < born_at` and reversed elapsed clocks are explicit errors, not a silent age reset. Callers must also detect reversals relative to a saved `last_observed_at`: this stateless module cannot remember a previously observed future timestamp. Very large valid ages are handled with constant work, not an age-proportional replay loop.
Without resolution, `phase` uses each range's **upper endpoint**; it never redraws durations while reading. Resolved samples can be persisted by the main owner with the policy version so later reads are reproducible. Stages are `seed`, `seedling`, `dependent`, `juvenile`, `mature`, `unknown`. A known dependency threshold can classify an animal as `dependent` even when its sexual maturity is unknown; after dependency ends its phase is `unknown` until a maturity field exists. `beyond_lifespan_range` is informational, never `dead`. Six explicitly enabled animal profiles authorize wildlife reproduction, subject to runtime pairing/resource/season/space checks. **Do not bypass that gate by reading `phase == "mature"`.** The pure observation still returns `reproductive_ready=false` because it cannot know those runtime conditions.
### Runtime persistence, resource transactions and remaining boundaries
1. **Root epochs:** succession persists `succ_birth_at`, separate `succ_initial_age_seconds`, `succ_last_observed_at`, `succ_lifecycle_version=1`, exact profile/seed/origin and `calendar_v1_conservative_max` policy. Natural recruits start at zero. Stage swaps retain the root anchor; components refer to that root. The full contract is in [botany](botany.md#root-persistence-and-migration).
2. **Established assumptions:** legacy natural roots adopt `legacy_established` at the current epoch with explicit stage-based initial age; worldgen stands use `worldgen_established`. These are documented generation/adoption assumptions, not historically observed ages. `succ_age` is not reinterpreted as years or a birth epoch. Unknown thresholds do not invent age.
3. **Full age, bounded work:** calendar age includes the entire unloaded interval. Reconciliation is limited to loaded, eligible natural roots/entities; no force loading, whole-world scan, missed-event queue or offline replay. Invalid metadata/versions, future clocks and rollback fail closed. Cold/dormancy can defer visible stages, never subtract age.
4. **Uncertainty:** source-reported ranges carry field evidence; fallback ranges remain unverified policy; false/not-applicable remains UNKNOWN. Succession uses conservative upper endpoints, not nutrient acceleration. Named trees require their real year/decade thresholds; fungi do not shorten seedling age by 25%. Unknown fungal/lichen maturity does not authorize seeding. Nitrogen-fixer eligibility starts at 90 calendar days; saprotrophic decay bonuses are finite and conserve substrate nutrients.
5. **Shared fruit:** humans and animals debit exactly `botany_ready_at`. A successful botanical harvest locks for `365.2425 * 86400` seconds, not 24 hours; a valid fruit season is still required. No accumulated missed-year payout. The initial existing crop is usable. This is conservative annual resource policy, not researched species phenology or organism lifespan. Animal feeding discards the rest of the batch and yields no inventory.
6. **Pollination credit:** succession reserves `succ_nectar_ready_at = now + 3600` and at most one `succ_pollinated_until = now + 86400` credit, not a stacking counter. Human harvest accepts only finite nonnegative pollen expiry at most one day ahead; a credit is live exactly when `expiry > now`. It adds **one** berry to the existing two-berry crop, never doubles inventory, bypasses fruit readiness or creates seeds. Both budgets are reserved before inventory callbacks. No-space/full-leftover rejection restores both original values; any successful partial transfer spends the batch and credit once. Invalid delivery results retain the lock to prevent unsafe retries.
7. **Fauna:** saved version-1 JSON retains epoch `born_at`, explicit `origin`, sex/seed and observation high-water clocks plus bounded reproductive counters. Establishment is immigration, not an adult birth. Unload advances age but creates no meals, dung or missed mating cycles. An already-persisted overdue brood may finish once on loaded budgeted ticks. Six profiles breed; nests, aquatic larvae, caste/cocoon development and delayed implantation remain incomplete. Numeric mean/record longevity is never an automatic death deadline. Hedgehog/frog/newt winter dormancy is a retained-resident policy, not death or a physiological metabolism model. See [fauna limits and animations](fauna.md).
8. **Seasonal presentation:** winter textures are not death evidence. Biennial second-season flowering, perennial dormancy, annual seed completion and colony/fruitbody age require distinct policies. No complete climate-survival, migration, historical degree-day or mortality model is claimed. No version bump, existing-world launch or save rewrite was performed for this integration.
No credentials, embedded certificates, runtime HTTP, dynamic evaluation or security-sensitive random identifiers are introduced. The deterministic selector is explicitly **not cryptography**. Defensive input bounds and copied read results are the relevant CodeGuard protections here.
## Focused validation
The earlier content integration ran the focused [content](../tests/content_spec.lua), [botany](../tests/botany_spec.lua), [journal/recipe](../tests/recipe_spec.lua), [character](../tests/character_spec.lua) and [species-effects](../tests/species_effects_spec.lua) suites. Checks use actual sibling factories, exact catalogue/schema counts and known UTC epochs. Require exact named counts, final markers and empty stderr: Fengari exit status alone can mask assertion failures. No engine, world, wiki-cache or art-generator changes are part of this pass. Native rendering, skeletal animation acceptance and full integrated validation remain with the main owner. The [lifecycle suite](../tests/lifecycle_spec.lua) now checks the actual 100-entry catalogue, all 23 registration flags and the updated reproductive profiles; all original 31 named contracts are retained and reconciled, not dropped.
Earlier content-integration results: **40 content, 44 botany, 52 journal/recipe, 19 character and 8 species-effects checks (163 total)**. These are historical results, not rerun by this lifecycle change.
Updated specs now define **32 lifecycle, 10 fauna catalogue and 57 wildlife
checks (99 total)**. They were not executed in this pass; prior focused runs
are historical only. Earlier baseline notes reported **31 lifecycle, 9 fauna
catalogue and 31 wildlife checks**, plus 85 succession checks. Tests inject UTC epochs instead of
waiting for real gestation or accelerating ages. Coverage includes funded
pairing, denied juvenile/same-sex/unhealthy/missing mates, incubation, overdue
restoration, capped partial litter restart, unsafe/no-player delivery delays,
hostile pregnancy fields, callback reentry, dependent care and postpartum locks.
No full-engine or full-repository test run is claimed.
+38 -9
View File
@@ -2,16 +2,43 @@
## Shipped source coverage
The bundle follows **all 80 actual Latin names** in [the botany catalogue](../mods/hearth/botany.lua), not their sometimes ambiguous common names. The maintenance run on **2026-11-03 UTC** queried all 80 names using eight MediaWiki API batches of ten, with at most two requests in flight.
The bundle follows **all 100 actual Latin names** in [the botany catalogue](../mods/hearth/botany.lua), not their sometimes ambiguous common names. The approved maintenance `--refresh` run on **2026-11-03 UTC** queried all 100 names using ten MediaWiki API batches of ten, with at most two requests in flight, using the absolute installed Node executable and only the hardcoded English Wikipedia API URL.
**78 entries contain real English Wikipedia plaintext introductions. Two entries contain original availability notes, not Wikipedia quotations:**
**98 entries contain real English Wikipedia plaintext introductions (78 previous + 20 new). Two entries contain original availability notes, not Wikipedia quotations:**
| Game ID | Exact catalogue Latin name | Source status |
| --- | --- | --- |
| `old_mans_beard` | *Usnea subfloridana* | [Exact species page](https://en.wikipedia.org/wiki/Usnea_subfloridana) missing |
| `cup_lichen` | *Cladonia chlorophaea* | [Exact species page](https://en.wikipedia.org/wiki/Cladonia_chlorophaea) missing |
For both missing species, the API explicitly returned `missing: true`; separate exact-page and REST-summary checks returned HTTP 404. No genus, species complex, similarly named lichen, invented revision, or synthetic Wikipedia prose was substituted. Consequently **80 actual species article excerpts are blocked by source availability**. The original notes make this visible to offline players. Refresh can pick up an article if Wikipedia creates one later.
For both missing species, the refreshed API explicitly returned `missing: true`; separate exact-page and REST-summary checks in the previous 80-species maintenance run returned HTTP 404. No genus, species complex, similarly named lichen, invented revision, or synthetic Wikipedia prose was substituted. Consequently **100 actual species article excerpts are blocked by source availability**. The original notes make this visible to offline players. Refresh can pick up an article if Wikipedia creates one later. All previous 78 introduction texts and two availability-note texts were preserved exactly (verified by matching SHA-256 over ordered ID/text/kind records before and after refresh); retrieval timestamps were refreshed from actual batch completion times.
### Twenty newly cached species references
All twenty new exact catalogue names resolved directly to the same Wikipedia title, without guessed aliases. Each entry contains an actual plaintext introduction, positive page/revision IDs, the API revision timestamp, actual retrieval timestamp, contributor attribution/history, and CC BY-SA 4.0 licensing. Retrieval occurred between **2026-11-03T19:54:51.978Z** and **2026-11-03T19:54:52.400Z**; per-entry timestamps are retained in the bundle. None of these additions required an availability note or shortening.
| Game ID | Exact catalogue Latin name / Wikipedia source title | Page ID | Permanent source revision |
| --- | --- | --- | --- |
| `crab_apple` | *Malus sylvestris* | 5958504 | [1380105044](https://en.wikipedia.org/w/index.php?oldid=1380105044) |
| `wild_cherry` | *Prunus avium* | 28331736 | [1380998001](https://en.wikipedia.org/w/index.php?oldid=1380998001) |
| `field_maple` | *Acer campestre* | 437923 | [1366038360](https://en.wikipedia.org/w/index.php?oldid=1366038360) |
| `honeysuckle` | *Lonicera periclymenum* | 811057 | [1354922261](https://en.wikipedia.org/w/index.php?oldid=1354922261) |
| `ivy` | *Hedera helix* | 5372245 | [1382798111](https://en.wikipedia.org/w/index.php?oldid=1382798111) |
| `gorse` | *Ulex europaeus* | 2482094 | [1382611269](https://en.wikipedia.org/w/index.php?oldid=1382611269) |
| `broom` | *Cytisus scoparius* | 510118 | [1351345770](https://en.wikipedia.org/w/index.php?oldid=1351345770) |
| `dandelion` | *Taraxacum officinale* | 10783290 | [1371025491](https://en.wikipedia.org/w/index.php?oldid=1371025491) |
| `daisy` | *Bellis perennis* | 1157853 | [1375045201](https://en.wikipedia.org/w/index.php?oldid=1375045201) |
| `red_clover` | *Trifolium pratense* | 1900302 | [1371055360](https://en.wikipedia.org/w/index.php?oldid=1371055360) |
| `birdsfoot_trefoil` | *Lotus corniculatus* | 1117322 | [1379332042](https://en.wikipedia.org/w/index.php?oldid=1379332042) |
| `ribwort_plantain` | *Plantago lanceolata* | 1250244 | [1377190915](https://en.wikipedia.org/w/index.php?oldid=1377190915) |
| `selfheal` | *Prunella vulgaris* | 12058348 | [1380124722](https://en.wikipedia.org/w/index.php?oldid=1380124722) |
| `knapweed` | *Centaurea nigra* | 21631605 | [1366977479](https://en.wikipedia.org/w/index.php?oldid=1366977479) |
| `oxeye_daisy` | *Leucanthemum vulgare* | 567709 | [1381123379](https://en.wikipedia.org/w/index.php?oldid=1381123379) |
| `field_scabious` | *Knautia arvensis* | 12057909 | [1371147715](https://en.wikipedia.org/w/index.php?oldid=1371147715) |
| `teasel` | *Dipsacus fullonum* | 4298215 | [1375022541](https://en.wikipedia.org/w/index.php?oldid=1375022541) |
| `cow_parsley` | *Anthriscus sylvestris* | 2649495 | [1375096601](https://en.wikipedia.org/w/index.php?oldid=1375096601) |
| `garlic_mustard` | *Alliaria petiolata* | 509457 | [1375930171](https://en.wikipedia.org/w/index.php?oldid=1375930171) |
| `holly_fern` | *Polystichum setiferum* | 5025872 | [1296456929](https://en.wikipedia.org/w/index.php?oldid=1296456929) |
Five catalogue names resolved through Wikipedia's own title redirects in this snapshot:
@@ -34,7 +61,7 @@ These are API-reported redirects, not guessed mappings or search results. The ca
- `/inspect` observes the first pointed node within **six blocks of the player's eye**, using a bounded ray and `get_node_or_nil`. Unknown or unloaded first surfaces stop inspection; nothing is emerged, force-loaded, harvested, placed or written. Species-specific seasonal plants, tree stems/stages, logs and leaves resolve through the actual `botany.kind()` API. Generic unnamed vegetation is not assigned an invented species.
- `/inspect oak` or `/inspect Quercus robur` opens an exact catalogue ID or canonical Latin name. Matching is exact and case-sensitive (surrounding whitespace is allowed), not fuzzy, common-name search or an arbitrary URL. `/botany [search]` remains the literal-search catalogue browser.
- Field notes offer **Inspect target**. The Botanical guide keeps its ten-row pages and adds a **Species article** dropdown/button for those rows. Selections are bounded numeric indices into the server session's current-page IDs, never submitted IDs or URLs. A HUD hint on known botanical targets advertises `/inspect species` (replace “species” with an ID, or use bare `/inspect` for the target). No harvesting or building callbacks are hijacked.
- The **Article** tab displays **Wikipedia excerpt (offline)** for the 78 real introductions. The two missing species use **No Wikipedia article cached (offline)** and show their original CC0 availability notes, never fake Wikipedia prose or revisions. No HTML, embedded browser or full-article availability is claimed.
- The **Article** tab displays **Wikipedia excerpt (offline)** for the 98 real introductions. The two missing species use **No Wikipedia article cached (offline)** and show their original CC0 availability notes, never fake Wikipedia prose or revisions. No HTML, embedded browser or full-article availability is claimed.
- **Current effects** comes directly from `species_effects.describe(record)`: **SIMULATED** and **DESCRIPTIVE (not simulated)** are separate labels. Reviewed evidence notes and evidence URLs are distinct from snapshot identity provenance. The excerpt may omit the body section supporting an effect; numbers, timings, niche thresholds and budgets remain game policy, not measurements from Wikipedia.
- Body text is limited to **8,000 Unicode code points**, in pages of **4,000**. All current introductions fit one page unchanged (maximum 3,417). Any reader-side cap is disclosed. Unnamed read-only, selectable textareas show source title, attribution, license/link, canonical URL, permanent revision URL, contributor history, revision/retrieval timestamps and changes. Links are copyable plain text, never fetched or automatically opened.
- Closing the reader clears only session navigation. Article/effect reading never changes needs, inventory, node metadata, protection, succession, food permission or persistent state. Submitted article text, source URLs, species IDs and page numbers are ignored; tabs and buttons use validated server-held state. No privileges, credentials, dynamic evaluation or runtime network access are added.
@@ -75,7 +102,7 @@ For an unavailable species, `kind = 'original_notes'`, `excerpt = false`, and `t
- Keep the warning that Wikipedia and game descriptions are **not identification, foraging, food preparation or medical advice**. Wikipedia can describe toxic species, traditional uses, disputed findings, or processing methods; copying its introduction does not endorse them or enable a game effect.
- Preserve Wikipedia attribution and [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) when redistributing or adapting Wikipedia excerpts. Share adaptations under the applicable ShareAlike terms and describe modifications. This source-text license is distinct from the game's original code/art licenses. No images, page HTML or full article bodies are copied.
The shipped 78 introductions have a maximum of **3,417 Unicode code points**; **zero** required additional shortening. Extraction omits article bodies and uses MediaWiki's `exintro=1` / `explaintext=1`; whitespace is trimmed only at the ends. Future overlong introductions are cut on a paragraph, sentence, or word boundary without adding invented continuation text, and set `truncated = true`.
The shipped 98 introductions have a maximum of **3,417 Unicode code points**; **zero** required additional shortening. Extraction omits article bodies and uses MediaWiki's `exintro=1` / `explaintext=1`; whitespace is trimmed only at the ends. Future overlong introductions are cut on a paragraph, sentence, or word boundary without adding invented continuation text, and set `truncated = true`.
## What the real sources support about effects
@@ -98,7 +125,7 @@ Existing game niche thresholds, nutrient caps, timing, protection rules, edibili
[scripts/wiki-species.mjs](../scripts/wiki-species.mjs) uses Node.js 22+ built-ins only. Arguments are deliberately limited to exactly one of:
- `--refresh`: read the fixed botany catalogue, query all 80 Latin names, validate complete results, then replace the fixed Lua output. Explicit missing pages become original availability notes. Print exact source counts and batch bounds.
- `--refresh`: read the fixed botany catalogue, require exactly 100 literal rows, query all 100 Latin names, validate complete results, then replace the fixed Lua output. Explicit missing pages become original availability notes. Print exact source counts and batch bounds.
- `--check`: parse and validate the generated bundle and its agreement with the current catalogue **offline**. Print source counts and `network requests: 0`.
- `--help`: print maintenance usage, without reading articles or making requests.
@@ -107,7 +134,7 @@ Invoke Node with this script path and one of those flags. There are **no URL, ti
### Security and failure behavior
- Only `https://en.wikipedia.org/w/api.php` is fetched. Latin names go through a strict binomial allowlist and `URLSearchParams`, never string-interpolated query syntax. Queries contain only the public catalogue Latin names, not player names or private data. No credentials, API keys, cookies or authorization headers are used. The descriptive `LastHearthSpeciesBundle/1.0` user agent identifies this manual offline-bundle use.
- Scope: at most 80 titles, ten per batch, two concurrent batches. A complete normal refresh makes **eight API requests**. No search queries, crawl, pagination, automatic retries, external links, or per-player network activity.
- Scope: at most 100 records in fetcher/serializer/parser helpers, ten titles per batch, two concurrent batches. A complete normal refresh makes **ten API requests**. No search queries, crawl, pagination, automatic retries, external links, or per-player network activity.
- HTTP redirect mode is manual. At most two redirects per batch are accepted, only to the same allowlisted HTTPS origin, exact API path and unchanged query. Credentials, non-default ports, fragments, other hosts (including mobile subdomains), other paths and query changes are rejected. Article redirects are API data, not HTTP fetch destinations.
- Each batch has a **15-second total deadline**, including its redirects and response body. The complete run has a **90-second deadline**. Failed workers abort the remaining work. Responses are limited to **2 MiB of decoded response bytes**, checked against both Content-Length and streamed bytes; JSON MIME and valid UTF-8 are required. TLS validation is left enabled; no certificates or secrets are embedded or bypassed.
- Reject API errors/warnings, incomplete batches, missing response records, HTML/wikitext in introductions, disambiguation pages, namespace/language mismatches, invented aliases, title/canonical-URL mismatches, and invalid or inconsistent page/revision metadata. Only explicit `missing: true` for the exact species gets an original note. A timeout, HTTP error or validation failure is **not** turned into an availability note.
@@ -124,6 +151,8 @@ The focused reader run passed **19 reader/HUD checks**, **43 recipe/journal chec
[tests/wiki-species.test.mjs](../tests/wiki-species.test.mjs) runs independently with Node's test runner. It does not launch Luanti or use the full-engine suite.
The verified run has **21 passing tests, 0 failures, 0 real network requests, and 33 mocked transport requests**. Coverage includes URL/query injection, SSRF redirects, redirect limits, timeout/abort cleanup, MIME/UTF-8/size rejection, page/revision validation, source-title redirects, missing-page honesty, Unicode/boundary clipping, malicious-text Lua serialization, exactly eight mocked batches at concurrency two, offline CLI checks, and byte-for-byte cache preservation after an HTTP failure.
The verified focused Node run has **21 passing tests, 0 failures, 0 real network requests, and 35 mocked transport requests**. Coverage includes URL/query injection, SSRF redirects, redirect limits, timeout/abort cleanup, MIME/UTF-8/size rejection, page/revision validation, source-title redirects, missing-page honesty, Unicode/boundary clipping, malicious-text Lua serialization, exactly ten mocked batches at concurrency two, rejection of 99-row catalogues and 101-record fetch/serialized inputs, all twenty new exact identities, offline CLI checks, and byte-for-byte cache preservation after an HTTP failure. The separate approved `--refresh` above did use the network; tests and `--check` do not.
Snapshot assertions intentionally expect **78 Wikipedia excerpts + 2 original availability notes**. After a successful future refresh, review changed text, source identities, license/attribution, missing-page status and any effects claims; update the snapshot expectations and this document when coverage changes. Never replace a failing source with made-up text merely to make the count reach 80.
A separate isolated Fengari execution of the actual current botany source compared every runtime `list()` ID/Latin pair with the maintenance parser: **100/100 exact ordered matches**, empty stderr. Runtime group counts were **trees 13, shrubs 14, grasses 10, herbs 22, ferns/mosses 11, fungi 10, aquatic 10, lichens 10**; no equal-ten-per-category assumption was imposed. No catalogue or source-effects code was modified and no full-engine run was launched. Offline `--check` verified **100/100 species: 98 introductions, 2 original notes; network requests: 0**.
Snapshot assertions intentionally expect **98 Wikipedia excerpts (78 previous + 20 new) + 2 original availability notes**. After a successful future refresh, review changed text, source identities, license/attribution, missing-page status and any effects claims; update the snapshot expectations and this document when coverage changes. Never replace a failing source with made-up text merely to make the count reach 100.
+46 -39
View File
@@ -208,54 +208,60 @@ generation scripts/assets. Species ecological-stage textures/meshes remain
the ecosystem/art owners' separate contract. Dynamic Lua tile names must be
explicitly enumerated by the art owner if its scanner cannot expand loops.
## Recommended v7 preset — NEW WORLDS ONLY
## Epic-scale landscape generator — NEW WORLDS ONLY
The following is a source-grounded **recommendation**, not an applied or
engine-playtested preset. Main should place it in game defaults and its
launcher **new-world creation** path, preserving existing saved mapgen params.
The current launcher writes `mg_name=v7` and seed 731104, but does not yet emit
these tuned noise values. Merely changing game defaults is not sufficient to
prove that managed-launcher new worlds received them.
New Last Hearth worlds use stock v7 as a cave/ore substrate and then the original
`epic_terrain` Lua mapgen mod reshapes each freshly generated mapchunk with
seeded, world-coordinate noise. The mod lives in the separate
`luanti-mapgenerator-epic` workspace repository and is bundled into the managed
Last Hearth runtime package. It does not copy Epic Terrain code, JSON density
functions, textures, or other assets. Its feature mapping is inspired by the
public Epic Terrain description/source, not a byte-for-byte or mathematical
port. The launcher applies the generator preset only when a world is first
created; it never rewrites saved map metadata or generated mapblocks.
```conf
mg_name = v7
water_level = 1
mapgen_limit = 512
mg_flags = caves,nodungeons,light,decorations,biomes,ores
water_level = 32
mapgen_limit = 4096
mg_flags = caves,nodungeons,light,biomes,ores
mgv7_spflags = mountains,ridges,nofloatlands,caverns
mgv7_mount_zero_level = 0
mgv7_np_terrain_base = 12, 65, (384, 384, 384), 82341, 5, 0.6, 2.0
mgv7_np_terrain_alt = 12, 28, (384, 384, 384), 5934, 5, 0.6, 2.0
mgv7_np_terrain_base = 12, 45, (900, 384, 900), 82341, 5, 0.6, 2.0
mgv7_np_terrain_alt = -8, 24, (900, 384, 900), 5934, 5, 0.6, 2.0
mgv7_np_terrain_persist = 0.6, 0.1, (1200, 1200, 1200), 539, 3, 0.6, 2.0
mgv7_np_height_select = -8, 16, (320, 320, 320), 4213, 6, 0.7, 2.0
mgv7_np_height_select = -4, 14, (1400, 320, 1400), 4213, 6, 0.7, 2.0
mgv7_np_filler_depth = 0, 1.2, (150, 150, 150), 261, 3, 0.7, 2.0
mgv7_np_mount_height = 230, 100, (700, 700, 700), 72449, 3, 0.6, 2.0
mgv7_np_mountain = -0.6, 1, (180, 280, 180), 5333, 5, 0.63, 2.0
mgv7_np_mount_height = 250, 100, (700, 700, 700), 72449, 3, 0.6, 2.0
mgv7_np_mountain = -0.6, 1, (320, 280, 320), 5333, 5, 0.63, 2.0
mgv7_np_ridge_uwater = 0, 1, (700, 700, 700), 85039, 5, 0.6, 2.0
mgv7_np_ridge = 0, 1, (100, 100, 100), 6467, 4, 0.75, 2.0
mg_biome_np_heat = 50, 50, (256, 256, 256), 5349, 3, 0.5, 2.0
mg_biome_np_humidity = 50, 50, (256, 256, 256), 842, 3, 0.5, 2.0
```
Why these parameters:
Design and source-grounded limits:
- Base/alternate terrain is lifted from stock offset 4 to 12, while retaining
an accessible low-relief alternate surface. This reduces ocean dominance
without pretending noise can guarantee a dry, safe spawn.
- Broad 384-node hills and 700-node mountain/river structure fit the current
small bounded world. Mountain density retains stock −0.6 bias; its longer
vertical than horizontal spread favors grounded massifs over spiky rubble.
Mount-height noise controls a density gradient, **not** an absolute peak cap.
- Ridges retain v7's native water-level channels and canyon walls. Neither
arbitrary block decorations nor exaggerated height offsets manufacture
cliffs. The river-width threshold remains the stock C++ value.
- 256-node climate fields make multiple coherent communities plausible inside
the ±512 limit; stock 1000-node climate fields can hide most variety there.
Existing small-scale heat/humidity blend noises can remain stock.
- Floatlands are explicitly disabled; native mountain noise can still produce
small overhangs/disconnected remnants. This is stock v7, not a guarantee of
mathematically erosion-correct terrain. Dungeons are disabled so unknown
mapgen dungeon aliases do not create unrelated structures.
- Continentalness is shaped by 5100-node plate and 1750-node warped-shelf fields;
a protected 620-node radius around world origin gives first arrival a dry
meadow, while distant islands, peninsulas and ocean shelves continue to vary
by seed.
- Broad uplift plus ridged mountain fields creates plains, alpine massifs and
high snow surfaces. A separate coast band uses a sharper elevation step for
sea cliffs and indented shore profiles.
- Major and minor river corridors are carved using separate warped ridge fields.
Their water levels descend in short quantized steps, creating waterfalls;
lakes form in closed noise basins. This is intentionally stylized flow, not a
global watershed/drainage solver, so a river need not reach the ocean in every
seed.
- `epic_terrain` keeps native v7 cave voids and ore nodes where possible, then
writes its own soil, water, block-cube trees and understory. Native v7
decorations are disabled for newly created worlds so flora does not float
above the reshaped land.
- Epic Terrain's Minecraft source replaces a density-function graph and custom
surface/rivers rules. Luanti does not expose that graph, so this is a feature-
level parody implemented with the public `register_on_generated`/VoxelManip
path, not a direct port. It is not a physical erosion simulation.
Never call `settings:set_default`, change mapgen at runtime, rewrite existing
map metadata, delete chunks, or automatically regenerate a player's world.
@@ -280,12 +286,13 @@ decoration/schematic definitions. In particular v7 generates biomes, caves,
ordered ores, then decorations; biome transitions must not be assumed to
extrude surface rock unchanged to the bottom of the map.
No full engine was run for this owner task. Main's remaining native gate is a
fresh disposable world with explicit saved preset verification, multiple seed
starts (including 731104), resource reachability, actual tree anchoring/media
loading and representative valley/forest/alpine views. Do not test by opening
the user's persistent world. Neither performance nor safe spawning is certified
by the standalone checks.
The native `test:seeds` gate creates eight disposable worlds, including seed
731104, and checks startup resources plus safe arrival; it is not a terrain
silhouette or biome-traversal test, and it does not require every seed to start
on dry land at the origin. A representative coastal, valley, river and alpine
visual survey is still needed before claiming those aesthetics. Never test by
opening the user's persistent world. Neither performance nor universal spawn
safety is certified by this preset or by the standalone checks.
Security review: no credentials, cryptography, certificates, network access,
filesystem writes, deserialization or player-supplied executable input are