Lots of content

This commit is contained in:
2026-11-03 12:41:09 -05:00
parent 6ebe95b208
commit d34388bf2c
126 changed files with 28076 additions and 1027 deletions
+157
View File
@@ -0,0 +1,157 @@
# Architecture and module contracts
Updated: 2026-11-02. Describes the actual M0–M3 source organization, **not proof
of human acceptance**. See [M0 decisions/evidence](m0-decisions.md),
[M1 evidence](m1-verification.md), [M2 evidence](m2-verification.md), [M3 evidence](m3-verification.md),
[roadmap](roadmap.md) and [operator procedures](../README.md).
## Bootstrap and dependency direction
[mods/hearth/init.lua](../mods/hearth/init.lua) creates `hearth.players` (validated
needs) and `hearth.sessions` (ephemeral UI/movement/timers), loads the pure model,
sets texture/notification helpers, obtains mod storage, loads the schema and
constructs the persistence adapter. It loads validated claims and wraps
`core.is_protected` while retaining the prior engine/mod protection function.
The actual top-level order is:
1. [model](../mods/hearth/model.lua) → [schema](../mods/hearth/schema.lua) →
[persistence factory](../mods/hearth/persistence.lua).
2. Claims/protection and `/claim` in init.
3. [content](../mods/hearth/content.lua), which constructs
[construction](../mods/hearth/construction.lua),
[interactions](../mods/hearth/interactions.lua) and
[processes](../mods/hearth/processes.lua) helper factories.
4. [interface](../mods/hearth/interface.lua) → [presentation](../mods/hearth/presentation.lua)
→ [feedback](../mods/hearth/feedback.lua) → [clock](../mods/hearth/clock.lua)
→ [environment](../mods/hearth/environment.lua)
→ [players](../mods/hearth/players.lua)
→ [runtime](../mods/hearth/runtime.lua)
→ [encounters](../mods/hearth/encounters.lua) → [course](../mods/hearth/course.lua).
UI/content callbacks reference later-installed APIs at invocation time. Do not
start scheduling needs before clock/environment/player lifecycle is installed.
## Responsibility and API map
| Module | Contract / boundary |
| --- | --- |
| [init](../mods/hearth/init.lua) | Bootstrap, shared globals, claim/home/access registries, role-aware protection wrapper, `/claim` membership and visible bounds; one 25 × 25 ±24-Y claim per owner. |
| [schema](../mods/hearth/schema.lua) | Pure `decode(kind, value)` returns schema-1 normalized envelope and migration boolean; `defaults()` returns a fresh needs copy. Kinds include needs, claims, homes, access, location and spawn. Access grants are limited to 16 valid player names per owner and roles `resident`/`storekeeper`. No JSON, I/O or engine. Reject unknown fields/kinds/versions and malformed/out-of-range data. |
| [persistence](../mods/hearth/persistence.lua) | Factory `(schema, core, storage)` → `load_world`, `save_world`, `load_player`, `save_player`. Owns serialization, legacy byte backups, readback checks, best-effort rollback/quarantine. World failures are fatal; callers must stop/kick on player failures. Access is an additive schema-1 world record; missing access defaults empty. |
| [model](../mods/hearth/model.lua) | Pure `location`, `solar`, `weather`, `needs`, `fuel_remaining`, `clamp`. Time is an input. Needs return a new table and danger contribution; elapsed needs step clamps to 0–2 seconds, not an offline catch-up engine. |
| [content](../mods/hearth/content.lua) | Declarative nodes/items/tools/recipes, terrain aliases/biomes/decorations, fire/bedroll callbacks, M3 building palette, claim-protected storage/drying rack and wearable cloak. |
| [construction](../mods/hearth/construction.lua) | Factory `(hearth, core)` returns `near`, action-aware `allowed`, `can_dig`, `place`, `place_building`, `owner`, `preview`, `removal_safe`. Six-node reach; support graph bounded to two horizontal links and sixteen vertical levels. Removal is refused if connected structures would lose support; unknown/unloaded and cross-household dependencies deny. No cascading collapse. |
| [interactions](../mods/hearth/interactions.lua) | Factory `(hearth)` returns `eat`: consume/replace a stack only when an applicable need can improve, update bounded session-owned needs. Not a new inventory persistence layer. |
| [processes](../mods/hearth/processes.lua) | Factory `(hearth, core, construction)` returns `use_fire`, `fire_tick`, `refresh_fire`, `fuel_left`, `ignite_fire` and `use_firestarter`. Access checks, real-time fuel/job deadlines, queue/reload reconciliation, one pending output and safe inventory collection. Cooking is six seconds; boiling is four; their heat cost is reserved on start. |
| [interface](../mods/hearth/interface.lua) | Inventory, searchable recipe/process guide, location forms, field handlers and HUD via `inventory`, `guide`, `recipes`, `location_form`, `make_hud`, `update_hud`. UI is not a substitute for server-side validation. |
| [clock](../mods/hearth/clock.lua) | Validate environment and preview settings, load/validate stored location even if overridden, enforce dedicated coordinates. Exports `set_location`, `clock_update`, `clock`, `weather`, `has_location`. Configured location takes precedence; singleplayer may use stored choice; unchosen local preview is disclosed. |
| [environment](../mods/hearth/environment.lua) | `environment(pos)` returns roof/shelter/enclosed/ventilated/open_sides/heat/smoke/rain/temperature. Flood-fill is bounded to 7 × 7 × 5 cells, cached for one second, and invalidated by room-geometry edits. Open door is an opening; a nearby roof vent exhausts smoke. Unknown/unloaded probes fail exposed. It is an approximation, not persistent smoke simulation. |
| [players](../mods/hearth/players.lua) | `player_join`, `player_leave`, `player_save`, `player_respawn`, `arrive`, `safe_arrival`: bounded emergence/recovery checks (fallback scans ±64 nodes), versioned homes, safe `/sethome` inside the owner's claim, home revalidation, safe-spawn scan and retained-inventory partial-needs death recovery. Clear sessions before kicking on load/save failure. |
| [runtime](../mods/hearth/runtime.lua) | Globalsteps coordinate sprint/brace speed, safe bedroll rest, cloak warmth/rain protection, sheltered heated/rack drying, 1-Hz needs/HUD/damage, ~10-second clock/save cadence and bounded local rain. Waiting/dead sessions do not accrue needs. |
| [movement](../mods/hearth/movement.lua) | Pure bounded-dt movement/stamina model; smooth walk/sprint/swim/brace speed and exhaustion release lock. Engine retains physical collision/camera/prediction. |
| [combat](../mods/hearth/combat.lua) | Pure server attack/guard validation: weapon-specific cadence, reach, aim, claims, stamina, line-of-sight and short displacement plausibility. Not a complete anti-cheat or lag-compensation system. |
| [presentation](../mods/hearth/presentation.lua) | Ephemeral whitelisted held-tool meshes attached to right hand; cleanup on weapon change, death, disconnect and shutdown. Cosmetic entities are not saved. |
| [feedback](../mods/hearth/feedback.lua) | Animated player state, material step/dig/landing and attack feedback, 10-Hz contextual target/placement HUD; static FOV is opt-in. |
| [encounters](../mods/hearth/encounters.lua) | Stationary reference and bounded opt-in moving sparring; deterministic wind-up/strike/recovery, safe steps, claim leash and no drops/lethal hits. Not wildlife AI or competitive netcode. |
| [course](../mods/hearth/course.lua) | Consent-gated local/staging-only fixed-volume movement route; mock generation/checkpoint tests and native no-consent preflight. `/course report` gives server-step/callback time, not FPS. |
## Save and migration contract
Hearth schema **1** is an envelope `{schema = 1, kind = ..., data = ...}` for
`needs`, `claims`, `homes`, `access`, `location` and `spawn`. Needs contain exactly food/water/warmth/
wet/stamina, each 0–100. Coordinates are finite bounded values; claim positions
are integer nodes. Complete valid legacy unversioned records migrate; malformed
or unknown future records fail rather than resetting silently.
| Storage owner | Current key / payload |
| --- | --- |
| Engine mod storage | `claims`, `homes`, `access`, `location`, `spawn` schema envelopes; legacy originals at `m0_backup:<kind>`. |
| Engine player metadata | `hearth:needs` envelope; immutable legacy bytes at `hearth:m0_backup:needs`; `hearth:arrived` remains a separate engine metadata flag. |
| Engine node metadata | Fire absolute `fuel_until`, job fields and pending output; structure owner and protected storage inventories; player metadata `hearth:cloak` stores cloak-worn state. These node/player fields are engine-owned and not envelope transactions. |
| Engine SQLite backends | World/map, player/auth/inventory data remain engine-managed; full world backup copies them rather than reimplementing their formats. |
| Memory only | Session HUD IDs, sprint/brace state, damage/save timers and arrival waiters; not durable schema records. |
Adapter load/save validates actual JSON serialization, suppresses raw parser
logging, saves original legacy bytes before migration and verifies writes by
readback. Failed commit attempts byte-exact rollback; failed rollback quarantines
that store/key for the adapter lifetime. Player metadata wrappers are cached to
prevent bypassing quarantine. These are **not transactions, CAS, or guaranteed
crash-durable writes**. Missing needs alone get defaults; absent claims get an
empty registry; absent location/spawn remain unset. Invalid existing records do
not become defaults. Startup world errors abort; player errors clear the session,
emit sanitized diagnostics and kick rather than write over corrupt state.
Full offline world snapshots complement per-key backups. Restore requires a new
destination and all writers stopped. No live-world migration/recovery success is
asserted without the [evidence ledger](m0-decisions.md#verification-and-acceptance-ledger).
## Clock, fuel and current behavior preservation
`model.solar()` derives UTC/date, local solar hour, seasonal temperature and a
disclosed engine-orbit approximation. Synthetic weather is deterministically
indexed to six-hour UTC fronts; it is not a remote forecast and can change
abruptly. Production follows host time. Fixed preview dates/weather are local or
staging-only; explicit dedicated coordinates are required even if storage has a
location. The 45°N, 0°E check fixture never selects a production location.
Fuel and queued jobs use real `os.time()` rather than the preview clock: sticks add
45 seconds, logs 180, capped reserve 600. Fire requires a reusable firestarter;
fuel does not self-ignite. Cooking takes six seconds and reserves 20 seconds of
fuel; boiling takes four and reserves 10. M4 smelting takes 60 seconds, firing
pottery 120, drying food 300, and making charcoal 60; each reserves 30 fuel
seconds before consuming its charge. Absolute deadlines continue during server
absence, so fuel may expire, while completed output remains queued until collected.
Deadlines are validated decimal strings; invalid job metadata fails closed and
timer/LBM reconciliation commits overdue output once. Offline player needs pause.
M4 crop growth samples historical local solar climate in six-hour intervals, pauses
below 3°C, slows below 10°C and caps replay at 30 days. Berry bushes renew after
24 hours; planted saplings attempt bounded, claim-safe tree growth after three days.
Hares are passive, non-dropping, nonpersistent and capped. Food has no general
spoilage; dried rations are shelf-stable. Death preserves inventory and most needs,
limiting stamina/warmth on respawn. M3 room checks are bounded to 7 × 7 × 5 voxels, cached one
second and invalidated by room-node edits; support removal refuses dependent work
rather than cascading. Shared claims add 16-member resident/storekeeper roles,
protected storage, and structural dependencies cannot cross household boundaries.
See [M2 verification](m2-verification.md) and [M3 verification](m3-verification.md).
## Tooling boundaries
| Script | Contract |
| --- | --- |
| [run](../scripts/run.mjs) | CLI dispatch; validate exact engine before staging/world access; launcher lease; temporary isolated `LUANTI_GAME_PATH`; persistent SP world with prelaunch snapshot or disposable tests. Not production deployment. |
| [launcher](../scripts/launcher.mjs) | Platform roots/absolute overrides, engine-version check, coordinate/fixture/environment validation, prelaunch snapshot. `HEARTH_HOME` preserves the old default root unless explicitly overridden. |
| [package](../scripts/package.mjs) | Read release contract; copy fixed config/license/mod payload, generate assets, copy JSON fixtures, write/verify exact SHA-256 manifest. New directory only; no engine or saves. |
| [assets](../scripts/assets.mjs) | Original deterministic procedural PNG/OBJ generation into package payload, not downloaded media. |
| [files](../scripts/files.mjs) | Absolute safe paths, regular-file inventories including empty directories, link/junction/case-collision rejection, trusted-parent exclusive staging/rename, manifest and launcher leases. No hostile-parent/portable ACL guarantee. |
| [backup](../scripts/backup.mjs) | Offline world snapshot/verification/restore with disjoint new destinations and lock/SQLite sidecar refusal; caller must stop remote/external writers. SHA-256 integrity, not authenticity. |
| [recovery](../scripts/recovery.mjs) | Private old-save copy-only exercise: retained snapshot, two restores/engine boots, synthetic staging probe and sanitized report. Source world is not booted. No connected-client/public-readiness certification. |
Package/backup formats are separate from save schema. The generated manifest
covers exact files/directories, not cryptographic publisher identity. Temporary
logs/config/worlds are cleaned up, retained snapshots are not automatically
pruned. Production data and backup custody remain operator responsibilities.
## Verification boundaries and next work
[Node tooling tests](../tests/tooling.test.mjs) and Lua
[schema](../tests/schema_spec.lua), [persistence](../tests/persistence_spec.lua),
[clock](../tests/clock_spec.lua), [model](../tests/model_spec.lua) and
[content](../tests/content_spec.lua), [access policy](../tests/access_spec.lua),
[room model](../tests/environment_spec.lua), [claim service](../tests/init_spec.lua),
[M2 timed processes](../tests/m2_processes_spec.lua), [M2 rest](../tests/m2_rest_spec.lua)
and [recipe index](../tests/recipe_spec.lua)
harnesses exist. The
[engine smoke mod](../tests/engine_smoke.lua) executes isolated Lua specs and
real registry/crafting/emergence checks; it does not play as a human client.
[Legacy fixture](../tests/fixtures/legacy-save.lua),
[scenario definitions](../tests/fixtures/scenarios.json) and
[recovery probe](../tests/recovery_probe.lua) support evidence collection, not
automatic milestone approval.
Known limitations are deliberately assigned, **not reported fixed**: M1 human
feel/repeat/performance gate; M2 unfamiliar-player and operator acceptance, and
community-location selection before shared climate staging; M3 two-player cabin,
comfort and grief-resistance acceptance; M4 renewal/preservation and other offline
simulation; M5 operational hardening. M3 room and support rules are bounded
prototypes, not general physics, and operational/public-service readiness remains open.
+983
View File
@@ -0,0 +1,983 @@
# The Last Hearth — woodland travelers and botanical pixel catalogue
## Authoritative character art contract (2026-11-03)
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),
[scripts/assets.mjs](../scripts/assets.mjs),
[scripts/character-art.mjs](../scripts/character-art.mjs),
[tests/character-art.test.mjs](../tests/character-art.test.mjs),
[tests/assets.test.mjs](../tests/assets.test.mjs) and this document.
No Lua, gameplay, collision, camera, engine, dependency or source-media edits.
All drawings are original procedural MIT work, with no downloaded art, network,
image service, third-party model, global tint or extra character-layer entities.
### Woodland traveler grammar and unchanged UV contract
Quiet mineral cloth, undyed linen, worn walnut leather and natural hair replace
the old flat blockperson skin. Small collars, rolled cuffs, stitched hems,
plackets, leather panels, coat openings, boot laces/soles and sparse linework give
the traveler a grounded, practical silhouette. Faces remain readable at eight
pixels: two iris landmarks, restrained brows, nose and mouth, no oversized white
cartoon eyes. Skin choices are independent of facial features and clothes.
Every runtime layer is **64×64 straight RGBA**, nearest-neighbor, with **0/255
alpha** and zero RGB behind transparent pixels. The atlas is still eight columns
of eight-pixel tiles, with the original half-texel UV inset. Tile coordinates:
| Row | Column 0 | Column 1 | Column 2 |
| --- | --- | --- | --- |
| 0 | face | hair (head back/top) | side (both head sides) |
| 1 | tunic (torso front/ends/sides) | back | sleeve |
| 2 | trousers | boot | skin (hands/head underside) |
| 3 | metal | wood | wrap |
The six base skins are opaque over **all 4096 pixels**, including a linen
underlayer and fallback trousers/boots. Skin tone changes exactly the four
exposed head/hand tiles, **256 pixels**, not the clothing or tool swatches.
Face overlays are skin-neutral lines, not light-colored face masks. Hair paints
the scalp, sides and hairline; long hair and braids also cover upper back and
shoulder pixels. Garments and accents paint only torso/back/sleeve tiles;
trousers and boots paint only their own tiles. No overlay touches tool tiles or
unused atlas space. The former metal/wood/wrap pixels are **byte-exact** in all
base skins and in the default **hearth_person.png**, so existing held-tool OBJ
materials keep their appearance. Existing tool geometry and icon art are retained.
### Exact IDs and generated layer manifest
`CHARACTER_OPTIONS` exports these frozen allowlists, without aliases:
| Group | IDs |
| --- | --- |
| bodies | compact, balanced, broad |
| skin | porcelain, sand, honey, olive, umber, ebony |
| faces | calm, angular, soft, weathered |
| eyes | hazel, brown, green, blue, grey |
| hairStyles | cropped, sidepart, wavy, braids, long, shaved, none |
| hairColors | ash, flax, chestnut, auburn, black, silver |
| facialHair | none, stubble, moustache, short_beard, full_beard |
| garments | tunic, workshirt, jerkin, coat |
| clothColors | sage, teal, slate, mauve, ochre, rust, linen, charcoal |
| trousers | earth, slate, moss, charcoal |
| boots | walnut, black, tan |
| accents | none, scarf, apron, sash, vest |
| details | none, freckles, scar, freckles_scar |
The **170** entries in `CHARACTER_ART_MANIFEST` use these exact filename
templates. Each row includes every Cartesian product, even transparent `none`:
| Filename template | Count |
| --- | ---: |
| hearth_char_skin_&lt;skin&gt;.png | 6 |
| hearth_char_face_&lt;face&gt;.png | 4 |
| hearth_char_eyes_&lt;eyes&gt;.png | 5 |
| hearth_char_hair_&lt;style&gt;_&lt;hairColor&gt;.png | 42 |
| hearth_char_beard_&lt;style&gt;_&lt;hairColor&gt;.png | 30 |
| hearth_char_garment_&lt;style&gt;_&lt;cloth&gt;.png | 32 |
| hearth_char_trousers_&lt;trousers&gt;.png | 4 |
| hearth_char_boots_&lt;boots&gt;.png | 3 |
| hearth_char_accent_&lt;style&gt;_&lt;accentColor&gt;.png | 40 |
| hearth_char_detail_&lt;detail&gt;.png | 4 |
Accent colors use the same eight `clothColors`; beard colors use the same six
`hairColors`. Exactly **21** images are intentionally empty: six hair-none, six
beard-none, eight accent-none and one detail-none. All **149 nonempty layers**
are distinct; equivalent empty-none color variants are deliberately identical.
Each manifest record includes `filename`, `kind`, `id`, `color` (null if absent),
`width`, `height`, `empty`, `license`, `author`.
`generateAssets()` writes the layers into the staged texture directory, and a
root-level **character-art.json** provenance document with `format:1`, `options`,
`meshes` and `images`. All outputs use the existing guarded `writeAsset`,
`relativeName` and `safePath`; no source-worktree assets are generated.
The document and every layer/mesh are included in the existing SHA256 package
inventory. It adds **171 PNGs** (170 layers + portrait sheet) and **two B3Ds**
to previous totals: **576 fixed PNGs**, plus source-dependent compatibility
swatches, **37 OBJ + three B3D**. The two-swatch fixture totals **578 PNGs**.
### Pure methods and Lua composition handoff
[The renderer module](../scripts/character-art.mjs) exports:
- `characterLayer(filename)` → fresh `{width:64,height:64,data:Uint8Array}`;
unknown filenames fail closed. `characterArt()` → deterministic filename Map
of all 170 fresh images.
- `characterLayers(options={})` → ten exact filenames in compositing order;
`characterTexture(options={})` → those names joined by `^`, ready for one
Luanti mesh texture. Only allowlisted fields/values are accepted; no path,
prototype key, inherited options object or arbitrary file input.
- `compositeCharacter(options={})` → pure fresh opaque 64×64 RGBA atlas using
the same crisp source-over order. No I/O or global recoloring.
- `characterMesh(body='balanced')` → exact mesh filename.
- `portraitSheet()` → opaque **512×64** RGBA contact sheet, eight assembled
64px portrait cells. `CHARACTER_PORTRAITS` gives the exact left-to-right
selections. Portraits sample actual composited face, tunic, sleeve, skin,
trousers and boot tiles; they are paper dolls, **not atlas swatches or 3D
engine-render screenshots**. Staging writes **hearth_character_contact_sheet.png**.
Selection fields and defaults (`DEFAULT_CHARACTER`): `body:balanced`,
`skin:sand`, `face:calm`, `eyes:hazel`, `hair:sidepart`, `hairColor:chestnut`,
`beard:none`, `garment:tunic`, `cloth:teal`, `trousers:earth`, `boots:walnut`,
`accent:scarf`, `accentColor:sage`, `detail:none`. The old **hearth_person.png**
is now this complete default composite, not a removed compatibility material.
**Required left-to-right texture order:** skin → face → eyes → garment →
trousers → boots → accent → hair → beard → detail. In filename form:
hearth_char_skin_&lt;skin&gt;.png ^ hearth_char_face_&lt;face&gt;.png ^
hearth_char_eyes_&lt;eyes&gt;.png ^ hearth_char_garment_&lt;garment&gt;_&lt;cloth&gt;.png ^
hearth_char_trousers_&lt;trousers&gt;.png ^ hearth_char_boots_&lt;boots&gt;.png ^
hearth_char_accent_&lt;accent&gt;_&lt;accentColor&gt;.png ^
hearth_char_hair_&lt;hair&gt;_&lt;hairColor&gt;.png ^
hearth_char_beard_&lt;beard&gt;_&lt;hairColor&gt;.png ^ hearth_char_detail_&lt;detail&gt;.png.
Remove the displayed spaces around carets when composing. Clothing **must precede
hair**, or long locks disappear under the upper-back/shoulder garment pixels.
Runtime appearance UI, persistence and attachment application belong to the Lua
owner; this work only supplies assets and the exact builder contract.
### Mesh profiles, rig and attachment invariants
`playerB3D(profile='balanced')` accepts exactly `compact`, `balanced`, `broad`.
Mesh filenames: **hearth_person.b3d** (balanced), **hearth_person_compact.b3d**,
**hearth_person_broad.b3d**. Each retains one textured mesh, **240 vertices,
120 outward-wound triangles**, rigid weight 1 per vertex, original UV assignments
and **17.5 model-unit height** (feet 0, head top 17.5 = 1.75 nodes). No collision,
camera/eye-height, reach, global scale or root-motion change.
Balanced head changes from X ±1.75 / Z ±1.5 to **X ±1.6 / Z ±1.4**;
Y remains 13.5–17.5. Torso shoulder half-width 2.2 → **2.15**, half-depth
1.25 → **1.18**; bottom waist is 86% width/depth. Arms half-width .85 → **.78**,
depth .9 → **.84**, tapering to 90% width/95% depth at the wrist. Legs half-width
.95 → **.91**, depth 1 → **.95**, tapering to 90% width/94% depth at the ankle.
Normals are recomputed from actual tapered face planes. Hands, boots and all
vertical extents stay unchanged. These are subtle tapered ten-part solids, not
additional joints or extra geometry.
Compact/broad multiply torso, upper-arm and leg widths by **.92 / 1.08**.
Broad uses a slightly fuller 90% waist width; all waist depths stay 86%.
Head, hands, boots, depths, height and rig pivots remain identical across profiles.
No profile moves an attachment hand to accommodate a wider body.
All nine bones, hierarchy and bind positions remain exactly unchanged: `Root`,
`Body`, `Head`, `Arm_Right`, `Hand_Right`, `Arm_Left`, `Hand_Left`, `Leg_Right`,
`Leg_Left`. Arm pivots X ±3.1/Y13; hands local [0,-4.8,0] under their arms.
All bones retain the exact 100 keyed poses, B3D frames 1–100 / engine 0–99.
Clips unchanged: idle 0–19@12, walk 20–39@24, run 40–59@32, strike 60–69@28,
guard 70–79@12, swim 80–99@20. Held grip stays at `Hand_Right`, local origin,
rotation {x=90,y=0,z=0}; the shared tool atlas is retained.
### Validation and security boundary
CodeGuard was read and applied: no credentials, secrets, certificates or crypto
protocols introduced; existing SHA256 stays integrity-only. Positive enum/key
validation, bounded allocations, pure offline drawing and existing link/junction
write refusal are retained. No shell/eval, new dependencies or downloads.
Tests independently parse generated PNG CRC/data and all three B3D chunk trees,
check every exact option, semantic mask, all 170 encoded layers, 149 nonempty
distinct images, meaningful skin differences, 21 intentional empty layers,
paper-doll samples, deterministic regeneration/package hashes, rig/keys/weights,
head/waist geometry and byte-exact held-tool swatches.
No engine launch, client rendering, FPS, collision-course or live-world claim.
Final focused character + asset run: **24 passed, 0 failed, 0 cancelled,
0 skipped**, exit 0, empty stderr, **65.60 seconds**, Windows Node **24.22.0**.
All five JavaScript syntax checks passed. All six owned files were read back
from disk for trailing whitespace and completion; the eight-portrait sheet was
visually reviewed. Optional full audio codec regeneration was not configured;
existing audio framing/CRC/hash checks passed. Directory-junction refusal passed;
the file-symlink subcase could not run without Windows symlink privilege.
Git diff checking was unavailable from this subprocess environment, so no Git
diff-clean claim is made. Runtime Lua appearance integration is not tested here.
## Authoritative catalogue-80 art contract (2026-11-02)
This section supersedes all folded historical counts and mesh integration advice.
Only [scripts/world-art.mjs](../scripts/world-art.mjs),
[scripts/flora-art.mjs](../scripts/flora-art.mjs),
[scripts/assets.mjs](../scripts/assets.mjs),
[tests/world-art.test.mjs](../tests/world-art.test.mjs),
[tests/flora-art.test.mjs](../tests/flora-art.test.mjs) and this document are owned.
The actual [botany catalogue](../mods/hearth/botany.lua),
[biomes](../mods/hearth/biomes.lua) and
[diversity](../mods/hearth/diversity.lua) were read, never edited. No engine tests
or client launch are part of this art work; runtime cube/season closure belongs
to the content and seasons owners.
### Original botanical grammar
All art is original procedural drawing under the [MIT license](../LICENSE.txt).
No copyrighted third-party images, downloads, fonts, random noise, dependencies
or generated-image service. Every specimen is drawn at **32×32**, straight RGBA,
nearest-neighbour with **crisp 0/255 alpha**, zero hidden RGB and one-pixel empty
borders. Small natural sage, bark, buff and mineral palettes dominate; flowers,
fruit and reproductive details are restrained accents. These are stylized field
journal specimens, **not identification, toxicity or eating advice**. No plate,
meal, bite, food badge or edibility permission is depicted, including toxic taxa.
- **Trees**: oak rounded lobes and stalked acorns; birch serrate diamonds and
catkins; pine long paired needle sprays; willow pendant narrow blades; alder
round notched leaves and conelets; beech oval parallel-veined leaves; aspen
scalloped round leaves and petioles; rowan pinnate leaflets and clustered pomes;
hazel heart-shaped leaves and husked nuts; spruce short radial needles and long
pendant cones. All ten get inventory/fieldguide specimens independently of cubes.
- **Shrubs**: low branching twig clusters, arching thorny bramble/rose canes,
upright raspberry, opposite compound elder, rigid blackthorn, lobed hawthorn,
spiny evergreen holly, juniper needle whorls, three-lobed guelder rose. Fruit
attachment, clusters and leaves differ, not just hue.
- **Grasses and graminoids**: dense tussocks below diagnostic panicles/spikes;
sedge has angular triangular culm faces, reedmace cylindrical brown heads,
cock's-foot one-sided clumps, timothy a tight long spike, vernal-grass short
golden ovals, Yorkshire-fog soft pink panicles, hair-grass airy rays, reed tall
jointed stems and broad blades, rush lateral inflorescences on smooth culms.
- **Herbs**: trifoliate clover and stolon, serrate opposite nettle, heather scale
sprays, one-sided bluebell bells, yarrow flat corymb and divided leaves,
foxglove tubular bells and rosette, meadowsweet cream froth, garlic star umbel,
cowslip nodding yellow tubes, anemone single flower over divided-leaf whorl.
- **Ferns/mosses**: triangular three-part bracken, fine lady-fern, robust male-fern,
entire hart's-tongue straps, polypody pinnae/sori versus low moss cushions:
sphagnum capitula, haircap shoots/capsules, plait-moss feathers, swept fork-moss
leaves, broad-leaved star-moss shoots. They do not share a generic bush glyph.
- **Fungi**: warted fly-agaric, scabrous birch-bolete stem, chestnut bay cushion,
folded chanterelle funnel, lilac blewit, shaggy cylindrical inkcap, banded turkey
tail shelves, lateral oyster fans, tufted ringed honey caps, kidney polypore.
No invented puffball is substituted: none occurs among these ten catalogue IDs.
- **Aquatic**: duckweed small paired fronds/roots; **round or oval notched lily
pads**, yellow cup versus many white tepals (not lotus horns); crowfoot divided
foliage, mint globe, forget-me-not curled cyme, brooklime raceme, starwort
terminal rosette, rootless hornwort forked whorls, long-petioled pondweed ovals.
- **Lichens**: fruticose reindeer forks, flattened oakmoss straps, hanging beard
filaments, map-lichen crust islands, broad dog-lichen lobes/rhizines, lungwort
reticulation, orange sunburst discs, grey hammered shields, distinct slender
mealy and stout pebbled pixie cups. Crust, foliose and branched forms differ.
Inactive images are **per ID**, not one shared dormant swatch: blooms/fruit are
omitted, leaves reduced or twigs exposed, fungi collapse to stems/shelves, growth
settles into a smaller dry silhouette. Persistent evergreen/moss/lichen images
also have an explicit inactive fallback, but **do not imply those species must
become dormant in Winter**; the seasonal registrar decides when each is used.
### Exact image and provenance manifests
`BOTANY_CATALOGUE` lists **80 unique exact IDs and 80 unique Latin names**,
transcribed from the actual Lua `add()` records, in catalogue order. Eight groups
of ten: trees, shrubs, grasses, herbs, ferns_mosses, fungi, aquatic, lichens.
Each record includes `id`, `latin`, `group`, diagnostic `morphology`.
`BOTANY_ART_NAMES` contains those exact 80 IDs; `BOTANY_SPRITE_NAMES` expands
them to **160** stems. Each has prefix `hearth_botany_` and PNG extension:
the ID alone for active art, the ID plus `_dormant` for inactive art.
`BOTANY_ART_MANIFEST` annotates **each of the 160 PNG outputs** with exact
`filename`, `id`, **`botanicalLatin`**, `group`, `morphology`, `state`, dimensions,
license and authorship. Generation writes this deterministically as a root-level
JSON provenance document named **botany-art.json** inside the private output tree.
It is included in the hash-verified package. No Lua evaluation, source-loop
guessing, network or platform-specific botanical database is needed. Tests parse
the actual plain Lua catalogue independently and compare every ID/Latin pair.
`worldArt()` now returns **360 distinct images**: **160 world/ecology tiles,
34 item icons, three UI tiles, three animal atlases, 160 catalogue specimens**.
This adds **192** images to the previous 168: twenty new tree faces, twelve new
deciduous seasonal leaf faces, and 160 catalogue specimens.
Generated package outputs: **405 fixed PNGs**, plus source-dependent compatibility
swatches. The minimal fixture has two swatches, thus **407 PNGs**. The fixed set
is 360 sprites + **38 opaque 64×32 atlases** (four generic, 34 species) + contact
sheet + four tool icons + player skin + crosshair alias. There remain **29 legacy
flora OBJ files**, **37 OBJ total plus one animated player B3D**. No new tree
meshes were authored; player, tools, wildlife and fifteen Ogg files are unchanged.
### Exact ten-tree cube material and season contract
All stems in this table take prefix `hearth_` and PNG extension. Each row
explicitly gives log end, log side, leaves and crafted boards in that order.
| Species / botanical identity | Log top | Log side | Leaves | Planks | Additional leaf seasons |
| --- | --- | --- | --- | --- | --- |
| oak / Quercus robur | oak_log_top | oak_log_side | oak_leaves | oak_planks | spring, autumn, winter |
| birch / Betula pendula | birch_log_top | birch_log_side | birch_leaves | birch_planks | spring, autumn, winter |
| pine / Pinus sylvestris | pine_log_top | pine_log_side | pine_leaves | pine_planks | none; evergreen |
| willow / Salix alba | willow_log_top | willow_log_side | willow_leaves | willow_planks | spring, autumn, winter |
| alder / Alnus glutinosa | alder_log_top | alder_log_side | alder_leaves | alder_planks | spring, autumn, winter |
| beech / Fagus sylvatica | beech_log_top | beech_log_side | beech_leaves | beech_planks | spring, autumn, winter |
| aspen / Populus tremula | aspen_log_top | aspen_log_side | aspen_leaves | aspen_planks | spring, autumn, winter |
| rowan / Sorbus aucuparia | rowan_log_top | rowan_log_side | rowan_leaves | rowan_planks | spring, autumn, winter |
| hazel / Corylus avellana | hazel_log_top | hazel_log_side | hazel_leaves | hazel_planks | spring, autumn, winter |
| spruce / Picea abies | spruce_log_top | spruce_log_side | spruce_leaves | spruce_planks | none; evergreen |
Season images append `_spring`, `_autumn`, `_winter` to the **leaf stem**.
Beech/aspen/rowan/hazel add twelve seasonal faces; spruce is evergreen like pine.
Forty base faces plus 24 seasonal leaf faces are explicit outputs. New bark
conveys smooth grey beech, diamond-scarred pale aspen, lenticellate rowan/hazel
and scaly spruce; endgrain is eccentric rings and boards show restrained joints.
All older generic/species leaf drawing code and encoded PNG bytes are preserved;
tests pin the six approved base leaf PNG hashes, plus the original generic RGBA
SHA256 `6ca3e11b7e8e98f46220db92a4dbe729515835c3ae39c76ac3003fa5494f3f4c`.
**New roots/stages use cubes**, no irregular canopy OBJ. Log face order is top,
top, side, side, side, side; existing `facedir` controls axis orientation. Each
seasonal woody-stage copy must retain that cube tile list and must not switch to
an atlas/mesh. Canopy cubes use the species/season leaf faces; tree seedlings,
inventory and guide can use catalogue sprites. Runtime geometry, migration and
seasonal swaps are other owners' work, not implemented by this art generator.
`LEGACY_MESH_SPECIES` freezes the original five species. Existing generic and
species OBJ exports are **deprecated compatibility assets only**, retained until
the native-closure owner excludes unused files. Do not infer use from packaging
or add five new sculptural models. `SPECIES_ATLAS_NAMES` has all ten species:
eight deciduous × four seasons + two evergreen × one = 34. Generic four + 34 = 38.
New atlases are compatibility/review bytes, **not cube stage tiles**. Each keeps
own bark byte-exact at left and own seasonal foliage at right with opaque
underpaint. Evergreen non-summer atlas requests fail closed.
### Bounded sorted review layout
`contactSheet()` accepts **1–512** validated RGBA 32px tiles: **16 columns**,
512px wide, height `ceil(count/16)*32`, maximum 1024px. Current **360-image
512×736** sheet has **23 rows**, eight unused final cells. Native pixels are not
resampled. Full filenames sort in codepoint order regardless of Map insertion
order; zero-based sorted index `i` maps to column `i%16`, row `floor(i/16)`.
Cells are composited on the journal checker, not opaque placeholder squares.
This formula and the complete named/Latin manifest are the authoritative legend,
not an obsolete manually maintained 10-column table. A separate botanical review
can select the 160 `hearth_botany_` images: the same sorted 16-column layout makes
**512×320** (ten rows), placing active and dormant specimens next to each other.
### Safety and validation boundary
Every PNG, retained OBJ, review sheet and provenance output passes the existing
`writeAsset` / `relativeName` / `safePath` path guards in private staging. Links
and junctions are refused; tile count, dimensions and decode allocations are
bounded. Source scanning remains mod-Lua-only. No secrets, cryptographic protocol
or external certificates are introduced; SHA256 is package integrity, not trust.
Focused Node suites check the actual 80 ID/Latin pairs, 160 unique images,
80 unique active and 80 unique inactive alpha masks, palette/occupancy/padding,
diagnostic botanical landmarks, preserved leaf PNGs, all cube faces and atlases,
determinism, independent PNG CRC/decode, exact package bytes and output link
refusal. Existing legacy topology/UV budgets remain checked without claiming the
models are used. **No engine rendering, mapgen, seasonal runtime closure or
performance certification is claimed.**
Final focused run: **39 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0,
empty stderr, **80.84 seconds**, Windows Node **24.22.0**. All three asset suites
were included; optional external audio codec regeneration was not configured.
The **512×736** complete sheet and **512×320** botanical sheet (also enlarged
nearest-neighbour 2× for inspection) were generated under the system temporary
review directory. Windows directory-junction refusal and exact fixture-package
bytes passed. Existing leaf PNG fingerprints passed without changing their art.
<details>
<summary>Historical art milestones — superseded counts and tree-mesh advice</summary>
# The Last Hearth — moonlit botanical field journal
## Current botanical extension — authoritative contract (2026-11-02)
This section supersedes the folded historical snapshot below. Only
[world-art.mjs](../scripts/world-art.mjs),
[flora-art.mjs](../scripts/flora-art.mjs),
[assets.mjs](../scripts/assets.mjs),
[world-art.test.mjs](../tests/world-art.test.mjs),
[flora-art.test.mjs](../tests/flora-art.test.mjs) and this document are owned.
The exact runtime contracts were read from
[biomes.lua](../mods/hearth/biomes.lua),
[diversity.lua](../mods/hearth/diversity.lua),
[world-generation.md](world-generation.md) and [ecosystems.md](ecosystems.md).
No gameplay, engine, package metadata or live-world edits are included.
### Original realistic botanical pixel grammar
All drawings and sculptures are original procedural source under the project's
[MIT license](../LICENSE.txt). No downloaded assets, image/model libraries,
network calls or random/hash noise. Mocha journal shadows support muted natural
mineral, loam, sage and timber colours; flowers and metal highlights remain small.
These are stylized natural materials, not photographic scans or plant-ID advice.
- **Base stone is unjointed parent rock**: irregular connected mineral microfacets,
not masonry, mortar outlines or a brick tessellation. Granite has feldspar and
quartz grains; basalt tight dark vesicles; limestone pale fossil/lens marks;
slate oblique cleavage; sandstone interrupted bedding and compact grains.
- **Soil is granular aggregate**: small discontinuous compacted lenses, crumbs and
organic marks, never full-width cartoon stripes. Forest floor has fragmented
litter, podzol needle litter, alluvium fine sediment/rounded grains, peat dark
organic fibres, alpine turf low mineral-interspersed growth.
- **Grass top is dense, non-symbolic tuft mosaic** across the whole surface, not
isolated outlined blades. Grass side has a variable organic cap and fine roots
into the same mineral soil body. Sand uses low-contrast ripples; gravel has
rounded clasts, scree angular fragments, snow sparse compressed wind marks.
- **Bark has wandering interrupted fissures** and branch scars, not repeated
straight planks. Endgrain rings are eccentric, warped and split. Birch is pale
with dark horizontal lenticels; pine scaly warm bark; willow fine dark furrows;
alder compact gray-brown bark. Planks remain visibly crafted with subdued joints
and flowing grain. Only foundations/weirs and named rock bricks use masonry.
- The approved generic **leaf texture is byte-for-byte unchanged**. Its RGBA
SHA256 is `6ca3e11b7e8e98f46220db92a4dbe729515835c3ae39c76ac3003fa5494f3f4c`.
New species leaves have distinct shapes: lobed oak, small airy birch, pine
needle sprays, narrow hanging willow and rounded alder. Spring adds sparse buds;
Autumn copper/ochre/wine; Winter open woody forks with no green leaf fill.
- Copper seams show oxidized green beside dull copper; quartz crystalline milky
facets; coal broken dark seams. Resource and tool icons have their own padded
silhouettes. The copper charge retains ore with the existing binding/tag grammar.
Every drawing is native 32×32 straight-alpha RGBA, nearest-neighbor, with an
explicit restrained palette. Natural rock/substrate and ore-host faces are opaque;
leaves/understory/items use crisp 0/255 alpha and zero hidden RGB. Item borders
remain empty. Existing window alpha-95 glass, water seams and legacy interfaces
are retained. Sand is horizontally seamless; water is seamless on both axes.
Different materials use different geometry rather than one recoloured tessellation.
### Exact output inventory
`worldArt()` returns **168 distinct 32×32 images**: **128 world/ecology tiles,
34 item icons, three UI tiles and three animal atlases**. The increase from 95 is
73 sprites: 41 biome faces, 12 deciduous seasonal leaf faces, 14 understory sprites
and six resource/tool icons. `WORLD_NAMES`, `BIOME_WORLD_NAMES`, `SPECIES_NAMES`,
`ROCK_NAMES`, `SPECIES_SEASONAL_NAMES`, `UNDERSTORY_NAMES`,
`DORMANT_UNDERSTORY_NAMES`, `PLANT_NAMES`, `ITEM_NAMES` are explicit bounded manifests.
All stems below take `hearth_` plus the PNG extension:
- For each of granite, basalt, limestone, slate, sandstone: the rock stem and
its `_bricks` stem (10); forest_floor, podzol, alluvium, peat, alpine_turf,
gravel, scree, snow (8); quartz_vein, copper_ore_vein, coal_ore_vein (3).
- For each oak, birch, pine, willow, alder: `_log_top`, `_log_side`, `_leaves`,
`_planks` (20). These **41 faces plus item_quartz, item_copper_ore, item_coal**
implement the exact 44-PNG biome contract, not aliases or compatibility swatches.
- Deciduous oak/birch/willow/alder `_leaves_spring`, `_leaves_autumn`,
`_leaves_winter` (12). Pine has no seasonal leaf/atlas/mesh duplicates.
- clover, nettle, heather, bluebell, sedge, bulrush, moss, lichen; the first six
also have `_dormant` (14). Dormant sprites lose flowers/green and reduce growth;
bluebell reuses the dormant sprite outside Spring. Moss/lichen remain persistent.
- Additional `item_copper_ingot`, `item_copper_charge`, `item_copper_pick` (3).
Quartz crystal, copper ore, coal, ingot, bound charge and pick are six distinct
icons, including when generated from a minimal source with no asset references.
`generateAssets()` additionally writes **21 opaque 64×32 tree atlases** (four
generic plus 17 species), the **320×544 contact sheet**, four existing tool icons,
the 64×64 player skin and engine crosshair alias. Thus there are **196 named PNG
outputs plus the separately source-dependent compatibility swatches**. No output
discovery depends on expanding Lua loops. Existing sound/player/tool generation
and fifteen Ogg files remain unchanged.
`FLORA_NAMES` contains **29 OBJ meshes**: six retained generic flora models plus
23 species models. Together with three animals, four held tools and the fallback
player this is **37 OBJ files**, plus the existing animated player B3D.
### Species sculpture, scale and seasonal contract
All flora uses 10 model units/node, node-centre origin, ground exactly Y=-5
model units (-0.5 nodes), `visual_scale=1`. Mature height is at most five nodes
and horizontal footprint at most ±1.5; young forms fit 2.5 high and ±0.75.
Seedlings are below one node high and ±0.3. Collision/placement/growth policy is
the ecosystem owner's responsibility, not inferred from the visual canopy.
Oak has broad spreading forks and an asymmetric multi-lobed crown. Birch has a
slim elevated bole and separated small airy lobes. Pine has tapered branch whorls
and a narrow leader, with daylight between tiers. Willow has long descending
terminal wood and six pendant leaf curtains under a spreading crown. Alder has
multiple upright leaders and a compact rounded riparian crown. These are closed
volumes, not crossed planes, leaf billboards or oversized cubes.
For every species generate `_seedling`, `_young`, `_mature` OBJ stems. Deciduous
species also generate `_young_winter`, `_mature_winter`; all original woody parts
are retained exactly and all foliage omitted. Seedlings retain their ordinary
mesh in Winter as runtime specifies. Pine stays evergreen. Generic deadwood stays
shared; species provenance is metadata, not extra deadwood art.
| Species | Seedling vertices / triangles | Young and mature vertices / triangles | Each winter young/mature |
| --- | --- | --- | --- |
| Oak | 84 / 152 | 332 / 596 | 176 / 308 |
| Birch | 84 / 152 | 300 / 540 | 144 / 252 |
| Pine | 110 / 200 | 546 / 988 | none |
| Willow | 84 / 152 | 558 / 1004 | 272 / 476 |
| Alder | 84 / 152 | 274 / 492 | 144 / 252 |
`SPECIES_MESH_NAMES` and `SPECIES_ATLAS_NAMES` enumerate exact outputs. Each
species uses its own `_atlas` PNG; deciduous trees additionally use
`_atlas_spring`, `_atlas_autumn`, `_atlas_winter`. `treeAtlas(art, season, species)`
preserves the original two-argument API. Unsupported species/seasons fail closed;
seasonal pine requests are rejected. Left half is own-species bark, byte-identical
through seasons; right half own-season foliage over opaque semantic underpaint.
Half-texel inset UVs and explicit outward normals are retained. Winter faces sample
only bark; canopy holes are gaps between lobes, never torn transparent faces.
### Sorted contact-sheet legend
`contactSheet()` sorts by complete filename using codepoint order, independent of
input Map insertion order. It accepts **1–320 validated 32px RGBA tiles**, always
10 columns, height `ceil(count/10)*32` (maximum 1024px). The current 168-tile sheet
is **320×544** with two unused checker cells. It contains every sprite, not the
separate 64×32 tree atlases. Native pixels are not resampled. Rows and columns
below are one-based; stems retain `item_` and omit only `hearth_` / PNG extension.
| Row | Columns 1 → 10 |
| --- | --- |
| 1 | alder_leaves · alder_leaves_autumn · alder_leaves_spring · alder_leaves_winter · alder_log_side · alder_log_top · alder_planks · alluvium · alpine_turf · autumn_mushrooms |
| 2 | autumn_mushrooms_dormant · basalt · basalt_bricks · bedroll · berry_bush · berry_bush_bare · birch_leaves · birch_leaves_autumn · birch_leaves_spring · birch_leaves_winter |
| 3 | birch_log_side · birch_log_top · birch_planks · bluebell · bluebell_dormant · bulrush · bulrush_dormant · clay_bank · clover · clover_dormant |
| 4 | coal_ore_vein · copper_ore_vein · crop_1 · crop_2 · crop_3 · crop_4 · crosshair · deadwood · deer · door |
| 5 | drying_rack · dung · fern · fern_dormant · fire · fire_lit · fish_trap · forest_floor · foundation · fox |
| 6 | frame · fungi · grain_patch · granite · granite_bricks · grass_side · grass_top · gravel · hare · heather |
| 7 | heather_dormant · hotbar · iron_ore_vein · item_autumn_mushrooms · item_berries · item_charcoal · item_charcoal_charge · item_clay · item_clay_jar · item_clay_jar_charge |
| 8 | item_cloak · item_coal · item_cooked_fish · item_cooked_food · item_copper_charge · item_copper_ingot · item_copper_ore · item_copper_pick · item_dried_fish · item_dried_food |
| 9 | item_dried_food_charge · item_firestarter · item_fish_bait · item_fish_drying_charge · item_grain · item_grain_seed · item_iron_axe · item_iron_ingot · item_iron_ore · item_quartz |
| 10 | item_raw_fish · item_raw_food · item_smelt_charge · item_spring_greens · item_vessel · item_water_clean · item_water_raw · leaves · leaves_autumn · leaves_spring |
| 11 | leaves_winter · lichen · limestone · limestone_bricks · litter · log_side · log_top · loose_stone · mature_tree · meadow_grass |
| 12 | meadow_grass_dormant · moss · nettle · nettle_dormant · oak_leaves · oak_leaves_autumn · oak_leaves_spring · oak_leaves_winter · oak_log_side · oak_log_top |
| 13 | oak_planks · pantry · peat · pine_leaves · pine_log_side · pine_log_top · pine_planks · planks · podzol · quartz_vein |
| 14 | reeds · roof · sand · sandstone · sandstone_bricks · sapling · scree · sedge · sedge_dormant · selected |
| 15 | slate · slate_bricks · snow · soil · spring_greens · spring_greens_dormant · stairs · sticks · stone · storage |
| 16 | thatch · torch · tree_seedling · vent · wall · water · weir_stone · wildflowers · wildflowers_dormant · willow_leaves |
| 17 | willow_leaves_autumn · willow_leaves_spring · willow_leaves_winter · willow_log_side · willow_log_top · willow_planks · window · young_tree · (two unused) |
### Packaging, security and verification boundary
Every named PNG/OBJ still passes through `writeAsset`, `relativeName`, `safePath`
and the existing guarded staging directories. Input/output links and junctions
remain refused; sheet and PNG memory are bounded. Minimal-source generation and
integrity-verified packaging check every new atlas and every mesh byte. No new
dependencies, secrets or certificates; SHA256 is integrity, PNG CRC32 a checksum,
neither an authenticity guarantee. This applies CodeGuard secret avoidance and
bounded safe-file handling without introducing a network/security subsystem.
The focused suites now cover exact independent manifests, actual content icon
coverage, approved leaf hash, rock versus brick structure, distributed turf,
aggregate soil/root transitions, multi-radius growth rings, unique palettes/masks,
understory dormancy, species dimensions/architecture, all 29 flora meshes' finite
indexed outward nondegenerate triangles, flat normals, closed oriented manifold
parts, positive volume, UV insets, every seasonal woody-part preservation, exact
atlas source pixels, complete package bytes, independent PNG decode and sorted
sheet cells. Larger species have explicit 640-vertex/1200-triangle ceilings;
legacy budgets and all topology assertions remain. The bounds are real serialized
geometry, not assumed from profile labels.
Node-only validation and contact-sheet inspection do **not** certify engine
rendering, growth, seasonal swaps, mapgen, collision, performance or live-world
acceptance. No engine was launched. Optional codec regeneration requires its
existing configured tool; Ogg framing/CRC/hash checks remain part of the unchanged
media suite.
Latest extension verification: **35 passed, 0 failed, 0 cancelled, 0 skipped**,
exit 0, empty stderr, **43.87 seconds** with the absolute Node **24.22.0**
executable. All five JavaScript/test modules passed syntax checks. The generated
**168-image 320×544** sheet was inspected separately. Optional codec
decode/regeneration was not configured; Ogg framing/CRC/hash checks passed.
Windows file-symlink privilege was unavailable; directory-junction refusal passed.
<details>
<summary>Historical pre-extension art snapshot (superseded counts, geometry and sheet order)</summary>
The following records the earlier 95-sprite/six-mesh milestone only. In particular
its old masonry base stone, broad earth bands, 160-tile limit, contact-sheet order,
unchanged generic bark statement and latest-run counts are no longer current.
Use the authoritative extension contract above for generation and integration.
## Direction and authorship
An original, quiet 32px pixel-art system: a naturalist's field journal left open
beside a warm hearth. Warm bark, layered earth, weathered stone and sage foliage
form the world; moonlit Mocha shadows hold it together. Mauve, teal and pale gold
are small points of interest, **not neon terrain fills**.
The design was checked against eom.dev's local
[Catppuccin stylesheet](../../www/htdocs/static/catppuccin.css) and
[home stylesheet](../../www/htdocs/static/home.css): Mocha base/mantle,
restrained raised surfaces, mauve accents and pale text. These are color/design
references, not borrowed illustrations. All pixel silhouettes, material motifs,
meshes and UV layouts here are newly authored in
[world-art.mjs](../scripts/world-art.mjs), under the project's
[MIT license](../LICENSE.txt). No downloaded art, animal model, font, image
library, runtime dependency, network fetch, random seed or generative-image
output is involved.
### Palette hierarchy
| Role | Palette anchors | Use |
| --- | --- | --- |
| Journal shadows | `#181825`, `#1e1e2e`, `#313244`, `#45475a` | Outlines, deep joints, interface |
| Warm ground | `#443a38`, `#665044`, `#876957`, `#ad7e68` | Earth strata, clay banks, plaster |
| Bark and cut timber | `#433b36`, `#685346`, `#92765a`, `#ad916b`, `#c6ad83` | Fissures, growth rings, sawn boards |
| Botanical greens | `#384c43`, `#556e55`, `#7d9570`, `#a4b28b` | Sage leaves with dark undersides |
| Spring growth | `#526b50`, `#8fa67c`, `#bac59a`, `#c19caa` | Fresh sage blades, sparse dusty-pink buds |
| Autumn canopy | `#64454c`, `#a26c49`, `#b59458`, `#845365` | Muted wine shadows, copper and ochre clusters; no green |
| Thatch and grain | `#766848`, `#a39061`, `#c6b581` | Overlapping straw and ripe seed heads |
| Water and iron | `#334952`, `#425e66`, `#638187`, `#909f9f` | Quiet ripples and small cool metal edges |
| Reserved accents | `#cba6f7`, `#94e2d5`, `#f9e2af`, `#fab387` | Flowers, selection, small sparks; never biome fills |
The complete immutable `PALETTE` export is authoritative. Each tile uses a small
subset. Plain dark pixels are intentional visual rest, not missing detail.
### Pixel grammar
- Exactly 32×32 logical pixels; nearest-neighbor filtering, no antialiasing.
- Connected 2–8px color clusters instead of independent hash/static noise.
- Material-specific geometry: irregular stone courses, broad soil layers, long
bark fissures, concentric end-grain, peg-and-joint planks, timber braces,
plaster cracks, tied overlapping thatch, inset storage and pantry jars.
- Light comes from upper left; edges catch warm parchment or cool moonlight.
- Plants have stems, paired leaves, distinct negative spaces and intentional
asymmetric silhouettes. Fruit is a mauve cluster, not a glowing block.
- Food and tools read by silhouette first. Charges retain the input ingredient
and add a diagonal binding and pale-gold paper tag; do not confuse a queued
process with a finished resource. Clean water has a crisp sparkle; raw water
has muted suspended marks as well as a different color.
- Water is static, fully opaque source art with sparse horizontal wavelets. Its
opposite edges match on both axes. Apply the game's desired liquid alpha in
node configuration. Sand/clay strata match left/right edges. Not every
decorative building tile promises identical edge pixels on all four sides.
## Generation and contracts
[generateAssets](../scripts/assets.mjs) always bundles the complete named atlas,
three animal meshes, and the contact sheet. It does **not** discover named assets
by parsing real gameplay declarations or opening named source binaries. Minimal
fixture trees containing only mod Lua still package correctly. Existing bounded
Lua color scanning remains for compatibility swatches. Those now use subdued
earth-tinted clusters, not noisy neon squares; semantic names below are the
intended integration target.
Every output still passes through `writeAsset`, `relativeName` and `safePath`.
Source symlinks/junctions, linked output ancestors and unsafe relative paths
remain refused. PNG dimensions/pixels and review-sheet tile count are bounded.
No credentials, certificates, insecure cryptography or new downloads were
introduced; PNG CRC32 is a file checksum, not a security primitive. This follows
the reviewed CodeGuard secret avoidance and bounded/safe-file-handling rules.
### Public pure exports
- `worldArt()` returns a fresh ordered `Map<filename, {width, height, data}>`.
`data` is straight-alpha RGBA `Uint8Array`; all tiles are 32×32.
- `WORLD_NAMES`, `PLANT_NAMES`, `SEASONAL_PLANT_NAMES`, `ITEM_NAMES`, `ANIMAL_NAMES`, `PALETTE` and `SIZE`
expose the fixed manifest and palette without accessing the filesystem.
- `animalOBJ(species)` returns deterministic original OBJ text; unknown species
are rejected. `animalBounds(species)` returns node-unit `min`, `max` and the
expected `visualSize`.
- `contactSheet()` returns a 320px-wide RGBA review atlas at **full native 32px cell
resolution**, not downsampled thumbnails. Height is `ceil(tileCount / 10) × 32`
(currently 320px). It accepts 1–160 validated tiles, including every seasonal
sprite and both picked-food icons. The 64×32 tree atlases remain separate;
their seasonal leaf drawings are reviewed in the corresponding sprite cells.
- `artContactSheetPNG()` in the asset generator exports its encoded PNG bytes.
`png(width, pixelCallback, height = width)` now supports bounded rectangular
output while preserving square callers.
### Output locations
Under any generated destination:
- `mods/hearth/textures/hearth_<world-name>.png`
- `mods/hearth/textures/hearth_item_<item-name>.png`
- `mods/hearth/textures/hearth_{hare,fox,deer}.png`
- `mods/hearth/models/hearth_{hare,fox,deer}.obj`
- `mods/hearth/textures/hearth_{hotbar,selected,crosshair}.png`
- `mods/hearth/textures/hearth_art_contact_sheet.png`
No binary assets need to be checked into the source tree. Normal packaging calls
the generator and includes these outputs in its existing integrity manifest.
## Complete named-art manifest / contact-sheet legend
The review sheet contains **95 distinct images**, plus the generated sheet
itself. There are ten columns, ten rows; rows/columns below are **one-based**.
Unused cells in the last row are checker paper. World identifiers use
`hearth_` + identifier + `.png`; item identifiers use `hearth_item_` + identifier
followed by `.png`. Interface and animal identifiers use `hearth_` + identifier
followed by `.png`.
| Row | Columns 1 → 10 |
| --- | --- |
| 1 — world | stone · soil · grass_top · grass_side · sand · log_top · log_side · leaves · planks · wall |
| 2 — construction | roof · thatch · foundation · frame · stairs · door · window · vent · storage · pantry |
| 3 — plants / clay | reeds · berry_bush · berry_bush_bare · sapling · grain_patch · crop_1 · crop_2 · crop_3 · crop_4 · clay_bank |
| 4 — resources / equipment | iron_ore_vein · water · fire · fire_lit · torch · sticks · loose_stone · bedroll · drying_rack · fish_trap |
| 5 — ecology | weir_stone · meadow_grass · wildflowers · fern · litter · fungi · deadwood · tree_seedling · young_tree · mature_tree |
| 6 — seasonal world | dung · leaves_spring · leaves_autumn · leaves_winter · meadow_grass_dormant · fern_dormant · wildflowers_dormant · spring_greens · spring_greens_dormant · autumn_mushrooms |
| 7 — patch, then items | **world:** autumn_mushrooms_dormant · **items:** berries · grain · grain_seed · raw_food · cooked_food · fish_bait · raw_fish · cooked_fish · fish_drying_charge |
| 8 — items | dried_fish · iron_ore · iron_ingot · charcoal · charcoal_charge · smelt_charge · clay · clay_jar · clay_jar_charge · dried_food |
| 9 — items, then UI | **items:** dried_food_charge · cloak · vessel · water_raw · water_clean · firestarter · iron_axe · spring_greens · autumn_mushrooms · **UI:** hotbar |
| 10 — UI / animal UV atlases | selected · crosshair · hare · fox · deer · (five unused cells) |
Counts: **61 world/ecology textures, 28 craftitem icons, 3 interface textures,
and 3 animal UV atlases: 95 total**. Existing four tool icons, the 64px player skin,
color-compatibility textures, original player/tool models and audio remain
separate; they are not silently included in this sheet's numbering.
### Alpha and main-agent node integration
- `leaves`, `reeds`, `berry_bush`, `berry_bush_bare`, `sapling`, `grain_patch`,
all four `crop_*`, `meadow_grass`, `wildflowers`, `fern`, `litter`, `fungi`,
`deadwood`, `tree_seedling`, `young_tree`, `mature_tree`, `dung`: crisp 0/255
alpha, zero RGB beneath transparent pixels. Use clip-compatible leaf/glasslike
or appropriate plantlike drawtypes; ground litter/deadwood may suit a low mesh
or nodebox better. Mature-tree art is a silhouette, **not** a new tree-building
algorithm. Ecology controls growth, placement and drops independently.
- All craftitems: crisp 0/255 alpha and empty outer borders. Set
`inventory_image = "hearth_item_<item>.png"` individually rather than tinting
one color square. Processing charges have their own distinct icons.
- `window`: empty outside, opaque frame/mullions, alpha-95 glass. Use blend
alpha and a suitable glass/nodebox drawtype, not opaque rendering.
- `fire`, `fire_lit`, `torch`, `sticks`, `loose_stone`, `bedroll`, `drying_rack`,
`fish_trap`: transparent silhouettes. Select clip-compatible rendering or use
them as inventory art for physical nodeboxes; an opaque cube will look wrong.
Fire is one static drawing, not an animation strip. Emission/light and flame
particles remain gameplay decisions.
- Remaining world materials and animal atlases: fully opaque. `water` source
art is opaque deliberately; liquid transparency must be configured by main.
- Six cube face ordering for logs: use `log_top` on the two ends and `log_side`
on four sides; grass uses `grass_top`, `soil` bottom and `grass_side` sides.
Do not continue multiplying these authored textures by saturated old tints.
- `hearth_hotbar.png` is a **single slot tile**, not a prebuilt eight-slot strip.
Main must repeat it into its chosen hotbar layout or use individual slot HUD
images; directly stretching it across the whole bar would stretch the border.
`hearth_selected.png` is a transparent gold/mauve slot outline.
`hearth_crosshair.png` has sparse parchment marks and a Mocha contrast outline.
Source pixels remain 32px; HUD scale/offset is main's responsibility.
## Original wildlife meshes — integration handoff
No external model or flat animal billboard. Hare has deep haunches, grounded
hind feet, long upright ears and a small cream tail. Fox has dark slim legs,
pointed ears, cream bib/muzzle and a long tapered, pale-tipped brush. Deer has
long separated legs, dark hooves, raised neck, broad ears and branched antlers.
These are static low-poly models, **not animated B3D rigs**; movement/idle logic
must not request player animation frames on them.
Select `visual="mesh"`, the species OBJ and the matching PNG texture, with
`visual_size={x=1,y=1}`. Mesh coordinates use **10 model units per node**, just
like the existing player contract. Feet are Y=0, origin at ground center,
forward is **+Z**. Do **not** apply another 10× scale. Each model is one texture,
with no external MTL/image references. Eight semantic 8px atlas patches use
half-texel inset UVs; stable indexed, outward-wound triangles avoid sampling
neighboring material patches. Use nearest-neighbor texture filtering.
| Model / matching texture stem | Vertex / triangle count | Min bounds (nodes) | Max bounds (nodes) |
| --- | --- | --- | --- |
| `hearth_hare` | 112 / 168 | (-0.185, 0, -0.370) | (0.185, 0.848159, 0.429500) |
| `hearth_fox` | 128 / 192 | (-0.155, 0, -0.820375) | (0.155, 0.786649, 0.592000) |
| `hearth_deer` | 192 / 288 | (-0.280, 0, -0.492526) | (0.280, 1.510000, 0.675000) |
`animalBounds()` is authoritative at full precision. These are **visual bounds**,
including ears/tails/antlers, not prescribed gameplay collision boxes. Main
should choose comfortable body collision/selection boxes, ground-origin spawn
offsets and facing/yaw conversion. Review the import orientation in-engine before
claiming a verified visual integration. This work does not edit wildlife Lua.
### Preserved player/media contract
The original 64×64 player skin, B3D bytes, skeleton, UVs and six animation clips
are unchanged. Existing held tools, compatibility OBJ and fifteen Ogg assets
are unchanged. The new animals do not reuse or disturb the player's atlas.
## Verification
Focused Node-only checks live in [world-art.test.mjs](../tests/world-art.test.mjs),
[flora-art.test.mjs](../tests/flora-art.test.mjs)
and the unchanged [assets.test.mjs](../tests/assets.test.mjs). No engine tests,
live worlds, native client launch or full application test gates are required or
claimed by this art task.
The tests cover exact named manifests; all 95 distinct byte-deterministic tiles;
restricted palettes; color-independent material geometry and contiguous clusters;
plant/item occupancy, negative space, alpha and inventory padding; monotonic
crop growth; berries versus bare foliage; water seams and sand/clay strata seams;
independent PNG signature/chunk/CRC/RGBA decoding; rectangular atlas dimensions
and exact unscaled cells; bounded PNG/contact-sheet input rejection; animal
topology, finite coordinates, nondegenerate outward triangles, UV patch bounds,
counts, species proportions and node-unit bounds; minimal fixture bundling; and
linked-output refusal. Existing tests additionally verify deterministic real-source
regeneration, fixture package integrity, source traversal limits, player B3D and
audio contracts.
Validation limitations remain explicit: optional codec decode/regeneration is not
run without its configured tool; Ogg CRC/framing/hash checks still run. File
symlink creation may be unavailable on Windows; directory-junction refusal is
always checked. Geometry tests and the pixel contact sheet are not a claim that
animal models have been rendered or that Lua references have been migrated.
Historical pre-season baselines were 19 tests for world/media art and 24 with
volumetric flora. The seasonal verification section below supersedes those
counts. No Lua or other source files are owned by this art task.
## Volumetric ecology trees — refinement
The newer ecology trees supersede the billboard recommendation above for
`tree_seedling`, `young_tree`, `mature_tree` and `deadwood` only. Original pure
generation lives in [flora-art.mjs](../scripts/flora-art.mjs), under the same MIT
license. No downloaded vegetation, tree-block construction, external MTL or new
dependency is used. Meadow grass, wildflowers, ferns and other ground sprites
keep their existing plantlike presentation.
`floraOBJ(name)` generates six deterministic OBJ models; `floraBounds(name)`
returns their actual serialized vertex bounds in node units. `FLORA_NAMES` is
the fixed manifest (four original models plus two bare winter trees). Each leafy
standing tree has a tapered eight-sided stem,
separate visible boughs and intersecting closed broadleaf lobes: two for the
seedling, four for the young tree, five for the mature tree. Three uneven rings
and offset tips per lobe produce asymmetric faceted silhouettes from every
direction, rather than a giant cube or crossed planes. Deadwood has a low fallen
trunk, two broken side branches and a fork. Every face is triangulated with an
explicit outward flat normal. Nearest-neighbor filtering preserves the pixel
marks on the three-dimensional surfaces.
### Texture and packaging contract
`treeAtlas(art = worldArt(), season = 'summer')` returns fresh **64×32 straight-alpha RGBA** data. The left 32px
patch is the original `log_side` bark drawing; the right patch is the original
`leaves` drawing over opaque `leafDark` underpainting. All alpha bytes are 255
deliberately: spaces between volumetric lobes, not punched-out polygon surfaces,
define the canopy. Each triangle stays within its bark/leaf patch with half-texel
UV insets. One texture and no `usemtl`/`mtllib` loading are required.
[generateAssets](../scripts/assets.mjs) always writes these through the existing
guarded `writeAsset`, including when the source has only minimal mod Lua:
- `mods/hearth/models/hearth_tree_seedling.obj`
- `mods/hearth/models/hearth_young_tree.obj`
- `mods/hearth/models/hearth_mature_tree.obj`
- `mods/hearth/models/hearth_deadwood.obj`
- `mods/hearth/models/hearth_young_tree_winter.obj`
- `mods/hearth/models/hearth_mature_tree_winter.obj`
- `mods/hearth/textures/hearth_tree_atlas.png`
- `mods/hearth/textures/hearth_tree_atlas_spring.png`
- `mods/hearth/textures/hearth_tree_atlas_autumn.png`
- `mods/hearth/textures/hearth_tree_atlas_winter.png`
Normal packaging includes all ten in its integrity manifest. They are generated
outputs, not checked-in binaries; the review sheet grows dynamically with sprites.
### Scale, ground and exact visual bounds
Coordinates use **10 model units per node**, node-center origin, with ground
exactly **Y = -5 model units = -0.5 nodes**. Use `drawtype="mesh"`,
`visual_scale=1`, the corresponding OBJ and `tiles={"hearth_tree_atlas.png"}`.
No additional scale or vertical offset is needed.
| Model stem | Vertices / triangles | Minimum (nodes) | Maximum (nodes) | Height (nodes) |
| --- | --- | --- | --- | --- |
| `hearth_tree_seedling` | 84 / 152 | (-0.25, -0.5, -0.175) | (0.241, 0.2, 0.1375) | 0.7 |
| `hearth_young_tree` | 168 / 304 | (-0.61, -0.5, -0.56) | (0.626, 1.5, 0.45) | 2.0 |
| `hearth_mature_tree` | 210 / 380 | (-1.04, -0.5, -1.04) | (1.064, 3.0, 1.064) | 3.5 |
| `hearth_deadwood` | 64 / 112 | (-0.4044987, -0.5, -0.8019998) | (0.4034683, -0.0422183, 0.8016249) | 0.4577817 |
These are visual bounds, not canopy-sized obstacles. The ecology registration
loop alone selects mesh rendering and disallows replacement for all four.
Young and mature trees are walkable with narrow trunk collision/selection boxes:
(-0.12, -0.5, -0.12) to (0.12, 0.6, 0.12) for young trees and to
(0.12, 1.2, 0.12) for mature trees. Deadwood has a low central-trunk box;
seedlings remain nonwalkable. The semantic-art registration helper preserves
explicit `def.tiles`; the loop supplies its mesh atlas directly. No
`override_item` API or mock is required.
Growth clearance, succession logic and canopy obstruction policy remain outside
this art change; no changes were made to the succession module.
## Seasonal flora — exact integration contract
The authoritative runtime declarations are
[seasons.lua](../mods/hearth/seasons.lua) and
[content.lua](../mods/hearth/content.lua), documented in
[ecosystems.md](ecosystems.md). All following files are generated into the
destination's texture/model directories, never written into source by packaging.
`TREE_SEASONS` exports `summer`, `spring`, `autumn`, `winter`; unsupported values
fail closed. Summer's atlas pixels and all four original meshes are unchanged.
### Trees and legacy leaf nodes
| Season | Mesh-tree texture | Legacy leaf-node texture | Young / mature mesh |
| --- | --- | --- | --- |
| Spring | `hearth_tree_atlas_spring.png` | `hearth_leaves_spring.png` | Existing leafy meshes |
| Summer | `hearth_tree_atlas.png` | `hearth_leaves.png` | Existing leafy meshes |
| Autumn | `hearth_tree_atlas_autumn.png` | `hearth_leaves_autumn.png` | Existing leafy meshes |
| Winter | `hearth_tree_atlas_winter.png` | `hearth_leaves_winter.png` | `hearth_young_tree_winter.obj`, `hearth_mature_tree_winter.obj` |
Every atlas is **64×32, fully opaque RGBA**: unchanged bark left, seasonal
botanical clusters right. Spring uses fresh sage with sparse dusty buds; Summer
retains deep sage; Autumn uses copper, ochre and muted wine with **no green**.
Winter's right patch contains warm woody twigs over bark-dark underpaint, not
green or snow-painted foliage. Legacy 32×32 leaf textures retain crisp 0/255
alpha; Winter is sparse interlacing twig forks with transparent negative space.
Both winter meshes contain **only trunk, boughs and lateral woody forks**. Every
original trunk/bough vertex, UV, normal and triangle is retained exactly; crowns
are omitted and each original bough gains one outward lateral fork. They stay
within the original leafy clearance envelopes, at ground Y=-0.5 nodes, scale 1,
10 model units/node. Every winter triangle samples only the left bark half.
| Model | Vertices | Triangles | Leafy triangles |
| --- | --- | --- | --- |
| Young winter | 112 | 196 | 304 |
| Mature winter | 144 | 252 | 380 |
Seedlings intentionally reuse `hearth_tree_seedling.obj` in **all seasons**, with
the corresponding atlas, exactly as runtime requests. There is no unreferenced
winter seedling mesh. Deadwood remains on its original mesh and summer atlas.
No additional summer variant, snow overlay or tree-block construction is added.
### Ground flora and seasonal food
All sprites/icons below are **32×32 straight-alpha RGBA**, with crisp 0/255
alpha and zero hidden RGB. Picked-item icons additionally have empty outer
borders. Dormant flora uses straw/bark/earth, **no green and no blooms**, and has
reduced, visibly different silhouettes rather than a palette-only recolour.
| Runtime node(s) | Generated texture(s) | Appearance / availability |
| --- | --- | --- |
| `meadow_grass_winter` | `hearth_meadow_grass_dormant.png` | Bent dry grass blades in Winter; original living art otherwise |
| `fern_winter` | `hearth_fern_dormant.png` | Curled spent fronds in Winter; original living art otherwise |
| `wildflowers_autumn`, `wildflowers_winter` | `hearth_wildflowers_dormant.png` | Spent stalks/seed heads, no bloom; original bloom in Spring/Summer |
| `spring_greens_patch` | `hearth_spring_greens_dormant.png` | Low dry rosette outside Spring |
| `spring_greens_patch_spring` | `hearth_spring_greens.png` | Broad fresh basal blades with veins; available only in Spring |
| `autumn_mushrooms_patch` | `hearth_autumn_mushrooms_dormant.png` | Low litter/mycelial trace without pickable caps outside Autumn |
| `autumn_mushrooms_patch_autumn` | `hearth_autumn_mushrooms.png` | Three staggered copper/ochre caps and pale stalks; available only in Autumn |
Picked craftitems `hearth:spring_greens` and `hearth:autumn_mushrooms` use
`hearth_item_spring_greens.png` and `hearth_item_autumn_mushrooms.png` respectively:
a bound greens bundle and gathered mushrooms. **Patch node identifiers are not
texture stems**: no `_patch.png` aliases are generated. Existing `fungi` remains
visually distinct decomposer art, not seasonal edible forage. These are original
fictional food drawings, not real-world plant/mushroom identification guidance.
The generator adds **15 PNGs and two OBJs**: three seasonal atlases, three leaf
textures, three dormant ground textures, four forage patch textures, two item
icons and two winter meshes. All names are bundled even from minimal Lua-only
fixture sources and included in the existing SHA256 package integrity inventory.
No Lua callbacks, harvest cooldowns, seasonal authority, metadata or placement
rules are changed. There are no new dependencies, downloads, credentials or
external material paths; existing bounded safe-path writers are retained.
### Seasonal verification
[flora-art.test.mjs](../tests/flora-art.test.mjs) checks all six models with the
unchanged finite/indexed/nondegenerate/outward-normal, positive-volume and closed
oriented-manifold-per-part validations. Added checks cover wood-only winter UVs,
fewer triangles, outward lateral forks, original trunk/bough geometry preservation,
clearance envelopes, all four opaque atlases and byte-exact bark. Generated PNG
bytes, exact OBJ text and complete package integrity are verified from fixtures.
The current Lua explicit-tile wiring and separate patch/item stem contract are
read-only assertions, not a Lua execution or engine test.
[world-art.test.mjs](../tests/world-art.test.mjs) checks the exact 95-image
manifest, determinism/uniqueness, restrained palettes, alpha/masks/padding,
Spring buds, non-green Autumn leaves, bare Winter twigs, bloom-free dormancy,
forage/decomposer silhouette differences, dynamic sheet dimensions and every
seasonal sprite cell. Existing material, animal, PNG and safe-output tests stay.
Run all three focused asset suites, including the unchanged
[assets.test.mjs](../tests/assets.test.mjs), with the absolute Node 24.22.0
executable. Optional codec regeneration is not required: existing Ogg
framing/CRC/hash tests remain active. These checks validate pixels, topology and
packaging, **not an in-engine seasonal visual acceptance pass**. No full engine
tests or concurrent engine launches are part of this art task.
Latest seasonal run: **27 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0,
empty stderr, **37.37 seconds**, using the absolute Windows Node 24.22.0
executable. All five owned JavaScript/test modules passed `node --check` with
exit 0 and empty stderr. The 95-image **320×320** generated contact sheet was
inspected separately. Optional codec decode/regeneration was not configured;
Ogg framing/CRC/hash checks passed. Windows file-symlink privilege was unavailable;
directory-junction refusal passed (the suite reported no skipped tests).
</details>
</details>
Executable
+241
View File
@@ -0,0 +1,241 @@
# Botany: 80 temperate taxa and integration contract
## 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.
Taxonomic/reference authorities for maintaining these names and broad habitats:
- **P**: [BSBI/UKCEH Plant Atlas 2020](https://plantatlas2020.org/) for vascular-plant regional distributions, habitat and nomenclature; [Kew Plants of the World Online](https://powo.science.kew.org/) for name/synonym checks. Kew blocked automated access during this implementation; no claim of an automated 80-record authority audit is made.
- **T**: [Woodland Trust British tree accounts](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/british-trees/a-z-of-british-trees/) and [plant accounts](https://www.woodlandtrust.org.uk/trees-woods-and-wildlife/plants/), complementary common names and woodland niches.
- **W**: [The Wildlife Trusts wildflower accounts](https://www.wildlifetrusts.org/wildlife-explorer/wildflowers), including freshwater plants.
- **M**: British Bryological Society, *Mosses and Liverworts of Britain and Ireland: A Field Guide* (2010), and *Atlas of British & Irish Bryophytes* (2014), for the five moss names/niches. Online BBS access also blocked automated retrieval; these are bibliographic authorities, not downloaded records.
- **F**: [British Mycological Society English names](https://www.britmycolsoc.org.uk/resources/english-names), [Index Fungorum](https://www.indexfungorum.org/), and [First Nature fungal accounts](https://www.first-nature.com/fungi/). Particularly [fly agaric](https://www.first-nature.com/fungi/amanita-muscaria.php) for root-host associations, [birch polypore](https://www.first-nature.com/fungi/piptoporus-betulinus.php) for birch/deadwood associations and its modern name, and [wood blewit](https://www.first-nature.com/fungi/lepista-nuda.php) for litter decomposition and late fruiting.
- **L**: [British Lichen Society species accounts and maps](https://britishlichensociety.org.uk/resources/species-accounts) and its [Lichens of Great Britain and Ireland](https://britishlichensociety.org.uk/identification/lgbi3). Names and species boundaries evolve; common lichen names are not universally standardized.
The numeric light/moisture/pH/depth windows below are **explicit game approximations**, not measured ecological tolerances, identification keys, conservation advice or a scientific simulation. The catalogue selects named species rather than inventing generic “magic herbs”. *Rubus plicatus* is one real blackberry species, not the *Rubus fruticosus* aggregate counted as a species. Meadow fescue *Schedonorus pratensis* is also encountered as *Festuca pratensis* / *Lolium pratense*. Wood blewit *Collybia nuda* is widely recorded as *Lepista nuda* / *Clitocybe nuda*. Birch polypore *Fomitopsis betulina* replaces the familiar *Piptoporus betulinus*. These synonyms are not extra entries.
**No real-world foraging, medicinal or consumption claims are provided.** Only three fictional bush outputs use the existing game item `hearth:berries`. This is a gameplay simplification, not a statement that a wild plant or lookalike is safe. Every fungus, herb, tree and lichen has `food=false`; toxic/stinging examples are flagged but `toxic=false` is **not** a safety certification. `browse` describes wildlife relationships, not player edibility. No new food items, recipes, poisoning system or damage to player plants are registered.
## 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.
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.
### trees (10; P/T)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| oak | Pedunculate oak | Quercus robur | E/D | Mesic woodland; ectomycorrhizal roots |
| birch | Silver birch | Betula pendula | E/D | Light acid pioneer woods |
| pine | Scots pine | Pinus sylvestris | E/D | Open acid woods; resin |
| willow | White willow | Salix alba | E/D | Wet alluvial woods; spring flowers |
| alder | Common alder | Alnus glutinosa | E/D | Wet woods; Frankia-associated nitrogen fixation |
| beech | European beech | Fagus sylvatica | D | Well-drained woodland, avoiding saturation |
| aspen | European aspen | Populus tremula | D | Light woodland gaps |
| rowan | Rowan | Sorbus aucuparia | D | Cool acid woodland/edges; flowers |
| hazel | Common hazel | Corylus avellana | D | Woodland understorey; ectomycorrhiza |
| spruce | Norway spruce | Picea abies | D | Moist acid conifer woods; resin |
### shrubs (10; P/T)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| bilberry | Bilberry | Vaccinium myrtillus | B | Acid heath/woodland; ericoid, not ectomycorrhizal |
| blackberry | Pleated blackberry | Rubus plicatus | B | Hedgerows/wood edges |
| raspberry | Red raspberry | Rubus idaeus | B | Woodland clearings |
| elder | Black elder | Sambucus nigra | B | Hedgerows; no game food |
| blackthorn | Blackthorn | Prunus spinosa | B | Thorny hedges; spring flowers |
| hawthorn | Common hawthorn | Crataegus monogyna | B | Hedgerows; spring flowers |
| dog_rose | Dog rose | Rosa canina | B | Open scrub; summer pollen |
| holly | European holly | Ilex aquifolium | B | Evergreen woodland cover; no game food |
| juniper | Common juniper | Juniperus communis | B | Open dry heath/scrub; resin |
| guelder_rose | Guelder rose | Viburnum opulus | B | Damp hedges/wet edges; no game food |
### grasses (10; includes sedges, rushes and reedmace; P)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| sedge | Greater tussock-sedge | Carex paniculata | E | Wet bank tussocks |
| bulrush | Bulrush (great reedmace) | Typha latifolia | E | Reedbed margin; bank-cell approximation |
| meadow_fescue | Meadow fescue | Schedonorus pratensis | B | Moist neutral grassland |
| cocksfoot | Cock's-foot | Dactylis glomerata | B | Mesic meadow/edge |
| timothy | Timothy | Phleum pratense | B | Moist meadow |
| sweet_vernal_grass | Sweet vernal-grass | Anthoxanthum odoratum | B | Acid grassland |
| yorkshire_fog | Yorkshire-fog | Holcus lanatus | B | Mesic grassland |
| tufted_hairgrass | Tufted hair-grass | Deschampsia cespitosa | B | Wet meadow tussocks |
| common_reed | Common reed | Phragmites australis | B | Wet banks/reedbed edges |
| soft_rush | Soft rush | Juncus effusus | B | Wet pasture/bog margin |
### herbs (10; P/T/W)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| clover | White clover | Trifolium repens | E | Open grassland; rhizobial nitrogen fixation |
| nettle | Common nettle | Urtica dioica | E | Nutrient-rich damp edges; larval host, not nectar |
| heather | Heather | Calluna vulgaris | E | Acid heath; late-season flowers |
| bluebell | English bluebell | Hyacinthoides non-scripta | E | Spring woodland floor |
| yarrow | Yarrow | Achillea millefolium | B | Meadow; summer/autumn flowers |
| foxglove | Common foxglove | Digitalis purpurea | B | Acid clearings; no game food |
| meadowsweet | Meadowsweet | Filipendula ulmaria | B | Wet meadow; pollen resource |
| wild_garlic | Wild garlic | Allium ursinum | B | Spring moist woodland; no game food |
| cowslip | Cowslip | Primula veris | B | Base-rich open grassland |
| wood_anemone | Wood anemone | Anemone nemorosa | B | Spring woodland floor; no game food |
### ferns_mosses (10; five ferns P/T, five mosses M)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| bracken | Bracken | Pteridium aquilinum | B | Acid slopes/clearings; competition descriptor |
| lady_fern | Lady-fern | Athyrium filix-femina | B | Damp shaded woods |
| male_fern | Male-fern | Dryopteris filix-mas | B | Shaded woods |
| harts_tongue | Hart's-tongue fern | Asplenium scolopendrium | B | Damp base-rich shade |
| polypody | Common polypody | Polypodium vulgare | B | Acid rock/bark/woodland |
| sphagnum | Blunt-leaved bog-moss | Sphagnum palustre | B | Saturated acid bog/wet woodland |
| haircap_moss | Common haircap | Polytrichum commune | B | Damp acid soils |
| moss | Cypress-leaved plait-moss | Hypnum cupressiforme | E | Shady soil, wood, bark and rock |
| fork_moss | Broom fork-moss | Dicranum scoparium | B | Acid woodland/heath |
| star_moss | Swan's-neck thyme-moss | Mnium hornum | B | Damp woodland banks |
### fungi (10; F)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| fly_agaric | Fly agaric | Amanita muscaria | B | Ectomycorrhizal: birch/pine/spruce |
| birch_bolete | Brown birch bolete | Leccinum scabrum | B | Ectomycorrhizal: birch only in this model |
| bay_bolete | Bay bolete | Imleria badia | B | Ectomycorrhizal: conifers/beech/oak |
| chanterelle | Chanterelle | Cantharellus cibarius | B | Ectomycorrhizal woodland roots |
| wood_blewit | Wood blewit | Collybia nuda | B | Litter saprotroph; autumn/winter |
| shaggy_inkcap | Shaggy inkcap | Coprinus comatus | B | Grassland-soil saprotroph |
| turkey_tail | Turkey tail | Trametes versicolor | B | Broadleaf deadwood saprotroph |
| oyster_fungus | Oyster mushroom | Pleurotus ostreatus | B | Broadleaf deadwood; late-season fruiting |
| honey_fungus | Honey fungus | Armillaria mellea | B | Deadwood here; parasitism descriptive only |
| birch_polypore | Birch polypore | Fomitopsis betulina | B | Birch deadwood here; parasitism descriptive only |
### aquatic (10; P/W)
| id | Common name | Scientific name | Owner | Placement niche |
|---|---|---|---|---|
| duckweed | Common duckweed | Lemna minor | B | Surface; 1–6 depth units |
| yellow_waterlily | Yellow water-lily | Nuphar lutea | B | Surface; 1–3, mud anchor |
| white_waterlily | White water-lily | Nymphaea alba | B | Surface; 1–3, mud anchor |
| water_crowfoot | Common water-crowfoot | Ranunculus aquatilis | B | Surface abstraction; 1–3, mud anchor |
| water_mint | Water mint | Mentha aquatica | B | Wet bank, dry destination |
| water_forgetmenot | Water forget-me-not | Myosotis scorpioides | B | Wet bank, dry destination |
| brooklime | Brooklime | Veronica beccabunga | B | Wet bank, dry destination |
| water_starwort | Common water-starwort | Callitriche stagnalis | B | Submerged abstraction; 1–4, mud anchor |
| hornwort | Rigid hornwort | Ceratophyllum demersum | B | Submerged/rootless; 1–4, water substrate |
| pondweed | Broad-leaved pondweed | Potamogeton natans | B | Surface; 1–3, mud anchor |
### lichens (10; L)
| id | Common name | Scientific name | Owner | Habitat emphasis |
|---|---|---|---|---|
| reindeer_lichen | Reindeer lichen | Cladonia rangiferina | B | Open acid heath; wildlife browse |
| oakmoss | Oakmoss | Evernia prunastri | B | Bark in light woodland |
| old_mans_beard | Old man's beard | Usnea subfloridana | B | Humid bark/branches |
| lichen | Map lichen | Rhizocarpon geographicum | E | Acid siliceous rock, not generic soil |
| dog_lichen | Dog lichen | Peltigera canina | B | Mossy soil; cyanobacterial fixation |
| lungwort_lichen | Lungwort lichen | Lobaria pulmonaria | B | Humid woodland bark; cyanobacterial cephalodia |
| sunburst_lichen | Common orange lichen | Xanthoria parietina | B | Well-lit nutrient-enriched bark/base-rich stone |
| hammered_shield | Hammered shield lichen | Parmelia sulcata | B | Woodland bark |
| cup_lichen | Mealy pixie-cup | Cladonia chlorophaea | B | Acid soil/humus, distinct species below |
| pixie_cup | Pebbled pixie-cup | Cladonia pyxidata | B | Soil/humus; not a duplicate of chlorophaea |
## Exact public interface
Loading the file returns **`factory(hearth, api) -> B`**. The factory has no registration side effects and publishes no globals. Main assigns `hearth.botany` before loading succession. Botany installs no scheduler, globalstep, ABM, LBM, mapgen callback, timer or liquid transformation. [mods/hearth/succession.lua](../mods/hearth/succession.lua) consumes this policy in its existing eight-probe loop.
### 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.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.
- Record fields: `id`, `common`, `latin`, `group`, `node`, `texture`, `existing`, `food`, `toxic`, `seasons` (boolean set), `tags` (boolean set), `niche`; flowering taxa also have `flowering`, the three game-fruit taxa have `fruiting` (both boolean sets).
- `niche` fields: `substrates` set, inclusive `light={min,max}` on 0–15, `moisture={min,max}` on 0–1, `ph={min,max}` in pH units, `water={min,max}` in game node-depth units, `aquatic` string. Some also require `hosts` set or `nutrients` minimum.
### Read-only habitat selection
`B.suitable(value, h, season) -> boolean` and `B.choose(p, h, season) -> bare_node_kind, record | nil` use only supplied observations. `p` must contain finite integer `x,y,z` within ±30990; `h` must have `loaded=true` and finite `light`, `moisture`, `ph`. Missing/invalid facts do not receive optimistic defaults. `season` is exactly `Spring`, `Summer`, `Autumn` or `Winter`.
`h.ground` is a canonical bare/full support node or node table. `h.substrate`, when supplied, overrides the direct substrate for land/bark/deadwood niches. Bark and deadwood are **semantic observations**, not assertions that arbitrary planks/logs are safe recruitment sites. The ecological owner must derive those observations from unmodified natural substrate and enforce owner/protection checks. Do not treat crafted timber or planted trees as free fungal substrate.
`h.host` is a nearby identified tree ID/stage/root association, mandatory for host-specific fungi. Its search radius and finite sampling budget belong to the existing ecological scheduler. Presence of an unrelated tree does not satisfy a birch specialist. `h.nutrients` must be at least one for nettle. pH is a game support-class observation; callers lacking it should explicitly derive a documented approximation from the substrate, not silently infer that all plants fit.
`h.aquatic` is `land`, `bank`, `surface` or `submerged`; land may omit it. `h.water` is **numeric column depth**, not a boolean or permission to replace a water node; land/bank may omit it (zero). The selection represents a candidate only:
| Mode | Required observations | Safe external placement rule |
|---|---|---|
| land | Depth zero; accepted substrate | Loaded air destination above accepted natural substrate |
| bank | Explicit `bank`, depth zero, wet support | Loaded air destination beside real freshwater, never inside/over it |
| surface | Explicit `surface`, `freshwater=true`, allowed positive column depth; accepted mud bottom | Flat pad in a distinct air cell above the actual surface; source node below stays unchanged |
| submerged | Explicit `submerged`, `freshwater=true`, positive column depth; rooted starwort needs mud bottom, rootless hornwort needs `substrate=water` | Succession places a **qualitative shallow-water stand sprite in the separate air cell above conserved source water**. It is not literally rendered inside the liquid. No source replacement, fake liquid, water clearing/refilling or flood-producing liquid flora |
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.
### Interactions and finite effects
- `B.has_tag(value, tag) -> boolean`.
- `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.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.
- `B.nitrogen(value, current) -> suggested_delta`: only nitrogen-fixer-tagged taxa; `min(1,max(0,2-current))`, zero for invalid/negative current. Alder/Frankia, clover/rhizobia and cyanobacterial lichen partners are distinct biological associations. **Ecology owner must apply a per-site contribution/cooldown and metadata cap of two; repeatedly calling this pure query is not permission for unlimited fertilizer.**
- `B.root_fungi(value, tree, budget) -> transfer_ceiling`: only ectomycorrhizal **fungi** matching the supplied host, at most one and at most the finite positive existing budget. A nearby host can receive a bounded establishment advantage through that owner's already-budgeted resource transfer. Caller debits the same budget; the function creates nothing and never independently raises soil nitrogen. Tree ectomycorrhizal tags alone are not free fungal boosts. Saprotrophs receive no root-fungus boost.
`saprotroph` describes recycling existing litter/wood: any later benefit must consume existing substrate, not mint nutrients. `engineer` describes physical substrate/water retention or aquatic cover; it does not grant terraforming. `nursery` means conditional cover/microhabitat. `parasite` and `allelopathy` are **disabled descriptive interactions**: honey fungus/birch polypore do not infect player trees and bracken never kills neighboring plants. The module mutates no ecological state.
### 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.
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.
`B.harvest(pos, player) -> success_boolean, count_or_reason` is the only resource mutation API. Berry callbacks return the original wielded stack. It:
1. Validates finite integer bounded coordinates and a real player; looks up **current** `hearth.construction.allowed(pos, player, "use")` at callback time. Missing policy denies access; no permissive fallback. Existing policy owns reach, claim and protection checks.
2. Re-reads the loaded node and requires its exact canonical three-bush ID, then current `hearth.climate_at().season` and game fruiting season.
3. Checks persisted string metadata `botany_ready_at`, rejecting malformed/negative/nonfinite values. Missing metadata means initial fruit stock; future timestamps deny until ready.
4. Requires inventory room for `hearth:berries 2`; full inventory neither changes stock nor starts cooldown.
5. Locks the bush by writing a finite timestamp **one real day** ahead before inserting at most two berries. This blocks callback reentrancy. A completely rejected insertion restores the old timestamp; partial delivery consumes that cycle, never exceeds two or drops overflow into the world. Unexpected insertion results/errors leave the lock in place rather than duplicate stock.
The factory may precede construction publication, but harvest cannot. Time uses optional injected `api.now()` (numeric Unix seconds, for tests), otherwise `os.time()`. No offline catch-up or accumulating fruit: one ready bush gives at most two per cooldown even after months away, and cooldown metadata survives module reload. New callbacks are installed only on `bilberry`, `blackberry`, `raspberry`; generic existing berry-bush behavior is untouched.
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
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.
`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.
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.
### 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.
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.
`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.
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.
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.
+98
View File
@@ -0,0 +1,98 @@
# Woodland traveler character creator
Use `/character` at any time while connected, or open **Character appearance**
from the inventory. Creation is opt-in: joining restores the saved appearance
without opening the creator. Existing location selection and field-note join
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.
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 |
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.
Transparent `none` layers and colors under absent hair/accents mean the raw
selection combinations are not all visually distinct; no uniqueness count is
claimed.
## Preview and explicit consent
The form contains a real Luanti `model[]` preview, not a screenshot, external
browser or URL. It uses one composited 64 × 64 texture and the selected B3D.
The preview turns continuously and supports mouse dragging; Front, Profile and
Back reset its camera angle. These controls never rotate the actual player.
Changing a dropdown, switching tabs, **Randomize** and **Reset draft** affect
only a private session draft. Randomize chooses from every fixed allowlist using
bounded `math.random`; it is not security randomness and introduces no secrets.
Reset copies the defaults rather than modifying shared state. **Cancel** or
Escape discards the draft, preserving both the real appearance and saved bytes.
Only **Save / Apply** commits. No need, inventory, identity or physics values
are changed. Drafts disappear when the session ends.
## Version-1 storage and recovery
The only new persistent value is the engine-owned player metadata string
`hearth:appearance`. It is independent of the existing global save schema and
world/mod storage. A complete flat JSON object contains exactly `version:1` and
these string selections:
`body`, `skin`, `face`, `eyes`, `hair`, `hairColor`, `beard`, `garment`, `cloth`,
`trousers`, `boots`, `accent`, `accentColor`, `detail`.
Defaults match the [authoritative art contract](art-direction.md): balanced,
sand, calm, hazel, sidepart, chestnut, none, tunic, teal, earth, walnut, scarf,
sage, none. An absent value uses defaults without writing metadata. Invalid,
unknown-version, missing/extra-field, malformed, nested or over-2048-byte values
show safe defaults and a fixed warning. Original corrupt bytes are **not
overwritten**, and appearance errors never kick a player. Only an explicit
successful Apply replaces that value. Back up the world before manual recovery.
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.
The held-tool material remains the existing compatibility texture.
Apply serializes and checks the roundtrip, then writes metadata **before**
publishing `mesh` and `textures`. A thrown/false metadata write or serialization
failure keeps the previous published appearance and saved session selection,
leaving the draft available to retry. Engine metadata durability still follows
normal engine player saving; this is not a new disk transaction service.
## Integration and validation boundary
[The factory](../mods/hearth/character.lua) receives `(hearth, api)`, registers
its own command/receive-fields callback, and exports `apply`, `on_join`, `show`,
`handle`, `defaults`, `options`, `validate`, `decode`, `encode`, `preview` and
snapshot-only `state`. Bootstrap instantiates it before the interface and
players; the player join handler calls it after base property initialization.
Only the current engine-supplied connected player object and its own session can
edit an open draft. Target names, arbitrary fields, hidden-tab selections and
invalid dropdown indices are rejected as a whole event. All displayed data is
formspec-escaped; text and input sizes are bounded.
CodeGuard input/storage guidance was applied: allowlisted keys and selections,
bounded parsing, session ownership, preserved corrupt data and sanitized errors.
No credentials, certificate handling, cryptography, network calls or dependencies
are introduced. No authentication or database infrastructure is changed.
Focused offline Lua harnesses cover character behavior, player join integration,
inventory navigation and bootstrap order. Mocks model native JSON through
registered exact fixture strings, not executable Lua. Native engine rendering,
mouse behavior and visual fit still require client review; no full-engine or
live-world test is claimed by this change.
+374
View File
@@ -0,0 +1,374 @@
# Landscape and ecosystem integration
This integrates the independent [terrain factory](../mods/hearth/biomes.lua),
[diversity policy](../mods/hearth/diversity.lua), and named botanical art into
[content.lua](../mods/hearth/content.lua). No save-schema/version change, engine
fork, new simulation scheduler, global API wrapper or runtime mapgen editing is
introduced. The precise upstream contracts remain [world generation](world-generation.md),
[ecosystems](ecosystems.md), and [original art](art-direction.md).
## Registration order and API
1. Construct/install `hearth.seasons`, then `hearth.botany` and `hearth.diversity`
**before** creating succession. Both extend seasonal policy; succession reads
`hearth.biomes` lazily when it needs habitat information.
2. Register all original nodes, ecological flora, seasonal food patches, resource
inputs, tools and recipes through the existing private registration facade.
3. Call the lazy biome factory with `(hearth, api, register_node, construction)`;
assign `hearth.biomes = biomes.register()`. This is the sole registration of
new terrain, full-block species trees, geology, aliases and decorations.
4. Call `diversity.register_nodes(register_node)`, then
`botany.register_nodes(register_node)`, then `seasons.register()`.
Species leaves and full-block tree stages now exist for seasonal copies.
5. Instantiate wildlife after these registrations.
The entire old mapgen section is removed, including its aliases, two biome
definitions, simple floor iron decoration, old weir/tree schematics and seep
registration. The biome module retains the canonical temperate/riverbank names,
canonical meadow timber and gathering routes, while supplying ten communities,
ten tree species and ordered real-host ore registrations. Its aliases are
**non-forcing** and honor existing registrations; nothing clears engine tables.
Its instance registration and diversity node registration are idempotent.
The private node helper honors explicit `def.tiles`. Species logs therefore keep
their top/top/side faces; soils keep their authored top and canonical earth sides;
full-block trees retain their species faces. Seasonal copies retain base placement,
protection, ownership and room-invalidation callbacks. Plantlike silhouettes use
their supplied texture rather than a guessed node-name image. All resource
craftitems use the existing `hearth_item_<name>.png` naming convention. The new
pick uses **hearth_item_copper_pick.png**, not a legacy `hearth_icon_` tool name.
No duplicate ore, wood, leaf or seasonal nodes are intentionally registered.
## Resource utilities and process handoff
| Resource | Content integration | Process-owner boundary |
| --- | --- | --- |
| Quartz | Three `hearth:quartz` shapelessly craft one existing `hearth:window` | No new furnace/glass process or scientific manufacturing claim |
| Copper | One `hearth:copper_ore` plus one `hearth:charcoal` shapelessly craft one `hearth:copper_charge` | A lit hearth processes one charge for **45 real seconds**, reserving **30 fuel seconds**, yielding **one** `hearth:copper_ingot` |
| Copper ingot | Three ingots across the top row and two central sticks craft one `hearth:copper_pick` | Ingot/charge registration alone does not implement smelting |
| Coal | Biome-owned coal seams drop two `hearth:coal` items | Each coal adds **240 real fuel seconds**, subject to the same **600-second** reserve cap and household authorization |
| Rock and timber | One granite/basalt/limestone/slate/sandstone crafts **four matching bricks**; **two matching rocks → two canonical stones**. One oak/birch/pine/willow/alder/beech/aspen/rowan/hazel/spruce log crafts **four matching planks**; **two matching species logs → two canonical logs**. One species plank crafts **one canonical plank** | All these conversions are shapeless; each pair uses two separate crafting slots, preserving mass (2:2). No single-log or single-rock canonical conversion; mixed types do not convert together |
The original reed/plank window recipe remains available; quartz is an alternate
mineral supply route to the same protected building node, not free reeds or a
new unsupported window. Clay keeps its existing pottery and charcoal roles.
### Exact process and thermal contract
The copper pick recipe is shaped: three `hearth:copper_ingot` across the top row,
one `hearth:sticks` in the center of each remaining row, empty side cells; output
is one pick. Quartz windows are shapeless: three quartz, one existing window.
The original window uses two identical rows of plank / reed / plank (four
canonical planks and two reeds). Each quartz/copper/coal seam drops two matching
items, but each copper charge consumes exactly one ore and one charcoal.
The direct exact-name fuel table accepts sticks **45s**, canonical logs **180s**,
oak/birch/pine/willow/alder/beech/aspen/rowan/hazel/spruce logs **180s each**,
charcoal **90s**, coal **240s**.
Fuel does not ignite itself. A whole fuel item is refused without consumption if
it would exceed 600 seconds. Planks, building nodes, raw ore, quartz and arbitrary
tree groups are not fuels. Coal is a geological, nonrenewable supply; its higher
heat value is not a renewable conversion loop.
Copper requires **75 seconds available at queue time** (45 duration + 30 cost).
The existing JOBS/RESULTS allowlists validate its distinct `smelt_copper` kind,
30 cost and copper-ingot output. Queueing reserves 30 seconds immediately and
consumes one charge; the absolute UTC fuel deadline also burns 45 seconds during
processing. Coal cannot replace the charcoal ingredient in the charge. Iron's
separate charge still takes 60 seconds with 30 reserved.
One canonical log + one clay shapelessly make one charcoal charge; species logs
must first convert in matching pairs: two same-species logs in separate crafting
slots yield two canonical logs, not a single-log conversion. The charcoal job takes 60 seconds and
reserves 30, producing one charcoal worth 90 seconds. There is no planks-to-log
recipe and no fuel gain from converting timber to charcoal. No recipe is added
by the process/UI integration; it exposes the content owner's actual recipes.
One hearth holds one job or one finished output. Queueing, fuel, ignition and
collection retain the same claim/external-protection, household-use and six-block
reach checks. Repeated timer/load reconciliation after restart or long absence
finishes the paid job only once, even after fuel expires; it never creates an
inventory item until authorized empty-handed collection. A full inventory or
rejected insertion leaves the one output waiting and blocks replacement jobs.
### Field notes and observational survey
The Mocha journal retains its original seven page indices and adds an eighth
regional geology/timber page. It describes all ten registered communities:
temperate meadow, oak woodland, birch forest, pine taiga, wet forest, floodplain,
dry scrub, alpine meadow, rocky peaks and riverbank. Recipe search includes both
window routes, copper charge/pick, all ten species planks, canonical log/plank/
stone conversions and all five regional rock bricks. Copper-ingot search also
finds its real-time process instructions; ingots are not direct crafting outputs.
The survey reads the actual `succession.habitat()` result once: support `ground`,
canopy/light/moisture, `pollination` (supporting plants, **not an insect census**)
and `nutrients` (bounded reserve out of eight). Missing optional observations are
shown as unavailable, not invented zeros. Ground labels are escaped for formspecs.
Pine and spruce retain evergreen winter shade; the other eight tree species
thin in winter. The API
does not expose separate evergreen/deciduous canopy counts, so the survey does
not invent them. Global active wildlife totals remain labeled as nonlocal.
Seasonal guidance explains pioneer birch, pine on podzol/alpine turf, wet-soil
willow/alluvium and alder, clover/flower pollinator support, spring bluebells,
autumn heather, wet sedge/bulrush, shade moss/ferns, rock lichen and nettle's
nutrient requirement. Clover adds at most two nutrient units per support across
replacement/restart while reserves are low; alder has a nitrogen-fixer habitat
profile by itself, **not an independent fertilizer process**; with botany installed,
the shared nitrogen-fixer policy also covers alder and other tagged species,
subject to the same finite support budget. Decomposers
recycle finite material. Habitat moisture is a bounded substrate/water/canopy
estimate, not fully simulated hydrology, watersheds, erosion or flooding. The
shared synthetic climate HUD and altitude-based environment remain unchanged.
Survey observation never advances succession, spawns wildlife or loads terrain.
### Eighty-species botanical field guide
The current catalogue is **80 named species, eight categories of ten each**.
It is a game catalogue of temperate north-west European organisms, **not a
real-world identification, toxicity or foraging guide**. Category membership is
an exclusive gameplay classification (for example, hazel is counted with trees,
and sedges/rushes with grasses), not a claim about taxonomic rank.
Names below follow the actual `hearth.botany.list()` records, including their
chosen scientific-name synonyms:
| Category (10 each) | Common name — scientific name |
| --- | --- |
| Trees | Pedunculate oak — *Quercus robur*; Silver birch — *Betula pendula*; Scots pine — *Pinus sylvestris*; White willow — *Salix alba*; Common alder — *Alnus glutinosa*; European beech — *Fagus sylvatica*; European aspen — *Populus tremula*; Rowan — *Sorbus aucuparia*; Common hazel — *Corylus avellana*; Norway spruce — *Picea abies* |
| Shrubs | Bilberry — *Vaccinium myrtillus*; Pleated blackberry — *Rubus plicatus*; Red raspberry — *Rubus idaeus*; Black elder — *Sambucus nigra*; Blackthorn — *Prunus spinosa*; Common hawthorn — *Crataegus monogyna*; Dog rose — *Rosa canina*; European holly — *Ilex aquifolium*; Common juniper — *Juniperus communis*; Guelder rose — *Viburnum opulus* |
| Grasses, sedges and rushes | Greater tussock-sedge — *Carex paniculata*; Bulrush (great reedmace) — *Typha latifolia*; Meadow fescue — *Schedonorus pratensis*; Cock's-foot — *Dactylis glomerata*; Timothy — *Phleum pratense*; Sweet vernal-grass — *Anthoxanthum odoratum*; Yorkshire-fog — *Holcus lanatus*; Tufted hair-grass — *Deschampsia cespitosa*; Common reed — *Phragmites australis*; Soft rush — *Juncus effusus* |
| Herbs | White clover — *Trifolium repens*; Common nettle — *Urtica dioica*; Heather — *Calluna vulgaris*; English bluebell — *Hyacinthoides non-scripta*; Yarrow — *Achillea millefolium*; Common foxglove — *Digitalis purpurea*; Meadowsweet — *Filipendula ulmaria*; Wild garlic — *Allium ursinum*; Cowslip — *Primula veris*; Wood anemone — *Anemone nemorosa* |
| Ferns and mosses | Bracken — *Pteridium aquilinum*; Lady-fern — *Athyrium filix-femina*; Male-fern — *Dryopteris filix-mas*; Hart's-tongue fern — *Asplenium scolopendrium*; Common polypody — *Polypodium vulgare*; Blunt-leaved bog-moss — *Sphagnum palustre*; Common haircap — *Polytrichum commune*; Cypress-leaved plait-moss — *Hypnum cupressiforme*; Broom fork-moss — *Dicranum scoparium*; Swan's-neck thyme-moss — *Mnium hornum* |
| Fungi | Fly agaric — *Amanita muscaria*; Brown birch bolete — *Leccinum scabrum*; Bay bolete — *Imleria badia*; Chanterelle — *Cantharellus cibarius*; Wood blewit — *Collybia nuda*; Shaggy inkcap — *Coprinus comatus*; Turkey tail — *Trametes versicolor*; Oyster mushroom — *Pleurotus ostreatus*; Honey fungus — *Armillaria mellea*; Birch polypore — *Fomitopsis betulina* |
| Aquatic plants | Common duckweed — *Lemna minor*; Yellow water-lily — *Nuphar lutea*; White water-lily — *Nymphaea alba*; Common water-crowfoot — *Ranunculus aquatilis*; Water mint — *Mentha aquatica*; Water forget-me-not — *Myosotis scorpioides*; Brooklime — *Veronica beccabunga*; Common water-starwort — *Callitriche stagnalis*; Rigid hornwort — *Ceratophyllum demersum*; Broad-leaved pondweed — *Potamogeton natans* |
| Lichens | Reindeer lichen — *Cladonia rangiferina*; Oakmoss — *Evernia prunastri*; Old man's beard — *Usnea subfloridana*; Map lichen — *Rhizocarpon geographicum*; Dog lichen — *Peltigera canina*; Lungwort lichen — *Lobaria pulmonaria*; Common orange lichen — *Xanthoria parietina*; Hammered shield lichen — *Parmelia sulcata*; Mealy pixie-cup — *Cladonia chlorophaea*; Pebbled pixie-cup — *Cladonia pyxidata* |
**How do I browse it?** Use `/botany [search]`, or the Botanical guide button in
the existing journal or ecosystem survey. The journal remains eight pages; no
ninth sidebar pane is added. The browser has ten species rows per page in
catalogue order, scrollable readable notes, Previous/Next, search, survey and
field-note controls. The ecosystem journal page also lists all eight categories
and their real common/scientific names. Missing or empty optional botany APIs
produce an unavailable message, not fictional substitute species.
**What does search mean?** Up to 48 characters of literal text, never Lua patterns.
It matches common/scientific names, IDs, category, habitat, seasons and roles.
Control characters become spaces; queries and all displayed catalogue text are
formspec-escaped. Page inputs are floored/clamped, including NaN and infinities;
no match and empty catalogue each retain one navigable page. New search resets
to page one; paging preserves the sanitized query.
**Which interactions are active?** The browser's substrate/light/moisture/pH,
host and aquatic-depth ranges are **game niche windows**, not measured field
data. Seasonal flowering supports the pollinator index, not spawned insects;
browse supports wildlife, not human food permission. Nursery cover reduces
recruit stress conditionally. Nitrogen-fixer policy consumes a finite two-unit
support budget while reserves are low. Host-compatible root fungi can accelerate
tree establishment while spending existing nutrient reserve. Saprotrophs can
advance an existing natural litter/deadwood clock; they do not create a second
nutrient payout. These require the appropriate season, habitat, natural origin,
protection checks and loaded simulation; a tag is not an unconditional aura.
Habitat engineering and resin-bearing labels are descriptive here, not a new
hydrology simulator or extraction recipe. Parasitism and allelopathy are explicitly
descriptor-only: no damage to player plants and no chemical killing effect.
**Does a missing toxic flag mean edible?** No. Toxic flags are catalogue labels,
not an implemented general poisoning system or real-world safety assessment.
Only bilberry, pleated blackberry and red raspberry have the catalogue's
seasonal game berry-harvest permission; this does not add food interactions for
other named fungi/herbs. Existing generic seasonal forage is a separate system.
Never use these notes to identify or eat real organisms.
**What does the survey count?** It reads the actual habitat API, not an 80-species
local census: grass/flower-support cover and combined shrub/fern/aquatic/tree
cover, canopy, light and moisture. Optional pH, nursery support, aquatic niche,
water depth, pollinator support and nutrients are displayed only when supplied;
missing optional fields say unavailable. In the botanical API, nearby water
cells (`near_water`) and aquatic depth (`water`) are different observations;
the legacy API's `water` cell count is not presented as depth. The pH is a coarse
substrate-class estimate, not a soil measurement. No species abundance is invented.
**How do fallen logs work now?** They remain ordinary full cubes laid sideways,
with an axe required and exactly one canonical log as the drop. There is no
hand-to-two-sticks harvest. The existing metadata-preserving orientation fix,
tree stages and ten-tree botany implementation are retained unchanged by this
process/journal pass. Claims and player-placed material remain protected from
autonomous succession. Voice work is explicitly deferred.
The copper pick mines `cracky=3` in **1.4 seconds**, with **70 uses**, versus
flint's 2.4 seconds and 35 uses. It retains mining `maxlevel=1`, drop level 1,
damage 3 and punch interval 1.2; it adds no tougher-rock capability and no wood
capability. It is not a replacement for the separate iron axe progression.
The exact new icons are `hearth_item_copper_ingot.png`,
`hearth_item_copper_charge.png`, and `hearth_item_copper_pick.png`; ore, quartz
and coal icons/nodes are registered by the biome owner only.
## Broad-relief v7 preset: new worlds only
[terrain.mjs](../scripts/terrain.mjs) exports pure `terrainSettings(newWorld)`
and `isTerrainSetting(line)` helpers. [run.mjs](../scripts/run.mjs) exports
`writeWorld()` for direct disposable tests; importing it does not invoke its CLI.
The launcher checks for existing world metadata **before** creating the world
identity. Either existing world.mt or map_meta.txt suppresses the new preset.
This also protects a legacy world without map metadata and an orphan map metadata
file. Every play still gets a fresh, exclusive session configuration, but existing
worlds get **no mapgen, seed, water-level or mapgen-limit overrides**, including
from an inherited fixture. Saved map metadata is neither parsed nor rewritten.
[Game defaults](../minetest.conf) and the launcher agree on v7, water level 1,
**mapgen limit ±4096**, caves, light, decorations, biomes and ores. Dungeons and
floatlands are disabled; mountains, ridges and caverns are enabled. The launcher
retains seed 731104 for fresh worlds, with explicit fresh-world fixture/seed-test
overrides taking precedence. Existing seed and mapgen remain engine-owned.
This applies the [source-reviewed recommendation](world-generation.md#recommended-v7-preset--new-worlds-only)
with three deliberate adjustments: terrain-base scale **45** instead of 65,
mount-height offset **250** instead of 230, and world bound **4096** instead of
512. Base offset 12 and alternate scale 28 keep gentler accessible terrain among
the relief. Hills have 384-node spread, mountains/ridge-water fields 700-node
spread and climate fields 256-node spread. Mountain density keeps the −0.6 bias
and 180×280×180 spread. Mount-height noise is a density-gradient input, **not an
absolute peak-height cap**. Neither the fixed seed nor these inputs guarantees
a low, dry or resource-complete starting location.
The goal is broad hills, grounded massifs, river channels/canyon walls, woodland
and alpine transitions—not floating islands or voxel confetti. This is native
v7 noise and registered geology, **not scientific erosion or watershed simulation**.
Small native overhangs/disconnected remnants remain possible despite nofloatlands.
There is no global per-frame voxel rewriting or custom terrain postprocessor.
### Existing worlds and transitions
Use a separate empty runtime root/new world to try the coherent preset. Do not
delete the current save or metadata to obtain it. Managed play retains its normal
prelaunch full backup and embedded-game identity checks. Existing generated chunks
remain untouched; the world bound and mapgen parameters are not expanded in place.
New biome/ore/decoration registrations can affect **future ungenerated chunks** of
an old world and create visible seams against old terrain. Existing mapgen noise
does not imply old biome registration tables are restored. There is no migration,
automatic regeneration, chunk deletion or retrofit of old geology/vegetation.
The operator must explicitly decide between a separate fresh world for coherent
terrain and accepting new-biome boundaries in unexplored parts of an old world;
this integration makes no automatic migration decision for existing saves.
## Focused validation and remaining native gates
### Botanical process/journal follow-up — 2026-11-03
**72 focused standalone checks passed:** hearth processes **34**, recipe/journal/
survey **38**. Both used in-memory snapshots of the actual on-disk modules and
required the exact named-check total, final suite marker and empty stderr.
The journal fixture loads the actual botanical factory and `list()` method,
checking all 80 common/scientific names across eight ten-row pages and category
counts. Coverage includes missing/empty botany APIs, all ten timber recipe/fuel
routes, building-fuel rejection, bounded literal/escaped queries, pagination,
chat/buttons/exit/session routing, footer/sidebar bounds, honest role/toxic
labels, optional survey fields and axe-only fallen-log guidance.
Only the seven requested process/UI/test/documentation files changed in this
follow-up. No voice work, full engine run, persistent-world launch or native
visual certification was performed. Historical results below remain historical;
they do not certify the newer 80-species pass.
### Final integrated verification — 2026-11-02
- Full pinned Luanti 5.18.0 check/smoke passed with **25 isolated Lua suites**;
the expanded suites cover 466 checks. The final invocation exited zero.
- Native registration/crafting checks resolved **63 recipes / 47 outputs**.
Species logs and rocks use distinct matching-pair conversion recipes; no
single-input canonical-conversion ambiguity remains.
- Native media closure checked **807 references**, with **zero missing files**.
- All **eight fixed seeds** passed in one complete run without seed retries.
Each had exposed water, food, flint, sticks, rushes, side-accessible timber,
and dry natural landing patches (412–658 sampled patches per seed).
Timber access checks inspect horizontal air beside trunks: a leaf canopy
above a trunk does not make it unreachable, while buried logs do not count.
- Four separated bounded worldgen probes observed four surface biomes and
granite/slate/limestone/sandstone strata plus quartz, copper, coal and iron.
This is sampled evidence, not proof every biome occurs in every seed.
- All **59 Node tests** passed, including original artwork, geometry,
deterministic packaging, terrain presets and saved-world preservation.
Final native, seed and Node commands each exited zero with empty stderr.
- Earlier integration checks exposed ambiguous conversion recipes and an
upland-rush generation ordering problem; both were fixed, not waived. Springs
now generate before adjacent water-dependent rushes. The original overhead-air
timber assertion was replaced with a genuine side-accessibility check.
These are disposable headless worlds. No persistent play world was opened or
regenerated. Native appearance, performance, ecological balance and human
acceptance remain unverified; noise-based relief is not a geological simulator.
Optional complete audio codec regeneration was not configured.
[Content tests](../tests/content_spec.lua) preserve all previous checks, add
`register_ore` to the mock, reject duplicate nodes/items/decorations, verify ten
unique biome registrations and nine ordered ore definitions, check seasonal
species order and idempotent registration, resolve every recipe/drop and validate
named mineral/species/understory images. Tool capabilities and both window routes
are checked. Art counts are checked by manifest roles rather than assuming every
inventory image is a craftitem (the copper pick is a tool).
[Tooling tests](../tests/tooling.test.mjs) compare game defaults to the pure preset,
verify new-world config emission and seed precedence, check repeat config writes
are exclusive, and assert byte-for-byte unchanged existing world identity/map
metadata. They also cover missing-map-metadata and orphan-metadata opt-outs. These
are Node-only disposable fixtures, not a launch of the original play world.
Previously recorded terrain/content integration results: **136 Lua checks passed** (content 33,
biomes 23, seasons 11, ecology 9, succession 49, diversity 11), with each suite's
expected final marker and empty stderr. The content suite also passed all 33
checks with a numeric flattened target and remapped sibling loader. **24 Node
tooling tests passed**, with zero failures, cancellations or skips and empty
stderr. All three changed JavaScript/test modules passed syntax checks. Editor
diagnostics and the owned tracked-file whitespace check were clean. No previous
content or tooling test was removed.
Resource process/journal follow-up: **54 focused standalone checks passed**:
[hearth processes](../tests/m2_processes_spec.lua) **29** and
[recipe/journal/survey](../tests/recipe_spec.lua) **25**. Both required their exact
named-check count and final suite marker, with empty stderr. Coverage includes
every species fuel, coal capacity/refusal, copper duration and reserve boundaries,
one-output collection, restart/long-offline idempotency, unauthorized interactions,
full/rejecting inventories, invalid metadata and raw-item rejection. UI checks
cover all five species/rock routes, both windows, copper crafting/process search,
eight-page bounds, seasonal guidance, optional/hostile habitat fields and Mocha.
The process fixture's notification closure was corrected to reference its local
hearth instance; no production authorization behavior was bypassed. No full engine
tests were launched for this follow-up, concurrently or otherwise. These mock
tests do not certify new terrain, native rendering or real seed reachability.
Standalone Fengari execution requires named checks **and** the final suite marker
with empty stderr; exit zero alone is insufficient. The content harness resolves
siblings through its mock modpath/dofile boundary, not a replacement of live core.
The recipe-collision regression keeps all **63 registered recipes / 47 distinct
outputs** and verifies exact output names/counts, unique shapeless ingredient
multisets (including repeated inputs), and shaped layouts, including potential
shaped/shapeless overlap. All **25 terrain recipes** remain: single logs/rocks
produce four species planks/bricks; matching pairs produce two canonical logs/
stones. The native smoke must still exercise every recipe through
`get_craft_result`; standalone signature checks are not a native engine pass.
Collision-fix validation passed **84 focused Fengari checks**: biomes **24**,
content **35**, and recipe/journal **25**, with exact named totals, each suite's
actual final marker (content uses `1..35`), and empty stderr. In-memory negative
probes separately restored the old single-log and single-rock conversions;
both were rejected by their regression contracts. No engine was launched and
no running player session was changed for this fix.
Native isolated runners flatten Lua filenames: sibling lookup must use a directory
plus explicit sibling basename, not `gsub("content.lua", ...)` on a numeric staged
filename. Focused registration tests do not generate or certify actual terrain.
No full engine tests were run for this integration. Main must still validate
saved preset values in a disposable native world, all seed starts including
731104, safe spawn heights, actual tree anchoring/media, and water/flint/sticks/
timber/forage/reeds/clay reachability. Existing emergence/resource fixtures limited
to Y=96 can miss high slopes/peaks; canonical-only soil/log searches can miss
species terrain. Adapt native probes and spawn policies rather than declaring
these mock passes proof of seed safety or handing out supplies. Finish native
process and visual/performance review separately. Historical
M4 eight-seed results do **not** certify the new terrain preset.
Security: no secrets, network calls, dependencies, cryptography or arbitrary Lua
execution are added. Bounded declarative registrations retain normal claim and
external-protection checks. Existing guarded filesystem paths, exclusive config
creation, snapshots and mod security remain in use; saved map metadata is read
only for existence and never rewritten by this integration.
+498
View File
@@ -0,0 +1,498 @@
# Living ecosystems
The Last Hearth's original succession model takes ecological inspiration from
[Dynamic Life](https://www.planetminecraft.com/data-pack/dynamic-life-1-21-5-and-above/).
No datapack code, species implementation, textures or other assets were copied.
This is a runtime simulation, not extra random map decorations or world regeneration.
## What changes
Unclaimed, unmodified ecological ground automatically recruits niche-appropriate
groundcover, including the eight understory plants described below.
Sunny, moderately grassy sites support wildflowers; dense grass competes with
flowers. Moist shaded sites support ferns. Trees reduce local light and retain
moisture; prolonged shade kills sun-loving ground cover. Rivers and biome humidity
also influence moisture. Established sparse meadow can recruit a tree seedling;
seedlings become young trees, then mature seed-bearing trees and finally 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
decompose; moist, shaded organic patches seed fungi. Decomposition releases finite
nutrients into the supporting soil (capped at eight). Fertile soil recruits ground
cover sooner and successful establishment consumes one nutrient. Fungi are
not edible forage; generic `fungi` decomposes, while botanical fungi have
host-specific root associations or saprotrophic substrate effects below.
These feedbacks permit shifting meadow/flower/
woodland patches rather than a single guaranteed final biome.
Fallen logs (`hearth:deadwood`) are **ordinary full wood cubes laid sideways**, not
irregular branch meshes. Their label is **Fallen log (axe; yields timber)**. Each
requires an axe and drops **one `hearth:log`**, exactly like standing timber: `tree=1`,
`choppy=2`, no hand-diggable `snappy` group. Default full-cube collision and selection
apply. Normal reach, claim/external protection and construction support-removal
checks remain; there is no right-click bypass. Loose `hearth:sticks` remain
hand-gatherable, preserving the first-tool path. Stone still needs a mining tool.
New woodland mapgen places sparse three-cube fallen logs, never force-placing them.
`paramtype2="facedir"`, `param2=12` points end grain along +X; a random schematic
rotation can align the line along Z (`param2=4` is +Z). The name `hearth:deadwood`
is retained for decomposition and species metadata. The `hearth:block_deadwood`
load callback rotates legacy vertical deadwood once, marks `succ_block_deadwood=1`,
and leaves already-horizontal orientation intact. It rechecks exact loaded node
identity, respects external protection, and uses `swap_node` to retain owner/player
markers, natural flags, age, metadata inventories and timers. It does not convert
other timber, rewrite surrounding nodes, or regenerate existing terrain.
### Active-time scales (minimums, not wall-clock promises)
| Process | Observed active time |
| --- | --- |
| Bare soil recruits ground cover | 3 minutes; 90 seconds with nutrients |
| Grass/flower/fern dispersal eligibility | 15 minutes; attempt every third evaluation |
| Severe niche stress to litter/deadwood | 10 minutes |
| Old meadow recruits a seedling | 2 hours; 4 in dense unfertilized grass, only suitable spatial niches |
| 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 |
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
ordinary full-cube log roots; **a mature tree needs an actual multi-node block trunk
and leaf canopy**, not one bare root cube. The read-only `diversity.tree_plan`
interface below shares the same geometry used by mapgen. Applying it, validating
every occupied destination and support, and tracking generated components belong
to the ecological growth owner. This content change installs no geometry-writing
scheduler or automatic load-time expansion. Do not retain the old 5×5×6 mesh-only
clearance assumption: mature profiles reach 9×10×9 and must be preflighted in full.
Unknown/player plants, water, unloaded nodes, claims and markers must block growth;
the plan's omitted air cells are never permission to clear or replace anything.
Existing mapgen log/leaves trees contribute shade but are not aged or destroyed.
Player-planted saplings, crops and berry bushes retain their existing time contract.
## 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
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
logs from one cube. Additional generated trunk cubes are separately harvestable.
Roots are hidden from creative inventory, use species log top/end and bark side
textures, `drawtype="normal"`, `tree=1`, `choppy=2`, and default cube collision.
Seedlings are conventional `plantlike` sprites. Mapgen full-block species
logs/leaves/planks belong to the biome module;
logs/leaves contribute canopy without being adopted as killable succession trees.
Recruitment is deterministic, not an all-oak fallback:
- Pine favors cold podzol and alpine turf. Warm podzol permits pioneer birch.
- Birch pioneers dry/open grass, earth and woodland gaps.
- Oak favors stable forest floor with an existing canopy.
- Beech has a broad domed crown and avoids saturation; aspen is tall and columnar;
rowan has an open crown; hazel is low coppice; spruce has a narrow evergreen spire.
All ten have distinct mature block silhouettes and deterministic recruitment.
- Willow favors moist alluvium/floodplain; alder favors peat and wet woodland.
Riparian recruitment requires moisture ≥0.65. Species soil support is delegated
to `hearth.biomes.supports_species(species, surface)` when present; its decision
is authoritative. A standalone soil policy works without the biome module.
- Ecological soils are grass, soil, forest floor, podzol, alluvium, peat and alpine
turf. Biome `surface(node)` moisture hints blend with humidity/water/canopy.
This is **not** an expansion of the separate crop-planting soil policy.
| Understory | Niche and feedback |
| --- | --- |
| Clover | Sunny, moderately moist gaps; edible; flowering pollinator habitat; fixes bounded nitrogen |
| Nettle | Moist, already fertile ground; edible natural browse, not an inventory food item |
| Heather | Dry, open acidic podzol/alpine turf; autumn pollinator habitat |
| Bluebell | Moist shade; blooms only in Spring, occupied and dormant otherwise |
| Sedge | Saturated, lit riparian soil; edible when active |
| Bulrush | Saturated, lit riparian soil; tall wetland cover, not edible |
| Moss | Moist shade; persistent through Winter, not edible |
| Lichen | Dry exposed rock/open ground; persistent through Winter, not edible |
These are active succession species: they establish, compete, spread into **air**,
undergo stress, and senesce. They do not merely decorate a fixed map. Suitable
understory appears on the first ordinary 90/180-second establishment cycle, rather
than waiting for tree maturation. Wet herbs can recruit riparian trees after two
hours; heather can recruit pine. Saturated sites cannot fall back to meadow grass,
and meadow grass cannot disperse into them. Exposed rock admits lichen only;
neither recruitment nor decay replaces rock with soil or fertilizes it.
Clover can add at most **two nitrogen units per supporting soil node**, recorded
as `succ_fixed_nitrogen` across restart, grazing and recolonization; additions stop
at local fertility four. The universal nutrient cap remains eight, and successful
establishment consumes one. Clover has no harvest/item drop or timer, and cannot
create an endless fertilizer-item loop. Habitat reads clamp corrupt/excessive
nutrient metadata to eight without rewriting it.
Natural blooming clover, wildflowers, spring bluebells and autumn heather increase
the read-only `habitat(pos).pollination` count. This exposes a bounded habitat input
for 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
are evergreen in the catalogue and seasonal node policy; deciduous trees
retain only 15% canopy influence.
Known unmarked worldgen diversity herbs, species stage roots and generic deadwood are naturalized by
`diversity.bootstrap(pos, node?)` from a load callback or the existing column probe.
Bootstrap checks the plant and support for ownership, ambiguous claims, external
protection and placement/unknown metadata. Hand placement marks `player_placed=1`.
It never adopts species/generic logs, leaves, planks, crops or berry bushes. There
is no general adoption of arbitrary flora, generic litter or fungi, and no new
scanning scheduler. Adopted deadwood needs loaded natural ecological soil and
decays to litter after **two observed active hours**, 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
legacy unmarked soil, provenance cannot distinguish third-party placements that
deliberately omit every marker; those integrations must mark or claim their flora.
## Seasonal flora and food
Appearance and availability use `hearth.climate_at(os.time()).season`: the same
actual-epoch/latitude-aware climate as ecology, **not the machine's local month**
and not a replay of an optional preview clock. Southern latitude reverses the
calendar through the existing solar model. Explicit test climates can preview it.
| Flora | Spring | Summer | Autumn | Winter |
| --- | --- | --- | --- | --- |
| Deciduous block leaves | Spring foliage | Original foliage | Autumn colours | Twig canopy texture |
| Young/mature block trunks | Bark/end grain | Bark/end grain | Bark/end grain | Same full cubes; no mesh swap |
| Pine/spruce leaves | Evergreen | Evergreen | Evergreen | Evergreen |
| Clover / nettle / heather / sedge / bulrush | Active | Active | Active | Dormant |
| Bluebell | Bloom | Dormant | Dormant | Dormant |
| Moss / lichen | Active | Active | Active | Active |
| Meadow grass / fern | Living | Living | Living | Dormant perennial |
| Wildflowers | Bloom | Bloom | Dormant | Dormant |
| Spring greens patch | Pickable | Dormant | Dormant | Dormant |
| Autumn mushroom patch | Dormant | Dormant | Pickable | Dormant |
Frost and seasonal dormancy pause living age, stress and reproduction; they do not
kill perennials at first frost. Spring resumes the same ecological age. Deadwood,
litter, dung and decomposer fungi continue returning nutrients in winter; mature
trees can continue shedding litter. First-night berry availability stays year-round.
Spring greens recruit and spread into moist (≥0.55), light ≥9 gaps in Spring.
Autumn mushrooms recruit and spread into moist (≥0.6), shaded (light ≤10) gaps in
Autumn. Deterministic sparse recruitment niches prevent every gap becoming food.
Dormant patches remain occupied: neither seeds nor normal placement replace them.
The existing decomposer `fungi` is still not edible.
Right-click a patch (or dig to pick) within six nodes with normal construction
`use` authorization. One picked greens item restores **12 food** when eaten.
One picked mushroom can be crafted shapelessly into existing `raw_food`, then
cooked at a lit, fuelled hearth. Raw mushrooms have no eat callback. These are
original game foods, not advice about identifying or eating real wild plants.
Picking never removes a patch. An empty inventory slot is required; one successful
item transfer writes `succ_harvest_ready_at = now + 86400` (one real day). Rejected
delivery grants nothing and writes no cooldown. The durable deadline survives
restart, unload and every seasonal toggle; returning to Spring/Autumn cannot refill
it. Harvest writes no age, owner or placement metadata and bypasses no claim.
`hearth.seasons.kind(node_or_name)` canonicalizes only registered controlled
variants; succession, grazing and habitat see the original kind and natural flag.
`name_for(kind, epoch)` selects appearance. `reconcile(pos, node?, epoch?)` swaps
only a loaded controlled node, preserves param1/param2 and all metadata, and does
not call construction/destruction callbacks or reset timers. Appearance-only
swaps may recolour claimed/hand-placed plants without changing permission or
provenance; external protection still blocks them. Ecological mutations retain
the stricter claim/placement exclusions below. No save schema/version change.
## Reviewed species effects and evidence
[species_effects.lua](../mods/hearth/species_effects.lua) is an offline policy
factory, `effects(hearth, api)`. It never fetches Wikipedia, interprets article
prose, registers a scheduler, or writes nodes/metadata. Succession uses an injected
`hearth.species_effects`, or loads this fixed sibling in the engine before its
existing globalstep is registered. Minimal standalone fixtures may omit it.
Content/bootstrap integration can initialize it before succession.
`E.describe(record_or_id)` returns an ordered list of
`{title, status, game, source_species, source_url, ...}`. Status is exactly
`simulated` or `descriptive`: simulated means the named bounded game mechanism
exists, **not** that all real-world consequences or field rates are simulated.
`E.source(id)` and `E.sources()` expose identity references for all 80 catalogue
IDs. Bundled Wikipedia page URLs/revision IDs are used when available; without
the bundle, Latin-title links are only identity pointers, not verified evidence.
Unreviewed records explicitly say that no species-effect evidence was reviewed
here. `identity_revision` / `identity_revision_url` refer to the bundled
introduction snapshot, **not** a claim that its excerpt contains the ecological
body section reviewed below. Effect review date: **2026-11-03**. No invented
revision IDs, biological rates, or undisclosed effect generation from tags.
### Implemented mechanics, not just catalogue descriptions
| 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. |
The sampler collects `species_counts` (including canonical alder roots, moss and
duckweed) within its existing **5x5x5** stencil. Mat feedback uses only the
sample's stand level, with natural/protection/placement checks on the mat and its
immediate support; claimed, planted, protected, unsupported or unloaded sources
do not contribute. A partially unloaded sample grants no derived mat effects.
`E.habitat(base, observations)` returns a copy and exposes `source_moisture`,
`source_ph`, and `source_light`. Succession calls it **once after deriving fresh
base moisture, support-class pH and aquatic classification**. Repeated habitat
reads cannot accumulate acidification or retention and never change metadata.
Consumers must not feed an already-derived habitat back as a new base sample.
All aquatic stands still occupy air above water: no rule clears, replaces,
refills or converts a water-source node. These habitat estimates drive botanical
niches, not the renderer's actual node light or a global pH map.
Existing season-gated browse, flowering/pollination and nursery counts are also
reported, as is canopy shade/moisture (pine/spruce evergreen; deciduous Winter
influence 15%). Nursery cover reduces stress accrual to 75%; pollination is an
input, not a new berry-yield promise. Engineer tags on other species remain
**descriptive / not simulated**: no automatic bank stabilization, erosion or rock
weathering. Resin, allelopathy, toxicity attacks and living-tree parasitism are
explicitly not simulated by this module. Article food/medicinal statements never
grant food permission, poison attacks, healing or recipes.
Focused evidence is in [species_effects_spec.lua](../tests/species_effects_spec.lua)
and [succession_spec.lua](../tests/succession_spec.lua): all-80 source mapping,
descriptor honesty, quantitative mat caps and changed real niche eligibility,
read-only/repeated sampling, source/support protection, shared durable nitrogen,
no duplicate fungal nutrients, and source-water preservation. These are standalone
Lua tests, not a claim of full-engine or multiplayer validation.
## Safety and execution contract
- One scheduler runs once per second, doing **at most eight column probes total**,
shared round-robin across connected players (not eight per player). Columns scan
a 25×25 square and twelve vertical nodes (+6..−5) around a player, seeking existing natural
ground. No connected players means no ecological work.
- Each probe checks at most twelve heights and one site. Encountered foliage,
including old unmarked mapgen leaves, is reconciled on that same probe: **at most
twelve appearance swaps per column, 96 per second total**, not a second player
radius scan. An LBM reconciles each controlled flora node on load without
emergence or a world registry; that load-driven work is one swap per callback,
separate from the scheduler's per-second budget. Habitat uses a fixed
5×5×5 stencil; site safety uses a fixed 3×3×3 stencil. Mutations per evaluated
site are at most two (mature seed plus litter); other stages write at most one.
Wildlife calls scan at most nine candidate sites and mutate at most one.
- Only `get_node_or_nil` reads are used. Missing/ignore nodes fail closed. There
is no emerge, force-load, mapgen edit, ABM, per-node timer or offline replay.
- Ages accrue from consecutive observations, not from absolute planting dates.
First observation after restart, load, cache eviction, disconnect, clock rewind,
or a gap longer than 1,200 seconds only establishes a baseline. The load callback
invalidates observations when vegetation or its natural support reloads. A stalled
server step gets one fixed budget, never a backlog of simulation steps.
- A bounded 8,192-entry in-memory observation cache is disposable (enough for ten
disjoint stationary-player scan squares). Age, stress,
next-attempt timestamp, natural-origin flag and attempt serial live in node
metadata; soil stores only its nutrient count. There is no global save schema.
Durable attempt timestamps/serials prevent repeated evaluation or rerolling
a blocked seed by reconnecting/restarting. Restarting 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.
- Source, destination and natural supports must all be unclaimed, externally
unprotected and free of placement metadata. All normal content placement marks
`player_placed=1`, including hand-placed soil/turf and soil left by crop harvest.
Recognized legacy markers are
`owner`, `planter`, `placed_by`, `hearth_placed`, `hearth_player_placed` and
`planted_at`. Unknown third-party non-succession metadata also opts out
conservatively. Any nonempty/nonzero marker opts out. Nearby markers/builds/farms
also block succession conservatively. Only explicitly natural simulation plants
may be consumed, replaced or decayed. Never replace water or arbitrary
`buildable_to` nodes. Old unmarked player earth cannot be distinguished from
mapgen earth: claim it to keep nature out.
## Wildlife interface
`hearth.succession` is installed by the content registrar:
- `forage(pos) -> boolean`: 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.
- `deposit(pos) -> boolean`: place exactly one dung nutrient patch in nearby
**air** over safe natural support. Never replaces existing plants or water.
Nutrients arrive through active decomposition, not immediately on deposit.
- `habitat(pos) -> table`: read-only counts `grass`, `flowers`, `cover`, `water`,
`edible`, `pollination`, plus `ground`, `nutrients` (0..8), biome `heat`, `canopy`,
estimated `light`, `moisture` and `loaded`. Counts use the fixed
local stencil; wildlife should reject an incomplete (`loaded=false`) sample.
- `step(dtime) -> number`: registered scheduler; returns columns processed.
Exposed constants document the scheduler's fixed production limits, not settings.
Positions must be finite near-world-range vectors; APIs round to node coordinates.
Callers supply animal ground-cover height (the air/plant cell above soil), not the
supporting soil or the animal's eye height. Calls return false when no safe target
exists. Habitat has no writes, timers or protection-violation reports.
## Assets and verification
### Exact registered node / art integration contract
All names below have the `hearth:` namespace. Existing canonical nodes retain their
names and callbacks/groups/drops. Summer always uses the existing canonical tree
and leaves node; no redundant summer variant is registered.
- Legacy `tree_seedling`, `young_tree`, `mature_tree`, `leaves` retain `_spring`,
`_autumn`, `_winter` IDs for save compatibility (12 variants). The content
registrar removes old mesh/atlas references from every legacy stage variant:
seedlings use `hearth_sapling.png`; young/mature trees use log top/side textures
and remain normal cubes. Seasonal appearance belongs to their separate leaves.
- Deadwood uses `hearth_log_top.png` on both ends and `hearth_log_side.png` on
the remaining faces, with no mesh, custom collision/selection or seasonal variant.
- Mapgen leaf variants use `hearth_leaves_spring.png`, `hearth_leaves_autumn.png`,
`hearth_leaves_winter.png`; canonical Summer uses existing `hearth_leaves.png`.
- `meadow_grass_winter`, `fern_winter`, `wildflowers_autumn`, `wildflowers_winter`
use `hearth_meadow_grass_dormant.png`, `hearth_fern_dormant.png` and
`hearth_wildflowers_dormant.png`. Other seasons use existing canonical nodes/art.
- `spring_greens_patch` and `autumn_mushrooms_patch` are dormant canonical nodes;
`spring_greens_patch_spring`, `autumn_mushrooms_patch_autumn` are active variants.
Textures: `hearth_spring_greens.png`, `hearth_spring_greens_dormant.png`,
`hearth_autumn_mushrooms.png`, `hearth_autumn_mushrooms_dormant.png`.
- Picked craftitems (not nodes): `hearth:spring_greens`, `hearth:autumn_mushrooms`;
icons `hearth_item_spring_greens.png`, `hearth_item_autumn_mushrooms.png`.
The legacy seasonal set adds **20 nodes total**, including both canonical food patches. All variants
are hidden from creative inventory, retain base callbacks and drop definitions,
and cannot become duplicate harvest sources by toggling. The registrar honours
explicit mesh tiles directly; it does not require `override_item` in engine mocks.
### Main-agent wiring and exact diversity interface
The 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:
1. Create/install `hearth.seasons = dofile(modpath .. "/seasons.lua")(hearth, api)`.
2. Create/install `hearth.botany = dofile(modpath .. "/botany.lua")(hearth, api)`.
3. **Before succession setup**, create/install
`hearth.diversity = dofile(modpath .. "/diversity.lua")(hearth, api)`.
This factory immediately extends `seasons.add_variants` with species policy.
4. Instantiate succession using its factory and assign `hearth.succession`.
5. After normal content, register/install biomes (ten 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
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.
- `grounds`, `rocks`: exact short-name membership sets; `is_ground(node_or_name)`,
`is_rock(node_or_name)` and `supports_ground(kind, support)` are boolean policies.
- `tree_info(kind_or_node) -> species, stage`: two Lua return values, or nil;
stages are `seedling`, `young`, `mature`. Seasonal names canonicalize first.
- `family(kind_or_node) -> canonical_family`: species stages map to legacy
`tree_seedling`, `young_tree`, `mature_tree`; species leaves/logs map to
`leaves`/`log`; herbs retain their own canonical kind.
- `supports_species(species, surface)`, `suitable(kind, habitat, season?)`:
support/niche policy with optional biome integration.
- `choose_recruit(pos, habitat, season) -> understory_kind_or_nil`;
`choose_tree(pos, habitat) -> species_seedling_kind_or_nil`. Habitats supply
`ground` as a short name and normalized `moisture`, `light`, `loaded`,
`nutrients`, `canopy` and optionally biome `heat`.
- `edible(kind, epoch?)`, `pollinator(kind, season)`;
`is_worldgen_flora(kind)`, `bootstrap(pos, node?) -> boolean`;
`register_nodes(register_node)`, idempotent node registration.
### Geometry handoff to ecological growth
`diversity.tree_plan(species, stage?) -> plan | nil` is read-only. Species is one
of the ten IDs or `"generic"` for legacy trees; stage is `"young"` or `"mature"`
(default mature). Invalid IDs/stages or missing biome integration return nil.
`plan` is a fresh dense array of `{x, y, z, name, param2}` records: integer offsets
relative to the stage root at `(0,0,0)`, fully qualified node names, and facedir.
`plan.size={x,y,z}` gives the bounding schematic dimensions. There are **no air
records**. The root is `hearth:<species>_<stage>`; remaining trunk/branch and canopy
records use `hearth:<species>_log` and `hearth:<species>_leaves`. Generic plans use
oak geometry with `young_tree`/`mature_tree` roots and canonical `log`/`leaves`.
Every non-root occupied cell exactly matches
`biomes.tree_schematic(species, canonical?, stage?)` (x-fastest, then y, then z).
Young plans are at most 3×5×3; mature plans at most 9×10×9. Mutating a returned
plan cannot change later plans or mapgen geometry.
**Ecology owner:** preflight the complete bounded plan, loaded supports and all
destination claims/protection/provenance before writing. Only the exact existing
natural root and verifiably owned tree components may be reused on stage growth;
never overwrite water, player nodes, farms or unrelated flora. Track component
ownership/root association and stale callbacks; reject partial growth, coordinate
overflow, unloaded cells and blocked transitions. Preserve species identity and
handle component removal/death within the existing scheduler budget. This API
neither applies geometry nor migrates/expands old roots on load. Mature roots
must not be treated as finished trees without their real block canopies.
When ecological death creates fresh `hearth:deadwood`, supply `param2=12` (or `4`
for a Z-oriented log) immediately; the load migration is for legacy saves, not
a substitute for orienting newly created fallen timber.
`seasons.add_variants(kind, definitions, active_seasons?)` is available immediately
after factory creation and before registration. Definitions map `Spring`, `Summer`,
`Autumn`, `Winter` to `{texture=..., mesh=...}`. An optional active-season boolean
set controls availability. Missing base nodes in mocks/optional integrations are
skipped without an assertion; a later registration call can register their variants.
No original 12-month climate calculation or preview-clock semantics changed.
### Exact species/understory art contract
For **all ten** species in `species_order`:
- Seedlings use `hearth_botany_<species>.png` on a plantlike sprite. Young/mature
roots and species timber use `hearth_<species>_log_top.png` on both ends and
`hearth_<species>_log_side.png` elsewhere. Planks use `hearth_<species>_planks.png`.
No ecological tree definition references a mesh or atlas.
- Biome-owned full-block leaves use `hearth_<species>_leaves.png`. Deciduous
seasonal variants use `hearth_<species>_leaves_spring.png`,
`hearth_<species>_leaves_autumn.png`, `hearth_<species>_leaves_winter.png`.
Pine **and spruce** leaves retain the base texture throughout the year.
- Deciduous stage/leaf variants append `_spring`, `_autumn`, `_winter`; Summer
restores the canonical node. Seedling variants keep the botany tile. Young/mature
seasonal IDs are retained for existing saves but have the **same bark cubes**,
never foliage atlases. Their availability still pauses in Winter for deciduous
species. Diversity registers those multi-face cube variants before the seasonal
foliage copier; seasons remains responsible for name selection and swapping.
Understory base textures are `hearth_clover.png`, `hearth_nettle.png`,
`hearth_heather.png`, `hearth_bluebell.png`, `hearth_sedge.png`,
`hearth_bulrush.png`, `hearth_moss.png`, `hearth_lichen.png`. Clover, nettle,
heather, bluebell, sedge and bulrush additionally use
`hearth_<plant>_dormant.png` in Winter (nodes append `_winter`). Bluebell also
has `_summer` and `_autumn` node variants using its same dormant texture.
Moss and lichen have no dormant art. No art is generated or modified here.
With all biome leaves installed, diversity adds **142 nodes**: 38 canonical nodes,
72 deciduous stage variants, 24 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.
The standalone succession fixture tests growth, niches, shade, competition,
seedlings, seeds, nutrient feedback, dung/fungi/decay, grazing, source/destination/
support protection, placement markers, unloaded nodes, work/read budgets, cold,
restart cooldowns and no-catchup behavior. Content tests check node registration,
named textures, wildlife API wiring and placement marking. Seasonal fixtures add
all four seasons, northern/southern calendars, seasonal foliage/restoration,
metadata/age conservation, forage cooldowns/restart, niches/occupation, protected
and unloaded no-write cases, target-volume clearance and the 96-swap bound.
The blockshape-focused standalone suites are diversity, biomes and content;
botany has its separate catalogue suite. They check exact ten-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.
Engine visuals,
multiplayer ecology balance and long-session woodland density still require
in-engine playtesting; unit fixtures are not a performance benchmark.
Security review: no credentials, cryptography, certificates, network requests,
deserialization or unbounded external inputs were introduced. Mutation authority
is least-privilege nature (no player identity); loaded/protection/marker checks
fail closed before writes.
+266
View File
@@ -0,0 +1,266 @@
# Season VI launch feature checklist — The Last Hearth
Updated: 2026-11-02. Carried forward from the engine-checkout checklist,
doc/season-vi-launch-features.md, with standalone-repository links. Status:
**proposed launch scope, not accepted features**. M0 technical gates and M1/M2/M3
implementations with automated preflight are complete; M4 technical implementation
and automated verification are complete. Human M1 repeat/performance, M2
first-night, M3 two-player, and M4 week-long acceptance remain open. No launch checkbox is
approved. See [M1](m1-verification.md), [M2](m2-verification.md), and [M3](m3-verification.md) evidence.
The working contract is exact Luanti **5.18.0**, game **0.5.0-m4**, save schema
**1**, native-first Windows/Linux intended development, macOS portable runner
unverified; browser launch readiness is not promised. User-directed M2/M3
implementation does not waive earlier human acceptance gates. The
[provisional hardware/network targets](m0-decisions.md#provisional-performance-acceptance-targets--not-measurements)
are not measurements or certified minimum specifications. The user/operator
must approve community location before shared climate staging; M0 synthetic 45°N,
0°E is not production selection.
Initial M1 slices exist for F01 (controller/course), F02 (tool/interact feedback),
F09 (opt-in practice/sparring), F14 (context/route onboarding), and F15 (generated
assets/audio). Their launch checkboxes stay open until the full criteria below,
including human and real-client evaluation, are evidenced.
## The launch promise
**An enjoyable first evening, a home worth improving, and a reason to return.**
Players should gather, prepare, build, and survive through decisions rather than
repetitive clicking or opaque recipes. Real-world days and seasons shape those
decisions. Cooperative settlement life provides the initial long-term purpose;
scripted adventures are not required.
The [prototype](../README.md) establishes plumbing and a small
interaction loop, not a finished survival game. Existing unit and engine tests
do not establish that it is fun, secure for public hosting, or performant at scale.
See the [implementation roadmap](roadmap.md) for ordering and gates.
## Priority and completion rules
- **Required:** blocks the public Season VI launch.
- **Conditional:** decide at the stated milestone; do not silently grow the scope.
- **Later:** explicitly outside the launch requirement.
Every required feature needs automated tests where appropriate, a documented
save-data contract, player-facing feedback, and a multiplayer playtest. A checkbox
stays open until its acceptance criteria are demonstrated, not merely coded.
All entries remain unchecked, including replacements for existing prototype behavior.
## Required gameplay features
### F01 — Responsive movement and camera
- [ ] Predictable walking, sprinting, jumping, stepping, slopes and swimming;
smooth speed transitions rather than stamina-threshold oscillation.
- [ ] Surface-aware footfalls, landing feedback, readable exertion, and settings
for camera motion/FOV. Avoid forced head bob or camera shake.
- [ ] A test course covering narrow doors, stairs, water edges, rough ground,
low ceilings, falling, and ordinary network latency.
**Acceptance:** no reproducible collision traps on the course; movement remains
pleasant without upgrades, at supported frame rates and the chosen latency envelope.
### F02 — Satisfying tools and physical interactions
- [ ] Recognizable held tools and first-person use feedback; distinct cutting,
chopping, digging, harvesting, placing and repair actions.
- [ ] Sensible reach, durability, material requirements, yields and pickup behavior.
- [ ] Context prompts and clear reasons when an action is impossible.
**Acceptance:** gathering and building feel substantially different from punching
untextured cubes; players understand tool choice without reading source code.
### F03 — Discoverable crafting and process progression
- [ ] Searchable recipe/process guide with ingredient sources, workstation needs,
quantities, alternatives and locked/unavailable explanations.
- [ ] A small complete progression: improvised tools → timber shelter/workbench →
fired pottery and storage → one useful early metalworking tier.
- [ ] Functional workstations, timed processing and repair/replacement routes.
Processing permits other activities instead of holding a button for minutes.
**Acceptance:** the first tool, meal, shelter and later production upgrade can
all be achieved without administrator help or a wiki; no circular recipe locks.
### F04 — Construction that creates useful homes
- [ ] A coherent palette: foundations, timber frames, walls, floors, roofs,
stairs, doors, windows and a simple chimney/vent.
- [ ] Placement preview, rotation, support feedback, recoverable mistakes and repair.
- [ ] Bounded support rules applied consistently to structural pieces, including
removal of supports. Choose refusal or controlled local failure, not arbitrary
survival of floating roofs or unbounded collapse cascades.
**Acceptance:** a small home is attractive, usable and realistically supported;
dirt, planks or decorative nodes cannot accidentally bypass the stated rules.
### F05 — Reliable shelter, heat and weather protection
- [ ] Replace four-ray enclosure with a bounded room/roof model that recognizes
doors, openings, material protection and ventilation.
- [ ] Explain heat, wetness, wind/rain exposure and drying through useful feedback.
- [ ] Predictable hearth heating and smoke risk; no damage through solid barriers
or unexplained penalties for a properly vented room.
**Acceptance:** exposed camp, roof-only lean-to and enclosed vented cabin have
different, understandable outcomes; performance remains bounded in a large house.
### F06 — Fire, lighting and cooking
- [ ] Fire-starting tools, tinder, fuel types, ignition feedback and clear burn time.
- [ ] Timed cooking/boiling with safe input/output handling and a visible process.
- [ ] Craftable torches or lamps so real-world nighttime is viable immediately.
- [ ] A deliberate initial fire policy: no uncontrolled spread across claims;
remove and refuel operations cannot duplicate resources.
**Acceptance:** lighting a first fire is satisfying, darkness is navigable, and
leaving/rejoining cannot refresh fuel or duplicate cooked output.
### F07 — Food, water and a sustainable resource loop
- [ ] Renewable forage, freshwater collection, and a few crops with seasonal behavior.
- [ ] At least two viable food strategies, including one that does not require combat.
- [ ] Useful cooking and preservation; simple pantry/storage benefits.
- [ ] Resource renewal rules for plants, timber and arrival-area depletion.
**Acceptance:** an established village remains viable without endlessly migrating
for berries; late joiners are not stranded by earlier harvesting.
### F08 — Needs, clothing, recovery and fair consequences
- [ ] Tune hunger/thirst/warmth/stamina as interacting systems, not simultaneous chores.
- [ ] A small functional clothing set with wetness, warmth and wear; rest/bedrolls
support recovery without skipping the shared world clock.
- [ ] Basic treatment and clear injury feedback; remove prototype death-as-free-refill.
- [ ] Pick a recoverable death policy, safe home respawn and fallback arrival,
with item protection/recovery and no unavoidable repeated deaths.
**Acceptance:** preparation changes survival outcomes; mistakes hurt without
routinely ending a player's participation. Initial launch does not require permadeath.
### F09 — Real combat and a small living world
- [ ] Replace the stationary target as the main demonstration with a moving opponent.
- [ ] Readable anticipation, contact and recovery; weapon reach, aim, defense,
stamina and positioning matter. Add audiovisual hit/miss feedback.
- [ ] A small wildlife set: a passive/foraging animal and one dangerous encounter,
with navigation, avoidance, bounded population and clear disengagement behavior.
- [ ] Server-side validation of attacks/resources, including reach, obstruction,
timing and plausible movement; test prediction/correction under latency.
**Acceptance:** one encounter is exciting repeatedly and has counterplay other
than repeated clicking or permanent stun lock. PvP remains off for initial launch.
### F10 — An interesting, fair starting world
- [ ] Distinct local terrain, vegetation and material distributions that reward
ordinary exploration without needing authored dungeons.
- [ ] Test arrival-area resources across many seeds, not only the fixed demo seed.
- [ ] Safe asynchronous arrival and revalidation of saved spawn/home locations.
- [ ] Remove the prototype's blanket teleport below Y=-100; distinguish actual
out-of-world recovery from legitimate underground travel.
**Acceptance:** valid starts provide a feasible path to water, tools, food and
shelter; emergence failures and modified spawn terrain do not trap players.
## Required calendar and persistence features
### F11 — Trustworthy real-world calendar and climate
- [ ] Player-selected approximate location for local worlds; explicit shared
community location for a dedicated server, with no silent production fallback.
- [ ] One real hour per world hour, correct hemisphere/day length and a documented
sky approximation or tested custom rendering improvement.
- [ ] Configured civil timezone display with UTC available; DST changes labels,
not elapsed time. Clearly distinguish solar time, clock time and season labels.
- [ ] Continuous seasonal temperature/weather with altitude effects and readable
forecasts/signs; avoid abrupt rerolled six-hour fronts.
- [ ] Test seasonal extremes, leap days, clock corrections and restarts using
isolated staging fixtures unavailable to ordinary production players.
**Acceptance:** a same-date restart preserves conditions, nighttime play is fun,
and players can survive the actual launch season rather than a hidden spring fixture.
### F12 — Explicit offline and unloaded-area simulation
- [ ] Publish per-system rules: absent players' needs pause; food/crops/fuel use
bounded, consistent elapsed-time accounting independent of loaded mapblocks.
- [ ] Preserve age through splitting, merging, transfers and logout; conservation
tests cover crafting, storage and process completion.
- [ ] Preservation/storage makes occasional attendance viable before severe decay
is enabled. No surprise irreversible backlog simulation after long downtime.
- [ ] Versioned save schemas, validated inputs, backups before migration, and
restart-safe process completion without duplicated output.
**Acceptance:** identical elapsed time produces equivalent outcomes in loaded and
unloaded areas; a week away does not kill a character or silently reset progress.
## Required community and presentation features
### F13 — Shared homes, storage and cooperative permissions
- [ ] Protected containers and workstations; explicit shared access and claim roles.
- [ ] Visible claim boundaries and understandable build/access denial.
- [ ] Protection across harvesting, fluid changes, construction failure, fire,
containers and combat—not just placement and digging callbacks.
**Acceptance:** two players can run a household together while an unauthorized
player cannot take its contents or indirectly destroy it.
### F14 — Onboarding and usable survival interface
- [ ] Short optional hands-on introduction, contextual help and craft discovery.
- [ ] Useful item icons, inventory sorting/transfers, stack feedback and hotbar flows.
- [ ] Needs explain causes/remedies, not only bars; scalable UI, readable contrast,
remappable controls and non-color-only warnings.
- [ ] Late-winter arrival and reconnect flows; pause needs during protected initial
setup where appropriate, without an exploitable permanent survival shield.
**Acceptance:** unfamiliar testers complete a first meal/tool/shelter route without
coaching. Suggested first-session budget: 30–60 minutes, adjusted after observation.
### F15 — Cohesive visual and sound identity
- [ ] Original or properly licensed terrain, tools, plants, character model and
movement/use animations; consistent scale and recognizable silhouettes.
- [ ] Positional material sounds, fire, water, weather, wildlife and combat feedback.
- [ ] Restrained original music with independent volume/accessibility controls.
**Acceptance:** a short gameplay clip looks and sounds like one authored game;
critical actions are not silent and feedback is not audio-only.
### F16 — A simple eom.dev entrance and social connection
- [ ] A clear Play page: premise, screenshots, supported clients, join instructions,
rules, privacy notice and troubleshooting; no misleading universal-browser claim.
- [ ] Account-linking path to platform identity, with control proven on both sides,
no username/email-only merges, and recovery/unlink policy.
- [ ] Optional Matrix bridge with public-chat disclosure, moderation/rate limits,
loop prevention and graceful failure; Discourse season thread and feedback links.
**Acceptance:** a new visitor knows how to join; external service failure cannot
stall gameplay or grant game privileges. No paid survival advantages.
## Required release engineering
### F17 — Reproducible client/game releases
- [ ] Stable game packaging outside the engine source lifecycle; pinned engine,
game, dependencies, assets, license manifest and versioned client contract.
- [ ] Repeatable install/update flow on supported systems and clean-machine testing.
- [ ] Decide native-first versus browser-at-launch early. If browser is advertised,
test actual hardware/input/JSPI constraints, security headers and proxy isolation.
**Acceptance:** a clean machine can install and join the exact tested release;
incompatible clients get clear errors instead of corrupt gameplay.
### F18 — Safe dedicated-server operation
- [ ] Staging and production isolation, persistent storage, graceful saves,
health/readiness checks, resource limits and protected configuration/secrets.
- [ ] Consistent backups and demonstrated full restoration of map, players, claims,
inventories and process state; an actual rollback procedure.
- [ ] Moderation/reporting tools, access/rate limits, dependency review and
production defaults that disable test/debug powers.
- [ ] Operational dashboards/logging without credentials or precise player-location
leakage; published maintenance and incident expectations.
**Acceptance:** host a closed community beta safely and recover it from a deliberately
simulated failure before production launch. Existing server role is not assumed ready.
### F19 — Performance, exploit and retention validation
- [ ] Functional, conservation, migration and restart tests; real clients in
multiplayer, not only fake-engine unit harnesses.
- [ ] Measure frame times, server step latency, corrections, memory, media transfer,
entity counts and unloaded catch-up cost against declared hardware/player targets.
- [ ] Test reach/cadence abuse, inventory duplication, claim bypass, clock changes,
disconnect/reconnect, full storage, and prolonged process interruptions.
- [ ] First-night, next-session and week-away playtests; record where people stop
enjoying themselves. Use opt-in feedback and minimal aggregate metrics.
**Acceptance:** no unresolved critical loss/duplication/security defect; returning
testers choose a self-directed next goal rather than only following instructions.
## Conditional features: decide, do not accumulate
| Feature | Decision point | Default if not justified |
| --- | --- | --- |
| Custom movement/combat engine fork | First feel prototype (M1) | Use stock engine only if it meets the feel target; otherwise fund a bounded fork spike, not a silent quality cut. |
| Exact latitude-aware sky orbit | Clock validation (M2) | A disclosed, validated approximation is acceptable; keep real calendar/day length. |
| Browser client at public launch | Platform contract (M0), proof in M1 | Native-first, with an honest supported-client list. Browser becomes required if promoted as launch-ready. |
| Dynamic local collapse | Building prototype (M3) | Refuse unsafe support removal with clear feedback; no global physics simulation. |
| Detailed nutrition, illness and tool-specific injuries | Survival playtests (M3/M4) | A small understandable needs/treatment model. |
## Explicitly after launch
Vehicles/moving cities, elaborate metallurgy beyond the first useful tier,
authored ruins/dungeons, supernatural threats, AI residents, unrestricted PvP,
faction economies, world-restoration machinery, complex breeding/ecosystems,
shared currencies with other services, and automatic scheduled world wipes.
No feature count makes the season ready by itself. The test is whether the
survival/building loop is enjoyable, understandable and reliable with real people.
+129
View File
@@ -0,0 +1,129 @@
# M0 decisions and verification ledger
Updated: 2026-11-02. **Historical M0 record:** M0 completed against its verified
Windows technical gates. M1 implementation and automated verification are now
complete; human route/repeat/performance acceptance remains pending (see
[M1 evidence](m1-verification.md)). This is the M0 working rules/engineering
baseline, not user/operator sign-off of policy or deployment. See
[roadmap](roadmap.md) and [launch acceptance checklist](launch-features.md).
## Version and platform contract
| Decision | Baseline / qualification |
| --- | --- |
| Engine | Exact stock Luanti **5.18.0**, checked by launcher version response. The broad minimum in [game.conf](../game.conf) does not widen this tested contract. |
| Game | **0.5.0-m4** current development release candidate, independent game repository/package; engine stays separately installed. M0/M1/M2/M3 versions below are historical evidence snapshots. |
| Save | **Schema 1** envelopes for needs, claims, homes, access, location and spawn; legacy validation/migration, not a promise to accept arbitrary future data. |
| Tooling | Node.js **22+**, built-in modules; release/package/backup format **1**. See [release metadata](../release.json). |
| Clients | Native-first; Windows integrated and GUI checks verified on the existing development host. Linux/macOS path selection unit-tested only; native execution unverified. No clean-machine certification. |
| Browser | Not promised launch-ready. Advertise only after an explicit scope decision and actual M1 input/network/graphics/performance proof. |
| Engine changes | Stock baseline first; only a bounded M1 spike justified by measured feel/API limitations. No fork assumed. |
## Rules retained through M0
| Area | Current rule / next decision |
| --- | --- |
| Death | Preserve inventory; respawn resets needs to existing defaults (food 85, water 85, warmth 80, wet 0, stamina 100) and attempts arrival. This temporary free-refill behavior remains a known limitation until M2; no final fair-recovery approval. |
| Offline | Logged-out player needs do not decay. Fuel uses absolute real-time expiry (maximum stored reserve read as 600 seconds). No queued cooking, crops or food spoilage yet; no invented offline crop/food contract. M4 owns bounded process accounting. |
| Claims | One per owner; 25 × 25 inclusive footprint (±12 X/Z), ±24 Y; up to 16 shared residents/storekeepers, action-specific access and claim bounds shown on request. Placement centers must be at least 26 blocks apart on an X or Z axis. General indirect-fluid/fire grief and public multiplayer authorization remain limited. |
| Progression ceiling | Preserve improvised flint tools, small timber/thatch palette, hearth, cup and forage loop. Pottery/storage and one useful metal tier are planned M4 scope, not M0 features. |
| Calendar | Host real UTC, local solar time and meteorological season labels; disclosed sky/climate approximation. No civil timezone/DST support yet (M2). Rest does not skip the shared clock. |
| Location | Local choice stays local; dedicated server needs explicit valid admin settings. No geolocation request. **User/operator must approve the community location before M2 climate staging.** No location is chosen by this document. |
| Fixtures | Seed 731104; synthetic 45°N, 0°E for M0 checks and fixed summer/winter. This is not production configuration or community-location approval. |
For deployment, administrators must explicitly set `hearth_environment` to
`staging` or `production` and set `hearth_latitude` and `hearth_longitude` to
approved values. Dedicated defaults to production; dedicated `local` is rejected.
Production rejects nonempty `hearth_preview_date`, weather other than `auto`, and
any presence of `hearth_launcher_mode` or `hearth_smoke_token`. Remove those test
keys, rather than assuming a false/empty launcher flag is safe. Local managed
play explicitly uses `local`. These guards are not a complete server deployment.
## Provisional performance acceptance targets — not measurements
These are **implementation decisions to test**, not measured results, certified
minimum hardware, or accepted launch budgets. They may be revised explicitly in
M1 after evidence, with user/operator review of any scope impact.
| Target | Provisional acceptance envelope |
| --- | --- |
| Native client | 4-core CPU, 8 GB RAM, integrated GPU supporting OpenGL 3.3; 1920 × 1080, low settings, view range 96 nodes. Target 60 FPS and p95 frame time ≤25 ms. |
| Server | 4 vCPU, 8 GB RAM, **10 concurrent real clients**; p95 server step ≤50 ms in the declared course/load scenario. |
| Network | Test RTT **0 / 80 / 150 ms**, jitter **20 ms**, packet loss **1%**; record actual achieved conditions and an unshaped reference. |
FPS and p95 frame time are separate checks; neither is inferred from the other.
Record CPU/GPU models, OS/driver, engine/game revision, settings, route duration,
warmup, sample count, client count, shaping method/direction and actual latency.
Report memory, corrections, media transfer and stalls as observations before
inventing additional limits. No benchmark or 10-player result was produced by
this M0 record. The [M1 fixture plan](roadmap.md#m1-course-fixture-specification-implemented-human-runs-pending)
defines the next controlled experiment.
## Storage, migration and recovery decisions
- `HEARTH_HOME` selects the local runtime root. Keep the existing Windows root
and world unchanged; use a different absolute override for staging and an
explicit `HEARTH_ENGINE` if needed. Production data/configuration belong to a
separately operated external deployment, not the repository or game package.
- Packaging is a new game directory with deterministic generated assets and
exact SHA-256 inventory, not an installer or engine bundle. Package/backup/
restore refuse existing destinations and unsafe paths/links.
- All relevant local/external/remote engines must be stopped before snapshots
or restore. `--offline` is mandatory for CLI backup/restore. Lock checks are
conservative signals, not proof of remote idleness.
- An existing managed play world gets a full prelaunch snapshot. Legacy records
also get immutable per-key backup bytes before schema migration. Per-key
rollback is best-effort, not an engine metadata transaction or crash-durability
guarantee. Full offline snapshots remain necessary.
- Backups contain private authentication/player/world data. The operator owns
access control, encryption, capacity/retention and off-host copies. SHA-256
detects corruption, not authenticity. No automatic retention deletion exists.
- Restore to a new destination, verify and rehearse before an explicit cutover;
never overwrite the sole old world. Recovery tooling boots copies only. See
[README procedures](../README.md#package-back-up-and-restore) and
[persistence architecture](architecture.md#save-and-migration-contract).
## Verification and acceptance ledger
**M0 technical verification is complete within the Windows scope below.** No
user/operator approval is recorded. The [final verification record](m0-verification.md)
contains current counts, package digests, environment, commands, bounded logs and
the distinct earlier recovery report. Private logs/snapshots remain outside the
repository; neither source paths nor source-world contents are published.
| Gate | Owner | Current evidence / status |
| --- | --- | --- |
| Extracted game loads all modules, exact release contract | Technical agent | Complete: Luanti 5.18.0, game 0.1.0-m0, schema 1; current check/smoke and GUI require the verified package-load marker. |
| Node tooling and integrated Lua/engine suites | Technical agent | Complete: 17 Node tests; 123 Lua checks in seven isolated suites; 24 Lua files compiled; check/smoke/test markers and zero exits. |
| Fresh package verifies and repeats; tampered/missing/extra content rejected | Technical agent | Complete: two new external exports, identical manifests, 57 payload files; tamper/missing/extra/empty-directory rejection covered by passing tooling tests. Stale default dist was not reused. |
| Original old save unchanged, immutable snapshot, two restores and copy-only restart/migration | Technical agent | Complete retained evidence: 160 assertions (79 seed + 81 restart), two byte-exact restores and two successful shutdowns. Earlier package digest explicitly retained; not a new final-package recovery run. Source had zero claims; fixture/mock protection checks are not preservation of source claims. |
| Clean package and GUI play on verified development setup | Technical agent | Complete Windows technical scope: fresh external export verifies; current play loads the identical manifest digest in disposable HEARTH_HOME, client joins/leaves, normal shutdown and embedded-copy cleanup. Not a new-machine/clean-checkout certification. Linux/macOS native unverified. |
| First-session, summer/winter, production rejection, invalid/future schema refusal | Technical agent | Complete: current tooling fixture/production test and integrated schema, persistence, clock, runtime and player suites. |
| Play continuity and staging isolation; storage custody | Technical agent; user/operator for storage | Current isolated GUI pass; earlier launcher-agent two-launch regression verified metadata/sentinel continuity, two snapshots and cleanup. Original world not opened. Private ACL/encryption/retention and production authorization remain operator duties, not claimed certifications. |
| Rules, provisional scope, community location and acceptance | User/operator; technical agent for record | Rules, provisional review targets and risk owners recorded, completing M0 technical decision capture. No user approval fabricated. M1 human review and community-location approval before shared climate staging remain outstanding. |
M0 technical work is complete; the initial M1 implementation and automated
verification are complete, but its human acceptance gate remains open. This does not
accept the full launch checklist F17–F19, authorize production, certify a new
machine or expand native support beyond the observed Windows run. Final product
and deployment acceptance stays with the user/operator. Provisional budgets and
later location approval retain their explicit milestone owners.
## Risk register and ownership
“Technical agent” owns implementation/investigation and evidence, not permission
to approve deployment. “User/operator” owns policy, infrastructure and acceptance.
| Risk / known limit | Owner | Milestone / mitigation |
| --- | --- | --- |
| Integration differs from isolated tests; regression counts become stale | Technical agent | M0 checks complete and digests recorded; rerun regression suites for each M1 code change. |
| Legacy migration/crash loses state; metadata writes lack transactions | Technical agent; user/operator for backup custody | M0 evidence and M5 recovery rehearsal; preserve original, verify copy-only restore, never claim atomic durable migration. |
| Shared-world writer defeats offline assumptions; private backup leakage/storage exhaustion | User/operator; technical agent for tooling safeguards | M0 onward: stop all engines, protected/encrypted external storage, capacity/retention review. |
| Linux/native or macOS portability/clean-machine assumptions | Technical agent | M0/M1: real host evidence before expanding supported-client claims. |
| Sprint oscillation, collision/camera problems; stationary combat not compelling | Technical agent | M1: course, feedback and bounded stock-versus-custom decision. Not fixed by module extraction. |
| Unsafe saved arrival, failed emergence or death refill | Technical agent | M2 technical implementation and eight fixed-seed engine verification complete; blanket Y=-100 teleport is replaced by map-bound safe recovery, ±64-node fallback, owned-claim homes and partial needs recovery. Unfamiliar-tester acceptance remains open; see [M2 evidence](m2-verification.md). |
| Wrong community location, seasonal imbalance, timezone or sky misunderstandings | User/operator for location; technical agent for model/validation | Approval before M2 climate staging; synthetic fixtures never imply production selection. |
| Bounded room/support model and shared roles remain prototype-level | Technical agent; user/operator for acceptance | M3 implementation and automated verification complete; two-player cabin, comfort and grief-resistance acceptance remains open. See [M3 evidence](m3-verification.md). |
| Resource depletion and incomplete offline processes | Technical agent | M4: renewal/preservation before consequential spoilage and bounded idempotent accounting. |
| Public auth/moderation/deployment not hardened | User/operator for access policy/operations; technical agent for implementation | M5/M6: security/recovery review and real closed alpha before announcement. |
| Provisional budgets fail or browser scope expands silently | Technical agent for measurements; user/operator for scope | M1: report actual hardware/network evidence; revise targets explicitly, no invented measurements. |
+150
View File
@@ -0,0 +1,150 @@
# M0 final technical verification
Local verification date: **2026-11-01**; retained run timestamps cross into
**2026-11-02 UTC**. **Point-in-time M0 record:** M0 was complete against its
Windows technical exit gates; M1 had not started at this verification. M1 source
and later verification are recorded separately in [m1-verification.md](m1-verification.md).
This M0 evidence is not user/operator approval,
production authorization, clean-machine certification or a performance report.
See [decisions and ledger](m0-decisions.md#verification-and-acceptance-ledger)
and [roadmap](roadmap.md).
## Environment and evidence handling
- Existing Windows development host; absolute Node.js **v24.22.0** executable,
Node built-ins only; separately installed official Luanti **5.18.0** selected
by explicit absolute `HEARTH_ENGINE`. The launcher verifies reported version,
not distribution authenticity. Game **0.1.0-m0**, save schema **1**.
- Final verification uses the M0-era uncommitted working tree, identified below
by exact package digests, not an invented clean commit/revision.
- Fresh check/smoke and GUI runs use distinct disposable `HEARTH_HOME` roots
outside the repository. No original world was opened by this final pass.
- Absolute Node invokes bounded subprocesses through `execFileSync`; tooling
deadline 180 seconds, combined engine deadline 240 seconds, package builds
90 seconds each, verification 30 seconds. Both test output streams are retained
in complete external logs, not inferred from a truncated terminal preview.
- Evidence root: `%TEMP%/hearth-m0-final-VB7xU2`; tooling, engine, package and GUI
logs remain there. `%TEMP%/hearth-m0-final-latest.json` records run metadata.
These are local evidence locations, not public/downloadable artifacts.
- Only README and the three M0/roadmap documentation files are changed by this
pass. No gameplay, scripts, fixtures or default dist export is edited.
- Final validation: every captured non-documentation input hash remained
unchanged; all four documents were confirmed saved and their local file links
resolved. Editor diagnostics reported no errors. Repository-wide
`git -c core.whitespace=cr-at-eol diff --check` completed successfully with
empty output (the setting accommodates Windows CRLF without rewriting files).
## Current automated and package results
Commands below identify existing entry points; every invocation used the
absolute Node executable, not an assumed PATH runtime.
| Entry point / observation | Actual result |
| --- | --- |
| `--test tests/tooling.test.mjs` | **17 passed, 0 failed, 0 skipped**; 17 named tests, not an empty-file pass. |
| `scripts/run.mjs test` | **PASS**; check, smoke and overall test markers, verified package-load markers and zero exits. No separate `HEARTH_LUA` interpreter was used. |
| Engine compilation | **24 Lua files** compiled; compilation is not counted as unit execution. |
| Isolated content suite | **19** checks. |
| Schema suite | **24** checks. |
| Runtime scheduler safety suite | **10** checks. |
| Player lifecycle safety suite | **16** checks. |
| Persistence suite | **23** checks. |
| Model suite | **11** checks. |
| Clock suite | **20** checks. |
| Integrated unit total | **123 checks across seven suites**; live-engine registry/crafting/terrain/resources/enclosure smoke also passed. |
| `scripts/run.mjs package <new-external-destination>` twice; `verify <fresh-package>` | **PASS**, identical exact manifests; **57 payload files / 62 inventory entries** (manifest itself excluded). |
The 17 tooling tests include repeatable assets/manifests, existing-output refusal,
atomic build cleanup, missing/extra/tampered file and empty-directory rejection,
binary backup/restore, offline locks and SQLite sidecars, prelaunch snapshots,
unmanaged-world refusal, links/junctions/traversal, portable path selection,
exact engine version checks, first-session/summer/winter/production guards and
CLI backup/restore/package verification. Integrated suites additionally cover
legacy/invalid/future schema behavior and runtime/player safety.
### Exact fresh package identity
The stale default dist export was neither trusted nor overwritten. Two entirely
new temporary destinations were built and verified. SHA-256 values:
| Definition | Digest |
| --- | --- |
| Complete manifest file bytes | `cfd0efdc6e21d99c9f861575a65222bb52838fad38f4d61a5dd77ca034438bc9` |
| Compact `JSON.stringify(manifest)`; launcher package-load marker | `167682853c55cda4a5b5e960ec5e2cf0ef0c2f69b604fdf28e8020efbb3e009d` |
| Pretty-printed `manifest.entries` plus final LF; recovery-style inventory digest | `c5979ccbbaf3f2e245073927d43a44316ad2ca4f04b782658f1bbeb8219436f8` |
These are different digest definitions, not contradictory checksums. The package
is a game directory, not an archive, engine bundle or installer. It excludes
worlds, launcher scripts and the full test suite. Documentation-only edits do not
affect its payload inventory.
## Current GUI verification
`scripts/run.mjs play` used a fresh disposable external home and explicit engine,
not the user's original world. The launcher repackaged the current inputs; its
required package-load marker matched the **exact compact manifest digest above**.
Thus GUI evidence covers identical verified payload bytes, not a direct launch
of an exported directory containing no launcher tooling.
The log records client join, client leave and server shutdown. An external,
bounded process called `CloseMainWindow` only after join output; the launcher
exited successfully and its owned embedded game copy was removed. No sleep or
gameplay/script modification was used. This is a startup/save/shutdown check,
not a movement, fun, long-session or performance playtest.
Earlier launcher-agent evidence separately records **two** GUI launches of a
disposable persistent-style world: metadata stayed byte-exact, a sentinel was
retained, two prelaunch snapshots were retained and embedded copies were cleaned.
That is earlier regression evidence, not two additional launches claimed here.
## Retained recovery evidence
The newest sanitized report was read from the external runtime backups area;
recovery was **not rerun against the original world** during this final pass.
Report identity: timestamp **2026-11-02T01:43:35.559Z**, token
`fa103b35-9f98-45e5-b429-2ecb6f38c29c`; publication mtime
**2026-11-02T01:43:56.319Z**. It is a sibling report outside the immutable snapshot.
Only sanitized summary fields were exposed; no source paths, raw records,
coordinates, credentials or player identities are reproduced here.
| Retained fact | Report result |
| --- | --- |
| Engine / public readiness | 5.18.0 / `publicReady: false`. |
| Source preservation | `sourceUnchanged: true`; nine source files. This is the earlier runner's inventory comparison, not a new source inspection. |
| Snapshot integrity | `backupVerified: true`; immutable legacy backup bytes verified. |
| Restores / engine shutdowns | **2 byte-exact restores**, **2 successful engine shutdowns**, no clients. |
| Assertions | **79 seed + 81 restart = 160**. |
| Existing / legacy records | **2 existing keys, 2 legacy keys**; migrated records preserved. |
| Existing source claims | **0**. No assertion that nonempty source claim ownership was preserved. |
| Additional fixture evidence | Saved spawn, protection checks, synthetic player adapter, node metadata/inventory and absolute fire expiry verified. |
That report identifies **57 package files** and recovery-style package inventory
SHA-256 `a8c1c4e1459f37ed49ca882d6b7aa68a6ddc1e2bfd0ad66c7c534b6566163ed1`.
It **differs from the fresh final inventory digest**. The report therefore proves
the earlier recovery run only; it is not relabeled as recovery of the final
export. Current final code is covered by the fresh 123 integrated checks,
17 tooling tests and GUI/package evidence above. No unsupported claim of byte
identity between earlier and final gameplay inputs is made.
Because the source had zero claims, claim-owner integration/protection evidence
comes from regression fixtures and mocked coverage, not preservation of actual
source claims. Synthetic player-adapter and node fixtures are not a live-client
recovery rehearsal. Neither this report nor current tests promise transactional
metadata writes or crash durability. Private backup custody/ACLs, encryption,
retention, capacity and an eventual operator-controlled cutover remain separate.
## Scope closed at M0; work retained for later milestones
- **Windows:** current native automated and GUI verification passed on the
existing development host. No clean checkout on a new machine was tested.
- **Linux/macOS:** portable path selection has unit coverage only; native
execution remains unverified. No broader support certification is claimed.
- **M1:** not started. Hardware/network figures remain provisional review
targets, not measured/accepted budgets. No FPS, p95, multiplayer load or
latency measurements were taken. Measure and review them in M1.
- **Before M2:** the user/operator must approve community coordinates before
climate staging. Synthetic fixtures do not choose that location and the
decision is not a blocker to completed M0 technical work.
- **Approval and release:** no user sign-off is fabricated. Browser parity,
production hardening, full F17–F19 launch acceptance and clean-machine
certification are not implied by M0 completion.
+76
View File
@@ -0,0 +1,76 @@
# M1 implementation and evidence — The Last Hearth
Updated: 2026-11-02. Scope: M1 implementation and available automated/native
checks. **Not a human-playtest report or milestone acceptance.** M2 implementation
is proceeding under explicit user direction while M1 human acceptance stays open.
M0 evidence remains in [m0-verification.md](m0-verification.md); current sequence
is summarized in the [roadmap](roadmap.md).
## M1 implementation present
| Area | Implemented | Boundary |
| --- | --- | --- |
| Movement | `mods/hearth/movement.lua` applies bounded frame-time steps, smooth speed transitions, sprint intent/recovery lockout, stamina, wet slowdown, water pace, and spear brace. Runtime samples support/water/vertical velocity and changes only the engine speed multiplier. | Engine still owns character physics, camera, collision, prediction and networking. This is not a custom movement controller or full anticheat. |
| Tool interaction | Original B3D player rig with six animated clips and a right-hand bone; fixed-whitelist attached knife, axe, pick and spear visuals; contextual target/dig/placement preview; material step/dig/landing sounds. | First-person/third-person visibility depends on the native Luanti renderer. Camera motion is opt-in static FOV only; no imposed camera bob. |
| Combat | Weapon-specific reach, cooldown, stamina, wear, aim cone, line of sight and short recent-displacement plausibility check; three-phase opponent wind-up, strike and recovery; opt-in evasion/brace; non-lethal two-health floor, arena leash and disengagement. Production refuses developer spawn commands. | The moving opponent is a deterministic one-step sparring opponent, not wildlife AI or a network-robust competitive combat system. No true latency compensation. PvP stays disabled. |
| Course | Fixed-volume movement/interaction route with halves, doorway, pool, falls, material/tool and build stations, arena and ordered checkpoints. Generator exists only in a fresh nonce-marked local/staging course world. The engine-load preflight rejects non-live users and observed all 41,041 course-volume nodes unchanged. Mock tests exercise consenting generation/checkpoints; a live player must opt in with `/course`. | The native preflight intentionally did not generate or traverse the course. Its timing report is server-step intervals and Lua callback wall duration, **not** client FPS or CPU profiling. |
| Assets/audio | Deterministic generated B3D animation, four held-tool meshes, skin/material PNG and hash-pinned original mono Vorbis sounds. | Optional FFmpeg decode/regeneration test was not run; the package's Ogg framing/CRC/hash checks did run. No third-party sound/mesh was downloaded. |
| Feedback | Readable action/status HUD, contextual placement/tool hints, hit/counter/brace notices and material-specific sound feedback. | Individual comfort/accessibility and readability still need external playtesting. |
## Verification performed on the current M1 source
- **Node:** 31 tooling/asset tests pass, 0 fail. This includes reproducible assets,
packaged Ogg validation, animation/model geometry, safe staging/course launcher
behavior, production fixture denial, and packaging/backup path tests.
- **Lua in real Luanti 5.18.0:** 36 Lua files compile; 13 isolated suites pass,
**228 total checks** covering clock, combat, construction, content, course
security/geometry, encounters, movement, persistence, player lifecycle,
presentation cleanup, runtime safety, schema and the pure model.
- **Engine smoke:** pass. Real game bootstrap, M1 API/entity registration, recipes,
terrain/resources and enclosure test pass. Package identity marker matches the
exact staged package. No persistent survival world is opened by `test`.
- **Native `test:course`:** pass. Fresh local/staging nonce checks, native module
registration and no-consent refusal verified; 41,041 ordinary map nodes remain
byte/param-identical before and after the probe. It does not create terrain, grant
a kit, emulate a player or claim movement/combat/client rendering was exercised.
- **Interactive GUI:** one disposable summer course session has been launched on
the current Windows host for hands-on testing. A normal screenshot/engine startup
is not evidence that the route was traversed or that movement/combat is fun.
- **Environment:** current host uses Luanti 5.18.0 and integrated graphics driver
NVIDIA 596.47. No clean-machine, other-OS, multiplayer-client, FPS, frame-time,
memory, network-latency, server-load, controller, audio-listener or user-study
measurement is claimed.
## Manual M1 acceptance still required
1. In the disposable course, wait for the guide/arrival to finish and run
**`/course`**. This explicitly generates the bounded layout and gives a temporary
kit; until that command, terrain is deliberately untouched.
2. Traverse the ten ordered route checkpoints at walking and sprint pace. Repeat
troublesome stations and note terrain, slope, doorway, water, falling and
collision-snags with exact reproduction steps.
3. Try each held tool and attack a target. Run `/spar` for the moving opponent;
strike to consent to a round, step aside/back to evade, and face + hold RMB with
the spear to brace. Compare this with stationary `/practice`.
4. Use **`/course report`**; interpret the displayed values only as server globalstep
interval and course-callback duration. Capture client FPS/frame time separately
with actual client instrumentation; do not infer them from the report.
5. Invite several real testers. Ask whether they voluntarily repeat any movement,
interaction, or encounter. Record controls, host/device, frame-rate settings,
sample period, achieved latency/jitter/loss, player count, defects and observations.
6. Make a written, evidence-based decision: stock Luanti is adequate for this slice,
or a narrowly scoped engine API/client spike addresses a reproduced blocker.
The course uses a synthetic **45°N, 0°E summer/clear fixture only**; this is not the
community-world location. Production remains separate. Current `/spar` is allowed
only in local play or server-privileged staging; never in production.
## M1 disposition
Implementation, packaging/asset tests and automated/native preflight gates are
complete for this M1 scope. The **roadmap exit gate is not accepted yet**: several
testers have not been evidenced as voluntarily repeating play, there is no measured
performance/network matrix, and the stock-versus-custom decision lacks human
measurement. Subsequent M2 implementation does not change this status. No player
telemetry is automatically uploaded; share only voluntarily and omit private
location/account data from reports.
+67
View File
@@ -0,0 +1,67 @@
# M2 implementation and verification — The Last Hearth
Updated: 2026-11-02. Scope: first-night gameplay implementation and technical
verification. This document does **not** claim unfamiliar-player acceptance, M1
human acceptance, community-location approval or production readiness. See the
[roadmap](roadmap.md), [M1 evidence](m1-verification.md), [architecture](architecture.md)
and [launch checklist](launch-features.md).
## M2 implementation present
| Area | Implemented behavior | Boundary |
| --- | --- | --- |
| Safe arrival and home | Bounded emergence and safe-surface checks reject unloaded/liquid/hazard/occupied/claimed/recently lethal locations. `/sethome` requires a safe point in the owner's claim. `/home` revalidates the saved point before trying a safe world spawn or a 64-node bounded rescan. | No automatic geolocation. The old persistent play world is not used by tests. |
| Death | Inventory, food, water and wetness persist; respawn recovers at a verified safe point with only stamina and warmth limited. | No item-recovery corpse or final player-approved death policy. |
| Fire and light | Cold fuel needs a reusable firestarter; fuel is not self-igniting. Craftable torch supports nighttime activity. | No uncontrolled fire spread, lamps or light-quality playtest. |
| Cooking and water | Six-second cooking and four-second boiling reserve their fuel charge at queue time. Absolute deadlines continue during server absence, fuel can expire, and completed output remains until safe collection. | Offline player needs remain paused. No crops, spoilage or generalized offline simulation. |
| Rest | A placed bedroll inside a claim requires five still seconds under sheltered, ventilated cover. Movement, damage, unsafe cover or permission changes interrupt; completion adds modest warmth and has a 30-second cooldown. | Does not advance or pause the shared world clock; environmental shelter remains M1's four-ray approximation until M3. |
| Discovery | `/recipes`, the recipe-index button and guide page provide paginated plain-text search across recipes/ingredients plus process durations and sources. | Existing recipes and the progression ceiling remain intentionally small; tester-led balance is still required. |
| Persistence | Save schema 1 now validates the `homes` world-storage map; hearth job metadata is validated/reconciled on timer/load and output completion is idempotent. | Engine node metadata is not an envelope-schema transaction or crash-durable database. |
| Map starts | `test:seeds` creates fresh real-engine worlds for eight fixed map seeds and checks required resources plus a dry, supported safe-arrival surface in the same bounds as fallback spawn. | This is deterministic fixture coverage, not exhaustive world-generation proof or human assessment of start quality. |
## Automated verification on the current Windows host
- **M2 historical contract:** Node.js 24.22.0; stock Luanti 5.18.0; game 0.3.0-m2; save schema 1. M3 release supersedes it.
- **Engine integration:** `scripts/run.mjs test` passed. The pinned real engine compiled
39 Lua files, ran 16 isolated suites totaling **254 checks**, and passed registry,
recipes, terrain/resources/enclosure, and native gravity/collision/animation smoke.
The native smoke explicitly does not prove rendered animation, audible playback,
movement feel or a client playtest.
- **Node tooling/assets:** 30 named tests passed across `tests/tooling.test.mjs` and
`tests/assets.test.mjs`. Optional full codec decode/regeneration was not run;
Ogg framing/CRC/hash checks did run. Windows did not grant file-symlink privilege;
directory-junction protection was exercised.
- **Multi-seed:** `scripts/run.mjs test:seeds` passed on eight fresh worlds for seeds
731104, 19027, 2026, 987654321, 271828, 314159, 42 and 8675309. Every world had
all required starting-resource types and a dry, supported safe-arrival surface;
the smallest checked fallback area had 229 such surfaces (seed 42). The aggregate
`LAST_HEARTH_SEED_TEST_PASS` marker was emitted. Seed 42 initially exposed that the
±32-node fallback bound had no dry arrival; arrival/emergence checks and the fixture
were aligned and widened to ±64, after which all seeds passed.
- No separate Lua interpreter, clean-machine run, Linux/macOS native run or test of
the persistent play world is claimed. The exact M1 human gate remains open.
## Manual M2 acceptance still required
1. Use a disposable local `HEARTH_HOME`; never use a fixture in the preserved play
world. Test both first-night summer and winter fixtures, without revealing recipe
answers to the tester.
2. Ask five unfamiliar testers to attempt 30–60 minutes without coaching. Observe
whether each can identify local resources, craft a useful tool, ignite a hearth,
produce food and clean water, make a usable shelter/bedroll, and recover from an
unsafe spawn or death.
3. Record time-to-goal, failed/dead-end recipes, fuel/output loss, nighttime visibility,
recovery fairness and actionable feedback. Do not collect location/account data.
4. Capture exact game/engine/fixture, seed, host/device, controls and reproducible
defects. Do not present synthetic 45°N/0°E fixture runs as community-location
approval or climate balance.
5. The user/operator must explicitly choose and configure community coordinates
before shared climate staging. M1 voluntary repeat-play and real-client
performance/network evaluation also remain outstanding.
## Disposition
M2 gameplay implementation is in place and the available automated integration and
Node test gates passed at its release. M3 has since been implemented; M2 is still
**not human-accepted** until the unfamiliar-tester and operator gates above are evidenced.
No claim is made that M2 completes the launch checklist or authorizes production.
+64
View File
@@ -0,0 +1,64 @@
# M3 implementation and verification — The Last Hearth
Updated: 2026-11-02. Scope: the M3 home-building systems and automated evidence.
This record does **not** claim a two-player acceptance playtest, a general physics
simulation, production readiness, or completion of any launch checklist checkbox.
See the [roadmap](roadmap.md), [M2 evidence](m2-verification.md),
[architecture](architecture.md), and [launch criteria](launch-features.md).
## M3 implementation present
| Area | Behavior | Boundary |
| --- | --- | --- |
| Room and shelter | Flood-fill from the player's head is limited to 7 × 7 × 5 cells (245 max), with a one-second, 256-entry cache. Building/room edits invalidate geometry. Closed doors block weather; open doors expose a side; roofs and structural openings change shelter. Unknown/unloaded probes fail exposed. | Approximate room model, not material-specific insulation, wind simulation, or persistent smoke diffusion. No exact guarantee on large spaces beyond the bounded region. |
| Ventilation and fire | A closed room is sheltered but unventilated; a roof vent or open door admits exhaust. An enclosed, unvented lit hearth reports smoke and applies the existing bounded damage rule. | No chimney path simulation or stored smoke concentration. The vent is the M3 exhaust control. |
| Building palette | Craftable stone foundation, timber frame, wall, roof, stairs, open/closed door, reed-lattice window, and roof vent. Doors are reversible; rotation is supported through native facedir metadata where applicable. Recipe index includes M3 items. | Window art is a procedural placeholder; no polished building textures or animation are claimed. |
| Support changes | Existing placement graph limits remain: two horizontal construction links and 16 vertical blocks. `can_dig` refuses removing a support if bounded connected construction would lose a valid support; unknown/unloaded dependency probes fail closed. Dependencies cannot cross household claim ownership. | Refusal instead of collapse. This is intentionally conservative, bounded support validation—not a general structural solver. If an engine/mod forcibly deletes nodes outside callbacks, no cascade guarantee is claimed. |
| Household roles | Versioned schema-1 `access` world record; missing record defaults empty. Owners may grant up to 16 named `resident` (build/dig/workstation use) or `storekeeper` (storage only) members using `/claim share <player> <role>`. `/claim unshare`, `/claim list`, and `/claim remove` manage grants. Invalid roles/usernames and ambiguous overlapping claims deny. | No new numeric schema version; this is an additive record kind. Member accounts are not authenticated/linked by this prototype. |
| Protected storage | Craftable 24-slot storage can be placed only within a claimed homestead. Every open and inventory put/take/move is authorized. Storekeeper is limited to storage; removal requires empty inventory and owner/resident authority. Existing container owner metadata prevents a later claim from adopting another household's contents. | Node inventories use engine metadata; no transactional guarantee beyond Luanti's normal persistence. No shared ownership transfer protocol. |
| Comfort | Craftable rain cloak toggles persisted player metadata; it slows wetting and adds modest warmth. A sheltered drying rack accelerates drying; hearth heat also dries. | Small prototype values, not balance acceptance; clothing wear, item durability, and broader clothing progression remain future work. |
| Claim visibility | `/claim show` reports coordinates and emits temporary player-local boundary markers. | Markers are a convenience, not a complete 3D boundary renderer or client-independent map overlay. |
## Automated verification on this Windows host
- **Contract:** Node.js 24.22.0, Luanti 5.18.0, game **0.4.0-m3**, save schema 1.
- **Real-engine integration:** `scripts/run.mjs test` passed after M3 integration. The
pinned engine compiled **43 Lua files**, ran **19 isolated suites / 281 assertions**,
and passed native registry/craft/resource/enclosure and stationary actor smoke.
The tests exercise role policy, claim commands, room/vent behavior, storage and
removal denial; they do not emulate two real players or prove appeal/comfort.
- **Node tooling/assets:** `node --test tests/tooling.test.mjs tests/assets.test.mjs`
passed **31/31** against the bumped release. It covers reproducible package and
asset inventory/path protections. Optional full codec decoding was not run; Ogg
framing/CRC/hash checks did run. Windows did not grant file-symlink privilege;
directory-junction protections were tested.
- **Diff hygiene:** run the existing line-ending-aware `git diff --check` workflow.
- No separate Lua interpreter, clean-machine run, Linux/macOS native certification,
browser client, sustained multiplayer load, or production run is claimed.
## M3 two-player acceptance still required
1. In a disposable world, have two real clients create separated homesteads. Owner A
builds a cabin with foundation, frame, wall, roof, door, window, vent and storage;
owner B shares as resident, then as storekeeper in a separate check.
2. Verify resident can build/use and access household storage; storekeeper can use
storage but cannot place/dig blocks or use other workstations. Revoke each role
while clients remain online and verify access denies immediately. An outsider
must not open, move, take, place, dig, or indirectly access either household's
assets.
3. Check `/claim show`, release and restore a claim, reconnect, server restart, and
a nonempty storage refusal. Verify old worlds with no access map load unchanged.
4. Build a lean-to and a small enclosed cabin. Compare displayed exposure, wetness,
cloak and drying-rack outcomes. Test closed door, open door, vented hearth and
unvented hearth; feedback must explain comfort/smoke differences.
5. Attempt to remove a foundation supporting a wall/roof; removal must be refused
without changing nodes. Then remove an independent block; it should succeed.
Test cross-claim supports, unloaded edges/restarts and ordinary natural terrain.
6. Record exact engine/game, save fixture, client count, actions and reproducible
defects. Do not claim a human pass from engine harnesses or screenshots.
M1 voluntary repeat-play and performance/network acceptance, M2 unfamiliar-first-night
acceptance, and operator approval of shared climate coordinates also remain separate
open gates. M3 implementation supports beginning M4 engineering by explicit user
direction, but M3 is **not human-accepted** until the two-player cabin and permission
checks are observed and recorded.
+141
View File
@@ -0,0 +1,141 @@
# M4 implementation and verification — The Last Hearth
Updated: 2026-11-02. Scope: renewable ecology, food preservation, pottery,
small-tier metalworking and bounded offline accounting. This record separates
automated evidence from the required group playtest. It does not claim public
service readiness or human acceptance.
## Offline contract
| System | Rule |
| --- | --- |
| Player needs | Pause while the player is logged out. |
| Hearth fuel | Uses absolute UTC epoch deadlines from `os.time()`, not preview/solar time. Sticks add 45 s, logs 180 s; stored reserve is capped at 600 s. Fuel may expire during absence. |
| Hearth jobs | Cooking 6 s / 20 reserved fuel seconds; boiling 4 s / 10; smelting 60 s / 30; charcoal production 60 s / 30; pottery firing 120 s / 30; drying 300 s / 30. Inputs are consumed only after a valid job and timer are established. A completed result stays queued until an authorized player collects it. Timer and load reconciliation are idempotent. |
| Charcoal | One log plus clay forms a charcoal charge. The process consumes 60 s plus its 30 s reserved cost and yields charcoal worth 90 s; remaining heat plus output cannot exceed the source log's 180 s. |
| Crops | Planting time is stored as a decimal epoch string. Growth replays local historical solar temperature in six-hour samples, pauses below 3°C, runs at half rate below 10°C and caps replay at 30 days. Stages are derived from planting time, not timer count. |
| Berries | Harvested bushes use one absolute 24-hour regrowth deadline; no repeated yield while bare. |
| Saplings | Planted trees wait three real days, then grow only if a bounded 5×6×5 volume is loaded, clear and allowed by claim/protection rules. No placement beyond map bounds. |
| Wildlife | Hares are passive, non-dropping, nonpersistent, have a 900-second loaded lifetime, and are limited to two near a player and 24 globally. |
| Food age | General spoilage is disabled. Dried rations are shelf-stable. No hidden or irreversible item-age simulation exists. |
## Automated verification
- **Verified technical contract:** Luanti 5.18.0, game 0.5.0-m4, save schema 1.
The automated technical gates listed below have passed; human M4 acceptance
remains open.
- **Engine:** run `scripts/run.mjs test` using the pinned Luanti 5.18.0 runtime. It
creates fresh disposable worlds; it must never access the persistent play world.
- **Map seeds:** run `scripts/run.mjs test:seeds` after ecology/mapgen changes.
- **Node tooling/assets:** run `node --test tests/tooling.test.mjs tests/assets.test.mjs`.
- **Coverage:** crop historic temperature, catch-up cap, stage derivation, invalid
timestamps; renewal and inventory conservation; charcoal/smelting/drying/pottery
jobs; output collection, offline completion and repeated reconciliation; wildlife
persistence and population bounds; registration and real-engine craft smoke.
- **Results:** Luanti 5.18.0, game 0.5.0-m4: final `scripts/run.mjs test` passed
check, native smoke and all 21 isolated suites, including six ecology tests,
fourteen process tests and three wildlife tests. Eight fixed-seed runs then
passed safe arrival and required resource checks after tree density was raised
to address a no-log seed. The final full engine run passed on the updated tree.
Node tooling/assets tests passed. A server warning reported maximum lag peak
around 1.23 seconds during the bounded smoke workload; this is not a client-FPS
or performance certification. No human acceptance is inferred from automated
tests.
## M4 group acceptance remains required
Have a small group play several sessions across at least one real week in a
disposable/staging world. Record returns after absences, crop and regrowth outcomes,
resource availability near arrival, pantry usefulness, process completion and
whether the group chooses new goals without daily chores. Compare loaded and
unloaded outcomes over equal elapsed time. Check harvesting under claim sharing,
full inventories and ordinary latency. Preserve test world identity, exact release,
actions, and reproducible defects. Do not enable general spoilage as part of this
test.
M1 voluntary repeat-play/performance, M2 unfamiliar-first-night, M3 two-player
cabin/permissions, and operator approval of shared climate coordinates remain
separate open gates. M4 technical completion permits M5 engineering to proceed by
explicit user direction; it does not mark M4 human-accepted or waive earlier gates.
## Botanical journal and ecosystem survey — pending main validation
2026-11-02 implementation addendum. The preceding evidence is retained unchanged
and describes its earlier tested tree. This newer ecosystem/art/UI increment does
not inherit that full-engine certification and does not bump the game version or
save schema. See [living ecosystems](ecosystems.md) and [art direction](art-direction.md).
Implemented in the owned interface and focused recipe/journal harness:
- Shared eom.dev Mocha base/mantle/raised/border and text/muted anchors; restrained
mauve, teal and yellow accents, pressed-sprig dividers, consistent buttons,
hover/pressed states, fields, inventory lists and tooltips across guide, recipe,
inventory, location and survey forms. Seven guide pages stay inside 14×10;
Living ecosystems replaces the optional Riverward goal without removing fishing
recipes/process discoverability or changing fishing nodes/mechanics.
- Read-only `/ecosystem`, guide button and refresh: one call to the actual
`hearth.succession.habitat(player:get_pos())` Lua API plus one
`hearth.wildlife.status()` snapshot. Plant counts are a local 5×5×5 natural-origin
sample, with fern/tree cover combined; incomplete/unloaded cells are disclosed.
Fauna counts are global active hare/deer/fox totals, explicitly not a local
census. No scheduler, forage, deposit, metadata write, entity scan/spawn,
emergence or force-load is invoked by the survey.
- Current wildlife status reports global cap 32, local cap 6 and per-player cap 6;
this supersedes the older hare-only contract above for the working ecosystem
implementation, not for that historic verification run. Wild succession is
loaded-only without offline replay; crop/berry/planted-sapling clocks remain
separate. Dynamic Life is inspiration only; implementation and art are original.
- HUD panel with labeled percentages, color-coded reserve tracks and low-reserve
warnings; established `title`, `clock`, `needs`, `context`, `help` session keys
remain. The authored hotbar slot is repeated using `[combine:320x32` at ten
32-pixel offsets (no leading parenthesis), with `hearth_selected.png`. Image
setters are optional for older mocks. Standard crosshair texture override is
part of the main generated-asset integration, outside the owned files here.
- Inventory craft/result/main actions and listrings are unchanged. Location writes
remain singleplayer-only with finite-coordinate validation and 16-character
input bounds. Recipe searches remain literal, escaped and 48-character bounded.
No credentials, cryptography, certificates or network access are introduced;
CodeGuard secret avoidance and bounded/escaped input handling were applied.
**Focused validation actually run:** the existing Fengari runner under Node
24.22.0 completed all **13 recipe/journal tests**, including survey read-only
wiring, escaped/bounded fields, guide bounds, inventory actions, graphical needs
and exact ten-slot combine syntax. It also completed **27 succession tests**,
including the real habitat API's read-only/loaded contract. Both emitted their
expected completion markers, exit 0 and empty stderr. Earlier focused attempts
exposed two harness issues (a missing heat fixture value and an escaped-comma
expectation); both were corrected before this successful journal run.
A related standalone wildlife-suite invocation was attempted, but the terminal
did not return a confirmed completion result; it is **not counted as passing**
here. Editor diagnostics reported no errors in the five owned files. No main
integrated/full-engine tests or native rendering checks were run by this UI task.
Generated-asset/native client rendering, UI scaling, contrast and main validation
remain pending. M1–M4 human gates, long-session ecological balance and operator
acceptance remain open.
## Integrated ecosystem, art and seasons verification
2026-11-02 final integration addendum (supersedes the pending automated status
above, not the historical evidence or human gates):
- Pinned Luanti 5.18.0: final disposable-world check/smoke passed; 52 Lua files
compiled and all **23 isolated suites / 377 checks** completed. Includes nine
seasonal, 36 succession, 18 wildlife, 31 content and 13 journal/recipe checks.
- Final eight fixed-seed run completed all eight individual success markers and
the aggregate seed PASS marker for starting resources and safe arrival.
- All **48 Node tests** passed, covering tooling, original art, models, seasonal
atlases, deterministic regeneration and package integrity. Optional full audio
codec regeneration was not configured; framing/hash/CRC checks still passed.
- Earlier validation attempts encountered a seed timeout and socket-bind failure;
these are not counted as passes. Subsequent complete runs exited zero.
- Generated pixel-art contact sheet inspected. In-game meshes, materials, alpha,
seasonal transitions and HUD scaling have **not** received native visual or
human acceptance. Headless checks cannot certify appearance, enjoyment or FPS.
- Seasonal leaf loss also reduces simulated canopy shade; dormant plants preserve
ecological age, and seasonal food patches preserve their harvest cooldown.
- Game contract remains 0.5.0-m4 / save schema 1; user-undone release changes were
not reinstated. No persistent play world was opened or regenerated for tests.
Native visual review, long-session population balance, multiplayer protection and
the existing human/operator gates remain open.
+60
View File
@@ -0,0 +1,60 @@
# M5 engineering — private staging deployment contract
Updated: 2026-11-02. This is the first bounded M5 engineering slice. It defines
and offline-tests a private Kubernetes staging workload in `ansible-role-luanti`;
it is not a live cluster deployment, closed alpha, or production authorization.
The game payload remains 0.5.0-m4 / Luanti 5.18.0 / save schema 1.
## Staging contract implemented
- One `Recreate` Deployment, one retained standalone world PVC, ConfigMap with
explicit staging settings, UDP-only `ClusterIP` service and ingress
NetworkPolicy restricted to explicit IPv4 client CIDRs.
- Deployment image must be an immutable `@sha256:<64 lowercase hex>` reference.
This repository does **not** build or publish the Luanti server image. The
operator-provided image must contain Luanti 5.18.0, the verified game under
`/opt/luanti/games/last_hearth`, and the documented non-root/volume/entrypoint
contract. A digest is pinning/integrity metadata, not publisher authentication.
- Production mode, mutable image tags, missing coordinates, empty/wildcard client
access, malformed CIDRs and invalid names fail validation before Kubernetes API
access. PvP, creative mode and announcements are disabled; Luanti mod security is
enabled; default player privileges are limited. The pod runs non-root with a
read-only root filesystem, dropped capabilities, no service-account token, and
bounded CPU/memory.
- No public Ingress, `NodePort`, or `LoadBalancer` is rendered; apply operations
use `state: present` without pruning/deletion. The cluster CNI must enforce
NetworkPolicy, and the VPN/routed client source addresses must be confirmed by
the operator.
- Admin bootstrap is a controlled staging procedure: initially allow only the
operator's VPN address, initialize the native account with a unique staging-only
password, verify privileges and a second ordinary account, then deliberately
widen the allowlist for invited testers. Never reuse platform passwords or put
passwords/secrets in role variables, ConfigMaps, or this repository.
- PVC encryption, backup custody/retention, stopping remote writers and offline
restore remain operator duties. This role does not create backups or prove
storage encryption.
## Offline checks
The role test script renders Kubernetes YAML locally, validates required resource
shape and safety invariants, and checks pinning, policy, storage, privilege and
exposure. Negative cases prove production mode, mutable image references, empty or
public client CIDRs and invalid coordinates are rejected. The portable test target
has no kubeconfig and must not contact a cluster. If Ansible CLI is available, the
script additionally runs role syntax, Ansible rendering and negative task tests.
**Local result:** direct Jinja render and all resource-shape/security invariants
passed, along with seven fail-closed configuration cases. The Docker daemon was
unavailable, and Ansible CLI cannot run under the configured Windows Python
console in this environment, so full Ansible task syntax/render validation remains
unverified. No Kubernetes API was contacted.
## Remaining M5 work
No server image/artifact has been built or scanned, no staging cluster has been
configured or contacted, and no invited client has joined. Still required:
artifact build/signing/provenance, cluster-specific access and encrypted storage
review, moderator/reporting policy, client and server telemetry, restart/outage/
disconnect tests, sustained multiplayer and concurrent workstation/storage tests,
and a witnessed backup-break-restore drill with measured recovery objectives.
Earlier M1–M4 human acceptance gates remain open. No production rollout or M5
exit-gate pass is claimed.
+101
View File
@@ -0,0 +1,101 @@
# Full-detail render distance
The managed launcher requests **512 nodes** of terrain visibility by default.
This applies to both new and existing play worlds, disposable courses, and
engine-check/test configs. It is a bounded full-detail terrain preset, **not
level of detail (LOD)**. Minecraft Distant Horizons and Sodium are conceptual
references for longer horizons and efficient rendering, not Luanti addons or
dependencies. This change implements neither their renderer nor their features.
## Settings and units
For Luanti 5.18.0, the engine's default `viewing_range` is 200 nodes (engine
maximum 4000), `max_block_send_distance` is 13 mapblocks, and
`max_block_generate_distance` is 10 mapblocks. A mapblock is 16 nodes per side.
Increasing only the client range would leave server-side terrain delivery and
generation as bottlenecks, including in single-player's local server.
The launcher writes the following five settings exactly once per config:
| Setting | Default preset | Purpose |
| --- | --- | --- |
| `viewing_range` | 512 | Client terrain viewing range in nodes |
| `max_block_send_distance` | 34 | Server terrain send limit in mapblocks |
| `max_block_generate_distance` | 34 | Server terrain generation limit in mapblocks |
| `client_mesh_chunk` | 2 | Groups mapblocks into larger client mesh chunks; can reduce draw overhead but increases mesh rebuild granularity |
| `fog_start` | 0.65 | Starts distance fog at 65% of the viewing range; does not replace terrain with cheaper geometry |
Both block limits use `ceil(distance / 16) + 2`, bounded at 66 mapblocks. The two
extra blocks provide headroom around the requested range; they are not a promise
that every block inside that radius is immediately generated or visible.
| Requested nodes | Send blocks | Generate blocks |
| --- | --- | --- |
| 128 | 10 | 10 |
| 512 (default) | 34 | 34 |
| 768 | 50 | 50 |
| 1024 | 66 | 66 |
## Environment override and config lifetime
Set `HEARTH_VIEW_DISTANCE` in the environment inherited by the launcher before
starting play, course, or tests. For example, the value `768` requests the
768-node row above; `1024` requests the maximum supported preset. Any integer
from **128 through 1024 inclusive** is accepted, not just the table entries.
Unset the variable to restore 512. Use 128 or 256 as lower-cost fallbacks.
Only canonical unsigned decimal strings are accepted. Empty values, whitespace,
leading zeros, signs, fractions, exponent/hex notation, out-of-range values,
newlines, and appended config syntax are rejected, not clamped or interpolated.
Validation occurs before engine invocation, staging, backups, or world/config
writes on launch commands. The shared writer also validates independently before
filesystem mutations. Supplying these five managed keys via extra config lines
is rejected so they cannot duplicate or override the preset. Package, verify,
backup and restore commands do not consume the graphics environment variable.
The config is regenerated for each invocation in the launcher's private session
directory under the runtime selected by `runtimeHome()` / `HEARTH_HOME`. It is
**not a persistent user config**: normal exit or failure removes the session
directory, including its config. Editing that temporary config is not a durable
way to tune graphics. Persist the environment override in the launching shell or
your own launch environment instead. No personal profile path is hardcoded and
no installed engine config is edited. In-game changes to these managed settings
are replaced by the preset on the next launch.
## World safety and simulation scope
Existing worlds receive the graphics preset without rewriting their saved
mapgen, seed, boundary, world identity, or database files. A saved world detected
by either its world metadata or map metadata continues to suppress creation-time
terrain overrides. New worlds retain the existing creation-time terrain preset.
Seeing farther does not expand an old world's saved mapgen boundary or recreate
its terrain; terrain beyond that boundary remains unavailable.
No active-block range, object activation range, simulation cadence, entity
distance, or gameplay radius is increased. Frustum/occlusion culling is not
disabled or otherwise changed. Larger send/generate limits still mean **more
terrain work**: generation, loading, lighting, mesh building, transfer, storage,
and memory can all increase even without a larger active simulation radius.
Newly generated terrain is ordinary saved world data, not a disposable LOD cache.
## Performance and verification limits
The 512-node default requests 2.56 times the engine's default linear viewing
range. That does **not** imply only 2.56 times the cost: visible area, terrain
volume, foliage/mesh complexity, hardware, camera direction, and exploration
speed all matter. 768 and 1024 are more demanding options, not certified quality
tiers. Fog and mesh chunking are pragmatic settings, not measured FPS guarantees.
Node-only tooling tests cover the default, every allowed integer, malformed and
injected inputs, 768/1024 config output, unique graphics keys, and new/existing
play/course/test config paths with saved mapgen bytes preserved. These tests do
not start Luanti, measure GPU/CPU/RAM use, verify visual loading distances, or
certify performance. No native engine or asynchronous game launch is needed for
this validation.
Before claiming a performance improvement, compare the same world, camera route,
resolution, weather and hardware at 128/256/512 and optional 768/1024. Measure
client frame-time percentiles, memory, mesh rebuild stalls and generation/loading
latency separately for cold exploration and already-generated terrain. Course
Lua callback/server-step telemetry is not client FPS or GPU profiling. Lower the
override if frame pacing or memory use is unacceptable.
+430
View File
@@ -0,0 +1,430 @@
# Season VI implementation roadmap — The Last Hearth
Updated: 2026-11-02. Carried forward from the authoritative engine-checkout
document, doc/season-vi-roadmap.md, into this standalone game repository. Links
are adapted locally; the delivery strategy, M0–M6 scope, dependencies and gates
are retained. Status distinguishes implementation, automated evidence and
operator/human acceptance; it does not invent tester feedback or approval.
Feature IDs refer to the [launch checklist](launch-features.md).
**Current state: M0 technical gates complete; M1 implementation and automated /
native preflight complete, human acceptance pending; M2 implementation and
automated technical verification complete, human acceptance pending; M3
implementation and automated verification complete, two-player acceptance
pending; M4 technical implementation and automated verification are complete,
with its week-long group acceptance pending.** M2/M3 progress does not waive the
open M1 human gate. See
the [M0 evidence ledger](m0-decisions.md#verification-and-acceptance-ledger),
[M1 evidence](m1-verification.md), [M2 record](m2-verification.md) and [M3 record](m3-verification.md).
Windows checks ran on the existing development host, not a clean machine.
Linux/macOS paths are unit-tested only; native execution is unverified. Browser
launch readiness is not promised. Current release candidate contract is Luanti
**5.18.0**, game **0.5.0-m4**, save schema **1**. M4's human exit gate and all
earlier human/operator gates remain open.
**New ecosystem and journal/art pass:** original Dynamic Life-inspired succession
and the eom.dev Mocha botanical field journal are implemented as a further working
increment, not a new release. Main integrated tests and native visual validation
for this increment remain pending; earlier automated results below are historical
evidence, not validation of the new tree. See [living ecosystems](ecosystems.md),
[art direction](art-direction.md), and the [append-only M4 update](m4-verification.md#botanical-journal-and-ecosystem-survey--pending-main-validation).
## Delivery strategy
Build small playable increments, with a working saved world at each milestone.
First establish feel and a satisfying evening; then progression and sustainability;
then a safe public service. Presentation, accessibility, tests and performance are
part of each increment, not a final cleanup phase.
There is **no launch date commitment** yet. The breadth of original art, animation,
game systems and possible engine changes warrants milestone-based planning.
Estimate calendar dates after M1 establishes scope and available contributor time.
Treat effort as implementation + assets + tests + playtest revision, not Lua alone.
## Current baseline: M0 foundation through M3 home-building slice
Already present: standalone game loading, gathering/crafting, first-night needs and
queued hearth jobs, a versioned home and safe arrival, bounded room/vent shelter,
a small structural palette with refused unsafe support removal, shared claim roles
and protected storage, smooth movement/stamina, animated player/tools, material
feedback, opt-in sparring, a disposable movement course, and approximate solar/
climate behavior. M1 and M3 human acceptance and performance measurement remain open.
M0 adds an independent versioned game package, split module responsibilities,
schema validation/legacy migration, offline snapshot/restore/recovery tooling,
runtime isolation and fixture/production guards. See the
[actual module contracts](architecture.md) and [decisions](m0-decisions.md).
Integrated checks, fresh exports and Windows GUI play are verified; retained
old-save recovery evidence is qualified by its earlier package digest. M0 technical
completion is not operator acceptance or launch readiness.
Not established: fun progression, production-quality controls/combat/art, renewable
settlement economy, complete offline simulation, public multiplayer security,
browser parity, or clean-machine releases. Do not equate passing tests with launch
readiness.
## M0 — Establish a maintainable game and a shared rules contract
**Goal:** make subsequent work reproducible and prevent expensive incompatible choices.
Primary features: F11, F12, F17–F19. Prerequisite: P0.
1. Extract the game into its own content repository/package while preserving history
and keeping a reproducible engine integration. Do not discard the current world.
2. Pin the stock engine baseline and document local/staging/production data paths;
make the launcher/install process work on the intended development platforms.
3. Split responsibilities: player state, clock/climate, construction, interactions,
inventory/processes, UI, encounters and persistence. Preserve behavior with tests.
4. Introduce schema/version identifiers, validation and migrations before new saved
item/process formats. Add pre-migration backup and restore fixtures.
5. Agree on minimum supported hardware, concurrent-player test target, client
platforms, browser launch status, and measured latency/performance budgets.
6. Record decisions on death/recovery, initial progression ceiling, calendar display,
offline processing and claim boundaries. Select the community location before
staging climate balancing; local worlds retain their own selection.
7. Preserve a deterministic first-session fixture and explicit summer/winter/date
fixtures. Production configuration cannot accidentally enable test shortcuts.
**Exit gate:** a clean checkout runs tests and opens a versioned prototype;
the old save restores; scope decisions and unresolved risks have owners.
**Status qualification:** M0 technical exit gates are complete for the verified
Windows development path: integrated tests, clean temporary package exports,
isolated GUI play, retained copy-only recovery and owned decisions/risks. This is
not a clean-checkout/new-machine or Linux/macOS native certification, and no
user/operator sign-off is invented. Hardware/network figures are
[provisional acceptance targets, not measurements](m0-decisions.md#provisional-performance-acceptance-targets--not-measurements)
and remain M1 measurement/review work, not an open M0 performance gate.
User/operator approval of the community location is required before shared-world
climate staging; the M0 synthetic 45°N, 0°E fixture is not production selection.
M2 changes do not alter the historical M0 evidence or approval status.
## M1 — Make five minutes of interaction feel good
**Goal:** make moving, gathering, placing and one encounter enjoyable before adding grind.
Primary features: F01, F02, F09, F14, F15; F17 client decision. Prerequisite: M0.
**Implementation:** movement/stamina model, fresh isolated course, original skinned
and animated player/tool meshes, material/attack audio, contextual target and
placement feedback, and opt-in mobile sparring are implemented. The stationary
practice target remains for comparison. The stock engine is the deliberate M1
baseline: current features do not justify a custom client/protocol fork, though
future measured playtest evidence may change that decision. Browser launch remains
unpromised.
**Automated/native preflight evidence:** 31 Node tooling/asset tests pass; the real
engine compiles 36 Lua files and passes 13 isolated suites totalling 228 checks;
native check/smoke passes. The separate headless course preflight verifies staging
and observes 41,041 untouched volume nodes before player consent. Mock course tests
verify generator geometry/checkpoints. Neither constitutes human traversal or fun.
**Human acceptance remains open:** the user/regular testers should use the
[M1 playtest instructions and evidence](m1-verification.md), run the route and
moving spar repeatedly, and record actual FPS, server-step, corrections and
latency on the documented envelope. No group playtest or performance measurement
is claimed. M2 implementation proceeds only by explicit user direction; this does
not mark M1 accepted.
## M2 — Make the first night a complete game
**Goal:** a satisfying arrival → tools → fire → food/water → temporary shelter loop.
Primary features: F03, F06, F08, F10, F11, F14, F15. Prerequisite: M1.
**Implementation:** bounded safe-arrival/home revalidation; home points restricted
to the player's own claim; inventory-preserving death with partial needs recovery;
firestarter ignition, craftable torch, queued six-second cooking/four-second
boiling, persistent output collection, bedroll rest, searchable recipe/process
guide and an eight-seed real-engine mapgen check were delivered in **0.3.0-m2**.
Player needs remain paused offline; fuel/jobs expire by absolute real-time deadline.
No crop, spoilage, comprehensive shelter, or civil-timezone system is implied.
The M1 human gate remains open; implementation proceeds under explicit user direction.
**Technical verification:** compilation, integrated real-engine checks, Node tooling
tests and all eight fixed-seed starts pass on the documented Windows setup. Named
results and limits are in [M2 verification](m2-verification.md).
1. Implement safe arrival/home validation and multi-seed resource checks. Remove
inappropriate underground auto-teleport and handle pending/failed emergence.
2. Add contextual harvesting and a searchable recipe/process guide; rebalance
ingredient costs from observed play rather than arbitrary scarcity.
3. Add ignition, torches/lamps, timed cooking and boiling with visible process state.
4. Make a lean-to, bedroll, first meal and clean water achievable; include useful
nighttime activities because real-world time is not accelerated.
5. Tune need rates and recovery, initial grace and death consequences. No free
survival refill exploit and no punitive tutorial-death loop.
6. Validate date/location, season extremes, sky approximation and timezone labeling;
add weather transitions and clearly disclosed synthetic conditions.
**Exit gate:** five unfamiliar testers attempt the first 30–60 minutes without
coaching. Most can identify a danger and correct it; failures reveal actionable
feedback, not hidden recipes. Summer and winter versions both have feasible starts.
**Operator prerequisite:** approve shared community coordinates and explicitly
configure `hearth_latitude`/`hearth_longitude` before community climate staging.
Do not silently promote M0 fixture coordinates. Civil timezone policy/display
also needs an explicit decision when implemented; none is invented here.
**Human acceptance remains open:** five unfamiliar testers must attempt the first
30–60 minutes without coaching, across summer/winter starts, and report actionable
feedback. M2 implementation supports moving into M3 engineering, but M2 is not
accepted until that gate and operator review are recorded. Community coordinates
still require explicit operator selection before shared climate staging.
## M3 — Make a home worth improving
**Goal:** construction solves practical survival problems and supports personal expression.
Primary features: F04, F05, F08, F13, F15. Prerequisite: M2.
**Implementation:** bounded 7 × 7 × 5 room flood-fill with one-second cache and
structure-edit invalidation; roof/door/vent shelter and smoke classification;
stone foundations, timber frames, stairs, doors, windows, roof vents; support-aware
refusal of unsafe removals; household resident/storekeeper roles; private storage,
drying rack and a reusable rain cloak. The historical M3 release was **0.4.0-m3**. Details and
named technical evidence are in [M3 verification](m3-verification.md).
**Technical verification:** the pinned real-engine smoke, isolated room/access/
construction/storage/schema/persistence suites, and Node tooling/assets tests pass.
M3 adds versioned access data under schema 1; old saves receive an empty access
map. No separate migration format is claimed.
Decision boundaries: support removal is refused, not cascaded. Room probing fails
exposed for unloaded/unknown cells and is limited to 245 voxels. Windows and
materials are prototype approximations, not a fully simulated cabin or structure.
**Human/operator acceptance remains open:** the exit gate needs two real players to
build and share a cabin, confirm room/vent feedback and verify outsider denial; it
has not been performed in this technical session. The M1 repeat-play/performance
gate remains separate. This implementation proceeds under the user's explicit
direction so M4 engineering may begin, but M3 is not marked accepted until observed
two-player results are recorded.
1. Introduce foundations/frames and the small complete architectural palette.
Add preview, rotation, stairs, doors, windows and a functional vent/chimney.
2. Replace the ray heuristic with bounded room/roof detection and cached invalidation
when relevant blocks change. Verify correctness before adding more simulation.
3. Implement useful material/weather protection, clothing and drying, and a
dependable relationship between ventilation, fire and indoor comfort.
4. Apply support rules to additions/removals consistently. Prevent hazardous
cross-claim effects and test restart/unloaded structural changes.
5. Add protected storage/workstations, household roles and visible claim boundaries.
6. Complete the first cohesive set of building textures, sounds and animation.
**Exit gate:** two players build and share a small functional cabin without an admin;
its protection and comfort outperform a lean-to for explainable reasons. Another
player cannot steal, flood or undermine it through an unhandled interaction.
## M4 — Make the next week worth playing
**Goal:** complete a small, sustainable survival progression rather than an infinite checklist.
Primary features: F03, F07–F09, F11, F12; extend F15. Prerequisite: M3.
1. Add renewable forage/timber, a few crops, wildlife population limits and a
noncombat food path. Test community harvesting pressure near arrival.
2. Implement pottery/storage and preservation before enabling consequential
spoilage. Preserve age and quantities through every inventory operation.
3. Add the first useful metal tier: find source material → prepare fuel → process
at a workstation → make a visibly better tool. Keep the chain short enough
to finish and maintain without mandatory specialist classes.
4. Add modest material/climate variation to make ordinary journeys worthwhile;
give players preparation and route choices without authored adventure content.
5. Finalize bounded offline/unloaded processing, historic climate accounting and
process-completion idempotency. Publish the exact offline contract.
6. Tune temporary shelter, established home and seasonal preparation together.
Do not require daily maintenance or real months to test winter.
**Exit gate:** a small group plays several sessions over at least a week, returns
after absences and chooses new personal goals. Preservation works before harsh
decay is enabled. Loaded/unloaded outcomes agree and economic conservation holds.
**Technical status:** implementation and automated engine, ecology/process,
tooling/assets and eight fixed-seed checks are complete on the existing Windows
host. See [M4 evidence and the offline contract](m4-verification.md). This permits
M5 engineering to begin under the user's direction; it does not mark M4 human-
accepted or claim the loaded/unloaded group observations have occurred.
**Further ecosystem/presentation implementation (validation pending):** the guide
now prioritizes Living ecosystems over the optional Riverward goal. Original
loaded-only succession connects light/moisture competition, meadow/flower/fern
niches, slow tree stages and nutrient/decomposer feedback to bounded wildlife.
`/ecosystem` and the guide's Ecosystem survey observe a fixed habitat patch and
active global species totals, without mutation, spawning or force-loading.
Fishing recipes/process entries remain searchable for compatibility; fishing
mechanics and nodes are unchanged by this UI pass.
The shared journal treatment follows local eom.dev Mocha anchors with restrained
mauve/teal/yellow accents, pressed-sprig dividers, consistent buttons/fields/lists/
tooltips, seven bounded sidebar entries and readable content/footer spacing.
Inventory lists, transfer rings and permissions retain their behavior. The HUD
adds labeled percentages and colored reserve tracks on a dark panel; the hotbar
tiles one authored slot ten times and uses the matching selection outline.
See [ecosystem contracts](ecosystems.md) and [original art contracts](art-direction.md).
Focused journal regression results are recorded in the M4 appendix; full main
validation, native visual review and every human acceptance gate remain open.
**Landscape/diversity integration:** the [content expansion](content-expansion.md)
installs seasons → diversity → succession, then biome-owned terrain/species
registrations → diversity canonical nodes → seasonal variants → wildlife.
The old duplicate mapgen block is replaced, not stacked. Copper tool inputs and
a quartz window alternative extend resource utility; coal fuel/copper smelting
remain with the process integration owner. New worlds receive explicit broad
v7 relief and a ±4096 bound; saved mapgen/seed/bounds and generated chunks are
not rewritten. New ungenerated chunks in old worlds may have visible seams.
Focused tests cover registration/art/recipe contracts and unchanged saved metadata.
The earlier eight-seed evidence is historical, not certification of this preset:
native high-altitude emergence, species-aware resource/spawn checks, saved preset
verification, visual review and human acceptance remain required.
## M5 — Closed community alpha on the real deployment path
**Engineering status:** the first M5 slice now defines a private, digest-pinned
Kubernetes staging contract in [ansible-role-luanti](../../ansible-role-luanti/README.md).
Its direct Jinja render and fail-closed configuration checks pass offline. No
server image has been built, no cluster has been contacted, and no deployment is
claimed. See [M5 engineering verification](m5-verification.md).
**Goal:** prove the game can be shared, operated and recovered—not merely run locally.
Primary features: F12, F13, F16–F19. Prerequisite: M4.
1. Build pinned game/server/client artifacts and a non-destructive staging
deployment. Adapt or replace the old server role rather than assuming it fits.
2. Configure shared coordinates/timezone and access policy; remove debug grants
and test-only privileges. Preserve native game credentials initially if linking
is not yet ready; never ask players to reuse platform passwords.
3. Add moderation/reporting, production validation, security review, performance
telemetry and resource budgets. Exercise invalid/malicious client inputs.
4. Test restart, outage, disconnect, concurrent workstation use, full storage,
migrations and representative sustained multiplayer load.
5. Implement platform account linking and optional moderated Matrix bridge with
outage/loop tests. Add a minimal Play page and season feedback thread.
6. Take a consistent backup, deliberately break a staging instance, and restore
the complete world/player/process state. Record measured recovery objectives.
**Exit gate:** invited regulars can install/join from clean clients; no critical
data-loss/duplication/protection defect; recovery is demonstrated, not theoretical.
## M6 — Release candidate and public Season VI launch
**Goal:** ship an honest, polished and supportable experience.
Primary features: acceptance of all required F01–F19. Prerequisite: M5.
1. Freeze features; fix blockers, balance and usability rather than introduce new
mechanics. Complete asset attribution, controls, accessibility and UI review.
2. Repeat first-night, nighttime, winter-arrival, week-away and clean-install
tests on the release artifacts, not just development builds.
3. Publish supported hardware/clients, installation/join guide, survival/offline
rules, moderation/privacy policies and incident/support channels.
4. Prepare screenshots and a short authentic gameplay trailer. Schedule an
optional community first-evening stream; the game must not depend on the stream.
5. Verify production storage/backups, rollback, secrets and location configuration.
Keep the previous season archived/available under an explicit transition policy.
6. Announce launch only after signing off the checklist with recorded evidence.
**Exit gate:** no unresolved critical defects; a self-directed fun first session;
repeat-session evidence; measured performance at the declared target; a rehearsed
operational recovery. A calendar deadline never overrides those gates.
## Dependency summary and safe parallel work
**Critical path:** M0 → M1 → M2 → M3 → M4 → M5 → M6.
- Art/audio and UI can progress alongside each milestone using the current playable
loop; do not produce a large asset library before scale/style are validated.
- Deployment/backup scaffolding starts in M0 and can develop alongside gameplay;
public-server signoff waits for actual state/process schemas and exploits tests.
- Identity/Play-page work can begin after M0's platform decision, but integrations
must not delay proving that survival is fun or become gameplay dependencies.
- Conservation/persistence testing starts in M0 and expands with every new item,
process and permission; it is not deferred until M5.
- Defer worldgen-breaking changes until a migration/new-world decision is documented.
Do not silently regenerate regions containing player work.
## M1 status and remaining acceptance
M1 implementation and automated verification are complete for this slice. See
[M1 evidence and playtest instructions](m1-verification.md). The 31 Node tests,
228 Lua checks, pinned-engine smoke, and native no-consent course preflight pass.
M1 is **not human-accepted**: real route traversal, several voluntary repeat
testers, measured frame-time/network/load data, and the stock/custom decision
remain outstanding. M2 implementation is proceeding by explicit user direction;
this does not mark M1 accepted. M0 extraction and save/recovery work are not
repeated.
## M1 course fixture specification (implemented; human runs pending)
**Fixture identity:** a versioned, isolated staging course, separate from M0
seed/date scenarios in [scenario definitions](../tests/fixtures/scenarios.json).
The bounded generator, consent command and checkpoint/timing callbacks are
implemented in [course.lua](../mods/hearth/course.lua). The launcher always creates
a fresh nonce-marked `course` world; production and the preserved play world are
not eligible. Headless `test:course` is a no-consent preflight only. To generate
the route and kit, launch `node scripts/run.mjs course` and opt in with `/course`.
Never build the course in the preserved world.
- **Isolation and repeatability:** separate absolute `HEARTH_HOME`, explicit engine
executable, dedicated `staging` configuration when network testing, fixed seed
731104, recorded layout revision and exact start/end positions. Use synthetic
45°N, 0°E clear summer conditions for the reference run; repeat winter/rain as
a variant. This choice is only a course fixture, not community location approval.
Fuel still follows real time even when climate preview time is fixed.
- **Reset contract:** the launcher creates a fresh world and nonce for each course
session; `/course retry` resets only the current participant's timer/kit/route.
For *measured repeats*, launch a fresh course world to avoid terrain/entity
accumulation. No original survival-world snapshot is used or modified. Fixture
gear and test shortcuts remain unavailable in production. Record cold asset load
separately from warm traversal.
- **Route (about five minutes, not a measured result):** level walk/start/stop and
turning; long sprint to exhaustion/recovery; jump and 0.6-step-height boundary;
rough terrain/slopes; one-node and two-node doorway widths, corners, stairs and
low ceilings; water entry/exit/swimming and shore edges; controlled short falls
and recovery; repeat the route in reverse. Label geometry dimensions and record
any expected obstruction separately from a collision trap. Doors/stairs here
are test geometry, not a claim that new craftable building parts exist.
- **Interaction stations:** gather flint/stick/timber samples; compare
knife/axe/spear use, targeting/reach/occlusion, supported and intentionally
unsupported placement, protected-edge denials and full-inventory behavior.
Use the stationary target as a control and opt in to `/spar` for the moving
training opponent. Neither one is wildlife AI or a PvP mode.
- **Controls/accessibility:** record bindings, sensitivity, camera/FOV settings,
camera-motion preferences and input devices. Evaluate keyboard/mouse first;
additional devices/platforms get explicit results, not assumed parity.
- **Load/network matrix:** run an unshaped reference and RTT 0/80/150 ms with
20 ms jitter and 1% loss; document actual achieved shaping and direction.
Traverse alone, then with up to 10 concurrent real clients on 4 vCPU/8 GB.
Fake sessions do not satisfy multiplayer evidence. Use the provisional
4-core/8 GB/integrated OpenGL 3.3 client at 1080p low/view 96; target 60 FPS,
p95 frame time ≤25 ms and p95 server step ≤50 ms. These are targets, not data.
- **Evidence:** record revision/package inventory, host/OS/GPU/driver/settings,
warmup and timed sample duration, frame-time distribution, server steps, memory,
corrections/stalls and invalid action handling. Keep an issue log with route
station, reproduction, observed versus expected behavior and severity. Use
opt-in anonymized observations; private coordinates/account data stay out of
public reports. Report failed and incomplete runs, not just averages.
- **Telemetry limit:** `/course report` exposes checkpoint/time progress, engine
globalstep `dt` p95 and course-callback wall duration p95. It explicitly does
**not** profile client FPS, rendered frames, or CPU usage. Obtain those using
client instrumentation and record the actual network shaper configuration/results.
- **Acceptance:** several testers choose to repeat the loop; no known collision
traps or timing exploits, readable distinct tool/encounter feedback, and a
written stock-versus-custom decision supported by measurements. Performance
target revisions must be explicit, not relabeled passes. Browser testing is
conditional on a separate launch-scope decision, never assumed from native tests.
No human route run or performance matrix is currently recorded. The M1 evidence
document explains safe use of `/course`, `/course report` limitations and the
remaining user/operator gate.
## How progress is tracked
For each feature, record: owner, milestone, dependency IDs, code/asset changes,
save impact, automated test evidence, playtest observations and open defects.
Statuses: planned → implementation → automated verification → playtest → accepted.
After every milestone, decide **continue, revise, or reduce scope**. Reduce breadth
(creature count, material count, decorative variants) before cutting movement feel,
clear feedback, sustainable play or save safety. Any change to a required feature
must be reflected in the checklist, not hidden by marking the milestone complete.
+129
View File
@@ -0,0 +1,129 @@
# Offline species Wikipedia references
## 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.
**78 entries contain real English Wikipedia plaintext introductions. 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.
Five catalogue names resolved through Wikipedia's own title redirects in this snapshot:
| Catalogue Latin name | Recorded source title |
| --- | --- |
| *Schedonorus pratensis* | Lolium pratense |
| *Phleum pratense* | Timothy (grass) |
| *Calluna vulgaris* | Calluna |
| *Anemone nemorosa* | Anemonoides nemorosa |
| *Evernia prunastri* | Oakmoss |
These are API-reported redirects, not guessed mappings or search results. The catalogue's current *Collybia nuda* already matches its current Wikipedia title; the script does not silently replace it with an older *Lepista* name. `Calluna` is the article reached by Wikipedia's exact *Calluna vulgaris* redirect, not a separately selected genus fallback.
## Lua/UI data contract
[mods/hearth/wiki_articles.lua](../mods/hearth/wiki_articles.lua) is a **plain Lua module returning a table keyed by botany ID**. It has no factory, global assignments, registrations, dependencies, HTTP requests or gameplay side effects. [Content initialization](../mods/hearth/content.lua) loads this table into `hearth.wiki_articles` and constructs `hearth.species_effects` after botany and before succession. [The interface](../mods/hearth/interface.lua) constructs the injected [reader factory](../mods/hearth/species_reader.lua) and owns its command/field callbacks.
## Reading in game
- `/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.
- **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.
This remains **not identification, foraging, food preparation or medical advice**. Native Luanti formspec, visual and connected-player integration validation of this reader is pending; isolated tests do not certify those surfaces.
Each Wikipedia entry exports:
| Field | Type / meaning |
| --- | --- |
| table key | Stable species ID, e.g. `oak`, `wood_blewit`, `old_mans_beard` |
| `latin` | Exact catalogue binomial, retained even after a redirect |
| `title` | Actual resolved English Wikipedia source title; never HTML `displaytitle` |
| `url` | Validated canonical HTTPS article URL; copyable full-article link |
| `pageid` | Positive, finite JavaScript-safe integer identifying the source page |
| `revision` | Positive, finite safe integer revision ID; must equal API `lastrevid` |
| `revision_url` | Permanent source URL using `oldid` |
| `history_url` | Article history URL identifying Wikipedia contributors/authorship |
| `revision_timestamp` | API source revision UTC timestamp |
| `retrieved` | UTC ISO timestamp captured after the batch response |
| `text` | Verbatim plaintext introduction prefix, at most 8,000 Unicode code points |
| `kind` | `wikipedia_intro` |
| `excerpt` | `true`: this is an introduction, **not the full article** |
| `truncated` | Whether the introduction itself was shortened to meet the cap |
| `attribution` | `Wikipedia contributors, "<actual title>"` |
| `license` | `CC BY-SA 4.0` |
| `license_url` | `https://creativecommons.org/licenses/by-sa/4.0/` |
| `changes` | Describes plaintext introduction extraction, body omission and any shortening; no translation |
For an unavailable species, `kind = 'original_notes'`, `excerpt = false`, and `truncated = false`. `text` explicitly says it is an original availability note, **not a Wikipedia excerpt**. `pageid`, `revision`, `revision_url`, and `revision_timestamp` are **nil/absent**, not zero, strings, or fake revision numbers. `url` and `history_url` retain the exact missing species destination; they are not claims that an article exists. The original availability notes are dedicated to **CC0 1.0**, with their own `attribution`, `license`, `license_url` and `changes` fields. Never display them under a Wikipedia-excerpt heading or the Wikipedia license.
### Presentation and attribution requirements
- Display `title`, `latin`, `text`, and an explicit **Wikipedia introduction excerpt** label for `wikipedia_intro`. An availability note must get its own distinct label.
- Display the stored attribution, license/link, retrieval date, permanent revision link, history link and changes notice with the excerpt. The permanent revision and linked page history provide access to the contributing authors; no author usernames or personal information are downloaded into the bundle.
- Render **plain text only**. Never interpret HTML, wikitext, Markdown, inline links or article text as executable Lua or formspec syntax. Apply the UI's formspec escaping to every displayed field; byte escaping in the Lua source does not replace presentation-layer escaping.
- Offer `url` and `revision_url` as copyable text. Do not fetch them, prefetch link previews, auto-open a browser, or follow links found in `text` at runtime.
- 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`.
## What the real sources support about effects
The actual text is available for in-game reading, not just a source-name label. These examples were reviewed against the cached introductions and their exact source revisions:
| Species / cached ID | Natural-history observation actually present in the cached introduction | Source revision |
| --- | --- | --- |
| *Quercus robur* / `oak` | Near-neutral lowland soils; supports herbivorous insects, acorn-eating birds/mammals and fungi. Supports a qualitative habitat/food-web explanation, not a numeric resource multiplier. | [Wikipedia revision 1377452916](https://en.wikipedia.org/w/index.php?oldid=1377452916) |
| *Alnus glutinosa* / `alder` | Wet habitats, association with *Frankia alni* in poor soils, wildlife food/shelter, and light-demanding pioneer regeneration. The introduction does not quantify a game nutrient budget. | [Wikipedia revision 1379114226](https://en.wikipedia.org/w/index.php?oldid=1379114226) |
| *Trifolium repens* / `clover` | Widely introduced and cultivated as a forage crop. This supports a forage description, not human edibility or a quantified nitrogen-fixation rate. | [Wikipedia revision 1376551191](https://en.wikipedia.org/w/index.php?oldid=1376551191) |
| *Sphagnum palustre* / `sphagnum` | Strong water absorption and wet-forest habitat. The article's reported absorption capacity is a natural-history statement, not a measured in-game moisture transfer. | [Wikipedia revision 1241630727](https://en.wikipedia.org/w/index.php?oldid=1241630727) |
| *Amanita muscaria* / `fly_agaric` | Tree symbioses and poisoning risk. Supports a qualitative mycorrhizal/toxicity explanation; does not establish a safe consumption procedure or specify all permitted game hosts. | [Wikipedia revision 1380994403](https://en.wikipedia.org/w/index.php?oldid=1380994403) |
| *Ceratophyllum demersum* / `hornwort` | Submerged, free-floating aquatic growth. Supports the catalogue's qualitative rootless/submerged explanation, not a numerical light or water-depth window. | [Wikipedia revision 1366317776](https://en.wikipedia.org/w/index.php?oldid=1366317776) |
**Do not claim that all existing botany effects are scientifically verified by these introductions.** For example, the cached *Pteridium aquilinum* introduction does not establish the catalogue's allelopathy descriptor; the cached *Peltigera canina* introduction does not establish its nitrogen-fixer descriptor. The white-clover introduction likewise does not establish nitrogen-fixation rates. Those claims need separately reviewed species-specific sources or relevant article sections before an effects UI may call them sourced. Historical medicinal uses and preliminary extract studies are not proof of clinical benefit.
Existing game niche thresholds, nutrient caps, timing, protection rules, edibility decisions, host lists and conservation budgets remain **game policy**, not quotations or measurements from Wikipedia. This bundle neither derives those constants from prose nor changes mechanics. Disabled descriptive effects stay disabled. Reading an article never grants food permission, healing, buffs, poisoning, resource creation, or plant damage. The source observations above provide real evidence for explanatory text while keeping the unverified and numerical parts explicitly separate.
## Explicit maintenance script
[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.
- `--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.
Invoke Node with this script path and one of those flags. There are **no URL, title, credential, output-path, retry, concurrency or timeout override arguments**. Importing the script does not run maintenance or contact Wikipedia. Tests can inject `fetchImpl` and `now` into exported pure/testable helpers; normal maintenance uses built-in `fetch`. Catalogue parsing is bounded literal-row parsing, **never Lua execution**.
### 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.
- 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.
- Excerpts are associated with the API's current revision metadata, with `lastrevid` and `revisions[0].revid` required to agree. MediaWiki's current-page extract service is not an immutable old-revision extractor; a concurrent edit or upstream cache issue remains an upstream consistency limitation. The bundle records the exact metadata and retrieval time returned by that query, rather than claiming an independently verified historical text reconstruction.
- Every string, including IDs, titles and external text, is serialized into fixed-width decimal **UTF-8 byte escapes** inside Lua quoted literals. Article text cannot inject Lua quotes, comments, long strings, or code. Offline checking uses a strict generated-data parser, not `eval`, `load` or a Lua interpreter.
- The catalogue path and output path are fixed relative to the script. Existing `safePath` checks reject symlink/junction components, and `O_NOFOLLOW` is used where supported. Trusted local parent directories and one maintenance writer are required, as with the project's other file helpers. No CLI path can redirect writes.
- The existing cache is not opened for writing until every batch and the complete generated-data round trip validate. Network/validation failure leaves its bytes unchanged. The final fixed-file write is not crash-atomic: interruption/disk failure during writing requires restoring the tracked bundle or rerunning a successful refresh. It creates no sidecar or temporary workspace files.
## Focused offline verification
[tests/species_reader_spec.lua](../tests/species_reader_spec.lua) exercises the actual cached bundle, botany and effects APIs with injected read-only mocks. [tests/recipe_spec.lua](../tests/recipe_spec.lua) preserves its original 38 checks and adds command, dropdown, navigation, target and forged-field regressions. These are isolated Lua suites, not full-engine tests. The new reader suite follows the existing same-name module discovery convention; no launcher or engine-harness edits are needed.
The focused reader run passed **19 reader/HUD checks**, **43 recipe/journal checks (38 original + 5 new)** and **38 existing content checks**, with exact final totals and empty stderr under Fengari. The content probe additionally verified that sources/effects exist before succession construction. All six owned Lua sources/specs compiled. These results do not replace native LuaJIT/client integration, visual review or the full-engine gates; no full-engine tests were run for this change.
[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.
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.
+294
View File
@@ -0,0 +1,294 @@
# World generation: watersheds, woodland, and bedrock
## Integration boundary
[biomes.lua](../mods/hearth/biomes.lua) is a lazy factory:
```lua
local worldgen = dofile(modpath .. "/biomes.lua")(hearth, api, register_node, construction)
hearth.biomes = worldgen.register()
```
The main content owner must replace the **entire old mapgen section**, from its
mapgen alias registrations through its old tree decoration, with this call.
Keep the subsequent wildlife load. Call after base nodes, resources, ecological
flora, and seasonal patches have registered, using content's **private**
`register_node` helper. Do not retain duplicate old biomes/decorations/iron seams.
This module does not load itself or edit the content/bootstrap/launcher/config.
`register()` returns the same module and is idempotent on that instance. Create
one instance per mod load, not one per player. Duplicate terrain node names fail
rather than override existing definitions. Nothing clears global registrations;
existing mapgen aliases/items retain precedence. Coexisting mods may therefore
alter the final landscape. Alias precedence is coexistence, not a promise that a
foreign mapgen-stone binding is compatible with this game's geology.
### Shared ecosystem API
- `species_order`: exactly `oak`, `birch`, `pine`, `willow`, `alder`, `beech`,
`aspen`, `rowan`, `hazel`, `spruce` (ten species).
- `profiles[species]`: height, width, crown, approximate heat/humidity preferences,
altitude limits, soils, biome names, shade and litter hints. Alder also has
`nitrogen_fixer=true`; willow and alder have `riparian=true`.
- `surface(short_name | qualified_name | {name=qualified_name})`: habitat table
or `nil`. Moisture, fertility and drainage are normalized 0–1 **preferences**,
not actual sampled soil water or mutable nutrient stores.
- `supports_species(species, surface)`: soil compatibility only; `false` for
unknown inputs. The ecology owner must also check climate, light, water,
claims/protection, loadedness and player-placed status before any growth.
- `tree_schematic(species [, canonical])`: fresh bounded table schematic.
`canonical=true` uses existing generic logs/leaves (legacy meadow resource
probes); normal calls use species blocks. Unknown species raises an error.
- `biome_profiles`, `surfaces`, `rocks`: declarative read-only-by-convention data.
This owner registers only species **log/leaves/planks**, not species seedling,
young, mature, seed, seasonal-variant or decay nodes. Ecological growth and
seasonal behavior belong to the ecosystem owner. Mapgen places full-sized trees,
not oversized plant sprites; existing understory nodes provide habitats, litter,
deadwood, fungi and seasonal forage for the ecological simulation. Registering a
canopy group alone does not make an older ecology module recognize species:
that owner must consume `profiles`/`hearth_canopy` and recognize these soils.
Seasonal retinting/leaf shedding of species canopies is likewise not implemented
by this registration module.
## Ten connected landscape communities
Heat and humidity below are **Voronoi centers**, not hard rectangular ranges.
The nearest eligible center wins; altitude limits are hard. These heat numbers
are mapgen climate coordinates, not Celsius or geographic latitude. Runtime
weather/seasons remain owned by the climate system.
| Registered biome | Y | Heat / humidity | Surface / bedrock | Community |
|---|---:|---:|---|---|
| `hearth_temperate` | 2–110 | 52 / 45 | Grass / limestone | Open meadow, grain, flowers, greens, sparse oak and canonical timber |
| `hearth_oak_woodland` | 3–110 | 65 / 62 | Forest floor / limestone | Oak, domed beech, coppice hazel, fern, litter, fungi, fallen wood |
| `hearth_birch_forest` | 6–150 | 36 / 58 | Forest floor / granite | Birch, columnar aspen, rowan and spruce; woodland forage |
| `hearth_pine_taiga` | 2–180 | 18 / 38 | Podzol / granite | Pine, spired spruce and rowan; acidic nutrient-poor litter, mushrooms |
| `hearth_wet_forest` | 2–65 | 42 / 90 | Peat, alluvium / slate | Moist fern/decomposer community; alder only at actual water edges |
| `hearth_floodplain` | 2–8 | 68 / 90 | Alluvium / sandstone | Fertile flood terraces, grain, greens, willow/alder beside water |
| `hearth_dry_scrub` | 2–135 | 83 / 18 | Grass, shallow earth / sandstone | Open grass/flower scrub, berries; no artificial woodland or springs |
| `hearth_alpine_meadow` | 111–210 | 30 / 48 | Alpine turf, scree / slate | Short grass/flowers above most woodland; occasional rowan through Y=180 |
| `hearth_rocky_peaks` | 211–31000 | 25 / 45 | Permanent snow, scree / granite | Snowline across all climates, exposed structurally unsupported rock |
| `hearth_riverbank` | −31000–1 | 50 / 50 | Sand, gravel / sandstone | Shore/channel substrate, riparian gathering and rare old weirs |
The last biome is also the underground altitude fallback, not a claim that the
deep world is literally a river. Explicit geological beds below correct that.
There is an eligible biome at every altitude. Woodland-to-alpine overlap allows
climate-dependent treelines through Y=180; above 210 there is only the peak
biome, with zero vertical blend. Snow is a stable full-block cap, **not** seasonal
snowfall or a falling node. Surface/altitude alone cannot distinguish every
steep slope: stock v7's structural-support checks expose bedrock where the node
below is air/water; this is not a custom slope/erosion simulator.
### Full-block trees
| Species | Schematic width × height × depth | Shape |
|---|---|---|
| Oak | 7 × 7 × 7 | Broad layered crown with lateral log branches |
| Birch | 5 × 8 × 5 | Narrow elevated diamond crown and slender trunk |
| Pine | 7 × 9 × 7 | Wide low branches narrowing to a single leafy tip |
| Willow | 9 × 7 × 9 | Spreading branches, hanging curtains down to the lower trunk |
| Alder | 5 × 6 × 5 | Short riparian trunk and compact upright crown |
| Beech | 9 × 8 × 9 | Broad domed crown |
| Aspen | 5 × 9 × 5 | Tall columnar crown |
| Rowan | 7 × 6 × 7 | Open crown |
| Hazel | 7 × 5 × 7 | Low coppice form |
| Spruce | 5 × 10 × 5 | Narrow spired evergreen crown |
All occupied cells use probability 255; empty cells use zero. Schematics are
x-fastest, then y, then z, centered horizontally with base offset +1 above the
support. They do not force placement or cut into terrain. Canopies register
before ground flora, preventing grass from blocking the base trunk. These are
distinct authored forms, not random blocks. Trees at steep/obstructed boundaries
can still be clipped by native non-forcing schematic placement; there is no
Lua post-generation scan/rebuild of partial crowns.
Fallen deadwood is an ordinary sideways full cube, not a plant sprite or a mesh.
It requires an axe and drops one canonical `hearth:log`; hands do not yield
sticks. Its existing metadata-preserving orientation fix and ecological
substrate identity remain separate from this documentation/process/UI pass.
The named botanical catalogue adds 80 species in eight categories of ten;
`/botany [search]` reads their real common/scientific names, habitat windows,
seasons, game toxicity flags and conditional roles. See the
[complete category list and caveats](content-expansion.md#eighty-species-botanical-field-guide).
Reeds, clay and weirs require nearby source/flowing water (at least one of the
native eight neighbors at support Y or Y−1). Willow/alder require the same
check as well as biome, altitude and soil compatibility. Wet climate alone is
not proof of a river. Floodplain selection itself is climate/altitude based,
not hydrological flood simulation.
## Geology and resource progression
Surface bedrock follows biome geology, then registered ores run in this order:
1. Replace sandstone/canonical stone with **granite basement below −80**,
**slate from −79 to −24**, and **limestone from −23 to −8**. Above that,
shallow sandstone and biome-specific rocks remain.
2. Rare coherent 24-node basalt intrusions in bedrock below −24 (48³ scarcity;
correlated 3D noise, not scattered basalt confetti).
3. Nested scatter clusters in the resulting actual host stones:
| Resource node | Allowed hosts | Y maximum | Cluster / spacing |
|---|---|---:|---|
| `hearth:quartz_vein` | Granite, slate | 160 | 6 attempts in 3³, scarcity 11³ |
| `hearth:copper_ore_vein` | Basalt, limestone, slate | 32 | 5 in 3³, scarcity 13³ |
| `hearth:coal_ore_vein` | Sandstone, slate, limestone | 70 | 9 in 3³, scarcity 10³ |
| Existing `hearth:iron_ore_vein` | Basalt, granite, limestone, canonical stone | −8 | 6 in 3³, scarcity 11³ |
| Same iron, sparse exposed/shallow seams | Same hosts | −7 through 85 | 3 in 2³, scarcity 19³ |
All deep ranges start at −31000 (actual mapgen limit still bounds the world).
Scarcity and cluster counts are expectations, not guaranteed yields. Ores
replace their host, never air/soil/leaves. They can be exposed by terrain/caves,
but are not fake simple decorations placed on cavern floors.
All ten species logs drop themselves; **one log crafts four matching species planks**.
Each is accepted by exact name as **180 seconds** of direct hearth fuel.
Planks, buildings and arbitrary `tree` groups are not direct fuels.
For canonical timber, place **two logs of the same species in separate crafting
slots**, shapelessly, to produce **two canonical `hearth:log`**. There is no
single-log canonical conversion. Species planks convert 1:1 to `hearth:planks`. This
preserves axe requirements and the existing charcoal-charge, sticks, wall,
storage and other exact-name recipes without destructive node aliases. There
is no reverse conversion that duplicates timber.
Every raw rock drops itself; **one rock produces four matching rock-brick
building blocks**. Place **two rocks of the same type in separate crafting
slots**, shapelessly, to produce **two canonical `hearth:stone`** for existing
foundation recipes. There is no single-rock canonical conversion. Both pair
conversions preserve mass (2:2), and cannot shadow the single-input plank/brick
recipes. Mixed species or rock types do not convert together.
Masonry uses `hearth_building=1` plus construction placement/ownership hooks,
so the private registration helper retains room invalidation and protection.
There are no new stair/wall variants or alternate protection callbacks here.
Loose gravel/scree drops canonical loose flint. New soils drop canonical soil;
hand placing that soil does not restore an intact woodland ecosystem.
Quartz/copper/coal ore nodes each drop two `hearth:quartz`, `hearth:copper_ore`,
or `hearth:coal` craftitems. Downstream integration supplies three-quartz windows,
copper charges (45-second smelt with 30 seconds reserved) and 240-second coal
fuel under the 600-second reserve cap. These are process/content contracts, not
additional biome-factory recipes. Existing iron definitions are reused.
Loose flint registers on **all** listed surfaces, and dry sticks on those
surfaces through Y=210. Meadow canonical grass/logs remain for old resource
probes; woodland berries, river reeds/clay and limited upland seeps retain
early-survival routes. This is coverage, not a verified guarantee for seed
731104 or every start. Main must update species/soil-aware spawn checks and
validate water, flint, sticks, timber, forage, reeds and clay after integration.
Do not silently hand players supplies or claim native seed validation from
mocked registration tests.
## Terrain material art contract: 64 PNGs
Every file has prefix **hearth_** and suffix **.png**. No downloads, texture
aliases, temporary inventory silhouettes or engine-default art are required.
**61 world tiles** (expand each listed brace alternative):
- `hearth_{granite,basalt,limestone,slate,sandstone}.png` — 5 opaque rock faces.
- `hearth_{granite,basalt,limestone,slate,sandstone}_bricks.png` — 5 opaque,
coursed masonry faces, one material per full block.
- `hearth_{forest_floor,podzol,alluvium,peat,alpine_turf}.png` — 5 opaque soil
**top** faces. Bottom and four sides reuse existing `hearth_soil.png`.
- `hearth_{gravel,scree,snow}.png` — 3 opaque full-block faces.
- `hearth_{quartz_vein,copper_ore_vein,coal_ore_vein}.png` — 3 complete opaque
ore-bearing rock faces (not transparent overlays).
- For **each** oak, birch, pine, willow, alder, beech, aspen, rowan, hazel, spruce: `hearth_<species>_log_top.png`,
`hearth_<species>_log_side.png`, `hearth_<species>_leaves.png`,
`hearth_<species>_planks.png` — 40 tiles total. Logs use top/top/side;
leaves use clipped alpha and full-block `allfaces_optional` rendering.
**3 inventory images:** `hearth_item_quartz.png`,
`hearth_item_copper_ore.png`, `hearth_item_coal.png`.
Canonical grass/log/leaves, iron ore, water, flora, seasonal forage, sand, dirt,
loose flint and weir textures are **existing** art. This module owns no art
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
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.
```conf
mg_name = v7
water_level = 1
mapgen_limit = 512
mg_flags = caves,nodungeons,light,decorations,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_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_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_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:
- 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.
Never call `settings:set_default`, change mapgen at runtime, rewrite existing
map metadata, delete chunks, or automatically regenerate a player's world.
New biome registrations can still change **future ungenerated** chunks in an
existing world; they do not retrofit old terrain and may create seams. Offer a
backed-up new world for the coherent preset and explicitly disclose seams for
existing saves. Keep existing world identity and backup protections intact.
## Validation and limitations
The standalone [biome tests](../tests/biomes_spec.lua) exercise an allowlisted
registration-only API mock, all material references and drops, canonical
crafting paths, non-forcing aliases, repeat registration, altitude coverage,
ordered geology, species silhouettes, riparian checks and art names. They do
not assert that mocked noise produced terrain.
Engine source/API reviewed locally: `MapgenV7Params::readParams`,
`MapgenV7::baseTerrainLevelAtPoint`, `getMountainTerrainAtPoint`,
`getRiverChannelFromMap`, `makeChunk`, `MapgenBasic::generateBiomes`,
`OreScatter::generate`, `OreStratum::generate`, and the documented biome/ore/
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.
Security review: no credentials, cryptography, certificates, network access,
filesystem writes, deserialization or player-supplied executable input are
introduced. Schematics are fixed-size bounded data; private node registration
preserves the caller's protection/construction hooks. Existing player worlds
and engine-global API functions are not mutated.