Tilemaps: compact cell storage in the project file, check total 1017 (#134)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
adminandClaude Opus 5 committed 2026-09-18 09:17:49 +02:00
1 parent 60be8d9aff
commit a0f279a031
3 files changed
+21 -5

No files matched your search

+1 -1
@@ -71,7 +71,7 @@ src/core/graph/ graph model, NodeRegistry/NodeContext/values, interpreter
lookup (what a node may look up: behaviors, state machines), keyRepeat; nodes/{events,flow,data,action,variable}/<Name>.ts lookup (what a node may look up: behaviors, state machines), keyRepeat; nodes/{events,flow,data,action,variable}/<Name>.ts
src/core/particles/ nodes (the catalogue), compile (a graph → one function per output), src/core/particles/ nodes (the catalogue), compile (a graph → one function per output),
emission (which particles are alive at a second — a pure rule, no state) emission (which particles are alive at a second — a pure rule, no state)
src/core/store/ serialize (untrusted JSON → Project), http (one API call), ProjectStore/HttpProjectStore, src/core/store/ serialize (untrusted JSON → Project), tilemapCells (the compact form of a layer's cells in the file), http (one API call), ProjectStore/HttpProjectStore,
FileStore (what the server holds of one kind) + HttpFileStore (one generic client over the three FileStore (what the server holds of one kind) + HttpFileStore (one generic client over the three
file collections) — TextureStore, FontStore and SoundStore are its type parameters, nothing else file collections) — TextureStore, FontStore and SoundStore are its type parameters, nothing else
src/core/time.ts the global clock: one scaled step a frame, shared by the world, the particles, the shaders and the animation preview src/core/time.ts the global clock: one scaled step a frame, shared by the world, the particles, the shaders and the animation preview
+2 -2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green # code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output ./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1013 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1017 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result
@@ -232,4 +232,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`, WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 1013 checks as here. WebGL 2.0, and the same 1017 checks as here.
+18 -2
@@ -67,10 +67,26 @@ tile is on the same step, so animated water does not run out of phase between tw
Drawing is done with **one mesh per tile and layer** rather than one sprite per cell, so a map of a Drawing is done with **one mesh per tile and layer** rather than one sprite per cell, so a map of a
thousand cells costs as many draw calls as it has tiles on that layer. thousand cells costs as many draw calls as it has tiles on that layer.
## In the project file
While you edit, a layer holds its cells as a map from `"x,y"` to a tile. The project file stores
them more compactly (#134): per layer the tiles it uses **once**, the top-left painted cell, and
then **one line per row** as runs of `index*count`. `index` counts from 1 into the tile list, `0`
is an empty cell, and a single cell drops the count:
```json
"tiles": ["grass", "path"],
"origin": { "x": 0, "y": 0 },
"rows": ["1*40 0*3 2", "1*44"]
```
Cells outside the grid are stored as well, so a map that was made smaller gets them back when it
grows again. Measured: a full map of 100 × 100 cells on two layers went from 736 KB to 11 KB.
**Older projects** still load — their cells are read as they are and written in the new form on
the next save.
## What it does not do yet ## What it does not do yet
- **No collision.** A tilemap is a picture; figures walk through it. Whether it should contribute - **No collision.** A tilemap is a picture; figures walk through it. Whether it should contribute
colliders is a question of its own and is not decided. colliders is a question of its own and is not decided.
- Nothing fills an area by flooding it, and there is no picking up a tile off the map. - Nothing fills an area by flooding it, and there is no picking up a tile off the map.
- The cells are stored one entry per painted cell (`"x,y"` → tile), uncompressed. Big maps
therefore make big project files; the format leaves room to pack them later.