- Object layers: all shapes (rectangle/ellipse/point/polygon/polyline/text) and
custom properties parse and are queryable — Tiled.object_count / Tiled.object /
Tiled.object_shape, and Tiled.prop / prop_int / prop_type over any object /
tile / layer / map.
- Custom types: Tiled.load_types reads an objecttypes.xml project custom-type
table so a class property resolves its default; enum values resolve as strings.
- Templates: Tiled.template reads a .tx/.tj, and Tiled.load merges each object's
`template` reference under the instance's overrides (field inheritance).
- Opt-in spawning: Tiled.spawn / Tiled.spawn_layer map an object's class +
properties onto Ludic components through the reflection ABI (Position from x/y,
each property to a like-named field). Off by default — no gameplay coupling in
the core load. Split into tiled_spawn.ludic, spliced only for a Tiled *game*
(the world table exists only under has_ecs), so a plain map-reading tool never
links against the reflection ABI.
Proven by library/tiled_p4.ludic (18 assertions) incl. the design's named
orthogonal-outside.tmx object shapes. x test: 95 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Animated tiles: a tileset <animation>'s {tileid, duration} frames advance as a
pure function of the fixed 60/s engine frame counter (the same clock SpriteAnim
rides), so an animated GID resolves at draw to the current frame's GID with its
flip flags preserved — deterministic, frame-identical across runs, ticks for
free. Tiled.frame_gid(map, gid, frame) / Tiled.animated(map, gid) expose it;
Tiled.draw_anim(map, camx, camy, frame) draws a map with animations advanced.
- Tile objects: object-layer entries with a `gid` draw the tile image (with their
own flip flags), bottom-anchored at the object position, as placeable sprites;
tmap_draw_full now renders object layers alongside tile layers.
Proven by library/tiled_p3.ludic (14 assertions): frame advance at authored
durations + wrap, flip flags riding an animation swap, tile-object parse + draw +
flip mirroring, and the design's named rpg/beach_tileset.tsx (33 <animation>
blocks / 131 frames). x test: 94 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Normalise three collision sources into the byte tilemap esys_move / Grid.* /
Path.* read, in the design's priority order (§3.5):
- per-tile <objectgroup> hitboxes (Tiled.tile_shapes) — the precise source;
- the solid/oneway/trigger bool property convention (Tiled.collision_kind);
- the designated collision layer — any non-zero GID solid (Tiled.project),
the fallback source, applied automatically on load.
Tiled.collide drives collision from a visual layer's per-tile metadata alone
(a tile with no collision metadata stays passable). tmap_collision_kind
classifies a GID (0 none / 1 solid / 2 one-way / 3 trigger) from its metadata;
the projection adds the collision-layer fallback.
Proven by library/tiled_p2.ludic (14 assertions): kind classification for each
source, the property convention and collision-layer fallback producing an
identical Solids feed, a per-tile-<objectgroup> floor blocking an esys_move
mover, and A* over a loaded map matching the hand-authored baseline. x test:
93 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The heart of Tiled support: load a map and draw it.
- rt_tmap model (Tmap/TmLayer/TmTileset in tiled.ludic): map header + ordered
layers (dense int32 GID arrays, heap-allocated to w*h, lifting the 96x64 cap)
+ tilesets. Built from the intermediate Value tree, so the TMX and TMJ paths
both feed it.
- GID resolver (Tiled.resolve): gid -> (tileset, localId, flipH/V/D); the three
flip flags masked off before the local-id lookup, returned alongside. gid==0
is empty.
- Image-backed render (Tiled.draw): every visible tile layer in file order,
blitting each tile from its tileset image with flips applied at draw.
- Compatibility projection (Tiled.project / auto on load): a designated
collision layer projects to the legacy byte tilemap ('#' solid, '=' one-way
via the oneway property, ' ' empty) so Grid.*/Path.*/esys_move are unchanged.
- Tiled.load resolves external tilesets + images relative to the map file and
auto-projects a collision/solids/walls layer.
- The grid and physics_tiles demos now run off a loaded map (grid_maze.tmx /
physics_map.tmx) instead of hand-authored Map.row strings, byte-identically.
Two compiler fixes fell out of this (see the changeset):
- emit_index_addr set g_addr_ty before evaluating the index, so slice[obj.field]
came back mis-typed; set it last, like the raw-pointer branches.
- @strcmp was declared by both the world table and the fs prelude; centralise
it in the head prelude so a game that uses Fs/Path links.
Proven by library/tiled_p1.ludic (14 assertions: loads+renders Kenney map
identically from .tmx and .tmj, flip mirroring) + the converted grid/
physics_tiles demos. x test: 92 passed; self-host bootstrap fixpoint intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tiled.read / Tiled.read_tsx (runtime/native/tiled.ludic): map the native
XML formats (TMX/TSX/TX) onto the SAME intermediate the JSON path produces
— a Value tree in Tiled's JSON schema — so one format-independent core
(P1) consumes either reader.
- tmx_to_value walks a <map> into {orientation, width/height, tilewidth/
height, tilesets[], layers[]}; every tile layer's <data> is decoded to a
dense GID int list (CSV split, or base64 -> zlib/gzip inflate ->
little-endian u32s), so a CSV .tmx and a base64 .tmj of the same map read
structurally identically.
- External tilesets keep the {firstgid, source} reference (as TMJ does);
embedded tilesets and standalone .tsx (tsx_to_value) inline full geometry
+ per-tile metadata (animation frames, collision objectgroup, class,
properties) for later phases.
- Object layers, shapes (rect/ellipse/point/polygon/polyline/text),
image/group layers and custom properties are parsed into the tree now so
P2/P4/P5 just read it.
- tmj_normalize decodes base64 `data` strings in a parsed .tmj so the JSON
path matches the XML path.
Proven by library/tiled_p05.ludic (30 assertions) incl. the in-repo Kenney
sampleMap.tmx + external sampleSheet.tsx and TMX-vs-TMJ structural
identity. Docs + inventory added; x test: 91 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The three parsing primitives the TMX path needs that were not already in
the tree (zlib inflate + the JSON reader already shipped):
- Xml.* — a minimal, deterministic pure-Ludic XML reader for the
element/attribute/CDATA subset TMX/TSX/TX use, spliced on demand. Handles
nested elements, single/double-quoted attributes, text + <![CDATA[…]]>,
comments, the <?xml?> prolog and <!DOCTYPE>, the five predefined entities
and numeric character references.
- Base64.* — standard base64 (RFC 4648) decode + a matching pure-Ludic
encoder; the decoder ignores the whitespace Tiled wraps into <data>.
Splicing Base64.* also pulls in inflate.ludic so a plain tool can run the
full base64 -> zlib/gzip decode chain.
- z_gunzip — gzip framing (RFC 1952) over the existing DEFLATE inflater:
skip the 10-byte header + optional fields, inflate the body, ignore the
CRC32/ISIZE trailer.
Golden corpus vendored under assets/tiled-fixtures/ (mapeditor/tiled
examples, attributed, + hand-authored multi-encoding fixtures). New
example library/tiled_p0.ludic proves all three (21 assertions); docs
pages + inventory added so check-impl stays green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Self-hosted compiler (selfhost/*.ludic), runtime, examples, editor tooling,
and docs. Phase 1 of the syntax-redesign cohesion pass has landed:
edge-system fix, signature-query, when-alias, and the documentation truth-pass.
Suite green (14/14), C-free bootstrap fixpoint holds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>