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) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-05-21 15:37:41 +02:00
parent 1562ee0068
commit 6af36e9c6e

View File

@@ -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 "<unnamed>"))
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.