feat(ecology): F3a — deterministic EcologySpawner (slot-scatter baseline + placement validation) #254

Merged
panda merged 9 commits from feat/ecology-f3 into main 2026-08-02 13:21:51 +02:00
Collaborator

Summary

F3a of the ecology foundation (epic #250, slice #251; on F1 #252 + F2 #253): the deterministic EcologySpawner — natural nodes now appear across the world without hand-placement, generated and validated server-side.

Owner-agreed design: the generation engine is C#; the policy (species↔biome, spacing) is data in content/ecology.txt; a future per-species Lua canSpawnAt predicate is a documented seam in EcologyPlacement, not built. Generation is lazy per chunk (EnsureChunkGenerated).

  • Slot-scatter determinism — the world is tiled into S×S slots per species (S = spacing); each slot hashes ONE candidate cell (SplitMix64 of worldSeed+slot+species), owned by the chunk that contains it. Order-independent, no shared state, same (worldSeed, world) → same baseline on every server/restart. Baseline nodes are planted at the epoch so even their growth clock is deterministic.
  • Server-authoritative placement (EcologyPlacement) — every candidate validated against the authoritative map: off-map / obstacle / non-walkable / road / wrong-biome / existing static or node / too-steep (8-neighbour ΔZ) / IsSuppressed seam (reserved for player structures, returns false).
  • GameOptions.WorldSeed + a bound EcologyOptions (spawner chunk size, max slope ΔZ); World gains thin EnsureChunkGenerated/EnsureChunkForCell delegates (the cell→chunk math lives in the spawner — World.cs stays delegation-only).

No in-game-visible surface yet (like F1/F2): nothing calls EnsureChunkGenerated at runtime until F4 wires it to AoI observation. Deferred: F4 (AoI trigger + NaturalNode* DTOs + protocol), F3b (baseline/delta persistence + offline recovery + self-GC), F5 (client render), and the harvest system + skills.

Heads-up for F3b (from the whole-branch review): baseline node identity is a per-session Guid; a regenerate-on-restart baseline mints fresh Guids, so F3b must key baseline identity on the cell (deterministic, one-per-cell), never assume Guid stability across restart.

How it was tested

  • Unit: EcologyScatter (determinism, seed-sensitivity, slot-owned-by-one-chunk, spacing-0 skip); EcologyPlacement (accept + every reject gate — water/wrong-biome/road/static/node/suppressed/slope); EcologySpawner via World (deterministic node set, idempotent regeneration, validation respected, cell→chunk routing); the spacing parser; a boot smoke test.
  • Whole solution builds 0 warnings; CSharpier 504 files clean; check-doc-refs resolves; full dotnet test green.
  • Built subagent-driven (TDD per task) + a whole-branch opus review: ready to merge — no Critical/Important; 4 Minor, of which 3 fixed in-branch (deterministic baseline epoch, integer floor-div, chunk-size guard) and 1 recorded for F3b (the Guid-identity note above).

Screenshots: N/A — no client-visible surface yet (the AoI trigger + render are F4/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)
  • World.cs delegation-only; single-threaded, no lock
  • Tests added for this change
  • Linked issue #251 (epic #250)
## Summary **F3a of the ecology foundation** (epic #250, slice #251; on F1 #252 + F2 #253): the **deterministic `EcologySpawner`** — natural nodes now appear across the world **without hand-placement**, generated and validated server-side. **Owner-agreed design:** the generation *engine* is **C#**; the *policy* (species↔biome, `spacing`) is **data** in `content/ecology.txt`; a future per-species Lua `canSpawnAt` predicate is a **documented seam** in `EcologyPlacement`, not built. Generation is **lazy per chunk** (`EnsureChunkGenerated`). - **Slot-scatter determinism** — the world is tiled into `S×S` slots per species (`S` = `spacing`); each slot hashes ONE candidate cell (SplitMix64 of `worldSeed`+slot+species), **owned by the chunk that contains it**. Order-independent, no shared state, same `(worldSeed, world)` → same baseline on every server/restart. Baseline nodes are planted at the **epoch** so even their growth clock is deterministic. - **Server-authoritative placement** (`EcologyPlacement`) — every candidate validated against the authoritative map: off-map / obstacle / non-walkable / road / wrong-biome / existing static or node / too-steep (8-neighbour ΔZ) / `IsSuppressed` seam (reserved for player structures, returns false). - **`GameOptions.WorldSeed`** + a bound **`EcologyOptions`** (spawner chunk size, max slope ΔZ); `World` gains thin `EnsureChunkGenerated`/`EnsureChunkForCell` delegates (the cell→chunk math lives in the spawner — `World.cs` stays delegation-only). **No in-game-visible surface yet** (like F1/F2): nothing calls `EnsureChunkGenerated` at runtime until **F4** wires it to AoI observation. Deferred: **F4** (AoI trigger + `NaturalNode*` DTOs + protocol), **F3b** (baseline/delta persistence + offline recovery + self-GC), **F5** (client render), and the **harvest** system + skills. **Heads-up for F3b** (from the whole-branch review): baseline node *identity* is a per-session `Guid`; a regenerate-on-restart baseline mints fresh Guids, so **F3b must key baseline identity on the cell** (deterministic, one-per-cell), never assume Guid stability across restart. ## How it was tested - **Unit:** `EcologyScatter` (determinism, seed-sensitivity, slot-owned-by-one-chunk, spacing-0 skip); `EcologyPlacement` (accept + every reject gate — water/wrong-biome/road/static/node/suppressed/slope); `EcologySpawner` via `World` (deterministic node set, idempotent regeneration, validation respected, cell→chunk routing); the spacing parser; a boot smoke test. - Whole solution **builds 0 warnings**; CSharpier **504 files** clean; `check-doc-refs` resolves; **full `dotnet test` green**. - Built **subagent-driven** (TDD per task) + a **whole-branch opus review: ready to merge** — no Critical/Important; 4 Minor, of which 3 fixed in-branch (deterministic baseline epoch, integer floor-div, chunk-size guard) and 1 recorded for F3b (the Guid-identity note above). **Screenshots:** N/A — no client-visible surface yet (the AoI trigger + render are F4/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) - [x] `World.cs` delegation-only; single-threaded, no lock - [x] Tests added for this change - [x] Linked issue #251 (epic #250)
panda merged commit dac8a09ef3 into main 2026-08-02 13:21:51 +02:00
panda deleted branch feat/ecology-f3 2026-08-02 13:21:51 +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!254
No description provided.