diff --git a/init.lua b/init.lua index f354013..2096b89 100644 --- a/init.lua +++ b/init.lua @@ -77,6 +77,83 @@ local function upgrade_v1_to_v2(v1) } end +-- ===================================================================== +-- Schema-validation helper (used by both v1 and v2 validators) +-- ===================================================================== + +-- Schema-validation helper: reads required field with type-check. +local function require_field(t, key, expected_type, source) + local v = t[key] + if v == nil then + error(string.format("maps.load: schema violation in %s: missing required field '%s'", + source, key)) + end + if type(v) ~= expected_type then + error(string.format("maps.load: schema violation in %s: field '%s' must be %s, got %s", + source, key, expected_type, type(v))) + end + return v +end + +-- ===================================================================== +-- Schema-v2 Layer-Whitelist + Validation +-- ===================================================================== + +local VALID_LAYER_NAMES = { + foundation = true, + subsurface = true, + surface = true, + topsurface = true, + lower_wall = true, + wall = true, + upper_wall = true, + canopy = true, +} + +local function validate_map_table_v2(t, source) + require_field(t, "schema_version", "number", source) + if t.schema_version ~= 2 then + error(string.format("maps.load: schema_version %d not supported (expected 2)", + t.schema_version)) + end + require_field(t, "id", "string", source) + local size = require_field(t, "size", "table", source) + require_field(size, "w", "number", source .. ".size") + require_field(size, "h", "number", source .. ".size") + local expected = size.w * size.h + + local atlases = require_field(t, "atlases", "table", source) + if #atlases == 0 then + error(string.format("maps.load: schema violation in %s: atlases[] must be non-empty", source)) + end + if #atlases > 256 then + error(string.format("maps.load: schema violation in %s: atlases[] has %d entries, max 256", source, #atlases)) + end + + local layers = t.layers or {} + for layer_name, layer_data in pairs(layers) do + if not VALID_LAYER_NAMES[layer_name] then + engine.print(string.format("maps.load: %s: ignoring unknown layer '%s'", source, layer_name)) + else + local tiles = require_field(layer_data, "tiles", "table", + source .. ".layers." .. layer_name) + if #tiles ~= expected then + error(string.format("maps.load: schema violation in %s.layers.%s: tiles length %d != %d", + source, layer_name, #tiles, expected)) + end + end + end + + if t.roof ~= nil then + if type(t.roof) ~= "table" then + error(string.format("maps.load: %s: roof must be array", source)) + end + if #t.roof ~= expected then + error(string.format("maps.load: %s: roof length %d != %d", source, #t.roof, expected)) + end + end +end + -- ===================================================================== -- Internal helpers -- ===================================================================== @@ -103,20 +180,6 @@ local function resolve_tilemap_id(ref) return engine.module.id() .. "." .. ref end --- Schema-validation helper: reads required field with type-check. -local function require_field(t, key, expected_type, source) - local v = t[key] - if v == nil then - error(string.format("maps.load: schema violation in %s: missing required field '%s'", - source, key)) - end - if type(v) ~= expected_type then - error(string.format("maps.load: schema violation in %s: field '%s' must be %s, got %s", - source, key, expected_type, type(v))) - end - return v -end - local function validate_map_table(t, source) require_field(t, "id", "string", source) require_field(t, "tilemap", "string", source) @@ -389,5 +452,6 @@ end M.encode_gid = encode_gid M.decode_gid = decode_gid M.upgrade_v1_to_v2 = upgrade_v1_to_v2 +M.validate_map_table_v2 = validate_map_table_v2 return M