feat: terrain Z server walkability + editor Z tools + docs (Slice C of #224) #228

Merged
panda merged 1 commit from feat/terrain-z-editor-server into main 2026-07-31 20:41:50 +02:00
Collaborator

Slice C of #224 (UO heightfield) — server walkability + editor Z authoring + docs. Completes the 3-slice plan (Slice A #225 and Slice B #227 are merged to main).

Summary

  • Server-authoritative step-height gate. GameOptions.MaxStepZ (typed, default 2). A step is rejected when |GroundZAt(into) − GroundZAt(from)| > MaxStepZ via TileMap.CanStep, enforced for players and creatures. Program.cs now loads the world.zmap sidecar so the server reasons on the same Z the client renders (absent ⇒ all-Z0, unchanged). Teleport and spawn stay direct placements (no step-delta, per the design). The move intent is a bare Direction, so a client can't forge Z — the server reads it from the shared map.
  • Map Editor Z authoring. MapModel carries per-cell Z: ZAt, SetZ (range-checked, rejects out-of-range with a readable error), RaiseZ (clamps at ±limits, no wrap); ToTileMap/FromTileMap round-trip it. The editor gains Raise / Lower / Set-Z tools (single/rect/fill brushes) + a hover-Z readout, and load/saves the .zmap sidecar so authoring persists.
  • Docs. docs/architecture.md gains the durable "Terrain elevation (Z)" section (the model, the vertical scale, the sidecar, server walkability, authoring).
  • *.zmap added to the client/server content-copy globs so a future authored sidecar ships.

No wire change (ProtocolVersion untouched).

Screenshots / recording

  • Server — no visible surface; behaviour is proven by unit tests below.
  • Editor — the editor is a MonoGame tool with no debug harness (only the game client has one), so I can't script/screenshot its Z brush here. Rather than fake a screenshot: the brush logic is the fully-tested MapModel (Raise/Lower/Set + clamp + round-trip), and save/reopen preservation is proven by composing tested units (MapModel round-trip → IMapFormat .zmap round-trip). The heightfield itself is verified in the client in Slice B (#227).

How it was tested

  • StepHeightTests (against World): a step of exactly MaxStepZ accepted; above it rejected; a steep descent rejected by the same absolute rule; teleport places onto a Z100 tile (no step-delta); a creature ringed by over-MaxStepZ drops stays put (same gate).
  • MapModelZTests: ZAt defaults 0; SetZ stores + accepts -225/0/255, rejects -226/256; RaiseZ clamps at both limits without wrapping; ToTileMap/FromTileMap round-trip; out-of-bounds is a no-op.
  • Whole solution builds 0 warnings / 0 errors; dotnet test all green (GameServer 276, MapEditor.Core 26, Shared 122, Client.Core 178, …).

Deferred (documented follow-ups, not regressions)

  • Editor WYSIWYG-Z preview. The editor still renders flat via GridRenderer (so its flat picking is correct for its flat view); pointing it at the client's windowed Z-mesh is a larger render integration, deferred.
  • Editor clone-per-stroke. EditorGame.RebuildRenderMap() rebuilds from ToTileMap() (a full clone) per stroke — pre-existing, not introduced here; the Z change does not worsen it (a small z-grid rides the same clone). The live-view fix is deferred with the WYSIWYG work.

Checklist

  • just lint passes (CSharpier + analyzers, zero warnings)
  • just test is green
  • The whole solution builds (client and tools included)
  • Multi-platform preserved (pure .NET; no OS-specific API)
  • Tests added/updated for this change
  • Linked the related issue (#224) — final slice; two documented editor-render follow-ups noted above
**Slice C of #224** (UO heightfield) — server walkability + editor Z authoring + docs. Completes the 3-slice plan (Slice A #225 and Slice B #227 are merged to `main`). ## Summary - **Server-authoritative step-height gate.** `GameOptions.MaxStepZ` (typed, default 2). A step is rejected when `|GroundZAt(into) − GroundZAt(from)| > MaxStepZ` via `TileMap.CanStep`, enforced for **players and creatures**. `Program.cs` now loads the `world.zmap` sidecar so the **server reasons on the same Z the client renders** (absent ⇒ all-Z0, unchanged). Teleport and spawn stay **direct placements** (no step-delta, per the design). The move intent is a bare `Direction`, so a client **can't forge Z** — the server reads it from the shared map. - **Map Editor Z authoring.** `MapModel` carries per-cell Z: `ZAt`, `SetZ` (range-checked, rejects out-of-range with a readable error), `RaiseZ` (clamps at ±limits, no wrap); `ToTileMap`/`FromTileMap` round-trip it. The editor gains **Raise / Lower / Set-Z** tools (single/rect/fill brushes) + a **hover-Z readout**, and load/**saves the `.zmap`** sidecar so authoring persists. - **Docs.** `docs/architecture.md` gains the durable "Terrain elevation (Z)" section (the model, the vertical scale, the sidecar, server walkability, authoring). - `*.zmap` added to the client/server content-copy globs so a future authored sidecar ships. No wire change (`ProtocolVersion` untouched). ## Screenshots / recording - **Server** — no visible surface; behaviour is proven by unit tests below. - **Editor** — the editor is a MonoGame tool with **no debug harness** (only the game client has one), so I can't script/screenshot its Z brush here. Rather than fake a screenshot: the brush **logic** is the fully-tested `MapModel` (Raise/Lower/Set + clamp + round-trip), and save/reopen preservation is proven by composing tested units (`MapModel` round-trip → `IMapFormat` `.zmap` round-trip). The heightfield itself is verified in the **client** in Slice B (#227). ## How it was tested - **`StepHeightTests` (against `World`):** a step of exactly `MaxStepZ` accepted; above it rejected; a steep **descent** rejected by the same absolute rule; **teleport** places onto a Z100 tile (no step-delta); a **creature** ringed by over-`MaxStepZ` drops stays put (same gate). - **`MapModelZTests`:** `ZAt` defaults 0; `SetZ` stores + accepts `-225`/`0`/`255`, **rejects** `-226`/`256`; `RaiseZ` **clamps at both limits without wrapping**; `ToTileMap`/`FromTileMap` round-trip; out-of-bounds is a no-op. - **Whole solution builds 0 warnings / 0 errors; `dotnet test` all green** (GameServer 276, MapEditor.Core 26, Shared 122, Client.Core 178, …). ## Deferred (documented follow-ups, not regressions) - **Editor WYSIWYG-Z preview.** The editor still renders flat via `GridRenderer` (so its flat picking is correct for its flat view); pointing it at the client's windowed Z-mesh is a larger render integration, deferred. - **Editor clone-per-stroke.** `EditorGame.RebuildRenderMap()` rebuilds from `ToTileMap()` (a full clone) per stroke — **pre-existing**, not introduced here; the Z change does not worsen it (a small z-grid rides the same clone). The live-view fix is deferred with the WYSIWYG work. ## 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 (pure .NET; no OS-specific API) - [x] Tests added/updated for this change - [x] Linked the related issue (#224) — final slice; two documented editor-render follow-ups noted above
feat: terrain Z server walkability + editor Z tools + docs — Slice C (#224)
All checks were successful
ci / Lua content lint (pull_request) Successful in 11s
ci / Lint & Test (pull_request) Successful in 4m47s
9516e65363
Server: GameOptions.MaxStepZ (typed, default 2); the authoritative step gate uses TileMap.CanStep
(|ΔZ| ≤ MaxStepZ) for players AND creatures, and Program.cs/GameServer load the world.zmap sidecar so
the server reasons on the same Z the client renders. Teleport/spawn stay direct placements (no step
delta). The move intent is a bare direction, so a client can't forge Z. Editor: MapModel carries Z
(ZAt/SetZ clamped+range-checked/RaiseZ clamped; ToTileMap+FromTileMap round-trip it), the editor gets
Raise/Lower/Set-Z tools + a hover-Z readout and load/saves the .zmap sidecar. Docs: architecture.md
gains the durable Z model. Tests: StepHeightTests (accept/reject step + descent, teleport bypass,
creature gate) and MapModelZTests (bounds, clamp-no-wrap, round-trip).
panda merged commit 7614a5dad1 into main 2026-07-31 20:41:50 +02:00
panda deleted branch feat/terrain-z-editor-server 2026-07-31 20:41:50 +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!228
No description provided.