Map editor (minimal): paint terrain + statics on the iso grid #113

Closed
opened 2026-07-20 18:58:29 +02:00 by marco · 2 comments
Owner

Part of the Alpha epic (pillar 2), split out of #89. A minimal, cross-platform map authoring tool that reads/writes the static-content format (#89) so maps (starting with the arena, #109) are authored visually instead of by hand-editing text.

Reuse the existing MonoGame stack — the Asset Editor (tools/IsoMmo.AssetEditor) is already MonoGame DesktopGL (WinExe, net10.0). Reuse GridRenderer/Camera2D/IsoProjection so the editor is WYSIWYG with the game's iso pipeline and stays cross-platform (no new GUI dependency). Design it to scale to a future 1000x1000 world.

Scope

  • Load/save a map in the #89 content format (terrain grid + sparse statics + the S spawn marker), via a TileMapWriter in Shared symmetric with TileMap.Load (round-trip tested).
  • Iso canvas with pan and zoom (a large map must be navigable, not one screenful).
  • Palette auto-discovered from the asset pack (land/ and static/ namespaces), intersected with StaticCatalog/BiomeVisuals so the editor can only place content the server can load — no hardcoded tile list (design principle: auto-discovery).
  • Brushes: single-tile paint, adjustable brush size (NxN), rectangle/fill, and an eraser (tile-by-tile alone is unusable at scale).
  • Place the spawn marker (S). (Resurrection points are out of scope: they shipped in #129 as GM-placed Ankh world items in world.sav, not map content — set in-game via /place ankh.)
  • Show a blocking indicator on statics so the author sees collision.

Out of scope

  • Undo/redo, layers UI polish, multi-tile stamps (future follow-ups).
  • In-game/live editing (offline authoring only).

Note

Not a hard blocker: a ~40x40 arena could be hand-authored in the #89 format. Build the editor because it is reused forever and hand-authoring statics is error-prone; if alpha time is tight it may slip to post-alpha, but the world-building track needs it regardless.

Definition of Done

Base DoD applies on top.

  • Runs on Windows and macOS (MonoGame DesktopGL); opens and saves a map in the #89 format round-trip (load -> edit -> save -> reload identical).
  • The palette is populated from the current asset pack's land//static/ namespaces, intersected with StaticCatalog (so saved maps always load). Adding a new static kind = one StaticCatalog row + its art (fully code-free palette awaits the data-driven catalog #143).
  • Pan, zoom, single-tile paint, adjustable brush size, rectangle/fill, and eraser all work; the spawn marker can be placed.
  • A map authored in the editor loads and renders correctly in the game client.
Part of the Alpha epic (pillar 2), split out of #89. A minimal, cross-platform **map authoring tool** that reads/writes the static-content format (#89) so maps (starting with the arena, #109) are authored visually instead of by hand-editing text. > Reuse the existing MonoGame stack — the Asset Editor (`tools/IsoMmo.AssetEditor`) is already MonoGame DesktopGL (WinExe, net10.0). Reuse `GridRenderer`/`Camera2D`/`IsoProjection` so the editor is WYSIWYG with the game's iso pipeline and stays cross-platform (no new GUI dependency). Design it to scale to a future 1000x1000 world. ## Scope - Load/save a map in the #89 content format (terrain grid + sparse statics + the `S` spawn marker), via a `TileMapWriter` in `Shared` symmetric with `TileMap.Load` (round-trip tested). - Iso canvas with **pan and zoom** (a large map must be navigable, not one screenful). - Palette **auto-discovered from the asset pack** (`land/` and `static/` namespaces), **intersected with `StaticCatalog`/`BiomeVisuals`** so the editor can only place content the server can load — no hardcoded tile list (design principle: auto-discovery). - Brushes: single-tile paint, **adjustable brush size** (NxN), **rectangle/fill**, and an **eraser** (tile-by-tile alone is unusable at scale). - Place the **spawn marker** (`S`). _(Resurrection points are **out of scope**: they shipped in #129 as GM-placed `Ankh` world items in `world.sav`, not map content — set in-game via `/place ankh`.)_ - Show a blocking indicator on statics so the author sees collision. ## Out of scope - Undo/redo, layers UI polish, multi-tile stamps (future follow-ups). - In-game/live editing (offline authoring only). ## Note Not a hard blocker: a ~40x40 arena could be hand-authored in the #89 format. Build the editor because it is reused forever and hand-authoring statics is error-prone; if alpha time is tight it may slip to post-alpha, but the world-building track needs it regardless. ## Definition of Done _Base DoD applies on top._ - [ ] Runs on Windows and macOS (MonoGame DesktopGL); opens and saves a map in the #89 format round-trip (load -> edit -> save -> reload identical). - [ ] The palette is populated from the current asset pack's `land/`/`static/` namespaces, intersected with `StaticCatalog` (so saved maps always load). Adding a **new** static kind = one `StaticCatalog` row + its art (fully code-free palette awaits the data-driven catalog #143). - [ ] Pan, zoom, single-tile paint, adjustable brush size, rectangle/fill, and eraser all work; the spawn marker can be placed. - [ ] A map authored in the editor loads and renders correctly in the game client.
marco added this to the Alpha milestone 2026-07-20 18:58:29 +02:00
Author
Owner

Technical design — agreed (critical-design-review, 2026-07-22)

Verdict: go-with-changes — agreed. This is an offline authoring tool, not a game client: it sends no intents, touches no World/wire/world.sav; its output is a content file the server re-validates at load exactly as a hand-authored map today.

Decisions settled

  1. Res markers dropped — resurrection shipped as GM-placed Ankh items (world.sav placed-items, #129), not map content. Arena res point = /place ankh in-game.
  2. Palette gated on StaticCatalog (Option A) — the palette offers only pack statics that are StaticCatalog.IsKnown, so a saved map always loads. A new static = 1 StaticCatalog row + art; the fully code-free palette is deferred to #143.

Build shape

  • Shared: add a pure TileMapWriter (model → .map/.statics text), symmetric with Load, round-trip unit-tested. TileMap stays immutable — mutability lives only in the tool (a char[,] grid + static rows), rendered by producing a TileMap via Parse/Load (which doubles as validation).
  • tools/IsoMmo.MapEditor.Core (no MonoGame, refs Shared): mutable model; brush/rect/fill/erase; picking (wraps IsoProjection.ScreenToGrid); palette derivation. Unit-tested >85%.
  • tools/IsoMmo.MapEditor (MonoGame DesktopGL exe): window/input/draw glue; refs IsoMmo.Client to reuse GridRenderer/Camera2D/AssetPackLoader/BiomeVisuals (WYSIWYG). Add default-1.0 Zoom to Camera2D (backward-compatible).
  • just map-editor + docs/map-editor.md.

Invariants Check (every line of ## Design checklist)

  • Scope — ✓ (2 rescopes above); additions (Shared writer, editor Core+exe) are necessary, not a new system.
  • Server-authoritative — N/A: no intent/endpoint; server re-validates output via TileMap.Load; save-time round-trip + StaticCatalog palette gate guarantee loadability.
  • GM authorization — N/A: no GmCommand/admin claim; edits local files.
  • Identity model — N/A: never authenticates.
  • Protocol versioned — N/A: nothing under Shared/Protocol; map content is files, not wire.
  • String catalog — N/A: no SystemNotice; button labels are dev-tool chrome (like AssetEditor).
  • Single-threaded sim — N/A: no World/Simulation/tick.
  • World.cs HARD GATE — N/A: doesn't touch GameServer.
  • Screen HARD GATE — ✓ (spirit): root Game only wires+delegates; logic in MapEditor.Core/renderer/input controller, not a god-class.
  • Client engine-independence — ✓: model/mutation/serialize/pick in no-MonoGame MapEditor.Core + Shared writer; exe is glue; reuses IsoProjection.ScreenToGrid.
  • Gameplay/Networking separation — N/A: neither present.
  • Act on the instance — ✓ (spirit): the mutable model is the single source; no shadow side-collection.
  • Extend by type, not switch — ✓: palette enumerates pack land//static/ names ∩ BiomeVisuals/StaticCatalog; no hand list/switch.
  • Server-paced actions — N/A: no timed action.
  • Persistence (GameServer) — N/A: writes authored content (.map/.statics), never world.sav/PlayerStore (this is why res-ankhs aren't map content).
  • Persistence (Auth) — N/A: no DbContext.
  • Process separation — N/A: standalone tool; refs neither Auth nor GameServer.
  • Typed options — N/A: CLI args (map+pack path), no GameOptions/IConfiguration.
  • Broadcasts / AoI — N/A: no broadcasts/observers.
  • Multi-platform — ✓: MonoGame DesktopGL Win/macOS/Linux; NativeFileDialog per-OS fallbacks.
  • Assets required — ✓: palette from the committed pack; missing art not offered/drawn (no PlaceholderArt shape); biome ground tint exception applies for legibility.
  • ModernUO as reference — ✓: MUO edits in-game ([tile/[add) + external tools over binary .mul; we diverge to an offline WYSIWYG diffable-text editor (our content is committed text), following the authoring/runtime split.
  • Docs & DoD in the same change — ✓: docs/map-editor.md + just map-editor; DoD refined above.

No HARD GATE tripped.

## Technical design — agreed (critical-design-review, 2026-07-22) **Verdict: go-with-changes** — agreed. This is an **offline authoring tool**, not a game client: it sends no intents, touches no `World`/wire/`world.sav`; its output is a content file the **server re-validates at load** exactly as a hand-authored map today. ### Decisions settled 1. **Res markers dropped** — resurrection shipped as GM-placed `Ankh` items (`world.sav` placed-items, #129), not map content. Arena res point = `/place ankh` in-game. 2. **Palette gated on `StaticCatalog`** (Option A) — the palette offers only pack statics that are `StaticCatalog.IsKnown`, so a saved map always loads. A new static = 1 `StaticCatalog` row + art; the fully code-free palette is deferred to #143. ### Build shape - **`Shared`:** add a pure `TileMapWriter` (model → `.map`/`.statics` text), symmetric with `Load`, round-trip unit-tested. `TileMap` stays **immutable** — mutability lives only in the tool (a `char[,]` grid + static rows), rendered by producing a `TileMap` via `Parse`/`Load` (which doubles as validation). - **`tools/IsoMmo.MapEditor.Core`** (no MonoGame, refs `Shared`): mutable model; brush/rect/fill/erase; picking (wraps `IsoProjection.ScreenToGrid`); palette derivation. Unit-tested >85%. - **`tools/IsoMmo.MapEditor`** (MonoGame DesktopGL exe): window/input/draw glue; refs `IsoMmo.Client` to reuse `GridRenderer`/`Camera2D`/`AssetPackLoader`/`BiomeVisuals` (WYSIWYG). Add default-`1.0` `Zoom` to `Camera2D` (backward-compatible). - `just map-editor` + `docs/map-editor.md`. ### Invariants Check (every line of `## Design checklist`) - **Scope** — ✓ (2 rescopes above); additions (`Shared` writer, editor Core+exe) are necessary, not a new system. - **Server-authoritative** — N/A: no intent/endpoint; server re-validates output via `TileMap.Load`; save-time round-trip + `StaticCatalog` palette gate guarantee loadability. - **GM authorization** — N/A: no `GmCommand`/admin claim; edits local files. - **Identity model** — N/A: never authenticates. - **Protocol versioned** — N/A: nothing under `Shared/Protocol`; map content is files, not wire. - **String catalog** — N/A: no `SystemNotice`; button labels are dev-tool chrome (like AssetEditor). - **Single-threaded sim** — N/A: no `World`/`Simulation`/tick. - **`World.cs` HARD GATE** — N/A: doesn't touch GameServer. - **`Screen` HARD GATE** — ✓ (spirit): root `Game` only wires+delegates; logic in `MapEditor.Core`/renderer/input controller, not a god-class. - **Client engine-independence** — ✓: model/mutation/serialize/pick in no-MonoGame `MapEditor.Core` + `Shared` writer; exe is glue; reuses `IsoProjection.ScreenToGrid`. - **Gameplay/Networking separation** — N/A: neither present. - **Act on the instance** — ✓ (spirit): the mutable model is the single source; no shadow side-collection. - **Extend by type, not switch** — ✓: palette enumerates pack `land/`/`static/` names ∩ `BiomeVisuals`/`StaticCatalog`; no hand list/switch. - **Server-paced actions** — N/A: no timed action. - **Persistence (GameServer)** — N/A: writes authored content (`.map`/`.statics`), never `world.sav`/`PlayerStore` (this is why res-ankhs aren't map content). - **Persistence (Auth)** — N/A: no DbContext. - **Process separation** — N/A: standalone tool; refs neither Auth nor GameServer. - **Typed options** — N/A: CLI args (map+pack path), no `GameOptions`/`IConfiguration`. - **Broadcasts / AoI** — N/A: no broadcasts/observers. - **Multi-platform** — ✓: MonoGame DesktopGL Win/macOS/Linux; `NativeFileDialog` per-OS fallbacks. - **Assets required** — ✓: palette from the committed pack; missing art not offered/drawn (no `PlaceholderArt` shape); biome ground tint exception applies for legibility. - **ModernUO as reference** — ✓: MUO edits in-game (`[tile`/`[add`) + external tools over binary `.mul`; we diverge to an offline WYSIWYG diffable-text editor (our content is committed text), following the authoring/runtime split. - **Docs & DoD in the same change** — ✓: `docs/map-editor.md` + `just map-editor`; DoD refined above. No HARD GATE tripped.
Author
Owner

Design delta (post-#143, 2026-07-22) — implementation kickoff

#143 landed, which simplifies this issue:

  • Palette is data-driven for free — it lists terrains + statics straight from the loaded TileData (content/tiledata.txt). The earlier 'gate the palette on StaticCatalog' decision is moot; anything in tiledata is loadable by definition, and a new kind is one tiledata row + art (no code).
  • Save path already exists + is round-trip testedIMapFormat.WriteMap/WriteTileData. The 'load→edit→save→reload identical' DoD is largely de-risked.

UI decision (owner): MonoGame + Dear ImGui, maximally off-the-shelf (internal tool, zero time on GUI):

  • MonoGame.ImGuiNet (ready-made MonoGame↔ImGui backend, restores clean) — no hand-written renderer.
  • ImGui widgets for all chrome (palette, brush-size slider, tool toggles, menu); reuse the existing NativeFileDialog for open/save.
  • Reuse GridRenderer/Camera2D/AssetPackLoader for the WYSIWYG iso canvas; add a default-1.0 Zoom to Camera2D (backward-compatible).

Build shape: tools/IsoMmo.MapEditor.Core (no MonoGame, unit-tested: mutable model + brush/rect/fill/erase + picking via IsoProjection.ScreenToGrid + palette from TileData) and tools/IsoMmo.MapEditor (MonoGame DesktopGL exe: ImGui chrome + canvas glue). just map-editor + docs/map-editor.md. Res markers still out of MVP (GM-placed ankhs).

## Design delta (post-#143, 2026-07-22) — implementation kickoff #143 landed, which simplifies this issue: - **Palette is data-driven for free** — it lists terrains + statics straight from the loaded `TileData` (`content/tiledata.txt`). The earlier 'gate the palette on `StaticCatalog`' decision is moot; anything in tiledata is loadable by definition, and a new kind is one tiledata row + art (no code). - **Save path already exists + is round-trip tested** — `IMapFormat.WriteMap`/`WriteTileData`. The 'load→edit→save→reload identical' DoD is largely de-risked. **UI decision (owner): MonoGame + Dear ImGui**, maximally off-the-shelf (internal tool, zero time on GUI): - `MonoGame.ImGuiNet` (ready-made MonoGame↔ImGui backend, restores clean) — no hand-written renderer. - ImGui widgets for all chrome (palette, brush-size slider, tool toggles, menu); reuse the existing `NativeFileDialog` for open/save. - Reuse `GridRenderer`/`Camera2D`/`AssetPackLoader` for the WYSIWYG iso canvas; add a default-1.0 `Zoom` to `Camera2D` (backward-compatible). **Build shape:** `tools/IsoMmo.MapEditor.Core` (no MonoGame, unit-tested: mutable model + brush/rect/fill/erase + picking via `IsoProjection.ScreenToGrid` + palette from `TileData`) and `tools/IsoMmo.MapEditor` (MonoGame DesktopGL exe: ImGui chrome + canvas glue). `just map-editor` + `docs/map-editor.md`. Res markers still out of MVP (GM-placed ankhs).
marco closed this issue 2026-07-22 16:18:32 +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#113
No description provided.