feat(ecology): F2 — NaturalNode runtime model (entity + registry + World wiring + startup load) #253

Merged
panda merged 5 commits from feat/ecology-f2 into main 2026-08-02 11:44:59 +02:00
Collaborator

Summary

F2 of the ecology foundation (epic #250, slice #251; builds on F1 #252): the pure server-side runtime model for natural nodes. No player-facing surface yet — nothing creates a node until the F3 spawner — so this is infrastructure, deliberately invisible in-game (like F1).

  • NaturalNode — authoritative entity (sibling of Mobile), species-agnostic: its current stage is derived lazily from (SpeciesDef lifecycle + stageEnteredAt + now) via F1's SpeciesDef.StageAtElapsed (wallclock seconds, survives restart, no scheduler). Intrinsic self-validating AdvanceStage.
  • NaturalNodeRegistryinternal, one-node-per-cell, exercised through the World facade like the 4 sibling registries (no InternalsVisibleTo).
  • World — owns the registry + EcologyData; thin AddNaturalNode (resolves species → baseline stage) + NaturalNodeAt/NaturalNodeCount; stays delegation-only.
  • Startup — loads content/ecology.txt once (like tiledata.txt; copied to GameServer + test output), never in the tick.

Deferred to later slices (NOT here): baseline/delta persistence + EcologySpawner (F3), AoI + NaturalNode* DTOs + protocol bump (F4), client render (F5), and the whole harvest system + skills.

Two decisions worth flagging:

  • Carries a 1-line fix (9ec9c64): main went red under Sonar S8969 when F1's dir! met the #220 SonarAnalyzer 10.31 bump (two green PRs → red main). This PR repairs it — merging greens main.
  • The plan's Task 2 (registry) + Task 3 (World wiring) were merged: an internal registry split from its only consumer fails the unused-member analyzer, and the repo tests registries via World, not directly — so no InternalsVisibleTo, and the registry is trimmed to what F2 uses (Add/At/Count; by-id lookup / removal / enumeration land with their F3 consumers).

How it was tested

  • 16 unit tests (IsoMmo.GameServer.Tests/Ecology): lazy stage (multi-hop growth, block-flags follow current stage, backwards clock-skew), AdvanceStage, one-node-per-cell, distinct ids, species resolution + guard rejections; a boot smoke test (server starts with ecology.txt loaded — RED without the csproj copy, GREEN with it).
  • Whole solution builds 0 warnings; CSharpier 496 files clean; check-doc-refs resolves; full dotnet test green.
  • Built subagent-driven (TDD per task) + a whole-branch opus review: ready to merge, clean — no Critical/Important; 3 forward-looking Minor notes for F3 (spawner validates the cell before AddNaturalNode; add a by-id index for harvest; the Guid is per-session so persistence keys on the cell).

Screenshots: N/A — F2 has no visible surface (no runtime node creation until the F3 spawner; no rendering until F5).

Checklist

  • just lint (CSharpier + analyzers, 0 warnings)
  • just test green (whole solution)
  • Whole solution builds
  • check-doc-refs green
  • Multi-platform preserved (pure C#/.NET, no OS dep)
  • Tests added for this change
  • Linked issue #251 (epic #250)
## Summary **F2 of the ecology foundation** (epic #250, slice #251; builds on F1 #252): the pure server-side **runtime model** for natural nodes. No player-facing surface yet — nothing creates a node until the F3 spawner — so this is infrastructure, deliberately invisible in-game (like F1). - **`NaturalNode`** — authoritative entity (sibling of `Mobile`), **species-agnostic**: its current stage is derived **lazily** from `(SpeciesDef lifecycle + stageEnteredAt + now)` via F1's `SpeciesDef.StageAtElapsed` (wallclock seconds, survives restart, no scheduler). Intrinsic self-validating `AdvanceStage`. - **`NaturalNodeRegistry`** — `internal`, one-node-per-cell, **exercised through the `World` facade** like the 4 sibling registries (no `InternalsVisibleTo`). - **`World`** — owns the registry + `EcologyData`; thin `AddNaturalNode` (resolves species → baseline stage) + `NaturalNodeAt`/`NaturalNodeCount`; stays delegation-only. - **Startup** — loads `content/ecology.txt` once (like `tiledata.txt`; copied to GameServer + test output), never in the tick. **Deferred to later slices (NOT here):** baseline/delta persistence + `EcologySpawner` (F3), AoI + `NaturalNode*` DTOs + protocol bump (F4), client render (F5), and the whole harvest system + skills. **Two decisions worth flagging:** - Carries a 1-line fix (`9ec9c64`): **main went red under Sonar S8969** when F1's `dir!` met the #220 SonarAnalyzer 10.31 bump (two green PRs → red main). This PR repairs it — merging greens main. - The plan's Task 2 (registry) + Task 3 (World wiring) were **merged**: an `internal` registry split from its only consumer fails the unused-member analyzer, and the repo tests registries via `World`, not directly — so no `InternalsVisibleTo`, and the registry is trimmed to what F2 uses (`Add`/`At`/`Count`; by-id lookup / removal / enumeration land with their F3 consumers). ## How it was tested - **16 unit tests** (`IsoMmo.GameServer.Tests/Ecology`): lazy stage (multi-hop growth, block-flags follow current stage, backwards clock-skew), `AdvanceStage`, one-node-per-cell, distinct ids, species resolution + guard rejections; a **boot smoke test** (server starts with `ecology.txt` loaded — RED without the csproj copy, GREEN with it). - Whole solution **builds 0 warnings**; CSharpier **496 files** clean; `check-doc-refs` resolves; **full `dotnet test` green**. - Built **subagent-driven** (TDD per task) + a **whole-branch opus review**: *ready to merge, clean* — no Critical/Important; 3 forward-looking Minor notes for F3 (spawner validates the cell before `AddNaturalNode`; add a by-id index for harvest; the `Guid` is per-session so persistence keys on the cell). **Screenshots:** N/A — F2 has **no visible surface** (no runtime node creation until the F3 spawner; no rendering until F5). ## Checklist - [x] `just lint` (CSharpier + analyzers, 0 warnings) - [x] `just test` green (whole solution) - [x] Whole solution builds - [x] `check-doc-refs` green - [x] Multi-platform preserved (pure C#/.NET, no OS dep) - [x] Tests added for this change - [x] Linked issue #251 (epic #250)
The #220 SonarAnalyzer 10.31 bump meets F1's EcologyContentTests (dir! after
Assert.NotNull) — each PR was green alone, main went red once both landed.
Registry is internal (like the sibling registries) and exercised through World, not
constructed directly in tests — so no InternalsVisibleTo. Trimmed to what F2's World uses
(Add/At/Count); by-id lookup, removal and enumeration arrive with their consumers in F3.
feat(ecology): load content/ecology.txt at startup; boot smoke test + docs (F2 of #251)
All checks were successful
ci / Lua content lint (pull_request) Successful in 9s
ci / Lint & Test (pull_request) Successful in 5m1s
28abe22168
panda merged commit c41543e5ac into main 2026-08-02 11:44:59 +02:00
panda deleted branch feat/ecology-f2 2026-08-02 11:45:00 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!253
No description provided.