Proposal: comprehensive Tiled (TMX/TMJ) map support — parse & consume every Tiled feature/output #66
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Summary
Ludic's tilemaps are text-based today:
Map.size(w, h)+Map.row(y, "…")write single-char codes intort_map/rt_tile(runtime/native/core.ludic), and everything downstream —Grid.*/Path.*(runtime/native/grid.ludic), theesys_movetile 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 ownphysics_tiles/griddemos 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).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)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-rectanglex/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) — feedsesys_move.<properties>.terraincorner indices (deprecated → Wang).1.4 Layers (order = draw order; all share
id,name,class,opacity,visible,offsetx/y,parallaxx/y(1.5),tintcolor,locked)<layer>:width,height,<data>+ blendmode(1.12: normal/add/multiply/screen/…).<objectgroup>:color,draworder(topdown/index).<imagelayer>:image,repeatx/repeaty(1.8), transparent color — parallax backdrops.<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.<chunk x y width height>segments instead of one dense array (JSON:chunks[], default 16×16).0x80000000horizontal,0x40000000vertical,0x20000000diagonal (anti-diag),0x10000000hex/rotated-120 (1.9).gid 0= empty cell.1610612787,3221225485,2147483698are 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.<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).gid→ renders a tile image (with its own flip flags) as a placeable/rotatable sprite.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-indexwangid) — 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.propertytypenames a project-defined custom type..tiled-project/ object-types export — needed to interpretclass/enum properties.1.9 Worlds (
.world, JSON)maps[] {fileName, x, y}, orpatterns[] {regexp, multiplierX/Y, offsetX/Y, mapWidth, mapHeight}, plusonlyShowAdjacentMaps. 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:
Grid.*/Path.*/esys_movealready make.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 aTiled.*/Map.*stdlib namespace (see the "splice-a-runtime" pattern used forRegex.*/Grid.*).3. Key design questions (to resolve during design, not here)
.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.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 generalizert_mapto(layer, x, y) → GIDwith a tileset/GID resolver, and keeprt_tileas a compatibility view of a chosen "solids" layer foresys_move/Grid?GID → (tileset, local id, flipH/V/D). Where does the flip transform live — bake into draw, or expose on query?<animation>frames onto the existing SpriteAnim engine-owned system so animated tiles tick for free?<objectgroup>hitboxes vs. a designated collision layer vs. a custombool/classproperty convention (e.g.solid,oneway) — how does an author mark solids/one-ways/triggers so it flows intoSolids{tile,wall,oneway}+esys_move?sourcepaths (../Tilemap/tilemap.png); how do these resolve against our asset pipeline at compile vs. run time?spawnLudic entities (mappingclass/properties → components), à la the controllers roadmap (#57–#61)? This is where Tiled becomes a level editor for gameplay, not just tiles.class/enum properties bind to Ludic properties/models?.worldstreaming now, or explicitly defer to a later "large worlds" issue?4. Suggested phasing (for a follow-up implementation plan)
grid/physics_tilesdemos with a loaded.tmj/.tmx.<objectgroup>and/or custom properties →Solids+esys_move; keepGrid.*/Path.*working over the loaded map.<animation>→ SpriteAnim; tile objects (gid) → sprites..worldstitching, gzip/zstd.5. Acceptance criteria for this issue (research/design)
rt_map)..tsx, 3 layers, CSV, and flip-flagged GIDs).No implementation is requested here — this issue scopes the research and the design decisions needed before an implementation plan.
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/tiledrepo (examples/+tests/), which I've read through and mapped to precise, verified feature coverage below. Recommendation: vendor a curated subset intoassets/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)examples/sticker-knight/(map/sandbox.tmx,sandbox2.tmx,objs.tsx,templates/*.tx,sticker-knight.world)<image>), tile objects with GID flip flags (e.g.gid="2147483655"= tile 7 h-flipped), multiple object layers with per-layerparallaxx/parallaxy,parallaxoriginx/y,backgroundcolor, objectrotation, object templates (.tx, with external<tileset>ref), and a.worldstitching two TMX maps + one JSON mapexamples/rpg/island.tmx+beach_tileset.tsx<animation>blocks / 131<frame>s (the "animated sprite" case)examples/orthogonal-outside.tmx<ellipse>,<point>,<polygon>,<polyline>; objecttype/class; per-tileprobability; map-levelcolorcustom property (#AARRGGBB)examples/perspective_walls.tsx<tileoffset>examples/sewers.tmxopacity, color-key transparency (trans="ff00ff")examples/desert.tmx+desert.tsx.tsx, base64+zlib — the "hello world"examples/isometric_grass_and_water.tmx<grid orientation="isometric">, Wang set (type="corner", colors +wangtile/wangid),<tileoffset>examples/isometric_staggered_grass_and_water.tmxstaggeraxis/staggerindex)examples/hexagonal-mini.tmx,test_hexagonal_tile_60x60x30.tmxhexsidelength,staggeraxis="y",staggerindex), base64+zlibtests/data/mapobject.tmxtypetests/data/,tests/wangtiles,tests/propertiesexamples/examples.tiled-project+objecttypes.xml.tiled-project, object-types XML) — needed forclass/enum propertiesexamples/sewer_automap/Residual gaps not cleanly covered by the official examples — hand-author in Tiled
<text>— font/align/style)<chunk>segments (both TMX and JSONchunks[])<group>) and image layers (<imagelayer>withrepeatx/repeaty)<objectgroup>hitboxes (rect/polygon) → theesys_movefeedermode(1.12)class/enum/object/fileproperty matrix on every element typeNet effect on this issue
mapeditor/tiledsubset above (SHA-pinned), plus the hand-authored gap fixtures. Keep the Kenney sample only as a minimal orthogonal-CSV smoke test.License note: Tiled's example assets carry their own licenses (see each folder's
*.license/ README); vendor with attribution.Sources: Tiled examples dir · tests.
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.mdto the wiki, #26).Two corrections from surveying the tree first
z_inflate) and the zlib wrapper (z_uncompress) ship today inruntime/native/inflate.ludic; a full JSON reader (Json.parse/Value.*) ships inruntime/native/value.ludic. So P0 shrinks to an XML reader + base64 decode + a gzip framing — the heavy items are done.mapeditor/tiledexamples/+tests/vendored intoassets/tiled-fixtures/with attribution, plus a few hand-authored gap fixtures. The in-repo KenneysampleMap.tmxstays only as the orthogonal-CSV + GID-flip smoke test (its CSV does carry real flip-flagged GIDs — verified:1610612787=0x60000033V+D,3221225485=0xC000000DH+V,3758096434=0xE0000032H+V+D).Decisions on the key questions
.tjtemplates +.world. v1 requires a JSON export; P0.5 adds the TMX reader so the native Kenney.tmxloads with no re-export. Compression v1 = CSV + base64 + base64+zlib (free); gzip is a P0 follow; zstd deferred to P6.rt_tmap(header + ordered layers of denseint32GID arrays + tilesets with agid → (tileset, localId, flipH/V/D)resolver), withrt_map/rt_tilekept as a compatibility projection of a designated collision layer soGrid.*/Path.*/esys_movekeep working byte-identically. Exposed as a newTiled.*namespace (splice-on-demand, likeRegex.*/Grid.*);Map.*keeps its glyph API.<animation>→ the SpriteAnim engine-owned system; collision = per-tile<objectgroup>→ property convention → collision-layer, all normalised intoSolids{tile,wall,oneway}; asset paths resolved relative to the map at compile time; objects→entities and properties→components deferred to P4; infinite/.worlddeferred to P6).Phasing → follow-up issues (with priorities)
rt_tmap+ GID resolver + renderesys_move.world, zstdEach follow-up names its exact golden fixture(s) so parser coverage is provable file-by-file.
Acceptance criteria
.tiled-session).rt_map) decisions recorded.mapeditor/tiledcorpus + hand-authored gaps; Kenney sample = smoke test.Closing as resolved.