feat: validate schema-v2 map tables with layer-name whitelist
Adds validate_map_table_v2 — enforces schema_version=2, required fields (id, size, atlases), non-empty atlases[], per-layer tile-count check against w*h, and a VALID_LAYER_NAMES whitelist that logs a warning for unknown layers instead of rejecting them. Moves require_field before the v2 validator so it is in scope at call time. engine.warn replaced with engine.print (no warn binding). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
92
init.lua
92
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
|
||||
|
||||
Reference in New Issue
Block a user