From 8cec7b6812d01e37f09b0de2f103045c46b3bb00 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Thu, 21 May 2026 15:54:27 +0200 Subject: [PATCH] Iterate layers in z-order with entity-slot split; multi-layer draw_map Adds LAYER_ORDER_PRE/POST_ENTITIES constants, iterate_layers_pre/post_entities helpers, draw_layer and draw_v1_legacy locals, and draw_map_pre/post_entities public functions. draw_map becomes a backward-compat wrapper over both. Co-Authored-By: Claude Opus 4.7 (1M context) --- init.lua | 158 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 128 insertions(+), 30 deletions(-) diff --git a/init.lua b/init.lua index e4fc4ba..e864a3f 100644 --- a/init.lua +++ b/init.lua @@ -99,6 +99,10 @@ end -- Schema-v2 Layer-Whitelist + Validation -- ===================================================================== +-- Layer z-order (back-to-front) split at entity slot +local LAYER_ORDER_PRE_ENTITIES = { "foundation", "subsurface", "surface", "topsurface" } +local LAYER_ORDER_POST_ENTITIES = { "lower_wall", "wall", "upper_wall", "canopy" } + local VALID_LAYER_NAMES = { foundation = true, subsurface = true, @@ -289,6 +293,80 @@ local function build_map_v2(t_map, resolved_atlases) } end +-- ===================================================================== +-- Internal render helpers (draw_layer, draw_v1_legacy) +-- These must be declared before the public draw_map* functions that call them. +-- ===================================================================== + +local function draw_layer(m, layer_name) + local layer = m.layers[layer_name] + if not layer then return end + local sz = m.size + local ts = m.tile_size + for y = 0, sz.h - 1 do + for x = 0, sz.w - 1 do + local idx = y * sz.w + x + 1 + local gid = layer.tiles[idx] or 0 + if gid ~= 0 then + local atlas_idx, tile_id, rot_quad = decode_gid(gid) + local atlas = m.atlases[atlas_idx + 1] + if atlas then + local tile = atlas.tiles[tile_id] + if tile then + local px = x * ts + local py = y * ts + local rot_deg = rot_quad * 90.0 + if tile.texture_handle then + engine.render.draw_sprite_transform( + tile.texture_handle, + px + ts / 2, py + ts / 2, + math.rad(rot_deg), + 1.0, 1.0, + ts / 2, ts / 2, + 0xFFFFFFFF + ) + else + local c = tile.color or { 100, 100, 100 } + engine.render.draw_rect(px, py, ts, ts, + engine.render.rgb(c[1], c[2], c[3])) + end + end + end + end + end + end +end + +local function draw_v1_legacy(m) + local tm = m.tilemap + if not tm then return end + local ts = tm.tile_size + for y = 0, m.size.h - 1 do + for x = 0, m.size.w - 1 do + local idx = y * m.size.w + x + 1 + local tile_id = m.tiles[idx] + local tile = tm.tiles[tile_id] + local rot_deg = (m.tile_rotations and m.tile_rotations[idx]) or 0 + local px = x * ts + local py = y * ts + if tile.texture_handle then + engine.render.draw_sprite_transform( + tile.texture_handle, + px + ts / 2, py + ts / 2, + math.rad(rot_deg), + 1.0, 1.0, + ts / 2, ts / 2, + 0xFFFFFFFF + ) + else + local c = tile.color or { 100, 100, 100 } + engine.render.draw_rect(px, py, ts, ts, + engine.render.rgb(c[1], c[2], c[3])) + end + end + end +end + -- ===================================================================== -- Public API -- ===================================================================== @@ -549,37 +627,57 @@ function M.load_textures(asset_aliases) end end -function M.draw_map() - local map_id = M.current() - if map_id == nil then return end - local m = map_registry[map_id] - local tm = m.tilemap - local ts = tm.tile_size - for y = 0, m.size.h - 1 do - for x = 0, m.size.w - 1 do - local idx = y * m.size.w + x + 1 - local tile_id = m.tiles[idx] - local tile = tm.tiles[tile_id] - local rot_deg = (m.tile_rotations and m.tile_rotations[idx]) or 0 - local px = x * ts - local py = y * ts - if tile.texture_handle then - -- Sprite mode: draw_sprite_transform with rotation about tile center. - engine.render.draw_sprite_transform( - tile.texture_handle, - px + ts / 2, py + ts / 2, - math.rad(rot_deg), - 1.0, 1.0, - ts / 2, ts / 2, - 0xFFFFFFFF - ) - else - -- Color fallback (Phase 1 mode). - local c = tile.color or { 100, 100, 100 } - engine.render.draw_rect(px, py, ts, ts, engine.render.rgb(c[1], c[2], c[3])) - end - end +function M.iterate_layers_pre_entities(fn, map_id) + local id = map_id or current_map_id + if not id then error("maps.iterate_layers_pre_entities: no current map") end + local m = map_registry[id] + if m.schema_version ~= 2 then + -- v1 (legacy): only surface conceptually + fn("surface") + return end + for _, name in ipairs(LAYER_ORDER_PRE_ENTITIES) do + if m.layers[name] then fn(name) end + end +end + +function M.iterate_layers_post_entities(fn, map_id) + local id = map_id or current_map_id + if not id then error("maps.iterate_layers_post_entities: no current map") end + local m = map_registry[id] + if m.schema_version ~= 2 then return end -- v1 has nothing post + for _, name in ipairs(LAYER_ORDER_POST_ENTITIES) do + if m.layers[name] then fn(name) end + end +end + +function M.draw_map_pre_entities() + local id = current_map_id + if id == nil then return end + local m = map_registry[id] + if m.schema_version ~= 2 then + draw_v1_legacy(m) + return + end + for _, name in ipairs(LAYER_ORDER_PRE_ENTITIES) do + if m.layers[name] then draw_layer(m, name) end + end +end + +function M.draw_map_post_entities() + local id = current_map_id + if id == nil then return end + local m = map_registry[id] + if m.schema_version ~= 2 then return end + for _, name in ipairs(LAYER_ORDER_POST_ENTITIES) do + if m.layers[name] then draw_layer(m, name) end + end +end + +-- Backward-compat: draw_map renders everything (pre + post, no entity slot) +function M.draw_map() + M.draw_map_pre_entities() + M.draw_map_post_entities() end -- Forward-compat stubs (DEPRECATED-MVP — implemented in later slices)