feat(stdlib): Tiled P0.5 — TMX/TSX reader over the XML reader (#68)
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>
This commit is contained in:
parent
79776d1f6a
commit
071c268de7
11 changed files with 5721 additions and 4986 deletions
65
assets/tiled-fixtures/handmade.tmj
Normal file
65
assets/tiled-fixtures/handmade.tmj
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"type": "map",
|
||||||
|
"version": "1.10",
|
||||||
|
"tiledversion": "1.10.2",
|
||||||
|
"orientation": "orthogonal",
|
||||||
|
"renderorder": "right-down",
|
||||||
|
"width": 4,
|
||||||
|
"height": 4,
|
||||||
|
"tilewidth": 16,
|
||||||
|
"tileheight": 16,
|
||||||
|
"infinite": false,
|
||||||
|
"nextlayerid": 3,
|
||||||
|
"nextobjectid": 1,
|
||||||
|
"tilesets": [
|
||||||
|
{
|
||||||
|
"firstgid": 1,
|
||||||
|
"source": "handmade.tsx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"type": "tilelayer",
|
||||||
|
"id": 1,
|
||||||
|
"name": "csv",
|
||||||
|
"width": 4,
|
||||||
|
"height": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"opacity": 1,
|
||||||
|
"visible": true,
|
||||||
|
"data": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
2147483649
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tilelayer",
|
||||||
|
"id": 2,
|
||||||
|
"name": "zlib",
|
||||||
|
"width": 4,
|
||||||
|
"height": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"opacity": 1,
|
||||||
|
"visible": true,
|
||||||
|
"encoding": "base64",
|
||||||
|
"compression": "zlib",
|
||||||
|
"data": "eJwNw4cNACAMBLEPvYaVGT1nySYpMbOwsrFzcHJx8/DS+WjSDw1EAPo="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -4,3 +4,4 @@ type: feat
|
||||||
**Tiled map support (#67–#74)** — load and draw [Tiled](https://www.mapeditor.org/) maps (TMX/TSX/TX and TMJ/TSJ/TJ), the design record from #66.
|
**Tiled map support (#67–#74)** — load and draw [Tiled](https://www.mapeditor.org/) maps (TMX/TSX/TX and TMJ/TSJ/TJ), the design record from #66.
|
||||||
|
|
||||||
- **P0 parsing primitives (#67)** — a minimal pure-Ludic XML reader (`Xml.*`) for the element/attribute/CDATA subset TMX/TSX/TX use; standard base64 decode/encode (`Base64.*`, RFC 4648), whose decoder ignores the whitespace Tiled wraps into `<data>`; and gzip framing (`z_gunzip`, RFC 1952) wrapping the existing DEFLATE inflater. zlib and the JSON reader already shipped. A curated, attributed golden corpus lands under `assets/tiled-fixtures/`.
|
- **P0 parsing primitives (#67)** — a minimal pure-Ludic XML reader (`Xml.*`) for the element/attribute/CDATA subset TMX/TSX/TX use; standard base64 decode/encode (`Base64.*`, RFC 4648), whose decoder ignores the whitespace Tiled wraps into `<data>`; and gzip framing (`z_gunzip`, RFC 1952) wrapping the existing DEFLATE inflater. zlib and the JSON reader already shipped. A curated, attributed golden corpus lands under `assets/tiled-fixtures/`.
|
||||||
|
- **P0.5 TMX/TSX reader (#68)** — `Tiled.read` / `Tiled.read_tsx` map the native XML formats onto the *same* intermediate the JSON path produces — a `Value` tree in Tiled's JSON schema, with every tile layer's data decoded to a dense GID list (CSV, base64, base64+zlib, base64+gzip). A CSV `.tmx` and a base64+zlib `.tmj` of the same map read structurally identically; the in-repo Kenney `sampleMap.tmx` + external `sampleSheet.tsx` load with no manual JSON re-export.
|
||||||
|
|
|
||||||
7
docs/language/tiled/_section.md
Normal file
7
docs/language/tiled/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
id: tiled
|
||||||
|
title: Tiled
|
||||||
|
order: 42
|
||||||
|
---
|
||||||
|
|
||||||
|
Load and draw <a href="https://www.mapeditor.org/">Tiled</a> maps. Both native format families — TMX/TSX/TX (XML) and TMJ/TSJ/TJ (JSON) — read onto one intermediate, a <a href="value"><code>Value</code></a> tree in Tiled's JSON schema, with layer data decoded to a dense GID list; so a CSV <code>.tmx</code> and a base64+zlib <code>.tmj</code> of the same map read identically. <a href="tiled-read"><code>Tiled.read</code></a> returns that intermediate tree for a map file and <a href="tiled-read_tsx"><code>Tiled.read_tsx</code></a> for a tileset file. Spliced on demand when a program mentions <code>Tiled.*</code>, alongside the <a href="xml"><code>Xml</code></a>, <a href="base64"><code>Base64</code></a> and <a href="value"><code>Value</code></a> runtimes it builds on.
|
||||||
14
docs/language/tiled/tiled-read.md
Normal file
14
docs/language/tiled/tiled-read.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
id: tiled-read
|
||||||
|
name: Tiled.read
|
||||||
|
category: tiled
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Tiled.read
|
||||||
|
sig: Tiled.read(path: string) -> value
|
||||||
|
tip: Read a map file into the intermediate value tree.
|
||||||
|
order: 1
|
||||||
|
ns: Tiled
|
||||||
|
member: read
|
||||||
|
---
|
||||||
|
|
||||||
|
Reads a Tiled map file at <code>path</code> — auto-detecting TMX (XML) vs TMJ (JSON) by its first byte — and returns the intermediate map <a href="value"><code>Value</code></a> tree in Tiled's JSON schema: <code>orientation</code>, <code>width</code>/<code>height</code>, <code>tilewidth</code>/<code>tileheight</code>, a <code>tilesets</code> list and a <code>layers</code> list. Every tile layer's <code>data</code> is decoded to a dense GID int list (CSV split, or base64 → zlib/gzip inflate → little-endian u32s), so the two formats yield structurally identical trees.
|
||||||
14
docs/language/tiled/tiled-read_tsx.md
Normal file
14
docs/language/tiled/tiled-read_tsx.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
id: tiled-read_tsx
|
||||||
|
name: Tiled.read_tsx
|
||||||
|
category: tiled
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Tiled.read_tsx
|
||||||
|
sig: Tiled.read_tsx(path: string) -> value
|
||||||
|
tip: Read a tileset file into a value object.
|
||||||
|
order: 2
|
||||||
|
ns: Tiled
|
||||||
|
member: read_tsx
|
||||||
|
---
|
||||||
|
|
||||||
|
Reads a standalone Tiled tileset file at <code>path</code> (TSX or TSJ) and returns a tileset <a href="value"><code>Value</code></a> object: <code>name</code>, <code>tilewidth</code>/<code>tileheight</code>, <code>spacing</code>, <code>margin</code>, <code>columns</code>, <code>tilecount</code>, the <code>image</code> and its size, and a <code>tiles</code> list of per-tile metadata (animation frames, collision shapes, properties). This is what a map's external <code>{firstgid, source}</code> tileset reference resolves to.
|
||||||
94
examples/library/tiled_p05.ludic
Normal file
94
examples/library/tiled_p05.ludic
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# tiled_p05.ludic — the Tiled P0.5 TMX/TSX reader (issue #68). The native XML
|
||||||
|
# formats map onto the SAME intermediate the JSON path produces: a Value tree in
|
||||||
|
# Tiled's JSON schema, with layer data decoded to a dense GID list. So a CSV
|
||||||
|
# `.tmx` and a base64+zlib `.tmj` of the same map read structurally identically.
|
||||||
|
#
|
||||||
|
# Reads the hand-authored fixture whose four layers carry the same 16 GIDs in
|
||||||
|
# CSV, base64-uncompressed, base64+gzip and base64+zlib.
|
||||||
|
#
|
||||||
|
# A full run prints: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||||
|
program TiledP05 {
|
||||||
|
entry {
|
||||||
|
let m = Tiled.read("assets/tiled-fixtures/handmade.tmx")
|
||||||
|
# --- map header ---
|
||||||
|
if Value.as_str(Value.get(m, "orientation")) == "orthogonal" { print(1) }
|
||||||
|
if Value.as_int(Value.get(m, "width")) == 4 { print(2) }
|
||||||
|
if Value.as_int(Value.get(m, "height")) == 4 { print(3) }
|
||||||
|
if Value.as_int(Value.get(m, "tilewidth")) == 16 { print(4) }
|
||||||
|
|
||||||
|
# --- tileset reference (kept as {firstgid, source}, like TMJ) ---
|
||||||
|
let tss = Value.get(m, "tilesets")
|
||||||
|
if Value.count(tss) == 1 { print(5) }
|
||||||
|
let ts0 = Value.at(tss, 0)
|
||||||
|
if Value.as_int(Value.get(ts0, "firstgid")) == 1 { print(6) }
|
||||||
|
if Value.as_str(Value.get(ts0, "source")) == "handmade.tsx" { print(7) }
|
||||||
|
|
||||||
|
# --- four layers, every encoding decodes to the SAME 16-GID array ---
|
||||||
|
let layers = Value.get(m, "layers")
|
||||||
|
if Value.count(layers) == 4 { print(8) }
|
||||||
|
let FLIP = 0 - 2147483647 # 0x80000001 as i32
|
||||||
|
var same = 1
|
||||||
|
var li = 0
|
||||||
|
while li < 4 {
|
||||||
|
let data = Value.get(Value.at(layers, li), "data")
|
||||||
|
if Value.count(data) != 16 { same = 0 }
|
||||||
|
if Value.as_int(Value.at(data, 0)) != 1 { same = 0 }
|
||||||
|
if Value.as_int(Value.at(data, 14)) != 15 { same = 0 }
|
||||||
|
if Value.as_int(Value.at(data, 15)) != FLIP { same = 0 }
|
||||||
|
li = li + 1
|
||||||
|
}
|
||||||
|
if same == 1 { print(9) }
|
||||||
|
# the CSV path and the gzip path agree on the flip-flagged GID
|
||||||
|
let csvdata = Value.get(Value.at(layers, 0), "data")
|
||||||
|
let gzdata = Value.get(Value.at(layers, 2), "data")
|
||||||
|
if Value.as_int(Value.at(csvdata, 15)) == FLIP { print(10) }
|
||||||
|
if Value.as_int(Value.at(gzdata, 15)) == FLIP { print(11) }
|
||||||
|
if Value.as_str(Value.get(Value.at(layers, 0), "type")) == "tilelayer" { print(12) }
|
||||||
|
if Value.as_str(Value.get(Value.at(layers, 0), "name")) == "csv" { print(13) }
|
||||||
|
|
||||||
|
# --- structural identity vs the TMJ export of the same map ---
|
||||||
|
let j = Tiled.read("assets/tiled-fixtures/handmade.tmj")
|
||||||
|
if Value.as_int(Value.get(j, "width")) == 4 { print(14) }
|
||||||
|
let jlayers = Value.get(j, "layers")
|
||||||
|
if Value.count(jlayers) == 2 { print(15) }
|
||||||
|
# both TMJ layers (a CSV int array + a base64+zlib string) normalise to the
|
||||||
|
# SAME GID array the TMX CSV layer produced
|
||||||
|
var jsame = 1
|
||||||
|
var jli = 0
|
||||||
|
while jli < 2 {
|
||||||
|
let jdata = Value.get(Value.at(jlayers, jli), "data")
|
||||||
|
if Value.count(jdata) != 16 { jsame = 0 }
|
||||||
|
var gi = 0
|
||||||
|
while gi < 16 {
|
||||||
|
if Value.as_int(Value.at(jdata, gi)) != Value.as_int(Value.at(csvdata, gi)) { jsame = 0 }
|
||||||
|
gi = gi + 1
|
||||||
|
}
|
||||||
|
jli = jli + 1
|
||||||
|
}
|
||||||
|
if jsame == 1 { print(16) }
|
||||||
|
|
||||||
|
# --- external tileset geometry via read_tsx ---
|
||||||
|
let ts = Tiled.read_tsx("assets/tiled-fixtures/handmade.tsx")
|
||||||
|
if Value.as_int(Value.get(ts, "tilewidth")) == 16 { print(17) }
|
||||||
|
if Value.as_int(Value.get(ts, "columns")) == 4 { print(18) }
|
||||||
|
if Value.as_int(Value.get(ts, "tilecount")) == 16 { print(19) }
|
||||||
|
if Value.as_str(Value.get(ts, "image")) == "handmade.png" { print(20) }
|
||||||
|
if Value.as_int(Value.get(ts, "imagewidth")) == 64 { print(21) }
|
||||||
|
let tiles = Value.get(ts, "tiles")
|
||||||
|
if Value.count(tiles) == 2 { print(22) } # tiles 4 (solid) and 6 (objectgroup)
|
||||||
|
|
||||||
|
# --- the in-repo Kenney sampleMap.tmx + external sampleSheet.tsx ---
|
||||||
|
let k = Tiled.read("assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx")
|
||||||
|
if Value.as_int(Value.get(k, "width")) == 32 { print(23) }
|
||||||
|
if Value.as_int(Value.get(k, "height")) == 20 { print(24) }
|
||||||
|
let kts = Value.at(Value.get(k, "tilesets"), 0)
|
||||||
|
if Value.as_str(Value.get(kts, "source")) == "sampleSheet.tsx" { print(25) }
|
||||||
|
let kdata = Value.get(Value.at(Value.get(k, "layers"), 0), "data")
|
||||||
|
if Value.count(kdata) == 640 { print(26) } # 32 * 20
|
||||||
|
if Value.as_int(Value.at(kdata, 0)) == 14 { print(27) }
|
||||||
|
if Value.as_int(Value.at(kdata, 1)) == 1610612787 { print(28) } # 0x60000033, V+D flipped
|
||||||
|
let ksheet = Tiled.read_tsx("assets/kenney/tiny-dungeon/Tiled/sampleSheet.tsx")
|
||||||
|
if Value.as_int(Value.get(ksheet, "tilecount")) == 132 { print(29) }
|
||||||
|
if Value.as_int(Value.get(ksheet, "columns")) == 12 { print(30) }
|
||||||
|
}
|
||||||
|
}
|
||||||
444
runtime/native/tiled.ludic
Normal file
444
runtime/native/tiled.ludic
Normal file
|
|
@ -0,0 +1,444 @@
|
||||||
|
# ============================================================================
|
||||||
|
# tiled.ludic — Tiled map support (`Tiled.*`), the design record of #66
|
||||||
|
# (issues #67–#74). Native TMX/TSX/TX (XML) and TMJ/TSJ/TJ (JSON) both map onto
|
||||||
|
# ONE intermediate — a generic `Value.*` tree in Tiled's JSON schema — and one
|
||||||
|
# format-independent core turns that into the runtime map model. So the two
|
||||||
|
# readers below (P0.5, #68) each feed the same `rt_tmap` builder (P1, #69).
|
||||||
|
#
|
||||||
|
# The JSON path is `Json.parse` + a normalisation pass; the XML path is the
|
||||||
|
# `xml_parse` walk in this file. Layer data is decoded to a dense GID int list at
|
||||||
|
# read time (CSV split, or base64 -> zlib/gzip inflate -> little-endian u32s), so
|
||||||
|
# a CSV `.tmx` and a base64 `.tmj` of the same map yield structurally identical
|
||||||
|
# trees.
|
||||||
|
#
|
||||||
|
# ludicc splices this file (with core + xml + base64 + value) when a program
|
||||||
|
# mentions `Tiled.*` (parse.ludic).
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ---- layer-data decode -----------------------------------------------------
|
||||||
|
# a CSV of GIDs -> a Value list of int nodes. Non-digit separators (commas,
|
||||||
|
# whitespace, newlines) delimit; overflow wraps to the correct 32-bit GID
|
||||||
|
# (a flip-flagged GID like 0x80000001 lands as the matching negative i32).
|
||||||
|
function tiled_csv_list(text: pointer) -> Val {
|
||||||
|
let out = value_list()
|
||||||
|
let n = len(text)
|
||||||
|
var i = 0
|
||||||
|
var cur = 0
|
||||||
|
var have = 0
|
||||||
|
while i < n {
|
||||||
|
let c = text[i]
|
||||||
|
if c >= 48 and c <= 57 { cur = cur * 10 + (c - 48); have = 1 }
|
||||||
|
else {
|
||||||
|
if c == 44 or c == 32 or c == 9 or c == 10 or c == 13 {
|
||||||
|
if have == 1 { push(out.kids, value_int(cur)); cur = 0; have = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
if have == 1 { push(out.kids, value_int(cur)) }
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
# a base64 (optionally zlib/gzip-compressed) blob of `count` little-endian u32
|
||||||
|
# GIDs -> a Value list of int nodes.
|
||||||
|
function tiled_b64_list(text: pointer, compression: pointer, count: int) -> Val {
|
||||||
|
let comp = bytes(len(text) + 4)
|
||||||
|
let clen = b64_decode(text, comp)
|
||||||
|
let outcap = count * 4 + 16
|
||||||
|
var raw = comp
|
||||||
|
var rawlen = clen
|
||||||
|
if compression == "zlib" {
|
||||||
|
let d = bytes(outcap); let dn = z_uncompress(comp, clen, d, outcap); raw = d; rawlen = dn
|
||||||
|
} else { if compression == "gzip" {
|
||||||
|
let d = bytes(outcap); let dn = z_gunzip(comp, clen, d, outcap); raw = d; rawlen = dn
|
||||||
|
} }
|
||||||
|
let out = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i + 3 < rawlen {
|
||||||
|
let g = raw[i] | (raw[i + 1] << 8) | (raw[i + 2] << 16) | (raw[i + 3] << 24)
|
||||||
|
push(out.kids, value_int(g))
|
||||||
|
i = i + 4
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
# decode a `<data>` element (child of a `<layer>`) into a Value list of GIDs.
|
||||||
|
function tiled_data_list(data: Xml, count: int) -> Val {
|
||||||
|
let enc = xml_attr(data, "encoding")
|
||||||
|
let comp = xml_attr(data, "compression")
|
||||||
|
if enc == "base64" { return tiled_b64_list(xml_text(data), comp, count) }
|
||||||
|
# csv (or the legacy tag-per-tile form we don't emit) -> split the text
|
||||||
|
return tiled_csv_list(xml_text(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- custom properties -----------------------------------------------------
|
||||||
|
# `<properties>` -> a Value list of {name, type, value} objects (P4 reads these;
|
||||||
|
# P2 reads the `solid`/`oneway`/`trigger` bool convention off the same list).
|
||||||
|
function tmx_props_list(parent: Xml) -> Val {
|
||||||
|
let out = value_list()
|
||||||
|
let props = xml_find(parent, "properties")
|
||||||
|
if xml_tag(props) != "properties" { return out }
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(props) {
|
||||||
|
let p = xml_child(props, i)
|
||||||
|
if xml_tag(p) == "property" {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "name", value_str(xml_attr(p, "name")))
|
||||||
|
var ty = xml_attr(p, "type")
|
||||||
|
if ty == "" { ty = "string" }
|
||||||
|
value_put(o, "type", value_str(ty))
|
||||||
|
# value may be an attribute or (for multiline) the element text
|
||||||
|
var v = xml_attr(p, "value")
|
||||||
|
if xml_has(p, "value") == 0 { v = xml_text(p) }
|
||||||
|
value_put(o, "value", value_str(v))
|
||||||
|
push(out.kids, o)
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- objects (shapes) ------------------------------------------------------
|
||||||
|
# one `<object>` -> a Value object mirroring Tiled's JSON object shape.
|
||||||
|
function tmx_object_to_value(ob: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "id", value_int(xml_attr_int(ob, "id", 0)))
|
||||||
|
if xml_has(ob, "name") == 1 { value_put(o, "name", value_str(xml_attr(ob, "name"))) }
|
||||||
|
if xml_has(ob, "type") == 1 { value_put(o, "type", value_str(xml_attr(ob, "type"))) }
|
||||||
|
if xml_has(ob, "class") == 1 { value_put(o, "type", value_str(xml_attr(ob, "class"))) }
|
||||||
|
value_put(o, "x", value_int(xml_attr_int(ob, "x", 0)))
|
||||||
|
value_put(o, "y", value_int(xml_attr_int(ob, "y", 0)))
|
||||||
|
value_put(o, "width", value_int(xml_attr_int(ob, "width", 0)))
|
||||||
|
value_put(o, "height", value_int(xml_attr_int(ob, "height", 0)))
|
||||||
|
if xml_has(ob, "gid") == 1 { value_put(o, "gid", value_int(xml_attr_int(ob, "gid", 0))) }
|
||||||
|
if xml_has(ob, "rotation") == 1 { value_put(o, "rotation", value_int(xml_attr_int(ob, "rotation", 0))) }
|
||||||
|
# shape markers: ellipse / point / polygon / polyline / text
|
||||||
|
let el = xml_find(ob, "ellipse"); if xml_tag(el) == "ellipse" { value_put(o, "ellipse", value_bool(1)) }
|
||||||
|
let pt = xml_find(ob, "point"); if xml_tag(pt) == "point" { value_put(o, "point", value_bool(1)) }
|
||||||
|
let pg = xml_find(ob, "polygon")
|
||||||
|
if xml_tag(pg) == "polygon" { value_put(o, "polygon", tmx_points_list(xml_attr(pg, "points"))) }
|
||||||
|
let pl = xml_find(ob, "polyline")
|
||||||
|
if xml_tag(pl) == "polyline" { value_put(o, "polyline", tmx_points_list(xml_attr(pl, "points"))) }
|
||||||
|
let tx = xml_find(ob, "text")
|
||||||
|
if xml_tag(tx) == "text" {
|
||||||
|
let t = value_object()
|
||||||
|
value_put(t, "text", value_str(xml_text(tx)))
|
||||||
|
if xml_has(tx, "pixelsize") == 1 { value_put(t, "pixelsize", value_int(xml_attr_int(tx, "pixelsize", 16))) }
|
||||||
|
if xml_has(tx, "bold") == 1 { value_put(t, "bold", value_bool(1)) }
|
||||||
|
if xml_has(tx, "italic") == 1 { value_put(t, "italic", value_bool(1)) }
|
||||||
|
if xml_has(tx, "halign") == 1 { value_put(t, "halign", value_str(xml_attr(tx, "halign"))) }
|
||||||
|
if xml_has(tx, "valign") == 1 { value_put(t, "valign", value_str(xml_attr(tx, "valign"))) }
|
||||||
|
value_put(o, "text", t)
|
||||||
|
}
|
||||||
|
let props = tmx_props_list(ob)
|
||||||
|
if len(props.kids) > 0 { value_put(o, "properties", props) }
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# "x,y x,y ..." -> a Value list of {x,y} objects.
|
||||||
|
function tmx_points_list(s: pointer) -> Val {
|
||||||
|
let out = value_list()
|
||||||
|
let n = len(s)
|
||||||
|
var i = 0
|
||||||
|
while i < n {
|
||||||
|
while i < n and (s[i] == 32 or s[i] == 9) { i = i + 1 }
|
||||||
|
if i >= n { break }
|
||||||
|
# read "x,y"
|
||||||
|
var xv = 0; var xn = 0; var xs = 0
|
||||||
|
if s[i] == 45 { xs = 1; i = i + 1 }
|
||||||
|
while i < n and s[i] >= 48 and s[i] <= 57 { xv = xv * 10 + (s[i] - 48); xn = 1; i = i + 1 }
|
||||||
|
if xs == 1 { xv = 0 - xv }
|
||||||
|
if i < n and s[i] == 44 { i = i + 1 }
|
||||||
|
var yv = 0; var ys = 0
|
||||||
|
if i < n and s[i] == 45 { ys = 1; i = i + 1 }
|
||||||
|
while i < n and s[i] >= 48 and s[i] <= 57 { yv = yv * 10 + (s[i] - 48); i = i + 1 }
|
||||||
|
if ys == 1 { yv = 0 - yv }
|
||||||
|
if xn == 1 {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "x", value_int(xv))
|
||||||
|
value_put(o, "y", value_int(yv))
|
||||||
|
push(out.kids, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- tilesets --------------------------------------------------------------
|
||||||
|
# one `<tile id=..>` inside a tileset -> a Value object with its metadata:
|
||||||
|
# animation frames, per-tile collision objectgroup, class/type, properties.
|
||||||
|
function tmx_tile_to_value(t: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "id", value_int(xml_attr_int(t, "id", 0)))
|
||||||
|
if xml_has(t, "type") == 1 { value_put(o, "type", value_str(xml_attr(t, "type"))) }
|
||||||
|
if xml_has(t, "class") == 1 { value_put(o, "type", value_str(xml_attr(t, "class"))) }
|
||||||
|
if xml_has(t, "probability") == 1 { value_put(o, "probability", value_str(xml_attr(t, "probability"))) }
|
||||||
|
# per-tile image (image-collection tilesets)
|
||||||
|
let img = xml_find(t, "image")
|
||||||
|
if xml_tag(img) == "image" {
|
||||||
|
value_put(o, "image", value_str(xml_attr(img, "source")))
|
||||||
|
value_put(o, "imagewidth", value_int(xml_attr_int(img, "width", 0)))
|
||||||
|
value_put(o, "imageheight", value_int(xml_attr_int(img, "height", 0)))
|
||||||
|
}
|
||||||
|
# animation frames
|
||||||
|
let anim = xml_find(t, "animation")
|
||||||
|
if xml_tag(anim) == "animation" {
|
||||||
|
let frames = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(anim) {
|
||||||
|
let fr = xml_child(anim, i)
|
||||||
|
if xml_tag(fr) == "frame" {
|
||||||
|
let f = value_object()
|
||||||
|
value_put(f, "tileid", value_int(xml_attr_int(fr, "tileid", 0)))
|
||||||
|
value_put(f, "duration", value_int(xml_attr_int(fr, "duration", 0)))
|
||||||
|
push(frames.kids, f)
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
value_put(o, "animation", frames)
|
||||||
|
}
|
||||||
|
# per-tile collision shapes
|
||||||
|
let og = xml_find(t, "objectgroup")
|
||||||
|
if xml_tag(og) == "objectgroup" {
|
||||||
|
let objs = value_list()
|
||||||
|
var j = 0
|
||||||
|
while j < xml_child_count(og) {
|
||||||
|
let ch = xml_child(og, j)
|
||||||
|
if xml_tag(ch) == "object" { push(objs.kids, tmx_object_to_value(ch)) }
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
let ogo = value_object()
|
||||||
|
value_put(ogo, "objects", objs)
|
||||||
|
value_put(o, "objectgroup", ogo)
|
||||||
|
}
|
||||||
|
let props = tmx_props_list(t)
|
||||||
|
if len(props.kids) > 0 { value_put(o, "properties", props) }
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# fill the geometry + tiles of a `<tileset>` element into `o` (shared by an
|
||||||
|
# embedded tileset and a standalone `.tsx` root).
|
||||||
|
function tmx_fill_tileset(o: Val, ts: Xml) -> void {
|
||||||
|
if xml_has(ts, "name") == 1 { value_put(o, "name", value_str(xml_attr(ts, "name"))) }
|
||||||
|
value_put(o, "tilewidth", value_int(xml_attr_int(ts, "tilewidth", 0)))
|
||||||
|
value_put(o, "tileheight", value_int(xml_attr_int(ts, "tileheight", 0)))
|
||||||
|
value_put(o, "spacing", value_int(xml_attr_int(ts, "spacing", 0)))
|
||||||
|
value_put(o, "margin", value_int(xml_attr_int(ts, "margin", 0)))
|
||||||
|
value_put(o, "columns", value_int(xml_attr_int(ts, "columns", 0)))
|
||||||
|
value_put(o, "tilecount", value_int(xml_attr_int(ts, "tilecount", 0)))
|
||||||
|
let img = xml_find(ts, "image")
|
||||||
|
if xml_tag(img) == "image" {
|
||||||
|
value_put(o, "image", value_str(xml_attr(img, "source")))
|
||||||
|
value_put(o, "imagewidth", value_int(xml_attr_int(img, "width", 0)))
|
||||||
|
value_put(o, "imageheight", value_int(xml_attr_int(img, "height", 0)))
|
||||||
|
}
|
||||||
|
let tiles = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(ts) {
|
||||||
|
let ch = xml_child(ts, i)
|
||||||
|
if xml_tag(ch) == "tile" { push(tiles.kids, tmx_tile_to_value(ch)) }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
if len(tiles.kids) > 0 { value_put(o, "tiles", tiles) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# a `<tileset>` child of a `<map>` -> a Value object. External (`source=`) keeps
|
||||||
|
# the {firstgid, source} reference (as TMJ does); embedded is inlined in full.
|
||||||
|
function tmx_tileset_to_value(ts: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "firstgid", value_int(xml_attr_int(ts, "firstgid", 1)))
|
||||||
|
if xml_has(ts, "source") == 1 {
|
||||||
|
value_put(o, "source", value_str(xml_attr(ts, "source")))
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
tmx_fill_tileset(o, ts)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# a standalone `.tsx` root -> a tileset Value object (no firstgid — that comes
|
||||||
|
# from the map's reference).
|
||||||
|
function tsx_to_value(root: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
tmx_fill_tileset(o, root)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- layers ----------------------------------------------------------------
|
||||||
|
function tmx_layer_common(o: Val, el: Xml) -> void {
|
||||||
|
value_put(o, "id", value_int(xml_attr_int(el, "id", 0)))
|
||||||
|
value_put(o, "name", value_str(xml_attr(el, "name")))
|
||||||
|
if xml_has(el, "class") == 1 { value_put(o, "class", value_str(xml_attr(el, "class"))) }
|
||||||
|
var vis = 1
|
||||||
|
if xml_has(el, "visible") == 1 { vis = xml_attr_int(el, "visible", 1) }
|
||||||
|
value_put(o, "visible", value_bool(vis))
|
||||||
|
if xml_has(el, "opacity") == 1 { value_put(o, "opacity", value_str(xml_attr(el, "opacity"))) }
|
||||||
|
if xml_has(el, "offsetx") == 1 { value_put(o, "offsetx", value_int(xml_attr_int(el, "offsetx", 0))) }
|
||||||
|
if xml_has(el, "offsety") == 1 { value_put(o, "offsety", value_int(xml_attr_int(el, "offsety", 0))) }
|
||||||
|
if xml_has(el, "parallaxx") == 1 { value_put(o, "parallaxx", value_str(xml_attr(el, "parallaxx"))) }
|
||||||
|
if xml_has(el, "parallaxy") == 1 { value_put(o, "parallaxy", value_str(xml_attr(el, "parallaxy"))) }
|
||||||
|
if xml_has(el, "tintcolor") == 1 { value_put(o, "tintcolor", value_str(xml_attr(el, "tintcolor"))) }
|
||||||
|
let props = tmx_props_list(el)
|
||||||
|
if len(props.kids) > 0 { value_put(o, "properties", props) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function tmx_tilelayer_to_value(el: Xml, mapw: int, maph: int) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "type", value_str("tilelayer"))
|
||||||
|
tmx_layer_common(o, el)
|
||||||
|
let w = xml_attr_int(el, "width", mapw)
|
||||||
|
let h = xml_attr_int(el, "height", maph)
|
||||||
|
value_put(o, "width", value_int(w))
|
||||||
|
value_put(o, "height", value_int(h))
|
||||||
|
let data = xml_find(el, "data")
|
||||||
|
value_put(o, "data", tiled_data_list(data, w * h))
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
function tmx_objectlayer_to_value(el: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "type", value_str("objectgroup"))
|
||||||
|
tmx_layer_common(o, el)
|
||||||
|
let objs = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(el) {
|
||||||
|
let ch = xml_child(el, i)
|
||||||
|
if xml_tag(ch) == "object" { push(objs.kids, tmx_object_to_value(ch)) }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
value_put(o, "objects", objs)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- map -------------------------------------------------------------------
|
||||||
|
# a `<map>` root -> the intermediate map Value tree (Tiled JSON schema).
|
||||||
|
function tmx_to_value(root: Xml) -> Val {
|
||||||
|
let m = value_object()
|
||||||
|
value_put(m, "type", value_str("map"))
|
||||||
|
value_put(m, "version", value_str(xml_attr(root, "version")))
|
||||||
|
value_put(m, "orientation", value_str(xml_attr(root, "orientation")))
|
||||||
|
value_put(m, "renderorder", value_str(xml_attr(root, "renderorder")))
|
||||||
|
value_put(m, "width", value_int(xml_attr_int(root, "width", 0)))
|
||||||
|
value_put(m, "height", value_int(xml_attr_int(root, "height", 0)))
|
||||||
|
value_put(m, "tilewidth", value_int(xml_attr_int(root, "tilewidth", 0)))
|
||||||
|
value_put(m, "tileheight", value_int(xml_attr_int(root, "tileheight", 0)))
|
||||||
|
value_put(m, "infinite", value_bool(xml_attr_int(root, "infinite", 0)))
|
||||||
|
if xml_has(root, "backgroundcolor") == 1 { value_put(m, "backgroundcolor", value_str(xml_attr(root, "backgroundcolor"))) }
|
||||||
|
if xml_has(root, "hexsidelength") == 1 { value_put(m, "hexsidelength", value_int(xml_attr_int(root, "hexsidelength", 0))) }
|
||||||
|
if xml_has(root, "staggeraxis") == 1 { value_put(m, "staggeraxis", value_str(xml_attr(root, "staggeraxis"))) }
|
||||||
|
if xml_has(root, "staggerindex") == 1 { value_put(m, "staggerindex", value_str(xml_attr(root, "staggerindex"))) }
|
||||||
|
let mapw = xml_attr_int(root, "width", 0)
|
||||||
|
let maph = xml_attr_int(root, "height", 0)
|
||||||
|
let tilesets = value_list()
|
||||||
|
let layers = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(root) {
|
||||||
|
let ch = xml_child(root, i)
|
||||||
|
let tag = xml_tag(ch)
|
||||||
|
if tag == "tileset" { push(tilesets.kids, tmx_tileset_to_value(ch)) }
|
||||||
|
else { if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(ch, mapw, maph)) }
|
||||||
|
else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) }
|
||||||
|
else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) }
|
||||||
|
else { if tag == "group" { push(layers.kids, tmx_group_to_value(ch, mapw, maph)) } } } } }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
let props = tmx_props_list(root)
|
||||||
|
if len(props.kids) > 0 { value_put(m, "properties", props) }
|
||||||
|
value_put(m, "tilesets", tilesets)
|
||||||
|
value_put(m, "layers", layers)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
# `<imagelayer>` -> a Value object (P5 renders these).
|
||||||
|
function tmx_imagelayer_to_value(el: Xml) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "type", value_str("imagelayer"))
|
||||||
|
tmx_layer_common(o, el)
|
||||||
|
let img = xml_find(el, "image")
|
||||||
|
if xml_tag(img) == "image" { value_put(o, "image", value_str(xml_attr(img, "source"))) }
|
||||||
|
if xml_has(el, "repeatx") == 1 { value_put(o, "repeatx", value_bool(xml_attr_int(el, "repeatx", 0))) }
|
||||||
|
if xml_has(el, "repeaty") == 1 { value_put(o, "repeaty", value_bool(xml_attr_int(el, "repeaty", 0))) }
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# `<group>` -> a Value object carrying its nested layers (P5 renders recursively).
|
||||||
|
function tmx_group_to_value(el: Xml, mapw: int, maph: int) -> Val {
|
||||||
|
let o = value_object()
|
||||||
|
value_put(o, "type", value_str("group"))
|
||||||
|
tmx_layer_common(o, el)
|
||||||
|
let layers = value_list()
|
||||||
|
var i = 0
|
||||||
|
while i < xml_child_count(el) {
|
||||||
|
let ch = xml_child(el, i)
|
||||||
|
let tag = xml_tag(ch)
|
||||||
|
if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(ch, mapw, maph)) }
|
||||||
|
else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) }
|
||||||
|
else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) }
|
||||||
|
else { if tag == "group" { push(layers.kids, tmx_group_to_value(ch, mapw, maph)) } } } }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
value_put(o, "layers", layers)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- JSON (TMJ) normalisation ----------------------------------------------
|
||||||
|
# The JSON reader already yields a Value tree; normalise it so it matches the XML
|
||||||
|
# path: decode any base64 `data` string into a dense GID int list, in place, for
|
||||||
|
# every tile layer (recursing into groups).
|
||||||
|
function tmj_normalize_layer(layer: Val) -> void {
|
||||||
|
if value_kind(layer) != 6 { return }
|
||||||
|
let ty = value_as_str(value_get(layer, "type"))
|
||||||
|
if ty == "group" {
|
||||||
|
let ls = value_get(layer, "layers")
|
||||||
|
var i = 0
|
||||||
|
while i < value_count(ls) { tmj_normalize_layer(value_at(ls, i)); i = i + 1 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ty != "tilelayer" { return }
|
||||||
|
let data = value_get(layer, "data")
|
||||||
|
if value_kind(data) == 4 { # a base64 string
|
||||||
|
let enc = value_as_str(value_get(layer, "encoding"))
|
||||||
|
let comp = value_as_str(value_get(layer, "compression"))
|
||||||
|
let w = value_as_int(value_get(layer, "width"))
|
||||||
|
let h = value_as_int(value_get(layer, "height"))
|
||||||
|
if enc == "base64" { value_put(layer, "data", tiled_b64_list(value_as_str(data), comp, w * h)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tmj_normalize(m: Val) -> Val {
|
||||||
|
let ls = value_get(m, "layers")
|
||||||
|
var i = 0
|
||||||
|
while i < value_count(ls) { tmj_normalize_layer(value_at(ls, i)); i = i + 1 }
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- top-level readers -----------------------------------------------------
|
||||||
|
# skip leading whitespace and return the first non-space byte (0 if none).
|
||||||
|
function tiled_first_byte(s: pointer) -> int {
|
||||||
|
var i = 0
|
||||||
|
let n = len(s)
|
||||||
|
while i < n {
|
||||||
|
let c = s[i]
|
||||||
|
if c == 32 or c == 9 or c == 10 or c == 13 { i = i + 1 } else { return c }
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# read a map file (TMX or TMJ, auto-detected by first byte) -> the normalised
|
||||||
|
# intermediate map Value tree.
|
||||||
|
function tiled_read(path: pointer) -> Val {
|
||||||
|
let text = Fs.read_text(path)
|
||||||
|
if text == null { return value_null() }
|
||||||
|
if text == "" { return value_null() }
|
||||||
|
if tiled_first_byte(text) == 60 { # '<' -> XML
|
||||||
|
return tmx_to_value(xml_parse(text))
|
||||||
|
}
|
||||||
|
return tmj_normalize(json_parse(text)) # '{' -> JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
# read a tileset file (TSX or TSJ) -> a tileset Value object.
|
||||||
|
function tiled_read_tsx(path: pointer) -> Val {
|
||||||
|
let text = Fs.read_text(path)
|
||||||
|
if text == null { return value_null() }
|
||||||
|
if text == "" { return value_null() }
|
||||||
|
if tiled_first_byte(text) == 60 { return tsx_to_value(xml_parse(text)) }
|
||||||
|
return json_parse(text)
|
||||||
|
}
|
||||||
|
|
@ -607,6 +607,13 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "decode") { bare = "base64_decode"; push(labels, "src") }
|
if (meth == "decode") { bare = "base64_decode"; push(labels, "src") }
|
||||||
if (meth == "encode") { bare = "base64_encode"; push(labels, "src") }
|
if (meth == "encode") { bare = "base64_encode"; push(labels, "src") }
|
||||||
}
|
}
|
||||||
|
# Tiled.* — Tiled map support (runtime/native/tiled.ludic, spliced on demand).
|
||||||
|
# read/read_tsx produce the intermediate Value tree (#68); load/gid/resolve/
|
||||||
|
# draw/prop operate on the loaded map model (#69+).
|
||||||
|
if (ns == "Tiled") {
|
||||||
|
if (meth == "read") { bare = "tiled_read"; push(labels, "path") }
|
||||||
|
if (meth == "read_tsx") { bare = "tiled_read_tsx"; push(labels, "path") }
|
||||||
|
}
|
||||||
# #62: a package-provided namespace (declared with @Namespace(Foo)) that none
|
# #62: a package-provided namespace (declared with @Namespace(Foo)) that none
|
||||||
# of the hardcoded core blocks matched — alias Foo.method to the bare function
|
# of the hardcoded core blocks matched — alias Foo.method to the bare function
|
||||||
# foo_method (positional args), the same generic path the core aliases use.
|
# foo_method (positional args), the same generic path the core aliases use.
|
||||||
|
|
|
||||||
10056
selfhost/ludicc.seed.ll
10056
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -681,5 +681,9 @@
|
||||||
"base64": [
|
"base64": [
|
||||||
"base64-decode",
|
"base64-decode",
|
||||||
"base64-encode"
|
"base64-encode"
|
||||||
|
],
|
||||||
|
"tiled": [
|
||||||
|
"tiled-read",
|
||||||
|
"tiled-read_tsx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,7 @@ function cmd_test() -> int {
|
||||||
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
||||||
feat_case("library/serialize", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "serialize.ludic (Value tree + Json encode/parse + Reflect.serialize/apply — bit-exact save/load; issue #44)")
|
feat_case("library/serialize", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "serialize.ludic (Value tree + Json encode/parse + Reflect.serialize/apply — bit-exact save/load; issue #44)")
|
||||||
feat_case("library/tiled_p0", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", "tiled_p0.ludic (Tiled P0: Xml reader + Base64 decode/encode + gzip framing over inflate; issue #67)")
|
feat_case("library/tiled_p0", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", "tiled_p0.ludic (Tiled P0: Xml reader + Base64 decode/encode + gzip framing over inflate; issue #67)")
|
||||||
|
feat_case("library/tiled_p05", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30", "tiled_p05.ludic (Tiled P0.5: TMX/TSX reader -> same Value-tree intermediate as TMJ; Kenney sampleMap.tmx; issue #68)")
|
||||||
feat_case("library/render", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "render.ludic (Screen pixel/oval/camera/clip/blend_mode/measure_text + Camera set/follow/shake, verified by pixel readback)")
|
feat_case("library/render", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "render.ludic (Screen pixel/oval/camera/clip/blend_mode/measure_text + Camera set/follow/shake, verified by pixel readback)")
|
||||||
feat_case("library/lighting", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "lighting.ludic (Light ambient/point radial falloff + occluder hard shadows — 2D light accumulation, verified by pixel readback)")
|
feat_case("library/lighting", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "lighting.ludic (Light ambient/point radial falloff + occluder hard shadows — 2D light accumulation, verified by pixel readback)")
|
||||||
feat_case("library/light_tiers", "", "1 1 1 1 1 1 1 1 1", "light_tiers.ludic (Light spot/falloff/soft/gel/normal/time_of_day — render-quality tiers 3-4; issue #49)")
|
feat_case("library/light_tiers", "", "1 1 1 1 1 1 1 1 1", "light_tiers.ludic (Light spot/falloff/soft/gel/normal/time_of_day — render-quality tiers 3-4; issue #49)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue