Files
last-hearth/docs/architecture.md
T
2026-11-03 12:41:09 -05:00

158 lines
15 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.