fix(client): a biome stops painting where the ground steps away from it #293

Open
panda wants to merge 1 commit from fix/blend-knows-a-bank into main
Collaborator

Summary

"Va insegnato al blend che cos'è un argine" — and "anche per costruzioni future".

On every lake shore the water was painted UP the bank: a wide, slope-shaded band of water colour sitting
above the waterline. The ocean did not do it, and the ocean is the control — beach_sand is the only land
terrain with no baseZ, so there the water and the sand are coplanar and there is no bank to climb.

The blend field had no idea a bank existed. TerrainBlendField.Compute took a 2-D biome grid and no Z
whatsoever
, so a water cell's SDF influence reached BlendWidth = 3 cells inland regardless of whether
those cells were 5 units higher — and inland is uphill.

So the fix is not about water. A new TerrainBank (Client.Core) says what an argine is — the step
between two adjacent pieces of ground that are not the same surface — derived from the authoritative
height, never declared
. One rule covers a lake shore, a beach against the inland, a paved platform, a
house foundation and the cut face of a mountain road, so a thing nobody has built yet is already covered
and there is no per-terrain flag and no "water doesn't blend" special case. Like the cutaway it reads a
difference, so nothing in it is absolute.

Compute now takes a height accessor; per cell, a biome's weight is multiplied by
TerrainBank.Continuity(dz) where dz is the minimum |ΔZ| between the cell and the cells of that biome
within blendWidth
— a 7×7 search, because the weight is already zero beyond that, so no distance
transform is needed. The cell's own biome is level with itself, so no cell is ever left without weight.

The threshold is not taste: every land terrain in content/tiledata.txt sits at baseZ 5 and every fluid
and beach at 0, so WorldRules.MaxWalkStepZ is the world's one level change. Below half of it is the
ripple of ordinary ground (the massif's flanks measure ~1 z/cell) and goes on blending, or every slope in
the world would go crisp.

BuildWindow already held snapshot.GroundZAt — the AUTHORITATIVE ground, never the smoothed
DisplayZ, because feeding the display height back in would let the smoothing erase the step this exists
to keep. No new data channel.

The design review, its full Invariants Check and the verification plan are in #292.

Not in this PR, deliberately: the display-Z cliff gate classifies a 5-step as a "walkable terrace"
(CliffStart = MaxWalkStepZ + 1 = 6) and smooths the waterline into a ramp, so the bank is still a soft
slope rather than an edge. That is geometry, not colour; it should read the same TerrainBank and it gets
its own change and its own screenshots.

Screenshots / recording

Fresh DB, just dev, driven through the debug harness (docs/debug-harness.md); the BEFORE frames are
from the same three coordinates on the parent commit.

before after
Swamp lake (3872,4160) — marsh z5 against dirty_water z0 swamp before swamp after
Redwood lake (2102,8200), 3x zoom on the edge redwood edge before redwood edge after
Beach against inland (4452,5424) — beach z0 against grass z5 beach before beach after
Ocean (4544,5338) — beach z0 against water z0, must NOT change ocean after

Commands, per stop: login testkey entertype /tp <x> <y>key enterscreenshot <path>.

The ocean is unchanged, measured rather than eyeballed. Comparing the after frame against the before
frame at the same camera, excluding every pixel that moved across a 4-frame before-burst (the animated
water) plus the HUD and the character card: 0 changed pixels out of 593,896 still pixels, and 0 out of
the 54,630 in the shoreline band itself.

How it was tested

  • 11 new unit tests in IsoMmo.Client.Core.Tests (pure logic, no engine): TerrainBankTests pins the
    curve's shape (1 at level, unchanged at a 1-2 ripple, 0 at the world's own step, symmetric in sign,
    monotone, a real ramp in between); TerrainBlendFieldTests adds the bank cases — a bank stops a biome
    painting across it; the same grid four steps higher blends identically, because a bank is a
    difference; a stride-height step is not a bank; a raised 3×3 paved platform keeps its material to itself
    and does not feather three cells out (the future-construction case); and every cell still sums to 1 on a
    grid where every cell is a step from all four neighbours (the Normalize divide-by-zero shape).
  • dotnet csharpier check . clean, dotnet build of the whole solution 0 warnings / 0 errors,
    dotnet test fully green (1,011 tests) — run in a clean worktree at this commit so the result is the
    repo's committed content, not my working tree.
  • Perf, from the client's own DEBUG telemetry over 7 distinct window builds while touring the same stops:
    window build median 431 → 465 ms, worst 558 → 558 ms. The added work is per boundary cell only.

Checklist

  • just lint passes (CSharpier + analyzers, zero warnings)
  • just test is green
  • The whole solution builds (client and tools included)
  • Multi-platform preserved (plain C# in Client.Core, no platform API)
  • Tests added/updated for this change
  • Linked the related issue (#292) and its Definition of Done is met

Closes #292

## Summary "Va insegnato al blend che cos'è un argine" — and "anche per costruzioni future". On every lake shore the water was painted UP the bank: a wide, slope-shaded band of water colour sitting above the waterline. The ocean did not do it, and the ocean is the control — `beach_sand` is the only land terrain with no `baseZ`, so there the water and the sand are coplanar and there is no bank to climb. The blend field had no idea a bank existed. `TerrainBlendField.Compute` took a **2-D biome grid and no Z whatsoever**, so a water cell's SDF influence reached `BlendWidth = 3` cells inland regardless of whether those cells were 5 units higher — and inland is uphill. So the fix is not about water. A new `TerrainBank` (Client.Core) says what an *argine* is — the step between two adjacent pieces of ground that are not the same surface — **derived from the authoritative height, never declared**. One rule covers a lake shore, a beach against the inland, a paved platform, a house foundation and the cut face of a mountain road, so a thing nobody has built yet is already covered and there is no per-terrain flag and no "water doesn't blend" special case. Like the cutaway it reads a *difference*, so nothing in it is absolute. `Compute` now takes a height accessor; per cell, a biome's weight is multiplied by `TerrainBank.Continuity(dz)` where dz is the **minimum |ΔZ| between the cell and the cells of that biome within `blendWidth`** — a 7×7 search, because the weight is already zero beyond that, so no distance transform is needed. The cell's own biome is level with itself, so no cell is ever left without weight. The threshold is not taste: every land terrain in `content/tiledata.txt` sits at `baseZ 5` and every fluid and beach at 0, so `WorldRules.MaxWalkStepZ` **is** the world's one level change. Below half of it is the ripple of ordinary ground (the massif's flanks measure ~1 z/cell) and goes on blending, or every slope in the world would go crisp. `BuildWindow` already held `snapshot.GroundZAt` — the AUTHORITATIVE ground, never the smoothed `DisplayZ`, because feeding the display height back in would let the smoothing erase the step this exists to keep. No new data channel. The design review, its full Invariants Check and the verification plan are in #292. Not in this PR, deliberately: the display-Z cliff gate classifies a 5-step as a "walkable terrace" (`CliffStart = MaxWalkStepZ + 1` = 6) and smooths the waterline into a ramp, so the bank is still a soft slope rather than an edge. That is geometry, not colour; it should read the same `TerrainBank` and it gets its own change and its own screenshots. ## Screenshots / recording Fresh DB, `just dev`, driven through the debug harness (`docs/debug-harness.md`); the BEFORE frames are from the same three coordinates on the parent commit. | | before | after | | --- | --- | --- | | Swamp lake (3872,4160) — marsh z5 against dirty_water z0 | ![swamp before](https://git.homelab.devncode.it/attachments/4d557211-6d59-40b5-a2b5-858f840809b7) | ![swamp after](https://git.homelab.devncode.it/attachments/f5a57c48-3790-4eb7-886d-bd2efd07dc04) | | Redwood lake (2102,8200), 3x zoom on the edge | ![redwood edge before](https://git.homelab.devncode.it/attachments/0c731eb6-6de1-425e-aa72-6de63118b873) | ![redwood edge after](https://git.homelab.devncode.it/attachments/b3381c57-1f1c-46e3-a36c-8bb3c8dc1d40) | | Beach against inland (4452,5424) — beach z0 against grass z5 | ![beach before](https://git.homelab.devncode.it/attachments/f91b5241-4547-44a9-87d7-1343563f087e) | ![beach after](https://git.homelab.devncode.it/attachments/9cfc620a-e629-467a-b4e4-93e7d0e44575) | | Ocean (4544,5338) — beach z0 against water z0, must NOT change | | ![ocean after](https://git.homelab.devncode.it/attachments/e49783aa-3a76-42ed-80e3-89d5e184e135) | Commands, per stop: `login test` → `key enter` → `type /tp <x> <y>` → `key enter` → `screenshot <path>`. **The ocean is unchanged, measured rather than eyeballed.** Comparing the after frame against the before frame at the same camera, excluding every pixel that moved across a 4-frame before-burst (the animated water) plus the HUD and the character card: **0 changed pixels out of 593,896 still pixels**, and 0 out of the 54,630 in the shoreline band itself. ## How it was tested - 11 new unit tests in `IsoMmo.Client.Core.Tests` (pure logic, no engine): `TerrainBankTests` pins the curve's shape (1 at level, unchanged at a 1-2 ripple, 0 at the world's own step, symmetric in sign, monotone, a real ramp in between); `TerrainBlendFieldTests` adds the bank cases — a bank stops a biome painting across it; **the same grid four steps higher blends identically**, because a bank is a difference; a stride-height step is not a bank; a raised 3×3 paved platform keeps its material to itself and does not feather three cells out (the future-construction case); and every cell still sums to 1 on a grid where every cell is a step from all four neighbours (the `Normalize` divide-by-zero shape). - `dotnet csharpier check .` clean, `dotnet build` of the whole solution 0 warnings / 0 errors, `dotnet test` fully green (1,011 tests) — run in a clean worktree at this commit so the result is the repo's committed content, not my working tree. - Perf, from the client's own DEBUG telemetry over 7 distinct window builds while touring the same stops: window build **median 431 → 465 ms**, worst **558 → 558 ms**. The added work is per boundary cell only. ## Checklist - [x] `just lint` passes (CSharpier + analyzers, zero warnings) - [x] `just test` is green - [x] The whole solution builds (client and tools included) - [x] Multi-platform preserved (plain C# in Client.Core, no platform API) - [x] Tests added/updated for this change - [x] Linked the related issue (#292) and its Definition of Done is met Closes #292
fix(client): a biome stopped painting where the ground steps away from it
All checks were successful
ci / Lua content lint (pull_request) Successful in 19s
ci / Lint & Test (pull_request) Successful in 5m22s
a24bb9887a
All checks were successful
ci / Lua content lint (pull_request) Successful in 19s
ci / Lint & Test (pull_request) Successful in 5m22s
Required
Details
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/blend-knows-a-bank:fix/blend-knows-a-bank
git switch fix/blend-knows-a-bank
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!293
No description provided.