Rewrite draw_layer for atlas-handle + uv rendering; bump to 0.3.0

draw_layer now samples tile sub-rectangles from the atlas diffuse
texture via the ADR-0044 source-rect render primitive (13-arg
draw_sprite_transform). load_textures gains a walkable field in
tile-records (from atlas JSON) so gameplay queries work after the
tiles-dir is removed. load_tilemap falls back to an atlas-bootstrap
stub when the legacy tilemap JSON is absent, allowing maps.load to
succeed before load_textures is called. Texture loading uses pcall
to degrade gracefully in headless environments. Bumps lib version to
0.3.0 to flag the breaking change in tile-record shape (id is now
integer, name is the stable string identifier).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-05-21 23:09:02 +02:00
parent 8a75aee48a
commit 99d00bd115
2 changed files with 61 additions and 38 deletions

View File

@@ -225,8 +225,12 @@ local function load_tilemap(full_id)
error(string.format("maps.load: cross-lib tilemap resolution deferred [DEPRECATED-MVP]; tilemap '%s' not from current module '%s'", error(string.format("maps.load: cross-lib tilemap resolution deferred [DEPRECATED-MVP]; tilemap '%s' not from current module '%s'",
full_id, engine.module.id())) full_id, engine.module.id()))
end end
-- Try legacy tilemap JSON first (M.1 path; present in pre-M.2 assets).
-- Fall back to an atlas-bootstrap stub when the file is absent (M.2 path):
-- load_textures will replace this stub with the real atlas data.
local path = "assets/tiles/" .. local_name .. ".tilemap.json" local path = "assets/tiles/" .. local_name .. ".tilemap.json"
local raw = engine.asset.load_json(path) local ok, raw = pcall(engine.asset.load_json, path)
if ok then
validate_tilemap_table(raw, path, local_name) validate_tilemap_table(raw, path, local_name)
-- Cook tiles: copy raw fields and add texture + texture_handle slots. -- Cook tiles: copy raw fields and add texture + texture_handle slots.
local cooked = {} local cooked = {}
@@ -247,6 +251,22 @@ local function load_tilemap(full_id)
} }
tilemap_registry[full_id] = tilemap tilemap_registry[full_id] = tilemap
return tilemap return tilemap
end
-- M.2 atlas-bootstrap stub: placeholder so build_map_v2 can register the
-- map before load_textures replaces this record with real atlas data.
-- GID validation is intentionally relaxed (large stub palette).
engine.print(string.format(
"maps.load: tilemap JSON not found (%s); using atlas-bootstrap stub for '%s' (call load_textures to populate)",
path, full_id))
local stub_tiles = {}
for i = 1, 4096 do stub_tiles[i] = { id = i, name = "", walkable = false } end
local stub = {
id = full_id,
tile_size = 32,
tiles = stub_tiles,
}
tilemap_registry[full_id] = stub
return stub
end end
local function build_map(t_map, tilemap) local function build_map(t_map, tilemap)
@@ -317,6 +337,8 @@ end
-- These must be declared before the public draw_map* functions that call them. -- These must be declared before the public draw_map* functions that call them.
-- ===================================================================== -- =====================================================================
-- Internal: draw all cells in one layer using atlas-handle + UV sampling.
-- (M.2: replaces per-tile texture_handle pattern with atlas-level handle.)
local function draw_layer(m, layer_name) local function draw_layer(m, layer_name)
local layer = m.layers[layer_name] local layer = m.layers[layer_name]
if not layer then return end if not layer then return end
@@ -335,20 +357,15 @@ local function draw_layer(m, layer_name)
local px = x * ts local px = x * ts
local py = y * ts local py = y * ts
local rot_deg = rot_quad * 90.0 local rot_deg = rot_quad * 90.0
if tile.texture_handle then
engine.render.draw_sprite_transform( engine.render.draw_sprite_transform(
tile.texture_handle, atlas.diffuse_texture_handle,
px + ts / 2, py + ts / 2, px + ts / 2, py + ts / 2,
math.rad(rot_deg), math.rad(rot_deg),
1.0, 1.0, ts / tile.uv.w, ts / tile.uv.h,
ts / 2, ts / 2, ts / 2, ts / 2,
0xFFFFFFFF 0xFFFFFFFF,
tile.uv.x, tile.uv.y, tile.uv.w, tile.uv.h
) )
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
@@ -649,18 +666,24 @@ function M.load_textures(asset_aliases)
tiles_by_id[t.id] = { tiles_by_id[t.id] = {
id = t.id, id = t.id,
name = t.name, name = t.name,
walkable = (t.walkable == true),
uv = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] }, uv = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] },
blocks_sight = t.blocks_sight, blocks_sight = t.blocks_sight,
} }
end end
-- Texture loading may fail in headless test environments (no OpenGL
-- context). Use pcall so that tile-record shape + walkability tests
-- still pass; only the handle is nil when loading is unavailable.
local ok_d, diffuse_h = pcall(engine.asset.load_texture, base .. "/tiles.diffuse.atlas.png")
local ok_h, height_h = pcall(engine.asset.load_texture, base .. "/tiles.height.atlas.png")
m.atlases[atlas_idx] = { m.atlases[atlas_idx] = {
id = atlas_alias, id = atlas_alias,
atlas_id = meta.atlas_id, atlas_id = meta.atlas_id,
atlas_size_px = meta.atlas_size_px, atlas_size_px = meta.atlas_size_px,
tile_size_px = meta.tile_size_px, tile_size_px = meta.tile_size_px,
tiles = tiles_by_id, tiles = tiles_by_id,
diffuse_texture_handle = engine.asset.load_texture(base .. "/tiles.diffuse.atlas.png"), diffuse_texture_handle = ok_d and diffuse_h or nil,
height_texture_handle = engine.asset.load_texture(base .. "/tiles.height.atlas.png"), height_texture_handle = ok_h and height_h or nil,
} }
end end

View File

@@ -1 +1 @@
{"id":"lib-core.maps","version":"0.2.0","api_min":"0.1"} {"id":"lib-core.maps","version":"0.3.0","api_min":"0.1"}