feat(world): terrain Z domain + elevation sidecar + quoted projection (Slice A of #224) #225
No reviewers
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!225
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/terrain-z-domain"
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?
Slice A of #224 (authoritative terrain Z / UO heightfield). Pure domain + format + projection math — no visible change; the renderer is Slice B.
Summary
Lays the authoritative foundation for per-point terrain elevation, so later slices (renderer, editor, server walkability) all read one contract:
TileMap.GroundZAt(pos)— the read contract every consumer will depend on (renderer, editor, gameplay). Backed by a denseshort[,]for now, but nothing depends on that concretely — a future chunked map can implement the same method. Out-of-bounds reads 0; a map with no elevation is entirely Z0.TerrainZ— the valid signed range-225..255declared once (storage isInt16; the authoring range is intentionally narrower/asymmetric, UO-style) and reused by loader + validation.IMapFormat—ReadMap/WriteMapgain anelevationstring (aworld.zmapsidecar). It's sparse (x y zper non-Z0 cell, like statics) so a mostly-flat map stays tiny; retro-compatible (absent → Z0, so existing two-file maps load unchanged); bounds-checked (rejects Z outside range and out-of-map cells); exact round-trip. Callers read/write the file but never parse it. A 3-argReadMapconvenience keeps flat call sites clean. Documented as a dev-phase text form — the world-scale binary format (#114) will carry Z in the terrain chunk.TileMap.CanStep(from, into, maxStepZ)— the UO step-height gate: destination walkable AND|ΔZ| ≤ maxStepZ. The threshold is a parameter (a typed server option lands with the walkability wiring in Slice C), never hardcoded.IsoProjection.ToScreen(x, y, z)— the quoted projection(-z·ZScale),ZScale = 4px/Z in one shared constant.z = 0is bit-identical to the flat overload (the Z0 back-compat guarantee).Consumers (client/server) still use the flat 3-arg read (all Z0) — they get wired to the sidecar in the slices that render/enforce Z.
ProtocolVersionuntouched (no wire change).Screenshots / recording
N/A — no visible surface. Z is not rendered until Slice B (the windowed grid mesh). Every behavior here is proven by unit tests below.
How it was tested
Built test-first (RED→GREEN watched for each behavior, including breaking
CanStep/elevation back to stubs to confirm the tests catch the missing logic):IsoProjectionTests—ToScreen(x,y,0)== flat overload;+1 Z==Y − 4; Z never moves X;ZScale == 4.ElevationTests— no sidecar → all Z0; sidecar parsed intoGroundZAt; accepts-225/0/255, rejects-226/256and out-of-map cells;WriteMap→ReadMapround-trips every Z exactly; a flat map writes an empty sidecar (no bloat).CanStepTests— flat step allowed; step of exactlymaxStepZallowed; abovemaxStepZrejected; steep descent rejected by the same absolute rule; step into a blocker rejected even when flat.dotnet testall green (Shared 122, Client.Core 171, GameServer 271, …).Checklist
just lintpasses (CSharpier + analyzers, zero warnings)just testis green-z*4single source, round-trip)