refactor(world): data-driven tiledata behind an IMapFormat seam (#143) #145

Merged
marco merged 1 commit from refactor/tiledata-data-file into main 2026-07-22 15:44:11 +02:00
Owner

Summary

Moves every tile definition out of code and into one committed data file, read through a swappable format seam — the foundation the map editor (#113) will build on. Full #143.

Before, adding a biome meant editing four code places (TerrainType enum + TerrainInfo + the map legend + BiomeVisuals), and a static meant a StaticCatalog code entry. Now:

  • content/tiledata.txt is the single source of truth (our UO tiledata.mul, as diffable text): one row per terrain/static giving its flags + art — terrain <id> <char> <walkable> <blocksSight> <landArt> <tint> and static <id> <blocks>. Adding a biome or static kind = one row + its art, zero code.
  • IMapFormat is the one seam that knows the on-disk shape; TextMapFormat is the text implementation (all parsing and serialization live there). TileData/TileMap are now pure domain objects. Swapping to a compiled binary at world scale (#114) = one new IMapFormat + one registration, no consumer touched. This is the #115 IMapSource seam.
  • The TerrainType enum is gone — terrain is a string id everywhere; TerrainInfo, StaticCatalog, BiomeVisuals are deleted, absorbed into TileData. (TerrainType was not a wire type, so no ProtocolVersion change.)
  • The map is now fully static — procedural generation removed. The client no longer hashes (x,y) to pick ground-tile variants/flips (GroundTileSelector, deleted) or to scatter marsh plants (VisibleMarshScatter/DrawMarshScatter, deleted). Each cell draws exactly its biome's one authored ground tile (UO-style). Decorations, when wanted, come back as authored passable statics (#141), not generated. No runtime code branches on marsh/tree/reeds any more — only on flags.

Loss of the enum's compile-time completeness is paid back by load-time validation (unique ids/chars, well-formed fields, unknown map char → FormatException): a bad definition fails loudly at startup, never silently at runtime.

The completed design + Invariants Check are recorded on #143; a heads-up was left for @andrea on #135/#137/#138/#140 (this refactors the #142 terrain foundation those build on — they will rebase onto the string-id + TileData model).

Screenshots / recording

Piloted from a fresh database via the debug harness. In-world at (24,26): biomes render as distinct bands (grass with real art; salt-plain/desert as the tiledata placeholder tints until biome art #137), trees render/occlude/collide from the static layer, ground items render — all driven through the new tiledata + IMapFormat pipeline, and the ground is uniform (no procedural variety/scatter, as intended). Server started with no error, which itself proves tiledata.txt + the map load correctly through the seam.

world

Commands: login test; goto 24 26; screenshot.

How it was tested

  • New unit tests: TextMapFormatTests (tiledata parse + malformed rejection: dup id/char, unknown row, bad bool/tint, missing fields; tiledata + map read↔write round-trip), TileMapTests rewritten to the string-id API (incl. unknown-map-char now rejected). Shared test scaffolding centralised in TestTiles / TestWorld.Parse (loads the committed tiledata once).
  • just lint (CSharpier + analyzers, zero warnings) and just test (407 tests) green; whole solution builds incl. client + tools.
  • Ran the client from a fresh DB (screenshot above): biomes + statics render, no crash, no regression.

Checklist

  • just lint passes (CSharpier + analyzers, zero warnings)
  • just test is green
  • The whole solution builds (client and tools included)
  • Multi-platform preserved (server on Win/macOS/Linux, client on Win/macOS)
  • Tests added/updated for this change
  • Linked the related issue (#143) and its Definition of Done is met

Closes #143.

## Summary Moves every tile definition out of code and into **one committed data file**, read through a **swappable format seam** — the foundation the map editor (#113) will build on. Full **#143**. Before, adding a biome meant editing **four** code places (`TerrainType` enum + `TerrainInfo` + the map legend + `BiomeVisuals`), and a static meant a `StaticCatalog` code entry. Now: - **`content/tiledata.txt`** is the single source of truth (our UO `tiledata.mul`, as diffable text): one row per terrain/static giving its flags + art — `terrain <id> <char> <walkable> <blocksSight> <landArt> <tint>` and `static <id> <blocks>`. **Adding a biome or static kind = one row + its art, zero code.** - **`IMapFormat`** is the one seam that knows the on-disk shape; `TextMapFormat` is the text implementation (all parsing **and** serialization live there). `TileData`/`TileMap` are now pure domain objects. Swapping to a compiled binary at world scale (#114) = one new `IMapFormat` + one registration, no consumer touched. This is the #115 `IMapSource` seam. - The **`TerrainType` enum is gone** — terrain is a string id everywhere; `TerrainInfo`, `StaticCatalog`, `BiomeVisuals` are deleted, absorbed into `TileData`. (`TerrainType` was not a wire type, so **no `ProtocolVersion` change**.) - **The map is now fully static — procedural generation removed.** The client no longer hashes `(x,y)` to pick ground-tile variants/flips (`GroundTileSelector`, deleted) or to scatter marsh plants (`VisibleMarshScatter`/`DrawMarshScatter`, deleted). Each cell draws exactly its biome's one authored ground tile (UO-style). Decorations, when wanted, come back as **authored passable statics** (#141), not generated. No runtime code branches on `marsh`/`tree`/`reeds` any more — only on flags. Loss of the enum's compile-time completeness is paid back by **load-time validation** (unique ids/chars, well-formed fields, unknown map char → `FormatException`): a bad definition fails loudly at startup, never silently at runtime. The completed design + Invariants Check are recorded on #143; a heads-up was left for @andrea on #135/#137/#138/#140 (this refactors the #142 terrain foundation those build on — they will rebase onto the string-id + `TileData` model). ## Screenshots / recording Piloted from a **fresh database** via the debug harness. In-world at (24,26): biomes render as distinct bands (grass with real art; salt-plain/desert as the tiledata placeholder tints until biome art #137), trees render/occlude/collide from the static layer, ground items render — all driven through the new `tiledata` + `IMapFormat` pipeline, and the ground is uniform (no procedural variety/scatter, as intended). Server started with no error, which itself proves `tiledata.txt` + the map load correctly through the seam. ![world](https://git.homelab.devncode.it/attachments/1e7f2da4-82c0-4491-a3dd-c61a721232f1) Commands: `login test`; `goto 24 26`; `screenshot`. ## How it was tested - **New unit tests**: `TextMapFormatTests` (tiledata parse + malformed rejection: dup id/char, unknown row, bad bool/tint, missing fields; tiledata + map read↔write **round-trip**), `TileMapTests` rewritten to the string-id API (incl. unknown-map-char now rejected). Shared test scaffolding centralised in `TestTiles` / `TestWorld.Parse` (loads the committed tiledata once). - `just lint` (CSharpier + analyzers, **zero warnings**) and `just test` (**407 tests**) green; whole solution builds incl. client + tools. - Ran the client from a fresh DB (screenshot above): biomes + statics render, no crash, no regression. ## 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 (server on Win/macOS/Linux, client on Win/macOS) - [x] Tests added/updated for this change - [x] Linked the related issue (#143) and its Definition of Done is met Closes #143.
refactor(world): move tile definitions to data (tiledata) behind an IMapFormat seam
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m33s
46bf28ee1e
marco merged commit 90af28ca2b into main 2026-07-22 15:44:11 +02:00
marco deleted branch refactor/tiledata-data-file 2026-07-22 15:44:12 +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!145
No description provided.