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
|
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
|
-- Internal helpers
|
||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
@@ -103,20 +180,6 @@ local function resolve_tilemap_id(ref)
|
|||||||
return engine.module.id() .. "." .. ref
|
return engine.module.id() .. "." .. ref
|
||||||
end
|
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)
|
local function validate_map_table(t, source)
|
||||||
require_field(t, "id", "string", source)
|
require_field(t, "id", "string", source)
|
||||||
require_field(t, "tilemap", "string", source)
|
require_field(t, "tilemap", "string", source)
|
||||||
@@ -389,5 +452,6 @@ end
|
|||||||
M.encode_gid = encode_gid
|
M.encode_gid = encode_gid
|
||||||
M.decode_gid = decode_gid
|
M.decode_gid = decode_gid
|
||||||
M.upgrade_v1_to_v2 = upgrade_v1_to_v2
|
M.upgrade_v1_to_v2 = upgrade_v1_to_v2
|
||||||
|
M.validate_map_table_v2 = validate_map_table_v2
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user