Proposal: comprehensive Tiled (TMX/TMJ) map support — parse & consume every Tiled feature/output #66

Closed
opened 2026-09-01 12:13:06 +02:00 by orkun · 2 comments
Owner

Summary

Ludic's tilemaps are text-based today: Map.size(w, h) + Map.row(y, "…") write single-char codes into rt_map/rt_tile (runtime/native/core.ludic), and everything downstream — Grid.*/Path.* (runtime/native/grid.ludic), the esys_move tile broadphase (runtime/native/systems_move.ludic, examples/library/physics_tiles.ludic) — treats a tile as one glyph = one tile, one conceptual layer, no tileset, no GID, no rendering, no animation, no metadata.

That model cannot express real content. Meanwhile a real Tiled export is already sitting unused in the tree: assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx (32×20, 3 layers, CSV data, GID flip flags) + sampleSheet.tsx (external tileset, 132 tiles, 12 cols, spacing 1). Our own physics_tiles/grid demos hand-author maps as string rows because there is no loader.

This is a research + design proposal only — no implementation in this issue. The goal is to scope comprehensive support for Tiled (the map editor by Thorbjørn Lindeijer): to consume every feature and every output Tiled can produce, and to define how each maps onto Ludic's runtime, renderer, collision, and grid/pathfinding — within our hard constraints (native/wasm, C-free, compiled, runtime = primitives only, deterministic, windowed 2D, Kenney art).


1. Complete Tiled feature & output inventory

This is the surface we are committing to understand and design for. Grouped so we can phase it (§4). Sources: the TMX Map Format, the JSON Map Format, Worlds, and Export references (current line: Tiled 1.11 / 1.12).

1.1 Map (<map> / top-level JSON object)

  • version, tiledversion, class (1.9).
  • orientation: orthogonal, isometric, staggered (isometric), hexagonal, (legacy oblique).
  • renderorder: right-down (default), right-up, left-down, left-up (orthogonal).
  • width, height (tiles); tilewidth, tileheight (px).
  • Hex/stagger: hexsidelength, staggeraxis (x/y), staggerindex (even/odd).
  • parallaxoriginx/y (1.8); backgroundcolor (#AARRGGBB/#RRGGBB).
  • compressionlevel; infinite (0/1) → chunked storage; nextlayerid, nextobjectid.

1.2 Tilesets (<tileset> / .tsx / .tsj)

  • Three storage modes: embedded in map, external source=".tsx"/.tsj" (our sample uses this), and image-collection (per-tile <image>, no shared sheet).
  • firstgid (map-side), name, class, tilewidth, tileheight, spacing, margin, tilecount, columns.
  • <image>: source, width, height, trans (color-key transparency, e.g. #FF00FF), format, optional embedded <data>.
  • <tileoffset> (x/y draw offset); <grid> (orientation/width/height for collision overlay).
  • objectalignment (topleft…bottomright), tilerendersize (tile/grid, 1.9), fillmode (stretch/preserve-aspect-fit, 1.9).
  • <transformations>: hflip, vflip, rotate, preferuntransformed (1.5) — which transforms the tileset permits.

1.3 Per-tile data (<tile> inside a tileset)

  • id, type/class, probability, sub-rectangle x/y/width/height.
  • <animation> → <frame tileid= duration=> — animated tiles (ms per frame). This is the "animated sprite" case the request calls out.
  • <objectgroup> collision shapes attached to a tile (per-tile hitboxes: rect/ellipse/polygon/point) — feeds esys_move.
  • Per-tile <properties>.
  • Legacy terrain corner indices (deprecated → Wang).

1.4 Layers (order = draw order; all share id, name, class, opacity, visible, offsetx/y, parallaxx/y (1.5), tintcolor, locked)

  • Tile layer <layer>: width, height, <data> + blend mode (1.12: normal/add/multiply/screen/…).
  • Object layer <objectgroup>: color, draworder (topdown/index).
  • Image layer <imagelayer>: image, repeatx/repeaty (1.8), transparent color — parallax backdrops.
  • Group layer <group>: nested layers; offset/opacity/visible/tint apply recursively.

1.5 Tile-layer data encodings (<data>)

  • encoding: CSV, base64, or unencoded <tile gid> (deprecated).
  • compression (on base64): none, gzip, zlib, zstd.
  • Infinite maps: <chunk x y width height> segments instead of one dense array (JSON: chunks[], default 16×16).
  • GID = 32-bit value: bits 0–28 = tile id, plus flip flags:
    • 0x80000000 horizontal, 0x40000000 vertical, 0x20000000 diagonal (anti-diag), 0x10000000 hex/rotated-120 (1.9). gid 0 = empty cell.
    • (Our sample's huge numbers like 1610612787, 3221225485, 2147483698 are exactly these flip-flagged GIDs — confirming we must decode them, not treat them as raw ids.)

1.6 Objects (<object> in an object layer)

  • id, name, type/class, x, y, width, height, rotation, opacity, visible.
  • Shapes: rectangle (default), <ellipse>, <point> (1.1), <polygon>, <polyline>, <capsule> (1.12), and <text> (font family, pixel size, bold/italic/underline/strikeout, color, wrap, h/v align, kerning).
  • Tile objects: gid → renders a tile image (with its own flip flags) as a placeable/rotatable sprite.
  • Templates template=".tx/.tj" → external reusable object definition (<template> with optional <tileset> + one <object>).

1.7 Terrain / Wang sets (<wangsets>, 1.1; supersedes <terraintypes>)

  • <wangset> (type: corner, edge, mixed), up to 254 <wangcolor> (name/color/tile/probability), and <wangtile> (tileid + 8-index wangid) — auto-tiling / terrain brushes. Design impact is mostly authoring, but the exported GIDs must still resolve.

1.8 Custom properties & types

  • <property> on any element: string (default), int, float, bool, color (#AARRGGBB), file (relative path), object (id ref), class (nested members, 1.8), and enum. propertytype names a project-defined custom type.
  • Project-level custom types live in .tiled-project / object-types export — needed to interpret class/enum properties.

1.9 Worlds (.world, JSON)

  • Stitch many map files into one large world: maps[] {fileName, x, y}, or patterns[] {regexp, multiplierX/Y, offsetX/Y, mapWidth, mapHeight}, plus onlyShowAdjacentMaps. Relevant for streaming/large levels.

1.10 Outputs / export formats Tiled can emit

Native: TMX/TSX (XML), TMJ/TSJ/JSON. Export-only: CSV (tile layers), Lua, JavaScript, GameMaker (1.4 + YY), Defold, Godot 4 (TSCN), tBIN/tIDE, rendered PNG image, plus JS/Python/C++ plugin exporters. Project/session files: .tiled-project, .tiled-session.


2. Constraints that shape the design

From the project memory / existing runtime:

  • Compiled + native/wasm only, C-free. Every parser and decoder must be pure Ludic — no libxml, no zlib. That means we own: an XML reader (TMX/TSX/TX), a JSON reader (TMJ/TSJ/TJ/.world), base64, and gzip/zlib/zstd inflate. (zstd in particular is a large undertaking — see §4 phasing.)
  • Runtime = primitives only; deterministic. Same map bytes → same in-memory model → same render/collision every run, matching the guarantees Grid.*/Path.*/esys_move already make.
  • Windowed 2D, Kenney art. Orthogonal is the priority orientation; isometric/hex are real Tiled features but lower priority for our target games. The existing Kenney Tiled sample is our first golden fixture.
  • Must integrate, not replace. A Tiled loader has to populate the same structures the engine already ticks: rt_map/rt_tile (or a richer successor), the SpriteAnim engine-owned system (animated tiles), the renderer, and the collision/grid layers — ideally exposed as a Tiled.* / Map.* stdlib namespace (see the "splice-a-runtime" pattern used for Regex.*/Grid.*).

3. Key design questions (to resolve during design, not here)

  1. Format target order. TMX (XML) matches the in-repo sample and .tsx; TMJ (JSON) is simpler to parse. Do we build the JSON reader first (also unlocks .world + reuse elsewhere) and require --export json, or parse the native TMX directly? Recommendation: JSON-first for the model, TMX reader as a fast follow, since both must exist eventually.
  2. In-memory model vs. rt_map. Today a tile is one int in a single grid. A real map is N tile layers + object layers + tilesets with per-tile metadata. Do we generalize rt_map to (layer, x, y) → GID with a tileset/GID resolver, and keep rt_tile as a compatibility view of a chosen "solids" layer for esys_move/Grid?
  3. GID resolution & flips. Renderer + collision need GID → (tileset, local id, flipH/V/D). Where does the flip transform live — bake into draw, or expose on query?
  4. Animated tiles. Map <animation> frames onto the existing SpriteAnim engine-owned system so animated tiles tick for free?
  5. Collision source of truth. Per-tile <objectgroup> hitboxes vs. a designated collision layer vs. a custom bool/class property convention (e.g. solid, oneway) — how does an author mark solids/one-ways/triggers so it flows into Solids{tile,wall,oneway} + esys_move?
  6. Compression scope. Is CSV + base64(uncompressed) + zlib enough for v1, deferring gzip/zstd? (Tiled defaults to CSV/zlib; zstd is opt-in.)
  7. Asset paths. TMX/TSX use relative source paths (../Tilemap/tilemap.png); how do these resolve against our asset pipeline at compile vs. run time?
  8. Objects → entities. Should object layers auto-spawn Ludic entities (mapping class/properties → components), à la the controllers roadmap (#57–#61)? This is where Tiled becomes a level editor for gameplay, not just tiles.
  9. Custom properties → components. How do class/enum properties bind to Ludic properties/models?
  10. Infinite maps / worlds. Support chunked + .world streaming now, or explicitly defer to a later "large worlds" issue?

4. Suggested phasing (for a follow-up implementation plan)

  • P0 — Foundations (C-free primitives): pure-Ludic XML + JSON readers; base64; zlib inflate. Golden-file tests against the in-repo Kenney sample.
  • P1 — Core orthogonal load: map + external/embedded tilesets, multiple finite CSV/base64 tile layers, GID decode incl. flip flags, render through the existing renderer. Replace the hand-authored rows in grid/physics_tiles demos with a loaded .tmj/.tmx.
  • P2 — Collision & grid integration: designated solids/one-way via per-tile collision <objectgroup> and/or custom properties → Solids + esys_move; keep Grid.*/Path.* working over the loaded map.
  • P3 — Animated tiles + tile objects: <animation> → SpriteAnim; tile objects (gid) → sprites.
  • P4 — Objects & properties: object layers (all shapes + text), custom properties/types, templates → optional entity spawning.
  • P5 — Breadth: image/group layers (parallax/repeat, tint, blend, opacity), Wang/terrain resolution, isometric/hex orientations.
  • P6 — Scale: infinite/chunked maps, .world stitching, gzip/zstd.

5. Acceptance criteria for this issue (research/design)

  • The feature inventory above is reviewed and agreed as the target surface (with any items explicitly cut).
  • A decision is recorded on §3.1 (format order) and §3.2 (model vs. rt_map).
  • The phasing in §4 is turned into concrete follow-up issues (P0…P6) with labels/priorities.
  • The in-repo Kenney Tiled sample is confirmed as the first golden fixture (and we note it currently exercises: external .tsx, 3 layers, CSV, and flip-flagged GIDs).

Sources: TMX Map Format · JSON Map Format · Worlds · Export · Tiled 1.11 release notes

No implementation is requested here — this issue scopes the research and the design decisions needed before an implementation plan.

## Summary Ludic's tilemaps are text-based today: `Map.size(w, h)` + `Map.row(y, "…")` write single-char codes into `rt_map`/`rt_tile` (`runtime/native/core.ludic`), and everything downstream — `Grid.*`/`Path.*` (`runtime/native/grid.ludic`), the `esys_move` tile broadphase (`runtime/native/systems_move.ludic`, `examples/library/physics_tiles.ludic`) — treats a tile as **one glyph = one tile, one conceptual layer, no tileset, no GID, no rendering, no animation, no metadata**. That model cannot express real content. Meanwhile a real Tiled export is already sitting unused in the tree: [`assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx`](assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx) (32×20, 3 layers, CSV data, GID flip flags) + [`sampleSheet.tsx`](assets/kenney/tiny-dungeon/Tiled/sampleSheet.tsx) (external tileset, 132 tiles, 12 cols, spacing 1). Our own `physics_tiles`/`grid` demos hand-author maps as string rows because there is no loader. This is a **research + design proposal only — no implementation in this issue.** The goal is to scope *comprehensive* support for [**Tiled**](https://www.mapeditor.org/) (the map editor by Thorbjørn Lindeijer): to consume **every feature and every output** Tiled can produce, and to define how each maps onto Ludic's runtime, renderer, collision, and grid/pathfinding — within our hard constraints (native/wasm, C-free, compiled, runtime = primitives only, deterministic, windowed 2D, Kenney art). --- ## 1. Complete Tiled feature & output inventory This is the surface we are committing to *understand and design for*. Grouped so we can phase it (§4). Sources: the [TMX Map Format](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/), the [JSON Map Format](https://doc.mapeditor.org/en/stable/reference/json-map-format/), [Worlds](https://doc.mapeditor.org/en/stable/manual/worlds/), and [Export](https://doc.mapeditor.org/en/stable/manual/export/) references (current line: **Tiled 1.11 / 1.12**). ### 1.1 Map (`<map>` / top-level JSON object) - `version`, `tiledversion`, `class` (1.9). - `orientation`: **orthogonal**, **isometric**, **staggered (isometric)**, **hexagonal**, (legacy **oblique**). - `renderorder`: `right-down` (default), `right-up`, `left-down`, `left-up` (orthogonal). - `width`, `height` (tiles); `tilewidth`, `tileheight` (px). - Hex/stagger: `hexsidelength`, `staggeraxis` (x/y), `staggerindex` (even/odd). - `parallaxoriginx/y` (1.8); `backgroundcolor` (`#AARRGGBB`/`#RRGGBB`). - `compressionlevel`; `infinite` (0/1) → chunked storage; `nextlayerid`, `nextobjectid`. ### 1.2 Tilesets (`<tileset>` / `.tsx` / `.tsj`) - **Three storage modes:** embedded in map, **external** `source=".tsx"/.tsj"` (our sample uses this), and **image-collection** (per-tile `<image>`, no shared sheet). - `firstgid` (map-side), `name`, `class`, `tilewidth`, `tileheight`, `spacing`, `margin`, `tilecount`, `columns`. - `<image>`: `source`, `width`, `height`, `trans` (color-key transparency, e.g. `#FF00FF`), `format`, optional embedded `<data>`. - `<tileoffset>` (x/y draw offset); `<grid>` (orientation/width/height for collision overlay). - `objectalignment` (topleft…bottomright), `tilerendersize` (`tile`/`grid`, 1.9), `fillmode` (`stretch`/`preserve-aspect-fit`, 1.9). - `<transformations>`: `hflip`, `vflip`, `rotate`, `preferuntransformed` (1.5) — which transforms the tileset permits. ### 1.3 Per-tile data (`<tile>` inside a tileset) - `id`, `type`/`class`, `probability`, sub-rectangle `x/y/width/height`. - **`<animation>` → `<frame tileid= duration=>`** — animated tiles (ms per frame). This is the "animated sprite" case the request calls out. - **`<objectgroup>` collision shapes** attached to a tile (per-tile hitboxes: rect/ellipse/polygon/point) — feeds `esys_move`. - Per-tile `<properties>`. - Legacy `terrain` corner indices (deprecated → Wang). ### 1.4 Layers (order = draw order; all share `id`, `name`, `class`, `opacity`, `visible`, `offsetx/y`, `parallaxx/y` (1.5), `tintcolor`, `locked`) - **Tile layer** `<layer>`: `width`, `height`, `<data>` + blend `mode` (1.12: normal/add/multiply/screen/…). - **Object layer** `<objectgroup>`: `color`, `draworder` (`topdown`/`index`). - **Image layer** `<imagelayer>`: `image`, `repeatx`/`repeaty` (1.8), transparent color — parallax backdrops. - **Group layer** `<group>`: nested layers; offset/opacity/visible/tint apply recursively. ### 1.5 Tile-layer data encodings (`<data>`) - `encoding`: **CSV**, **base64**, or unencoded `<tile gid>` (deprecated). - `compression` (on base64): **none**, **gzip**, **zlib**, **zstd**. - **Infinite maps:** `<chunk x y width height>` segments instead of one dense array (JSON: `chunks[]`, default 16×16). - **GID = 32-bit** value: bits 0–28 = tile id, plus flip flags: - `0x80000000` horizontal, `0x40000000` vertical, `0x20000000` diagonal (anti-diag), `0x10000000` hex/rotated-120 (1.9). `gid 0` = empty cell. - *(Our sample's huge numbers like `1610612787`, `3221225485`, `2147483698` are exactly these flip-flagged GIDs — confirming we must decode them, not treat them as raw ids.)* ### 1.6 Objects (`<object>` in an object layer) - `id`, `name`, `type`/`class`, `x`, `y`, `width`, `height`, `rotation`, `opacity`, `visible`. - **Shapes:** rectangle (default), `<ellipse>`, `<point>` (1.1), `<polygon>`, `<polyline>`, `<capsule>` (1.12), and **`<text>`** (font family, pixel size, bold/italic/underline/strikeout, color, wrap, h/v align, kerning). - **Tile objects:** `gid` → renders a tile image (with its own flip flags) as a placeable/rotatable sprite. - **Templates** `template=".tx/.tj"` → external reusable object definition (`<template>` with optional `<tileset>` + one `<object>`). ### 1.7 Terrain / Wang sets (`<wangsets>`, 1.1; supersedes `<terraintypes>`) - `<wangset>` (type: **corner**, **edge**, **mixed**), up to 254 `<wangcolor>` (name/color/tile/probability), and `<wangtile>` (`tileid` + 8-index `wangid`) — auto-tiling / terrain brushes. Design impact is mostly *authoring*, but the exported GIDs must still resolve. ### 1.8 Custom properties & types - `<property>` on any element: `string` (default), `int`, `float`, `bool`, `color` (`#AARRGGBB`), `file` (relative path), `object` (id ref), `class` (nested members, 1.8), and enum. `propertytype` names a project-defined custom type. - Project-level custom types live in `.tiled-project` / object-types export — needed to interpret `class`/enum properties. ### 1.9 Worlds (`.world`, JSON) - Stitch many map files into one large world: `maps[] {fileName, x, y}`, or `patterns[] {regexp, multiplierX/Y, offsetX/Y, mapWidth, mapHeight}`, plus `onlyShowAdjacentMaps`. Relevant for streaming/large levels. ### 1.10 Outputs / export formats Tiled can emit Native: **TMX/TSX (XML)**, **TMJ/TSJ/JSON**. Export-only: **CSV** (tile layers), **Lua**, **JavaScript**, **GameMaker (1.4 + YY)**, **Defold**, **Godot 4 (TSCN)**, **tBIN/tIDE**, **rendered PNG image**, plus JS/Python/C++ **plugin exporters**. Project/session files: `.tiled-project`, `.tiled-session`. --- ## 2. Constraints that shape the design From the project memory / existing runtime: - **Compiled + native/wasm only, C-free.** Every parser and decoder must be **pure Ludic** — no libxml, no zlib. That means we own: an XML reader (TMX/TSX/TX), a JSON reader (TMJ/TSJ/TJ/.world), **base64**, and **gzip/zlib/zstd inflate**. (zstd in particular is a large undertaking — see §4 phasing.) - **Runtime = primitives only; deterministic.** Same map bytes → same in-memory model → same render/collision every run, matching the guarantees `Grid.*`/`Path.*`/`esys_move` already make. - **Windowed 2D, Kenney art.** Orthogonal is the priority orientation; isometric/hex are real Tiled features but lower priority for our target games. The existing Kenney Tiled sample is our first golden fixture. - **Must integrate, not replace.** A Tiled loader has to populate the *same* structures the engine already ticks: `rt_map`/`rt_tile` (or a richer successor), the SpriteAnim engine-owned system (animated tiles), the renderer, and the collision/grid layers — ideally exposed as a `Tiled.*` / `Map.*` stdlib namespace (see the "splice-a-runtime" pattern used for `Regex.*`/`Grid.*`). --- ## 3. Key design questions (to resolve during design, not here) 1. **Format target order.** TMX (XML) matches the in-repo sample and `.tsx`; TMJ (JSON) is simpler to parse. Do we build the JSON reader first (also unlocks `.world` + reuse elsewhere) and require `--export json`, or parse the native TMX directly? Recommendation: **JSON-first for the model, TMX reader as a fast follow**, since both must exist eventually. 2. **In-memory model vs. `rt_map`.** Today a tile is one int in a single grid. A real map is *N tile layers + object layers + tilesets with per-tile metadata*. Do we generalize `rt_map` to `(layer, x, y) → GID` with a tileset/GID resolver, and keep `rt_tile` as a compatibility view of a chosen "solids" layer for `esys_move`/`Grid`? 3. **GID resolution & flips.** Renderer + collision need `GID → (tileset, local id, flipH/V/D)`. Where does the flip transform live — bake into draw, or expose on query? 4. **Animated tiles.** Map `<animation>` frames onto the existing SpriteAnim engine-owned system so animated tiles tick for free? 5. **Collision source of truth.** Per-tile `<objectgroup>` hitboxes vs. a designated collision layer vs. a custom `bool`/`class` property convention (e.g. `solid`, `oneway`) — how does an author mark solids/one-ways/triggers so it flows into `Solids{tile,wall,oneway}` + `esys_move`? 6. **Compression scope.** Is CSV + base64(uncompressed) + zlib enough for v1, deferring gzip/zstd? (Tiled defaults to CSV/zlib; zstd is opt-in.) 7. **Asset paths.** TMX/TSX use relative `source` paths (`../Tilemap/tilemap.png`); how do these resolve against our asset pipeline at compile vs. run time? 8. **Objects → entities.** Should object layers auto-`spawn` Ludic entities (mapping `class`/properties → components), à la the controllers roadmap (#57–#61)? This is where Tiled becomes a level editor for gameplay, not just tiles. 9. **Custom properties → components.** How do `class`/enum properties bind to Ludic properties/models? 10. **Infinite maps / worlds.** Support chunked + `.world` streaming now, or explicitly defer to a later "large worlds" issue? --- ## 4. Suggested phasing (for a follow-up implementation plan) - **P0 — Foundations (C-free primitives):** pure-Ludic XML + JSON readers; base64; zlib inflate. Golden-file tests against the in-repo Kenney sample. - **P1 — Core orthogonal load:** map + external/embedded tilesets, multiple **finite CSV/base64 tile layers**, **GID decode incl. flip flags**, render through the existing renderer. Replace the hand-authored rows in `grid`/`physics_tiles` demos with a loaded `.tmj`/`.tmx`. - **P2 — Collision & grid integration:** designated solids/one-way via per-tile collision `<objectgroup>` and/or custom properties → `Solids` + `esys_move`; keep `Grid.*`/`Path.*` working over the loaded map. - **P3 — Animated tiles + tile objects:** `<animation>` → SpriteAnim; tile objects (`gid`) → sprites. - **P4 — Objects & properties:** object layers (all shapes + text), custom properties/types, templates → optional entity spawning. - **P5 — Breadth:** image/group layers (parallax/repeat, tint, blend, opacity), Wang/terrain resolution, isometric/hex orientations. - **P6 — Scale:** infinite/chunked maps, `.world` stitching, gzip/zstd. --- ## 5. Acceptance criteria for *this* issue (research/design) - [ ] The feature inventory above is reviewed and agreed as the target surface (with any items explicitly cut). - [ ] A decision is recorded on §3.1 (format order) and §3.2 (model vs. `rt_map`). - [ ] The phasing in §4 is turned into concrete follow-up issues (P0…P6) with labels/priorities. - [ ] The in-repo [Kenney Tiled sample](assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx) is confirmed as the first golden fixture (and we note it currently exercises: external `.tsx`, 3 layers, CSV, and flip-flagged GIDs). > Sources: [TMX Map Format](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/) · [JSON Map Format](https://doc.mapeditor.org/en/stable/reference/json-map-format/) · [Worlds](https://doc.mapeditor.org/en/stable/manual/worlds/) · [Export](https://doc.mapeditor.org/en/stable/manual/export/) · [Tiled 1.11 release notes](http://www.mapeditor.org/2024/06/27/tiled-1-11-released.html) _No implementation is requested here — this issue scopes the research and the design decisions needed before an implementation plan._
orkun added the
proposal
priority:medium
area:stdlib
area:rendering
labels 2026-09-01 12:13:06 +02:00
Author
Owner

Correction to §5 — the in-repo Kenney sample is not comprehensive

The assets/kenney/tiny-dungeon/Tiled/ sample only exercises the narrow case (orthogonal, external .tsx, 3 CSV tile layers, flip-flagged GIDs). It has no animations, objects, per-tile collision, custom properties, compression, isometric/hex, infinite/chunks, groups, image layers, or Wang sets. It's fine as a smoke test, not as the design's golden corpus.

No single Tiled file covers the whole surface. The authoritative corpus is the official mapeditor/tiled repo (examples/ + tests/), which I've read through and mapped to precise, verified feature coverage below. Recommendation: vendor a curated subset into assets/tiled-fixtures/ (SHA-pinned) as the golden set, and hand-author the few features the official examples don't cover.

Fixture Features it exercises (verified by reading the file)
examples/sticker-knight/ (map/sandbox.tmx, sandbox2.tmx, objs.tsx, templates/*.tx, sticker-knight.world) image-collection tileset (per-tile <image>), tile objects with GID flip flags (e.g. gid="2147483655" = tile 7 h-flipped), multiple object layers with per-layer parallaxx/parallaxy, parallaxoriginx/y, backgroundcolor, object rotation, object templates (.tx, with external <tileset> ref), and a .world stitching two TMX maps + one JSON map
examples/rpg/island.tmx + beach_tileset.tsx animated tiles — 33 <animation> blocks / 131 <frame>s (the "animated sprite" case)
examples/orthogonal-outside.tmx object shapes: <ellipse>, <point>, <polygon>, <polyline>; object type/class; per-tile probability; map-level color custom property (#AARRGGBB)
examples/perspective_walls.tsx per-tile custom properties (bool), <tileoffset>
examples/sewers.tmx base64 + zlib data, layer opacity, color-key transparency (trans="ff00ff")
examples/desert.tmx + desert.tsx canonical simple orthogonal, external .tsx, base64+zlib — the "hello world"
examples/isometric_grass_and_water.tmx isometric orientation, <grid orientation="isometric">, Wang set (type="corner", colors + wangtile/wangid), <tileoffset>
examples/isometric_staggered_grass_and_water.tmx staggered isometric (staggeraxis/staggerindex)
examples/hexagonal-mini.tmx, test_hexagonal_tile_60x60x30.tmx hexagonal orientation (hexsidelength, staggeraxis="y", staggerindex), base64+zlib
tests/data/mapobject.tmx base64 + gzip data fixture, object type
tests/data/, tests/wangtiles, tests/properties reader/property/wang edge-case fixtures — pull specific ones as we implement each parser
examples/examples.tiled-project + objecttypes.xml custom types / object-types export (.tiled-project, object-types XML) — needed for class/enum properties
examples/sewer_automap/ AutoMapping rule maps (authoring-side; out of runtime scope but documents the GIDs)

Residual gaps not cleanly covered by the official examples — hand-author in Tiled

  • Text objects (<text> — font/align/style)
  • Infinite maps with <chunk> segments (both TMX and JSON chunks[])
  • Group layers (<group>) and image layers (<imagelayer> with repeatx/repeaty)
  • Per-tile collision <objectgroup> hitboxes (rect/polygon) → the esys_move feeder
  • base64 + zstd (opt-in compression) and layer blend mode (1.12)
  • Full class/enum/object/file property matrix on every element type

Net effect on this issue

  • §5's "in-repo Kenney sample is the first golden fixture" → superseded: golden corpus = the curated mapeditor/tiled subset above (SHA-pinned), plus the hand-authored gap fixtures. Keep the Kenney sample only as a minimal orthogonal-CSV smoke test.
  • This also lets each phase (P0–P6 in the body) pick the exact fixture that exercises it, so parser coverage is provable file-by-file.

License note: Tiled's example assets carry their own licenses (see each folder's *.license / README); vendor with attribution.

Sources: Tiled examples dir · tests.

## Correction to §5 — the in-repo Kenney sample is *not* comprehensive The `assets/kenney/tiny-dungeon/Tiled/` sample only exercises the narrow case (orthogonal, external `.tsx`, 3 CSV tile layers, flip-flagged GIDs). It has **no** animations, objects, per-tile collision, custom properties, compression, isometric/hex, infinite/chunks, groups, image layers, or Wang sets. It's fine as a *smoke test*, not as the design's golden corpus. No single Tiled file covers the whole surface. The authoritative corpus is the **official `mapeditor/tiled` repo** (`examples/` + `tests/`), which I've read through and mapped to precise, *verified* feature coverage below. Recommendation: **vendor a curated subset into `assets/tiled-fixtures/` (SHA-pinned) as the golden set**, and hand-author the few features the official examples don't cover. ### Verified coverage (raw links, from `mapeditor/tiled@master`) | Fixture | Features it exercises (verified by reading the file) | |---|---| | [`examples/sticker-knight/`](https://github.com/mapeditor/tiled/tree/master/examples/sticker-knight) (`map/sandbox.tmx`, `sandbox2.tmx`, `objs.tsx`, `templates/*.tx`, `sticker-knight.world`) | **image-collection tileset** (per-tile `<image>`), **tile objects** with GID flip flags (e.g. `gid="2147483655"` = tile 7 h-flipped), multiple **object layers with per-layer `parallaxx/parallaxy`**, `parallaxoriginx/y`, `backgroundcolor`, object `rotation`, **object templates** (`.tx`, with external `<tileset>` ref), and a **`.world`** stitching two TMX maps + one **JSON** map | | [`examples/rpg/island.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/rpg/island.tmx) + [`beach_tileset.tsx`](https://github.com/mapeditor/tiled/blob/master/examples/rpg/beach_tileset.tsx) | **animated tiles** — 33 `<animation>` blocks / 131 `<frame>`s (the "animated sprite" case) | | [`examples/orthogonal-outside.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/orthogonal-outside.tmx) | **object shapes**: `<ellipse>`, `<point>`, `<polygon>`, `<polyline>`; object `type`/class; per-tile `probability`; map-level `color` custom property (`#AARRGGBB`) | | [`examples/perspective_walls.tsx`](https://github.com/mapeditor/tiled/blob/master/examples/perspective_walls.tsx) | **per-tile custom properties** (bool), `<tileoffset>` | | [`examples/sewers.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/sewers.tmx) | **base64 + zlib** data, layer `opacity`, **color-key transparency** (`trans="ff00ff"`) | | [`examples/desert.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/desert.tmx) + `desert.tsx` | canonical simple orthogonal, external `.tsx`, base64+zlib — the "hello world" | | [`examples/isometric_grass_and_water.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/isometric_grass_and_water.tmx) | **isometric** orientation, `<grid orientation="isometric">`, **Wang set** (`type="corner"`, colors + `wangtile`/`wangid`), `<tileoffset>` | | [`examples/isometric_staggered_grass_and_water.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/isometric_staggered_grass_and_water.tmx) | **staggered** isometric (`staggeraxis`/`staggerindex`) | | [`examples/hexagonal-mini.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/hexagonal-mini.tmx), [`test_hexagonal_tile_60x60x30.tmx`](https://github.com/mapeditor/tiled/blob/master/examples/test_hexagonal_tile_60x60x30.tmx) | **hexagonal** orientation (`hexsidelength`, `staggeraxis="y"`, `staggerindex`), base64+zlib | | [`tests/data/mapobject.tmx`](https://github.com/mapeditor/tiled/blob/master/tests/data/mapobject.tmx) | **base64 + gzip** data fixture, object `type` | | [`tests/data/`](https://github.com/mapeditor/tiled/tree/master/tests/data), [`tests/wangtiles`](https://github.com/mapeditor/tiled/tree/master/tests), [`tests/properties`](https://github.com/mapeditor/tiled/tree/master/tests) | reader/property/wang edge-case fixtures — pull specific ones as we implement each parser | | [`examples/examples.tiled-project`](https://github.com/mapeditor/tiled/blob/master/examples/examples.tiled-project) + [`objecttypes.xml`](https://github.com/mapeditor/tiled/blob/master/examples/objecttypes.xml) | **custom types** / object-types export (`.tiled-project`, object-types XML) — needed for `class`/enum properties | | [`examples/sewer_automap/`](https://github.com/mapeditor/tiled/tree/master/examples/sewer_automap) | AutoMapping rule maps (authoring-side; out of runtime scope but documents the GIDs) | ### Residual gaps not cleanly covered by the official examples — **hand-author in Tiled** - **Text objects** (`<text>` — font/align/style) - **Infinite maps** with `<chunk>` segments (both TMX and JSON `chunks[]`) - **Group layers** (`<group>`) and **image layers** (`<imagelayer>` with `repeatx/repeaty`) - **Per-tile collision `<objectgroup>`** hitboxes (rect/polygon) → the `esys_move` feeder - **base64 + zstd** (opt-in compression) and **layer blend `mode`** (1.12) - Full **`class`/enum/`object`/`file` property** matrix on every element type ### Net effect on this issue - §5's "in-repo Kenney sample is the first golden fixture" → **superseded**: golden corpus = the curated `mapeditor/tiled` subset above (SHA-pinned), plus the hand-authored gap fixtures. Keep the Kenney sample only as a minimal orthogonal-CSV smoke test. - This also lets each phase (P0–P6 in the body) pick the exact fixture that exercises it, so parser coverage is provable file-by-file. License note: Tiled's example assets carry their own licenses (see each folder's `*.license` / README); vendor with attribution. _Sources: [Tiled examples dir](https://github.com/mapeditor/tiled/tree/master/examples) · [tests](https://github.com/mapeditor/tiled/tree/master/tests)._
Author
Owner

Resolved — design recorded, phasing filed

This research/design issue is done. No implementation was in scope; the four acceptance criteria are met as follows.

Design record

Full decisions live on the wiki: Design: Tiled maps (placed there per the repo-cleanup convention that moved *-DESIGN.md to the wiki, #26).

Two corrections from surveying the tree first

  1. Half the "P0 foundations" already exist. DEFLATE (z_inflate) and the zlib wrapper (z_uncompress) ship today in runtime/native/inflate.ludic; a full JSON reader (Json.parse/Value.*) ships in runtime/native/value.ludic. So P0 shrinks to an XML reader + base64 decode + a gzip framing — the heavy items are done.
  2. Golden corpus (this supersedes §5, per the correction comment above): the golden set is a SHA-pinned curated subset of mapeditor/tiled examples/+tests/ vendored into assets/tiled-fixtures/ with attribution, plus a few hand-authored gap fixtures. The in-repo Kenney sampleMap.tmx stays only as the orthogonal-CSV + GID-flip smoke test (its CSV does carry real flip-flagged GIDs — verified: 1610612787=0x60000033 V+D, 3221225485=0xC000000D H+V, 3758096434=0xE0000032 H+V+D).

Decisions on the key questions

  • §3.1 Format order → JSON (TMJ) first, TMX/TSX a fast follow. Decisive because the JSON reader already exists; the TMX path needs a new XML reader. One JSON reader also unlocks TSJ + .tj templates + .world. v1 requires a JSON export; P0.5 adds the TMX reader so the native Kenney .tmx loads with no re-export. Compression v1 = CSV + base64 + base64+zlib (free); gzip is a P0 follow; zstd deferred to P6.
  • §3.2 Model → new rt_tmap (header + ordered layers of dense int32 GID arrays + tilesets with a gid → (tileset, localId, flipH/V/D) resolver), with rt_map/rt_tile kept as a compatibility projection of a designated collision layer so Grid.*/Path.*/esys_move keep working byte-identically. Exposed as a new Tiled.* namespace (splice-on-demand, like Regex.*/Grid.*); Map.* keeps its glyph API.
  • §3.3–3.10 are all decided in the design doc (flips on the resolver query not baked; <animation> → the SpriteAnim engine-owned system; collision = per-tile <objectgroup> → property convention → collision-layer, all normalised into Solids{tile,wall,oneway}; asset paths resolved relative to the map at compile time; objects→entities and properties→components deferred to P4; infinite/.world deferred to P6).

Phasing → follow-up issues (with priorities)

Phase Issue Priority
P0 — XML reader + base64 decode + gzip framing #67 high
P0.5 — TMX/TSX/TX reader #68 high
P1 — core orthogonal load: rt_tmap + GID resolver + render #69 high
P2 — collision & grid → esys_move #70 medium
P3 — animated tiles (→ SpriteAnim) + tile objects #71 medium
P4 — objects/shapes/text, properties/types, templates #72 low
P5 — image/group layers, Wang, isometric/hex #73 low
P6 — infinite/chunked, .world, zstd #74 low

Each follow-up names its exact golden fixture(s) so parser coverage is provable file-by-file.

Acceptance criteria

  • Feature inventory (issue §1) agreed as the target surface; cut list recorded (legacy oblique, terrain authoring, AutoMapping, non-native export targets, .tiled-session).
  • §3.1 (format order) and §3.2 (model vs rt_map) decisions recorded.
  • §4 phasing turned into concrete follow-up issues P0–P6 (#67–#74) with labels/priorities.
  • Golden fixture confirmed — superseded per the correction: curated mapeditor/tiled corpus + hand-authored gaps; Kenney sample = smoke test.

Closing as resolved.

## Resolved — design recorded, phasing filed This research/design issue is **done**. No implementation was in scope; the four acceptance criteria are met as follows. ### Design record Full decisions live on the wiki: **[Design: Tiled maps](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design%2FTiled)** (placed there per the repo-cleanup convention that moved `*-DESIGN.md` to the wiki, #26). ### Two corrections from surveying the tree first 1. **Half the "P0 foundations" already exist.** DEFLATE (`z_inflate`) and the zlib wrapper (`z_uncompress`) ship today in `runtime/native/inflate.ludic`; a full JSON reader (`Json.parse`/`Value.*`) ships in `runtime/native/value.ludic`. So P0 shrinks to **an XML reader + base64 *decode* + a gzip framing** — the heavy items are done. 2. **Golden corpus** (this supersedes §5, per the correction comment above): the golden set is a **SHA-pinned curated subset of `mapeditor/tiled` `examples/`+`tests/`** vendored into `assets/tiled-fixtures/` with attribution, plus a few hand-authored gap fixtures. The in-repo Kenney `sampleMap.tmx` stays only as the orthogonal-CSV + GID-flip **smoke test** (its CSV does carry real flip-flagged GIDs — verified: `1610612787`=`0x60000033` V+D, `3221225485`=`0xC000000D` H+V, `3758096434`=`0xE0000032` H+V+D). ### Decisions on the key questions - **§3.1 Format order → JSON (TMJ) first, TMX/TSX a fast follow.** Decisive because the JSON reader already exists; the TMX path needs a new XML reader. One JSON reader also unlocks TSJ + `.tj` templates + `.world`. v1 requires a JSON export; P0.5 adds the TMX reader so the native Kenney `.tmx` loads with no re-export. Compression v1 = CSV + base64 + base64+zlib (free); gzip is a P0 follow; **zstd deferred to P6**. - **§3.2 Model → new `rt_tmap`** (header + ordered layers of dense `int32` GID arrays + tilesets with a `gid → (tileset, localId, flipH/V/D)` resolver), with **`rt_map`/`rt_tile` kept as a compatibility projection** of a designated collision layer so `Grid.*`/`Path.*`/`esys_move` keep working byte-identically. Exposed as a new `Tiled.*` namespace (splice-on-demand, like `Regex.*`/`Grid.*`); `Map.*` keeps its glyph API. - §3.3–3.10 are all decided in the design doc (flips on the resolver query not baked; `<animation>` → the SpriteAnim engine-owned system; collision = per-tile `<objectgroup>` → property convention → collision-layer, all normalised into `Solids{tile,wall,oneway}`; asset paths resolved relative to the map at compile time; objects→entities and properties→components deferred to P4; infinite/`.world` deferred to P6). ### Phasing → follow-up issues (with priorities) | Phase | Issue | Priority | |---|---|---| | P0 — XML reader + base64 decode + gzip framing | #67 | high | | P0.5 — TMX/TSX/TX reader | #68 | high | | P1 — core orthogonal load: `rt_tmap` + GID resolver + render | #69 | high | | P2 — collision & grid → `esys_move` | #70 | medium | | P3 — animated tiles (→ SpriteAnim) + tile objects | #71 | medium | | P4 — objects/shapes/text, properties/types, templates | #72 | low | | P5 — image/group layers, Wang, isometric/hex | #73 | low | | P6 — infinite/chunked, `.world`, zstd | #74 | low | Each follow-up names its exact golden fixture(s) so parser coverage is provable file-by-file. ### Acceptance criteria - [x] Feature inventory (issue §1) agreed as the target surface; cut list recorded (legacy oblique, terrain authoring, AutoMapping, non-native export targets, `.tiled-session`). - [x] §3.1 (format order) and §3.2 (model vs `rt_map`) decisions recorded. - [x] §4 phasing turned into concrete follow-up issues P0–P6 (#67–#74) with labels/priorities. - [x] Golden fixture confirmed — superseded per the correction: curated `mapeditor/tiled` corpus + hand-authored gaps; Kenney sample = smoke test. Closing as resolved.
orkun closed this issue 2026-09-01 12:34: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: workshopsoft/ludic#66
No description provided.