Cliff faces: render visible vertical faces at steep terrain Z-steps (extends #239) #245

Closed
opened 2026-08-01 19:21:48 +02:00 by panda · 2 comments
Collaborator

Extends #239 (the unfinished half: hillshade shipped in #242, cliff faces did not).

Problem

The ground is one continuous heightfield mesh with shared vertices, so a hard terrain
Z-step becomes a 1-cell-wide ramp, never a vertical face. In the fixed iso camera that ramp is
visible looking up-slope (its face toward the camera) but invisible from the opposite side;
combined with maxStepZ walkability gating, a real step reads as an invisible wall / lag
(owner-reported, correctly diagnosed as "the dislivello, with nothing showing it").

Goal

Render an explicit vertical rock/earth face at steep Z-steps so they read from every angle.
Client-rendering only — maxStepZ walkability is already server-authoritative and correct;
movement is unchanged.

Design (full spec in the branch)

docs/superpowers/specs/2026-08-01-cliff-faces-design.md on feat/cliff-faces.

  • tiledata: new optional cliffArt per terrain (string art id, default empty), positional
    after baseZ. A cell draws its face with its own terrain's material; empty ⇒ no face.
  • Five materials (asset-naming HARD GATE): earth_cliff, stone_cliff, basalt_cliff,
    sandstone_cliff, frozen_cliff — each reused by several biomes. Briefs + target prompts in
    art/cliff/cliff-face-prompts.md (64×64 RGBA, seamless vertical tiling, one height = 16 Z).
  • Skirt geometry in GroundBlendRenderer: at each camera-facing edge (E/S) where
    ΔZ ≥ CliffThreshold (default 8, above the ΔZ-5 coast step so coasts stay ramps), a
    vertical quad from the neighbour's Z up to the cell's Z, UV tiled vertically.
  • Shader: a Cliff technique in ground_blend.fx; per-material texture with AddressV = Wrap
    (no atlas — only ~5), top→bottom darkening + hillshade ambient. Drawn after the ground
    (painter's), forward after the #243 composite.
  • Edge detection is a pure Client.Core helper (CliffEdges), unit-tested.

Invariants Check (critical-design-review)

✓ cites the concrete element; N/A is falsifiable. Full wording in the spec.

  • Scope ✓ · Server-authoritative N/A (display-only; maxStepZ unchanged, server-side) · GM N/A ·
    Identity N/A · Protocol N/A (no wire/enum/DTO; derived client-side; no ProtocolVersion
    bump) · String catalog N/A · Single-threaded sim N/A · World.cs gate N/A · Screen gate ✓
    (logic in CliffEdges + GroundBlendRenderer) · Engine-independence ✓ (CliffEdges in
    Client.Core, MonoGame-free, tested) · Gameplay/Networking N/A · Act-on-instance N/A ·
    Extend-by-type ✓ (new material = tiledata row + PNG, no runtime switch) · Typed
    content-def ✓
    (cliffArt is an art id like landArt → string) · Server-paced N/A ·
    Persistence (GameServer) N/A · Persistence (Auth) N/A · Process separation N/A · Typed
    options ✓
    (CliffThreshold a render const like SlopeScale, not GameOptions/IConfiguration)
    · Broadcasts/AoI N/A · Multi-platform ✓ (MonoGame + Client.Core, SM3) · Assets ✓
    (real art/cliff/*; missing ⇒ not drawn, no placeholder) · Asset naming ✓
    (<material>_cliff, mirrored in uo-asset-map.md) · ModernUO ✓ (adopt "explicit face at a
    height discontinuity"; diverge = mesh skirt, not a static sprite, to sit on the SDF heightfield)
    · Docs & DoD ✓ (uo-asset-map + tiledata header + terrain-Z doc, this issue's DoD).

Definition of Done

  • tiledata parses optional cliffArt (default empty); round-trip test; header comment updated.
  • CliffEdges (Client.Core) unit-tested: emit on camera-facing step, no-emit below threshold,
    no-emit on N/W, and the tiling (v>1) case.
  • Steep steps (ΔZ ≥ threshold) render a textured vertical face from GroundZAt, via the
    Cliff technique after the ground, on both the deferred (#243) and single-pass paths, no top
    crack, no coast-wide wall at the default threshold.
  • Face uses the terrain's cliffArt; missing material/texture ⇒ no face (no crash/placeholder).
  • Five materials authored per art/cliff/cliff-face-prompts.md, baked into assets.isoa, ids
    mirrored in docs/uo-asset-map.md.
  • Assistant-piloted screenshots (fresh DB) show a cliff legible from the previously-invisible
    angle, ≥ 2 materials.
  • Multi-platform preserved; base DoD (tests green, whole solution builds, zero warnings).

Out of scope / follow-ups

  • Entity-behind-tall-cliff occlusion (the existing ground-before-entities limitation) — not fixed here.
  • Per-biome cliff art beyond the five materials; real rock detail passes.
Extends #239 (the unfinished half: hillshade shipped in #242, cliff faces did not). ## Problem The ground is one continuous heightfield mesh with **shared vertices**, so a hard terrain Z-step becomes a 1-cell-wide *ramp*, never a vertical face. In the fixed iso camera that ramp is visible looking up-slope (its face toward the camera) but **invisible from the opposite side**; combined with `maxStepZ` walkability gating, a real step reads as an *invisible wall / lag* (owner-reported, correctly diagnosed as "the dislivello, with nothing showing it"). ## Goal Render an explicit **vertical rock/earth face** at steep Z-steps so they read from every angle. **Client-rendering only** — `maxStepZ` walkability is already server-authoritative and correct; movement is unchanged. ## Design (full spec in the branch) `docs/superpowers/specs/2026-08-01-cliff-faces-design.md` on `feat/cliff-faces`. - **`tiledata`**: new optional `cliffArt` per terrain (string art id, default empty), positional after `baseZ`. A cell draws its face with its own terrain's material; empty ⇒ no face. - **Five materials** (asset-naming HARD GATE): `earth_cliff`, `stone_cliff`, `basalt_cliff`, `sandstone_cliff`, `frozen_cliff` — each reused by several biomes. Briefs + target prompts in `art/cliff/cliff-face-prompts.md` (64×64 RGBA, seamless vertical tiling, one height = 16 Z). - **Skirt geometry** in `GroundBlendRenderer`: at each camera-facing edge (E/S) where `ΔZ ≥ CliffThreshold` (default **8**, above the ΔZ-5 coast step so coasts stay ramps), a vertical quad from the neighbour's Z up to the cell's Z, UV tiled vertically. - **Shader**: a `Cliff` technique in `ground_blend.fx`; per-material texture with `AddressV = Wrap` (no atlas — only ~5), top→bottom darkening + hillshade ambient. Drawn after the ground (painter's), forward after the #243 composite. - Edge detection is a **pure Client.Core helper** (`CliffEdges`), unit-tested. ## Invariants Check (critical-design-review) `✓` cites the concrete element; `N/A` is falsifiable. Full wording in the spec. - Scope ✓ · Server-authoritative N/A (display-only; `maxStepZ` unchanged, server-side) · GM N/A · Identity N/A · **Protocol N/A** (no wire/enum/DTO; derived client-side; no `ProtocolVersion` bump) · String catalog N/A · Single-threaded sim N/A · `World.cs` gate N/A · **`Screen` gate ✓** (logic in `CliffEdges` + `GroundBlendRenderer`) · **Engine-independence ✓** (`CliffEdges` in Client.Core, MonoGame-free, tested) · Gameplay/Networking N/A · Act-on-instance N/A · **Extend-by-type ✓** (new material = tiledata row + PNG, no runtime switch) · **Typed content-def ✓** (`cliffArt` is an art id like `landArt` → string) · Server-paced N/A · Persistence (GameServer) N/A · Persistence (Auth) N/A · Process separation N/A · **Typed options ✓** (`CliffThreshold` a render const like `SlopeScale`, not `GameOptions`/`IConfiguration`) · Broadcasts/AoI N/A · **Multi-platform ✓** (MonoGame + Client.Core, SM3) · **Assets ✓** (real `art/cliff/*`; missing ⇒ not drawn, no placeholder) · **Asset naming ✓** (`<material>_cliff`, mirrored in `uo-asset-map.md`) · **ModernUO ✓** (adopt "explicit face at a height discontinuity"; diverge = mesh skirt, not a static sprite, to sit on the SDF heightfield) · **Docs & DoD ✓** (uo-asset-map + tiledata header + terrain-Z doc, this issue's DoD). ## Definition of Done - [ ] `tiledata` parses optional `cliffArt` (default empty); round-trip test; header comment updated. - [ ] `CliffEdges` (Client.Core) unit-tested: emit on camera-facing step, no-emit below threshold, no-emit on N/W, and the tiling (`v>1`) case. - [ ] Steep steps (ΔZ ≥ threshold) render a textured vertical face from `GroundZAt`, via the `Cliff` technique after the ground, on both the deferred (#243) and single-pass paths, no top crack, no coast-wide wall at the default threshold. - [ ] Face uses the terrain's `cliffArt`; missing material/texture ⇒ no face (no crash/placeholder). - [ ] Five materials authored per `art/cliff/cliff-face-prompts.md`, baked into `assets.isoa`, ids mirrored in `docs/uo-asset-map.md`. - [ ] Assistant-piloted screenshots (fresh DB) show a cliff legible from the previously-invisible angle, ≥ 2 materials. - [ ] Multi-platform preserved; base DoD (tests green, whole solution builds, zero warnings). ## Out of scope / follow-ups - Entity-behind-tall-cliff occlusion (the existing ground-before-entities limitation) — not fixed here. - Per-biome cliff art beyond the five materials; real rock detail passes.
Author
Collaborator

Approach pivoted rock-textures → ambient occlusion. During the live visual gate, both rock-based approaches were rejected: vertical skirt faces show only S/E in the fixed iso camera (empty corners), and rock-textured slopes read as too artificial. The shipped solution darkens the foot of steep steps with a non-directional ambient-occlusion term folded into the existing hillshade (TerrainOcclusion, Client.Core) — cliffs read as depth from every angle, no art. Design: docs/superpowers/specs/2026-08-01-cliff-ambient-occlusion-design.md. PR: #247.

**Approach pivoted rock-textures → ambient occlusion.** During the live visual gate, both rock-based approaches were rejected: vertical skirt faces show only S/E in the fixed iso camera (empty corners), and rock-textured slopes read as too artificial. The shipped solution darkens the **foot of steep steps** with a non-directional ambient-occlusion term folded into the existing hillshade (`TerrainOcclusion`, Client.Core) — cliffs read as depth from every angle, no art. Design: `docs/superpowers/specs/2026-08-01-cliff-ambient-occlusion-design.md`. PR: #247.
Author
Collaborator

Marco aiutami invece di farti le unghie a tema pokemon 💅

Risolta via relief / cliff-rounding su main (#247 · #249). Le facce verticali letterali sono state scartate (in camera iso fissa lasciano angoli vuoti) — l'obiettivo (scogliere leggibili sui salti di Z) è raggiunto.

Marco aiutami invece di farti le unghie a tema pokemon 💅 Risolta via relief / cliff-rounding su main (#247 · #249). Le facce verticali *letterali* sono state scartate (in camera iso fissa lasciano angoli vuoti) — l'obiettivo (scogliere leggibili sui salti di Z) è raggiunto.
panda closed this issue 2026-08-02 00:47:17 +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#245
No description provided.