From 6af36e9c6e5b666ce92ee30ec52fd49a6336d7fb Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Thu, 21 May 2026 15:37:41 +0200 Subject: [PATCH] Load schema-v2 maps with multi-layer storage and roof field Replace the v1-only M.load path with a unified v2 load path that auto-upgrades v1 JSON on read. Add build_map_v2 to produce map records with atlases[], layers{}, and roof arrays. Add atlas_count, atlas_id_at, has_layer, and is_indoor to the public API. Update tile_at to decode GIDs from the surface layer for v2 maps, keeping M.create/build_map for programmatic callers that supply a v1-style tilemap_table directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- init.lua | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/init.lua b/init.lua index 2096b89..c6a31ea 100644 --- a/init.lua +++ b/init.lua @@ -269,6 +269,26 @@ local function build_map(t_map, tilemap) } end +local function build_map_v2(t_map, resolved_atlases) + local size = t_map.size + return { + id = t_map.id, + schema_version = 2, + size = size, + tile_size = resolved_atlases[1].tile_size, -- assume uniform; first atlas wins + atlases = resolved_atlases, + atlas_aliases = t_map.atlases, -- alias strings, parallel to atlases + layers = t_map.layers or {}, + roof = t_map.roof, + -- DEPRECATED-MVP stubs + walls = {}, + regions = {}, + edges = {}, + state = "Active", + pinned = false, + } +end + -- ===================================================================== -- Public API -- ===================================================================== @@ -277,10 +297,20 @@ local M = {} function M.load(path) local raw = engine.asset.load_json(path) - validate_map_table(raw, path) - local full_id = resolve_tilemap_id(raw.tilemap) - local tilemap = load_tilemap(full_id) - local map = build_map(raw, tilemap) + if raw.schema_version == nil or raw.schema_version == 1 then + raw = upgrade_v1_to_v2(raw) + engine.print(string.format("maps.load: auto-upgraded v1 map '%s' to v2", raw.id or "")) + end + validate_map_table_v2(raw, path) + + -- Resolve atlas-aliases (each entry is a tilemap-id, possibly local or fully-qualified) + local resolved_atlases = {} + for i, alias in ipairs(raw.atlases) do + local full_id = resolve_tilemap_id(alias) + resolved_atlases[i] = load_tilemap(full_id) + end + + local map = build_map_v2(raw, resolved_atlases) if map_registry[map.id] then error(string.format("maps.load: map-id '%s' already registered", map.id)) end @@ -334,6 +364,18 @@ function M.tile_at(a, b, c) if tx < 0 or tx >= m.size.w or ty < 0 or ty >= m.size.h then return nil end + -- v2 path: read from surface layer using GID encoding + if m.schema_version == 2 then + local surface = m.layers and m.layers.surface + if not surface then return nil end + local gid = surface.tiles[ty * m.size.w + tx + 1] + if not gid or gid == 0 then return nil end + local atlas_index, tile_id, _ = decode_gid(gid) + local atlas = m.atlases[atlas_index + 1] + if not atlas then return nil end + return atlas.tiles[tile_id] + end + -- v1 legacy path (should not be reached after M.load auto-upgrades, kept for M.create) local palette_id = m.tiles[ty * m.size.w + tx + 1] return m.tilemap.tiles[palette_id] end @@ -346,7 +388,12 @@ end function M.tilemap_id(map_id) local id = map_id or current_map_id - return map_registry[id].tilemap.id + local m = map_registry[id] + -- v2: return first atlas alias (backwards-compat for single-atlas maps) + if m.schema_version == 2 then + return m.atlas_aliases and m.atlas_aliases[1] or (m.atlases[1] and m.atlases[1].id) + end + return m.tilemap.id end function M.current() @@ -368,6 +415,38 @@ function M.list() return out end +function M.atlas_count(map_id) + local id = map_id or current_map_id + if not id then error("maps.atlas_count: no current map") end + local m = map_registry[id] + if m.schema_version ~= 2 then return 1 end -- v1 (pre-upgrade) always 1 + return #m.atlases +end + +function M.atlas_id_at(idx, map_id) + local id = map_id or current_map_id + if not id then error("maps.atlas_id_at: no current map") end + local m = map_registry[id] + return m.atlas_aliases and m.atlas_aliases[idx + 1] + or m.atlases[idx + 1] and m.atlases[idx + 1].id +end + +function M.has_layer(layer_name, map_id) + local id = map_id or current_map_id + if not id then error("maps.has_layer: no current map") end + local m = map_registry[id] + return m.layers and m.layers[layer_name] ~= nil +end + +function M.is_indoor(x, y, map_id) + local id = map_id or current_map_id + if not id then error("maps.is_indoor: no current map") end + local m = map_registry[id] + if not m.roof then return false end + if x < 0 or x >= m.size.w or y < 0 or y >= m.size.h then return false end + return m.roof[y * m.size.w + x + 1] == 1 +end + -- ==================================================================== -- Resolve tilemap-tile atlas-ids to texture-handles via asset-lib. -- Operates on the current map's tilemap; call after maps.set_current.