Compare commits
22 Commits
6b2dc22227
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e42a034f0 | ||
|
|
6e0d39e1a7 | ||
|
|
5351923dd6 | ||
|
|
c5039e5bf2 | ||
|
|
89d68d7668 | ||
|
|
9a5ef5e832 | ||
|
|
b5621c5d08 | ||
|
|
e3b6e38c5a | ||
|
|
b4c0e0f4b5 | ||
|
|
be6d419061 | ||
|
|
9bd4e5a415 | ||
|
|
714ec57010 | ||
|
|
d0f4d17b65 | ||
|
|
f8994c7ffa | ||
|
|
5cf07c8549 | ||
|
|
d80c0ecb5f | ||
|
|
84d1980c18 | ||
|
|
45b1f93363 | ||
|
|
b8705c0ec7 | ||
|
|
e5a350fcb2 | ||
|
|
99d00bd115 | ||
|
|
8a75aee48a |
133
README.md
133
README.md
@@ -1,11 +1,75 @@
|
||||
# lib-core.maps
|
||||
|
||||
Single tile-grid map implementation. Loads JSON map + tilemap files, registers maps by id, and provides tile-fetch + walkability + size queries with current-map sugar.
|
||||
Multi-layer tile-grid map implementation with vertex-painted autotile
|
||||
(blob-14), sparse per-cell overrides, packed-u32 GID legacy path, atlas
|
||||
loading + UV resolution, walkability + sight-blocking queries, and a
|
||||
v3 multi-layer render pipeline with opaque-ceiling cache.
|
||||
|
||||
**Version:** 0.2.0
|
||||
**Lib-ID:** lib-core.maps
|
||||
**Requires:** (none)
|
||||
**Tags:** maps, tile-grid, walkability, tilemap
|
||||
**Version:** 0.5.7
|
||||
**Lib-ID:** lib-core.maps
|
||||
**Requires:** (none)
|
||||
**Tags:** maps, tile-grid, multi-layer, vertex-painting, autotile, blob-14, override, walkability, tilemap
|
||||
|
||||
## Schema versions
|
||||
|
||||
| Version | Highlights |
|
||||
|---|---|
|
||||
| v1 | Single-layer `tiles[]` + tilemap path. Loaded but auto-upgraded to v2 on `maps.load`. |
|
||||
| v2 | Multi-atlas + multi-layer with packed-u32 GIDs (atlas_idx + tile_id + rotation). `set_cell_gid`, `save_to_disk`. |
|
||||
| v3 | Per-layer `material` (atlas-alias), optional `vertices` ((W+1)·(H+1) grid), optional `overrides` ({"x:y": int OR {slot, rot, flip}}). Renderer derives slot/rot/flip from neighbour-bitmask blob-gating; overrides force-place specific orientations. |
|
||||
|
||||
## Painting model (v3 autotile, dual-grid)
|
||||
|
||||
Two grids offset by half a tile. The PAINT grid (called `vertices` in
|
||||
storage, "map-tiles" in design docs) is what users paint. The RENDER
|
||||
grid (called `cells`) is offset by (+0.5, +0.5) tile and is where
|
||||
sprites sit. Each render-cell spans 4 surrounding paint-tiles which
|
||||
act as its 4 corners (TL, TR, BL, BR).
|
||||
|
||||
- **Paint grid**: (W+1)·(H+1) cells. Any-corner rule: render-cell
|
||||
(x,y) is material iff any of its 4 corner paint-tiles is painted.
|
||||
- **Bitmask + SLOT_LOOKUP** (0.5.6 dual-grid native): each material
|
||||
render-cell's 8-bit neighbour-bitmask (clockwise from N) is derived
|
||||
from its own 4 corner paint-tiles — cardinal bit set iff ≥1 of the
|
||||
edge's 2 paint-tiles painted, diagonal bit set iff the corner
|
||||
paint-tile is painted. Blob-gating then zeroes diagonals whose 2
|
||||
adjacent cardinals are not both set. The gated bitmask maps to one
|
||||
of 14 canonical slots × {0,1,2,3} rotation × {0,1} flip via a
|
||||
D4-orbit table.
|
||||
- **Override sublayer**: sparse `{"x:y": slot}` or `{"x:y": {slot, rot,
|
||||
flip}}`. Takes precedence over the bitmask-derived slot AND forces
|
||||
material-presence on the cell.
|
||||
|
||||
**0.5.6 fix:** pre-0.5.6 derived the bitmask from the material status
|
||||
of the 8 neighbour render-cells, which violated dual-grid semantics —
|
||||
two cells whose shared edge had no painted paint-tiles still saw each
|
||||
other as material whenever any unrelated corner of either was painted,
|
||||
producing connected blobs across visually empty paint-tile gaps. See
|
||||
plan `2026-05-29-painting-model-rethink`.
|
||||
|
||||
### Cell-Tile material path (0.5.7)
|
||||
|
||||
A second, optional painting path: each layer can now also have a
|
||||
`cells_material` array (W·H bool, lazy-allocated). Cells where
|
||||
`cells_material[x, y]` is set use the classical 47-blob 8-neighbour
|
||||
bitmask rule on effective material (vertex-derived OR cell-derived),
|
||||
unlocking all 14 atlas slots. Cells whose material comes only from
|
||||
the vertex grid continue to use the FIX-A 4-own-corner rule (5
|
||||
reachable slots, dual-grid look).
|
||||
|
||||
Both paths coexist in the same layer; the bitmask rule is decided
|
||||
per-cell based on whether `cells_material[x, y]` is true. A
|
||||
vertex-only cell adjacent to a cell-tile cell remains structurally
|
||||
blind to its neighbour because its 4-corner rule only reads its own
|
||||
corner vertices — this asymmetric seam is documented behaviour. For
|
||||
clean visuals use a single painting path per layer.
|
||||
|
||||
Public API: `set_cell_material(layer, x, y, painted, map_id?)` and
|
||||
`get_cell_material(layer, x, y, map_id?) -> bool`.
|
||||
|
||||
The `cells_material` field is optional and absent on every existing
|
||||
v3 map; save output is byte-identical to 0.5.6 when no cells_material
|
||||
entries are non-zero.
|
||||
|
||||
## Topology
|
||||
|
||||
@@ -20,6 +84,51 @@ graph LR
|
||||
|
||||
## API
|
||||
|
||||
> **Note:** the API section below was authored against v0.2.0 and is
|
||||
> being progressively updated. Entries marked `[v0.x added]` are the
|
||||
> additions since 0.2.0. See `init.lua` source for the full surface.
|
||||
|
||||
### APIs added v0.3.0 — v0.5.4 (summary)
|
||||
|
||||
- **v0.3.0** — atlas-baker integration: `load_textures(asset_aliases)`
|
||||
rewrite for M.2 atlas format; height-field API; atlas-bootstrap
|
||||
stub when tilemap JSON missing.
|
||||
- **v0.4.0** — write-APIs for editors / procedural-gen:
|
||||
- `set_cell_gid(layer_name, x, y, gid, map_id?)`
|
||||
- `set_roof(x, y, value, map_id?)`
|
||||
- `save_to_disk(map_id, path)`
|
||||
- **v0.5.0a-e** — schema-v3 multi-layer terrain stack:
|
||||
- 8 layer slots (`foundation`, `subsurface`, `surface`, `topsurface`,
|
||||
`lower_wall`, `wall`, `upper_wall`, `canopy`)
|
||||
- `LAYER_Z` + `LAYER_ORDER_TOP_DOWN` constants
|
||||
- Vertex-painted autotile renderer (any-corner + blob-14 SLOT_LOOKUP)
|
||||
- Empty-layer + opaque-ceiling caches for render-opt
|
||||
- **v0.5.0d** — sparse override sublayer:
|
||||
- `set_override(layer_name, x, y, slot_or_entry, map_id?)`
|
||||
- `clear_override(layer_name, x, y, map_id?)`
|
||||
- `get_override(layer_name, x, y, map_id?)`
|
||||
- **v0.5.1** — override-entry format extension to `{slot, rot, flip}`
|
||||
(object form, backwards-compat with bare integer). Auto-compacts to
|
||||
bare int when canonical orientation (rot=0+flip=0). `tile.opaque`
|
||||
flag consumed from atlas-baker v0.2.0 alpha-analysis.
|
||||
- **v0.5.2** — public vertex-grid write APIs:
|
||||
- `set_vertex(layer_name, vx, vy, painted, map_id?)` — any-corner
|
||||
rule fills up to 4 cells; lazy-allocates grid on first paint
|
||||
- `get_vertex(layer_name, vx, vy, map_id?) -> bool`
|
||||
- **v0.5.3** — bugfix: `load_textures` now refreshes the
|
||||
`atlas_by_alias` dict after replacing `m.atlases[i]`, fixing the
|
||||
v3 vertex/material render path which was resolving through the
|
||||
pre-load stub (no texture handle → MISSING_ASSET_COLOR fallback).
|
||||
- **v0.5.4** — public atlas accessors for palette consumers:
|
||||
- `atlas_diffuse_handle(atlas_idx, map_id?)` — raylib texture handle
|
||||
- `atlas_tile_size_px(atlas_idx, map_id?)` — int, usually 64
|
||||
- `atlas_tile_uv(atlas_idx, slot, map_id?)` — `{x, y, w, h}` in
|
||||
atlas pixel coords, or `nil`. Resolves slot via the same
|
||||
`slot_NN_` name regex used internally by the renderer.
|
||||
|
||||
### Original v0.2.0 entries
|
||||
|
||||
|
||||
### `maps.load(path)`
|
||||
**Syntax:** `maps.load(path: string) -> string`
|
||||
|
||||
@@ -286,6 +395,20 @@ maps.draw_map_post_entities()
|
||||
|
||||
**Description:** Draws all post-entity layers of the current map (`lower_wall` through `canopy`). Must be called after entity rendering when using the split draw model. No-op if no current map.
|
||||
|
||||
### Write APIs (v0.4.0+)
|
||||
|
||||
#### `maps.set_cell_gid(layer_name, x, y, gid, map_id?)`
|
||||
|
||||
Writes a single cell into the named layer of the current (or named) map. The layer must be one of `VALID_LAYER_NAMES`; bounds are checked against `map.size`. If the layer does not yet exist on the map it is allocated and initialised to all-zero before the write. Sets an internal `_dirty` flag so callers (e.g. the map-editor) can track unsaved changes.
|
||||
|
||||
#### `maps.set_roof(x, y, value, map_id?)`
|
||||
|
||||
Writes a single roof flag (`0` or `1`) at the named cell. Allocates the roof array on demand if the map did not previously have one. Same bounds-check as `set_cell_gid`. Throws on values other than 0 or 1.
|
||||
|
||||
#### `maps.save_to_disk(map_id, path)`
|
||||
|
||||
Serialises the in-memory map to v2 JSON and writes it to `path`. Output is pretty-printed with 2-space indent and is byte-deterministic for the same map state (sorted object keys, fixed array order). Reverses the internal atlas-resolution back to atlas-ID strings on disk. Resets the `_dirty` flag on success.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Pixel-coords + tile-coords kept distinct: `size`/`tiles` index in tile-units; `tile_size` is the conversion to pixels.
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"id":"lib-core.maps","version":"0.2.0","api_min":"0.1"}
|
||||
{"id":"lib-core.maps","version":"0.5.7","api_min":"0.1"}
|
||||
Reference in New Issue
Block a user