feat(client): cliff-face legibility — explicit faces at Z-breaks, keeping relief smoothness #262

Closed
opened 2026-08-02 16:58:55 +02:00 by panda · 2 comments
Collaborator

Problem

A real terrain Z drop renders nearly flat, so a step the server legitimately blocks (|dz| > MaxWalkStepZ=5) looks like walkable ground — an invisible wall. Found live on asterra with the admin terrain-z probe: player z15, a tile z5 (a 10-unit drop) reads as flat brown.

Diagnosis (why it looks flat)

The ground is geometrically Z-lifted (mesh vertex at IsoProjection.ToScreen(x,y,z), ZScale=4 → dz10 = 40px). The culprit is the display-height smoothing from #247 (TerrainDisplayHeight): where an 8-neighbour per-tile relief is between CliffStart and CliffEnd, it blends the vertices toward a Gaussian-blurred Z, rounding the sheer face into a ramp — to kill the mesh "teeth". But the band starts at CliffStart = MaxWalkStepZ + 1 = 6, so every just-blocked step (relief 6–12) falls inside the smoothing band and is rounded visually flat. Blocked-but-drawn-flat = the smoothing. The old cliff-face machinery (CliffEdges / Cliff shader technique / cliffArt, the original Cliff T1–T6) was removed by #247 (cf50c6d); today the only cliff cue is a per-pixel contact shadow (CliffStrength, relief A-channel) + the rounded geometry.

Decision (owner)

Lever B — draw explicit cliff FACES at Z-breaks (a vertical wall so a drop reads as a wall), subject to the hard constraint: keep the current graphical fluidity — no return of the mesh "teeth", keep the smooth relief look everywhere else. (Rejected: A retune-smoothing-only — just moves the threshold; C stronger-shading-only — cheaper but doesn't truly read as a wall. B may still borrow C's shading to seat the face.)

Design sketch (to refine in the session)

  • Keep the relief mesh smoothing exactly as now (fluid terraces, no teeth).
  • Where a per-tile Z break exceeds MaxWalkStepZ (a true gameplay cliff), detect the edge (CliffEdges-style) and draw a distinct vertical face filling the gap between the high lip and the low foot — a cliff/rock art strip or a procedurally-shaded band — in its own pass (cf. the removed Cliff T5 "build+draw cliff faces"), integrated with the relief pipeline.
  • Net: smooth walkable terraces + a legible wall at real drops. The face is the visual counterpart of the server's step-height gate, so "blocked" is always visible.

Tooling in hand

The admin terrain-z probe (HUD readout: player-tile Z, cursor-tile Z, step dz, adjacent-step BLOCKED flag; admin-gated, client-only) is the measuring instrument for this work — pending its own small PR.

Next

Own session: measure the per-tile Z gradient at the problem spot (confirm smoothed-step vs gentle data) → brainstorm face integration → critical-design-review → implement in tasks with live screenshot iteration. Superseding note: this revisits #247's cliff Z-rounding for legibility (keeps its smoothness, adds the missing face).

## Problem A real terrain Z drop renders nearly flat, so a step the server legitimately blocks (`|dz| > MaxWalkStepZ=5`) looks like walkable ground — an invisible wall. Found live on asterra with the admin terrain-z probe: player z15, a tile z5 (a 10-unit drop) reads as flat brown. ## Diagnosis (why it looks flat) The ground **is** geometrically Z-lifted (mesh vertex at `IsoProjection.ToScreen(x,y,z)`, `ZScale=4` → dz10 = 40px). The culprit is the **display-height smoothing** from #247 (`TerrainDisplayHeight`): where an 8-neighbour per-tile relief is between `CliffStart` and `CliffEnd`, it blends the vertices toward a Gaussian-blurred Z, rounding the sheer face into a ramp — to kill the mesh "teeth". But the band starts at `CliffStart = MaxWalkStepZ + 1 = 6`, so **every just-blocked step (relief 6–12) falls inside the smoothing band and is rounded visually flat**. Blocked-but-drawn-flat = the smoothing. The old cliff-**face** machinery (CliffEdges / Cliff shader technique / cliffArt, the original Cliff T1–T6) was **removed** by #247 (`cf50c6d`); today the only cliff cue is a per-pixel **contact shadow** (`CliffStrength`, relief A-channel) + the rounded geometry. ## Decision (owner) **Lever B — draw explicit cliff FACES at Z-breaks** (a vertical wall so a drop reads as a wall), **subject to the hard constraint: keep the current graphical fluidity** — no return of the mesh "teeth", keep the smooth relief look everywhere else. (Rejected: A retune-smoothing-only — just moves the threshold; C stronger-shading-only — cheaper but doesn't truly read as a wall. B may still borrow C's shading to seat the face.) ## Design sketch (to refine in the session) - Keep the relief mesh smoothing exactly as now (fluid terraces, no teeth). - Where a per-tile Z break exceeds `MaxWalkStepZ` (a true gameplay cliff), detect the edge (CliffEdges-style) and draw a distinct **vertical face** filling the gap between the high lip and the low foot — a cliff/rock art strip or a procedurally-shaded band — in its own pass (cf. the removed Cliff T5 "build+draw cliff faces"), integrated with the relief pipeline. - Net: smooth walkable terraces + a legible wall at real drops. The face is the visual counterpart of the server's step-height gate, so "blocked" is always visible. ## Tooling in hand The **admin terrain-z probe** (HUD readout: player-tile Z, cursor-tile Z, step dz, adjacent-step BLOCKED flag; admin-gated, client-only) is the measuring instrument for this work — pending its own small PR. ## Next Own session: measure the per-tile Z gradient at the problem spot (confirm smoothed-step vs gentle data) → brainstorm face integration → `critical-design-review` → implement in tasks with live screenshot iteration. Superseding note: this revisits #247's cliff Z-rounding for legibility (keeps its smoothness, adds the missing face).
Author
Collaborator

Visual gate outcome — approach ① (procedural floating face) REJECTED, pivot to ①b (stepped ground)

Approach ① was fully built on branch feat/cliff-face-legibility (Tasks 1–4: CliffEdges, a procedural Cliff shader technique, a face pass in GroundBlendRenderer, and removal of the #247 Z-rounding — all reviewed, 862 tests green, whole solution builds 0 warnings) and taken through the visual gate on asterra. It failed, owner-rejected, for two reasons:

  1. Detached from the rise. The vertical face is a separate quad drawn over the ground. But the ground mesh is a cells-as-vertices heightfield (one vertex per cell at its Z; adjacent cells interpolate), so a cliff renders as a near-flat ramp — a vertical quad has no edge to attach to and floats over terrain that still reads flat. This is almost certainly the same failure that killed the #245 skirt faces ("empty corners / detached").
  2. Foreign colour. A fixed dark-earthy tone reads as a wall of a different material, not the terrain's own surface in shadow.

How UO actually does it (owner's insight, from a UO:NL screenshot): UO never draws a separate procedural face on a smooth mesh. Its land tiles are 4-corner and STRETCH to connect the four corner heights — so a slope is the terrain tile deformed (attached by construction, terrain-coloured), never a floating quad. Dramatic cliffs/walls/buildings are authored static art placed at z. Two mechanisms, neither of which is "a vertical wall bolted onto a smooth heightfield".

New direction — ①b: the cliff becomes part of the GROUND mesh (hybrid smooth/stepped heightfield).

  • Walkable slopes (ΔZ ≤ MaxWalkStepZ) stay smooth exactly as now (keeps the fluidity).
  • A cliff edge (ΔZ > MaxWalkStepZ) renders as a flat lip + a vertical drop whose surface is TERRAIN — textured by the ground shader, so it is in-tone by construction and attached by construction (it is the mesh, not an overlay). No separate quad, no art (keeps the clean look; avoids the #245 "artificial" rejection).

Branch feat/cliff-face-legibility is parked as reference (the CliffEdges cliff-detection and the pipeline understanding are reusable), not merged — main is untouched. Next: a design pass on ①b (critical-design-review), then a fresh plan + implementation.

## Visual gate outcome — approach ① (procedural floating face) REJECTED, pivot to ①b (stepped ground) Approach ① was fully built on branch `feat/cliff-face-legibility` (Tasks 1–4: `CliffEdges`, a procedural `Cliff` shader technique, a face pass in `GroundBlendRenderer`, and removal of the #247 Z-rounding — all reviewed, 862 tests green, whole solution builds 0 warnings) and taken through the visual gate on asterra. **It failed, owner-rejected**, for two reasons: 1. **Detached from the rise.** The vertical face is a *separate quad* drawn over the ground. But the ground mesh is a **cells-as-vertices heightfield** (one vertex per cell at its Z; adjacent cells interpolate), so a cliff renders as a near-flat **ramp** — a vertical quad has no edge to attach to and *floats* over terrain that still reads flat. This is almost certainly the same failure that killed the #245 skirt faces ("empty corners / detached"). 2. **Foreign colour.** A fixed dark-earthy tone reads as a wall of a different material, not the terrain's own surface in shadow. **How UO actually does it (owner's insight, from a UO:NL screenshot):** UO never draws a separate procedural face on a smooth mesh. Its **land tiles are 4-corner and STRETCH** to connect the four corner heights — so a slope *is the terrain tile deformed* (attached by construction, terrain-coloured), never a floating quad. Dramatic cliffs/walls/buildings are **authored static art** placed at z. Two mechanisms, neither of which is "a vertical wall bolted onto a smooth heightfield". **New direction — ①b: the cliff becomes part of the GROUND mesh (hybrid smooth/stepped heightfield).** - Walkable slopes (`ΔZ ≤ MaxWalkStepZ`) stay **smooth** exactly as now (keeps the fluidity). - A cliff edge (`ΔZ > MaxWalkStepZ`) renders as a **flat lip + a vertical drop whose surface is TERRAIN** — textured by the ground shader, so it is **in-tone by construction** and **attached by construction** (it is the mesh, not an overlay). No separate quad, no art (keeps the clean look; avoids the #245 "artificial" rejection). **Branch `feat/cliff-face-legibility` is parked as reference** (the `CliffEdges` cliff-detection and the pipeline understanding are reusable), **not merged** — `main` is untouched. Next: a design pass on ①b (`critical-design-review`), then a fresh plan + implementation.
Author
Collaborator

Delivered on main by PR #264 (933fea3, feat(client): watertight cliff rendering — edge smoothing + continuous relief darkening). Verified against main: the continuous cliff darkening (option B) is in ground_blend.fx and GroundBlendRenderer (WallShade), and the fractional-grid projection for tessellated sub-vertices is in IsoProjection. Closing as done.

Delivered on `main` by PR #264 (`933fea3`, *feat(client): watertight cliff rendering — edge smoothing + continuous relief darkening*). Verified against `main`: the continuous cliff darkening (option B) is in `ground_blend.fx` and `GroundBlendRenderer` (`WallShade`), and the fractional-grid projection for tessellated sub-vertices is in `IsoProjection`. Closing as done.
panda closed this issue 2026-08-08 15:50:48 +02:00
Sign in to join this conversation.
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#262
No description provided.