Commit graph

8 commits

Author SHA1 Message Date
bf36bc8a8f refactor(runtime,packages,examples): named constants, package enums, idiom sweep
- runtime: HEADLESS_FRAME_PATH, STICK_DEADZONE / STICK_LEFT_X/Y, key and
  byte codes as char literals throughout (`k == 'w'`, `fill(rt_map, ' ', …)`)
- ludic.gameplay/stats: drop the duplicate `stat_field` (it answered "atk"
  for every build stat); Stats.base uses stats_field_name
- ludic.shooter: compare aim modes and fire patterns with AimMode.* and
  WeaponPattern.* instead of raw ints; STICK_RIGHT_X/Y
- ludic.npcai: DecisionMade / brain_set_state use AiState.*
- examples/games/menu.ludic uses Font.load / Ui.* with FONT_PATH and
  BACKDROP named; strings.ludic header says what it prints
- whole tree: `x = x + 1` → `x += 1` (single-term right-hand sides only),
  `0 - x` → `-x`, ASCII codes → char literals; every .ludic and every
  ```ludic fence reformatted with the fixed formatter (whitespace only)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 01:12:26 +03:00
764a0296ce feat(stdlib): Tiled P6 — infinite/chunked maps, .world stitching, base64+zstd (#74)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 22s
ci / build-and-test (push) Successful in 2m4s
commit-lint / conventional-commits (push) Successful in 4s
docs / build-and-deploy (push) Successful in 28s
- Infinite/chunked maps: <chunk x y width height> (TMX) and JSON chunks[]
  (default 16x16) decode and flatten into the dense layer array, sized to the
  chunk union; the map's 0/0 header dimensions fall back to the flattened bounds.
- .world stitching: Tiled.world / Tiled.world_count / Tiled.world_map read a
  .world (JSON, reusing Json.parse) and list its member maps at their offsets.
- base64+zstd: a self-contained pure-Ludic Zstandard decompressor
  (runtime/native/zstd.ludic, RFC 8878) — the design's "largest single item,
  explicitly last". Frame header + raw/RLE/compressed blocks; raw/RLE and
  direct-weight Huffman literals; the full FSE sequence path (predefined,
  transcribed exactly from zstd's hardcoded tables since they're not rebuildable
  from the default distributions; RLE; FSE-described) with repeat offsets and
  execution. Decodes the low-entropy GID streams a tilemap produces; a high-
  entropy FSE-compressed-Huffman-weights block fails cleanly with -1 rather than
  emitting wrong bytes (documented scope).

Proven by library/tiled_p6.ludic (12 assertions): TMX + TMJ chunk flattening,
.world offsets, and a real zstd-compressed tile layer decoding byte-exactly to
its CSV baseline. x test: 97 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 16:07:10 +03:00
78a5719fae feat(engine): Tiled P5 — image/group layers, iso/hex/staggered coords, Wang (#73)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 21s
ci / build-and-test (push) Successful in 1m59s
commit-lint / conventional-commits (push) Successful in 4s
docs / build-and-deploy (push) Successful in 28s
- Image layers: <imagelayer> renders with parallax + optional repeatx/repeaty
  tiling across the view; the image loads relative to the map file. Group layers
  flatten at build (children render in file order); layer offset/opacity/tint are
  queryable (Tiled.layer_kind / layer_offsetx / layer_offsety / layer_opacity /
  layer_tint), a group's tint applying recursively.
- Orientation transforms: Tiled.cell_x / Tiled.cell_y compute a tile cell's
  screen position for orthogonal, isometric ((x-y)*tw/2, (x+y)*th/2), staggered,
  and hexagonal (rows step by (tileh+hexsidelength)/2, alternate rows shoved per
  staggerindex) — the tile draw now places cells through them.
- Wang: exported Wang-set GIDs are ordinary GIDs and resolve through the standard
  GID resolver; the terrain-corner authoring concept is editor-side (design cut).

Proven by library/tiled_p5.ludic (14 assertions) over the real
isometric_grass_and_water.tmx (iso + Wang) and hexagonal-mini.tmx, plus a
hand-authored image+group fixture. x test: 96 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 15:14:36 +03:00
b8c71d2a8e feat(stdlib): Tiled P4 — objects, custom props/types, templates, opt-in spawn (#72)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 21s
ci / build-and-test (push) Successful in 1m57s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 27s
- 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>
2026-09-01 15:08:06 +03:00
58e1105f2d feat(engine): Tiled P3 — animated tiles (deterministic frame clock) + tile objects (#71)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 21s
ci / build-and-test (push) Successful in 1m53s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 28s
- 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>
2026-09-01 14:58:21 +03:00
0cb57774d7 feat(stdlib): Tiled P2 — collision normalisation into the Solids feed (#70)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 20s
ci / build-and-test (push) Successful in 1m51s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 28s
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>
2026-09-01 14:50:27 +03:00
bc301c8d17 feat(engine): Tiled P1 — rt_tmap model + GID resolver + render + projection (#69)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 20s
ci / build-and-test (push) Successful in 1m48s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 27s
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>
2026-09-01 14:43:29 +03:00
071c268de7 feat(stdlib): Tiled P0.5 — TMX/TSX reader over the XML reader (#68)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 20s
ci / build-and-test (push) Successful in 1m45s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 28s
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>
2026-09-01 14:15:41 +03:00