---
title: Map Editor Decision — LDtk
date: 2026-05-18
context: Exploration session preceding next milestone (map editor + publish workflow + in-game map)
status: decided
---

# Map Editor Decision — LDtk

## Decision

**LDtk** (https://ldtk.io) is the baseline map editor for REBNO authoring.

## Context

Next milestone needs three composing pieces:

1. Map editor — author rooms, place floor tiles + entities, set per-instance properties
2. Publish/test workflow — quickly iterate on map updates against the running game
3. In-game minimap UI

This decision settles **(1)** so milestone planning can proceed.

## Legacy data shape (load-bearing)

Inspected `extracted/client-5-8/rooms/0058-BNCentral/`:

- BNCentral: **8000×6400 px**, ~6000 entity instances, **zero tile-layer tiles** — every floor tile + entity is an `instance` at free `(x, y)` with optional per-instance `creationCode` (GML)
- Floor tiles include animated species, DYNAMIC neighbor-sensitive species (17+ variants per LEGACY_FEATURE_REFERENCE.md), interactive species (hexporter, hexsling, hexbridge, warp, spectral-gate)
- Hexport entities require sub-tile depth: under-piece renders below the hexporting player, over-piece renders above
- Tile-side sprites render under floor tiles; most entities render above floor tiles
- Future intent: combine zones onto a single overworld OR show relative placement on minimap

REBNO migrates to a tile-grid + entity-layer split:

- Static floor tiles → tile-grid (paint-fast, small files)
- Anything with per-instance state (mover speed, warp dest, platform path, NPC dialog, hexport directions) → entity with typed custom fields

## Candidates evaluated

| Candidate | Verdict |
|---|---|
| **LDtk** | ✓ Chosen |
| Tiled | Close second — built-in Phaser loader, weaker auto-tile UX |
| Godot 4.x TileMap | Rejected — per-cell custom data missing, no Godot→Phaser exporter, heavier |
| Unity | Rejected — heaviest, no advantage over Godot for editor-only use |
| Sprite Fusion | Rejected — no entity layer with per-instance properties (fatal for ~6000 entities) |
| OGMO Editor 3 | Rejected — stagnant, weak auto-tile |
| Phaser Editor 2D | Rejected — defers tilemap authoring to Tiled |
| GameMaker Studio 2 | Rejected — `.gmd` 5.3a import chain hostile, no clean JSON export |
| Construct 3 / GDevelop | Rejected — consume editors, not authoring competitors |
| Pyxel Edit / PixLab / Tilesetter | Rejected — art tools or preprocessors, not level editors |
| Custom in-house | Deferred — 3-6 engineer-weeks; only if LDtk + Tiled + Sprite Fusion all fail |

## Why LDtk

1. **Auto-tile rule UX dominates the DYNAMIC tile zoo.** LDtk's pattern editor (3×3 neighborhood, NOT/AND, randomization weights, mirror/rotate, per-rule chance) is the headline feature. Cleanest path to authoring 17+ Lone/Edge/Bridge/Corner/Surrounded variants.
2. **Typed custom fields on entities AND tiles.** `F_Point`, `F_GridPoint`, `F_EntityRef`, `F_Tile`, primitives, arrays. Matches per-instance property needs without per-tile-type ceiling Godot hits.
3. **Multi-worlds (1.3+) + GridVania/Free layouts.** Gives a free path to a combined-zones overworld and to minimap adjacency without rearchitecture.
4. **Multiple entities co-locate on one cell.** Hexporter + Warp + custom-trigger stacking just works.
5. **Layer-as-z-order is enough for hexport depth.** Floor → HexUnder → [player drawn at runtime] → HexOver → Entities. Within-layer no z-order, but layer stacking solves all current depth cases.

## Accepted tradeoffs

- **No maintained Phaser loader.** `ldtk-ts` archived 2022. Plan = `LdtkJson.js` (QuickType-generated types, always current) + custom Phaser scene loader. Budget: ~1 task in milestone plan.
- **Editor lag risk at 8000×6400.** Documented lag for IntGrid editing above ~5000 px ([deepnight/ldtk#1029](https://github.com/deepnight/ldtk/issues/1029), [#1073](https://github.com/deepnight/ldtk/issues/1073)). Mitigation: split BNCentral into GridVania chunks. Smaller zones (Bahoo, Noiya, Schweisstar, etc.) sit under the threshold.
- **No native animated-tile timing.** Animation metadata stored as `F_Tile` array on entity/IntGrid value; Phaser owns frame timing. Standard pattern; not extra work.
- **No native hot-reload.** Vite file-watch on `.ldtk` + custom Phaser scene-reload hook (Excalibur's official plugin uses same pattern).

## Open questions deferred to milestone planning

- Exact mapping of legacy `objectId` set → LDtk Entity definitions vs IntGrid values
- Round-trip importer for `extracted/client-5-8/rooms/*` → LDtk projects (one-shot vs reusable)
- Multi-world overworld: build now or defer to a later phase
- See: [research/questions.md → zone adjacency](../research/questions.md)

## Sources

- LDtk research findings (2026-05-18 session)
- Godot 4.x research findings (2026-05-18 session)
- Broad-sweep editor landscape findings (2026-05-18 session)
- `docs/LEGACY_FEATURE_REFERENCE.md` — floor tile + entity species catalog
- `docs/extracted-engine/scene-room-model.md` — 44×40 px constant + room model
- `extracted/client-5-8/rooms/0058-BNCentral/{meta,instances}.json` — legacy data shape

[doc->REQ-DEP-04]
