fix(render): water climbs the bank on every lake shore — a 5-unit waterline is one under the cliff threshold #292
Labels
No labels
alpha:wave-0
alpha:wave-1
alpha:wave-2
alpha:wave-3
area:assets
area:combat
area:ecology
area:infra
area:render
area:scripting
area:ui
area:world
enhancement
epic
migration
post-alpha
roadmap
tech-debt
type:bug
type:chore
type:design
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
marco/IsoMmo#292
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
On a lake shore the water texture is painted UP the bank: a wide, slope-shaded band of water colour sitting above the waterline. The ocean does not do it. Both facts have the same cause and the ocean is the control.
What the map actually says
Transect across the swamp lake shore (authored data, read through
CompactWorldMap):The waterline is a single-cell drop of exactly 5. No bank cells, no ramp — the ground is authored crisp.
The ocean beach, same probe:
No step at all —
beach_sandis the one land terrain incontent/tiledata.txtwith nobaseZ, so it sits at 0 like the water. That is why the ocean shore looks right and the lakes do not.Root cause
Two things compose, and the margin on the first is one unit.
The waterline is smoothed into a ramp.
GroundBlendRenderer.DisplayusesCliffStart: WorldRules.MaxWalkStepZ + 1f= 6, andTerrainDisplayHeight.CliffGatereturns 0 — "walkable terrace, keep it smooth" — where the largest 8-neighbour relief is <= CliffStart. The shoreline's relief is 5. So every waterline againstbaseZ 5land is classified as a terrace and gets the fullSigma: 1.4Gaussian, which spreads a one-cell drop over several cells of ramp.The blend has no idea there is a bank.
TerrainBlendField.Compute(width, height, biomeAt, blendWidth, chunkSize, voidId)takes a 2-D biome grid and no Z whatsoever. A water cell's SDF influence therefore reachesBlendWidth = 3cells inland regardless of whether those cells are 5 units higher — and inland is uphill.Water texture, over a ramp, shaded by the slope: water climbing the bank.
Direction (needs a design review before implementing)
The shoreline is a structural feature, not a height magnitude — the same argument the terrain cutaway already makes about itself. The targeted fix is to force the cliff gate open where a cell's neighbourhood contains both a fluid and a non-fluid terrain, instead of deciding by Z. Terrain
Walkable == falselooks like exactly the fluid set today, so it may need no new data.Stopping the blend from crossing the waterline (the same shape as "a road cell keeps weight 1 and its neighbours feather into it") is complementary and probably wanted too, but on its own it leaves the water SURFACE tilting up at the edge, because the smoothing moves the vertices, not just the colours.
Explicitly do NOT just lower
CliffStarttoMaxWalkStepZ: it is one character and it re-classifies every 5-unit terrace in the world, not only shores.Definition of Done
Design review — agreed 2026-08-09
Owner approved all three open decisions ("via"): (1) beach-against-inland becomes a bank too, (2) the
threshold is
WorldRules.MaxWalkStepZ(ramp from half, full at 5), (3) the display-Z cliff gate readingthe same concept is a separate change.
The design
A pure
TerrainBankinIsoMmo.Client.Core: an argine is the step between two adjacent pieces of groundthat are not the same surface, derived from the authoritative Z, never declared.
Continuity(deltaZ)returns 1 for coplanar ground and 0 at a full bank, ramping from
MaxWalkStepZ / 2toMaxWalkStepZ.TerrainBlendField.Computegains aFunc<int,int,int> groundZAt(padded like the biome grid, clamped atthe border so the map edge is not a false bank). Per cell, after
SmoothstepWeightand BEFORENormalize, a biome's weight is multiplied byTerrainBank.Continuity(dz)where dz is the minimum|deltaZ| between the cell and the cells of that biome within
blendWidth— a 7x7 search, because theweight is already zero beyond
blendWidth. The cell's own biome always has dz 0, so no cell is leftwithout weight.
Chosen over: "water never blends with non-water" (a special case the extend-by-type gate exists to
refuse, and it does nothing for future constructions); geodesic SDF / terrace components (a new algorithm,
and components clipped by the window edge would seam between rebuilds); fixing only the cliff gate (it
removes no water colour from the bank).
BuildWindowalready holdssnapshot.GroundZAt— the AUTHORITATIVE Z, not the smoothedDisplayZ— sono new data channel is needed.
Invariants Check
CanExecuteProtocolVersion.Currentstays 21BuildWindowworker, which reads only the snapshot;WorlduninvolvedWorld.csHARD GATEWorld.csScreenHARD GATETerrainBlendField/TerrainBank(Core), glue inGroundBlendRendererTerrainBankand the extendedComputeare in Client.Core, no MonoGame, unit-testedtiledata.txtfield; the bank is derived, so there is no free string to typeStart/Fullare rendering constants derived fromWorldRules.MaxWalkStepZ, siblings ofBlendWidth/Display/Relief; not configuration, so NOT onGameOptions(server-side), declared in one placeMaxSlopeZorTileMapRules; readsMaxWalkStepZas a constant onlysnapshot.GroundZAt, not the smoothedDisplayZ, as the cutaway rule requires; output is presentation-onlyGroundTileSelector: derived, deterministic, display-only)CLAUDE.mdbeside "Occupancy contains render" in the same change, plus its## Design checklistbulletHard gates with no checklist bullet (the index needs them): Content-scripting "authored in Lua" → N/A
(engine primitive, which that section assigns to C#); "Filesystem = namespace" → N/A (no script);
"API self-describing" → N/A (does not touch
IScriptContext); Code style "one type per file" → OK.Server-side validation
N/A, and not as a shortcut: there is no player intent. The field is derived deterministically from the
authored map that client and server read alike. A modified client that alters its own blend changes only
its own pixels — walkability still comes from
TileMapRulesserver-side, which knows nothing about thisfield. Same category as
GroundTileSelector.Verification plan
IsoMmo.Client.Core.Tests, pure logic — the level this file already uses): two coplanarbiomes produce weights bit-identical to today (the ocean non-regression); the same grid with a
5-step drops the foreign biome to 0 and the own biome to 1; a 1-2 step is unchanged; a 3x3 pedestal
raised by 5 inside one biome (the future foundation) does not bleed past its edge; and a cell whose
only weight is its own biome, pinning that
Normalizenever divides by zero.TerrainBank.Continuity: monotone, 1 at dz 0, 0 at dz >= Full, no NaN.just dev, the three shores already captured asBEFORE —
/tp 3872 4160(swamp lake),/tp 2102 8200(redwood lake),/tp 4544 5338(ocean). Thefirst two must change; the third must be identical. Plus a beach-against-inland boundary to judge
decision (1).
FRAMES ... build Xmsline the client already prints in DEBUG, same framing before andafter. Baseline measured during the tour: build median 431 ms, worst 558 ms.