Axel Meyer 1562ee0068 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>
2026-05-21 15:26:48 +02:00
2026-05-16 18:04:34 +02:00

lib-core.maps

Single tile-grid map implementation. Loads JSON map + tilemap files, registers maps by id, and provides tile-fetch + walkability + size queries with current-map sugar.

Version: 0.1.2
Lib-ID: lib-core.maps
Requires: (none)
Tags: maps, tile-grid, walkability, tilemap

Topology

graph LR
  this["lib-core.maps"]
  engine["engine.*"]
  this --> engine

API

maps.load(path)

Syntax: maps.load(path: string) -> string

Example:

local id = maps.load("maps/forest.json")
maps.set_current(id)

Description: Loads a JSON map-file from disk, resolves + loads its referenced tilemap (local module-tilemap-id or fully-qualified), validates the map-table, and registers it. Returns the map-id. Errors if the id is already registered.

maps.create(t)

Syntax: maps.create(t: {id: string, tilemap_table: table, ...}) -> string

Example:

local id = maps.create({
    id = "test-tiny",
    size = { w = 4, h = 4 },
    tile_size = { w = 16, h = 16 },
    tilemap_table = my_tilemap,
    tiles = { 0,0,0,0, 0,1,1,0, 0,1,1,0, 0,0,0,0 },
})

Description: Programmatic creation for tests + procedural generators. Caller must supply a fully-built tilemap_table (not a path/id reference). Otherwise identical to load.

maps.size(map_id)

Syntax: maps.size(map_id: string | nil) -> {w: integer, h: integer}

Description: Returns map-size in tiles. Falls back to current-map when map_id is nil. Errors if no current map.

maps.tile_size(map_id)

Syntax: maps.tile_size(map_id: string | nil) -> {w: integer, h: integer}

Description: Returns tile-size in pixels. Falls back to current-map when nil.

maps.tile_at(a, b, c)

Syntax: maps.tile_at(map_id: string, tx: integer, ty: integer) -> table | nil (also: maps.tile_at(tx, ty) uses current-map)

Example:

local tile = maps.tile_at(5, 3)
if tile then engine.print(tile.id) end

Description: Arity-flex sugar: tile_at(tx, ty) uses current-map; tile_at(map_id, tx, ty) is explicit. Returns the resolved tile-record from the tilemap, or nil if out-of-bounds.

maps.is_walkable(a, b, c)

Syntax: maps.is_walkable(map_id: string, tx: integer, ty: integer) -> bool (also: is_walkable(tx, ty) uses current-map)

Description: Convenience: returns true iff the tile exists and has walkable == true. Out-of-bounds is false.

maps.tilemap_id(map_id)

Syntax: maps.tilemap_id(map_id: string | nil) -> string

Description: Returns the fully-qualified tilemap-id referenced by a map.

maps.current()

Syntax: maps.current() -> string | nil

Description: Returns the currently-active map-id, or nil if none.

maps.set_current(map_id)

Syntax: maps.set_current(map_id: string) -> void

Description: Switches the current-map pointer. Errors if map_id is not registered.

maps.list()

Syntax: maps.list() -> string[]

Description: Returns an array of all registered map-ids.

maps.load_textures(asset_aliases)

Syntax: maps.load_textures(asset_aliases: {[string]: string}) -> void

Example:

maps.set_current(map_id)
maps.load_textures({ terrain = "lib-core.terrain-assets" })

Description: Resolves the current tilemap's tile texture atlas-ids to texture-handles via the asset-lib indirection. asset_aliases maps the tilemap's asset_pack alias-key to a lib-id; the lib's assets/atlas.json is read to locate each texture file. Populates tile.texture_handle on each tile in-place. No-op if the tilemap has no asset_pack. Call once at module init after set_current; do not call repeatedly (texture handles are not auto-cached).

maps.draw_map()

Syntax: maps.draw_map() -> void

Description: Draws the current map grid. Tiles with a resolved texture_handle render via engine.render.draw_sprite_transform with rotation about the tile center (using the map's optional tile_rotations parallel array). Tiles without a texture handle fall back to engine.render.draw_rect with tile.color (Phase 1 mode). No-op if no current map.

maps.state(map_id)

Syntax: maps.state(map_id: string) -> string

Description: DEPRECATED-MVP stub. Always returns "Active" in v0.1.x. Full lifecycle (Virgin/Inert/Passive/Active/Pinned) lands in the multi-map slice.

maps.pin(map_id, reason)

Syntax: maps.pin(map_id: string, reason: string) -> void

Description: DEPRECATED-MVP stub. Emits a warn in v0.1.x. Pinning prevents lifecycle-eviction in the multi-map slice.

Conventions

  • Pixel-coords + tile-coords kept distinct: size/tiles index in tile-units; tile_size is the conversion to pixels.
  • Tilemap-ids may be local (<this-module-id>.<name>) or fully-qualified; load() resolves both.
  • tile_at is bounds-checked: out-of-bounds returns nil (not error).
  • Y-down-positive per ADR-0031.

Consumer pattern

local maps = require("lib-core.maps")

local id = maps.load("maps/forest.json")
maps.set_current(id)

local sz = maps.size()
for ty = 0, sz.h - 1 do
    for tx = 0, sz.w - 1 do
        if not maps.is_walkable(tx, ty) then
            -- mark blocked cell
        end
    end
end

CHANGELOG

v0.1.2

  • Sprite-mode in draw_map: tilemap-tiles with texture atlas-id render via engine.render.draw_sprite_transform; tiles without texture fall back to color-rect render (Phase 1 mode).
  • Map-data tile_rotations parallel array support: per-cell 90-degree rotation in {0, 90, 180, 270}, applied at draw via rotation about the tile center. Optional; absent means all-zero.
  • New API: maps.load_textures(asset_aliases) resolves the current tilemap's tile atlas-ids to texture-handles via the asset-lib indirection. Mirrors the puppet.load_textures pattern.
  • Tilemap schema additions: tilemap.asset_pack (alias-key), tile.texture (atlas-id).
  • Backward-compat: existing Phase 1 color-only tilemaps render unchanged.

v0.1.1

  • Tilemap-id resolution handles module-ids with dots (prefix-match instead of first-dot split).

v0.1.0 (P.0)

  • Initial release: load/create + tile_at + is_walkable + current-map management.

References

  • Spec v0.1.0 (P.0): meta/docs/superpowers/specs/2026-05-09-p0-lib-maps-design.md
  • Architecture: meta/docs/architecture/map-topology.md
  • ADR-0001 (engine knows verbs, libs bring nouns)
  • ADR-0031 (pixel-convention: Y-down-positive)
  • ADR-0038 (API-Doc-Convention)
Description
P.0.module.skeleton
Readme 509 KiB
Languages
Lua 99.5%
Shell 0.5%