From e4f9d421705fd9b8d6d2844d86f0175759ea8c09 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Tue, 9 Jun 2026 13:39:58 +0000 Subject: [PATCH] feat(v0.2.0): draw_entities tag-driven entity-render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A.2: draw_entities(filter) iterates composition.list_by_tag and renders each entity per its sprite-properties. Three paths: 1. sprite_atlas + sprite_uv.{x,y,w,h} → atlas sub-rect via engine.render.draw_sprite_transform (with texture-handle cache) 2. sprite_color + sprite_w + sprite_h → engine.render.draw_rect 3. magenta 16x16 fallback rect for entities without sprite-properties position.x/position.y is treated as visual-CENTER for all paths. Filter shape: {tag = "..."} — anything else is loud-error (template-id / property-predicate filters deferred per A-Q3). New dep: lib-core.composition v0.1.0. draw_map (v0.1) unverändert. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 55 ++++++++++++++++++++--- init.lua | 120 ++++++++++++++++++++++++++++++++++++++++++++++++--- manifest.lib | 2 +- 3 files changed, 165 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7367502..1f3cc8d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # lib-core.render -Colored-quad tile-grid renderer. Reads `lib-core.maps`'s current (or specified) map + its tilemap, iterates tiles, draws one colored rect per tile via `engine.render.draw_rect`. Fallback to magenta `[255, 0, 255]` for tiles with missing or malformed color. +Colored-quad tile-grid renderer + tag-driven entity-render. Reads +`lib-core.maps`'s tilemap (v0.1: `draw_map`), and `lib-core.composition`'s +tag-index (v0.2: `draw_entities`). -**Version:** 0.1.0 -**Lib-ID:** lib-core.render -**Requires:** lib-core.maps v>=0.1.1 -**Tags:** render, tiles, draw, colored-quads +**Version:** 0.2.0 +**Lib-ID:** lib-core.render +**Requires:** lib-core.maps v>=0.5.7, lib-core.composition v>=0.1.0 +**Tags:** render, tiles, draw, colored-quads, entities ## Topology @@ -15,6 +17,8 @@ graph LR this["lib-core.render"] lib_core_maps["lib-core.maps"] this --> lib_core_maps + lib_core_composition["lib-core.composition"] + this --> lib_core_composition engine["engine.*"] this --> engine ``` @@ -35,6 +39,39 @@ end **Description:** Iterates the tiles of the specified map (or current-map when nil), drawing one colored rect per tile at `(tx * tile_size, ty * tile_size)`. Tile color is read from the tilemap's `tile.color = {r, g, b}`; if missing or malformed, falls back to magenta as a visible "missing-color" debug-marker. Must be called from inside a render hook. Returns the number of tiles drawn (for smoke verification + debug). +### `render.draw_entities(filter)` +**Syntax:** `render.draw_entities({tag: string}) -> integer` + +**Example:** +```lua +function render_fn(ctx) + camera.begin() + render.draw_map() + render.draw_entities{ tag = "renderable" } + camera.finish() +end +``` + +**Description:** Iterates entities tagged with `filter.tag` (via +`composition.list_by_tag`) and renders each according to its +sprite-properties. `position.x`/`position.y` is treated as the visual +CENTER of the entity in all render-paths. Returns the number of +entities drawn. + +Three sprite-property paths are tried per entity, in order: + +1. **`sprite_atlas` (string, atlas-texture-path) + `sprite_uv.{x,y,w,h}` + (numbers)** — renders the sub-rectangle of the atlas via + `engine.render.draw_sprite_transform`. Atlas textures are loaded + once and cached internally on first use; no eviction. +2. **`sprite_color` (uint32 0xRRGGBBAA) + `sprite_w` + `sprite_h` + (numbers)** — renders a colored rect via `engine.render.draw_rect`. +3. **None of the above** — magenta 16×16 fallback rect (debug-marker). + +Filter shape `{tag = "..."}` is the only supported form in v0.2.0; +template-id-based or property-predicate filters are deferred (loud-error +for malformed filters; see Phase-A Spec A-Q3). + ## Conventions - Colored-quads only in v0.1.0. Texture-rendering, multi-layer composition, post-processing, parallax, shaders, hot-reload all DEPRECATED-MVP. @@ -59,12 +96,20 @@ end ## CHANGELOG +### v0.2.0 (Phase A.2 — 2026-06-09) +- Added `draw_entities(filter)` tag-driven entity-render. Two sprite- + property paths (`sprite_atlas`+`sprite_uv` / `sprite_color`+`sprite_w`+ + `sprite_h`) plus magenta-fallback. `position.x`/`.y` is visual-center. +- Atlas-texture cache (no eviction in v0.2.0). +- New dep: `lib-core.composition` v0.1.0. + ### v0.1.0 (P.0) - Initial release: `draw_map` colored-quad iterator. ## References - Spec v0.1.0 (P.0): `meta/docs/superpowers/specs/2026-05-09-p0-lib-render-design.md` +- Spec v0.2.0 (Phase A): `meta/docs/superpowers/specs/2026-06-09-phase-A-inactive-entities-composition-actor-reentry-design.md` - Architecture: `meta/docs/architecture/render-pipeline.md` - ADR-0001 (engine knows verbs, libs bring nouns) - ADR-0031 (pixel-convention: Y-down-positive) diff --git a/init.lua b/init.lua index 567e989..2b11e26 100644 --- a/init.lua +++ b/init.lua @@ -1,16 +1,45 @@ -- ===================================================================== --- lib-core.render — Tile-Grid Rendering (P.0) +-- lib-core.render v0.2.0 — Tile-Grid + Entity Rendering -- See: meta/docs/superpowers/specs/2026-05-09-p0-lib-render-design.md +-- meta/docs/superpowers/specs/2026-06-09-phase-A-... -- --- Scope: colored-quads only. Texture-rendering, PROTOTYPE-Lib-Pattern, --- camera, layers, post-processing, parallax, shaders, hot-reload — --- alle DEPRECATED-MVP, kommen in späteren Slices. +-- v0.2.0 additions: +-- - draw_entities(filter) — tag-based entity-render via composition. +-- Supports two sprite-property paths and a fallback: +-- (1) sprite_atlas + sprite_uv.{x,y,w,h} → atlas-sub-rect render +-- via engine.render.draw_sprite_transform +-- (2) sprite_color + sprite_w + sprite_h → colored rect +-- via engine.render.draw_rect +-- (3) none of above → magenta fallback rect (debug-visible) +-- `position.x` and `position.y` are visual CENTER for all modes. +-- Texture-handles for sprite_atlas paths are cached internally. +-- +-- v0.1.0 API unverändert: draw_map. -- ===================================================================== -local maps = require("lib-core.maps") +local maps = require("lib-core.maps") +local composition = require("lib-core.composition") local FALLBACK_COLOR = {255, 0, 255} -- magenta — "missing color" debug-marker +-- Texture-handle cache for sprite_atlas paths. lazy-loaded on first +-- draw_entities call that references the path. Lives for the engine +-- process; no eviction in v0.2.0. +local atlas_cache = {} + +-- Fallback-rect size (px) for entities without any sprite-property. +-- Magenta is the debug-marker; size is small enough to be intrusive +-- without dominating the screen. +local FALLBACK_RECT_SIZE = 16 + +local function load_atlas(path) + local h = atlas_cache[path] + if h ~= nil then return h end + h = engine.asset.load_texture(path) + atlas_cache[path] = h + return h +end + -- Reads tile-color from TileType-table; fallback to magenta if missing or malformed. local function tile_color(tile_type) local c = tile_type.color @@ -44,8 +73,87 @@ function M.draw_map(map_id) return count end +-- --------------------------------------------------------------------- +-- v0.2.0 — Entity Rendering +-- --------------------------------------------------------------------- + +-- Try to read an entity-property as a number; returns nil if absent +-- (instead of the engine's typed default), so callers can branch on +-- presence without conflating with default-zero. +-- +-- engine.entity:get_property currently returns nil only if the property +-- isn't declared at all; declared-with-default-value yields the default. +-- v0.1 composition declares whatever's in the template — any property +-- not declared is genuinely absent and yields the engine fallback. +local function get_num(entity, key) + local v = entity:get_property(key) + if type(v) == "number" then return v end + return nil +end + +local function get_str(entity, key) + local v = entity:get_property(key) + if type(v) == "string" and v ~= "" then return v end + return nil +end + +-- Draws all entities tagged with `filter.tag`. +-- Filter shape: { tag = "" } — anything else is loud-error. +-- Returns the number of entities drawn (for smoke verification). +function M.draw_entities(filter) + if type(filter) ~= "table" then + error("render.draw_entities: filter must be a table with field 'tag'") + end + if type(filter.tag) ~= "string" or filter.tag == "" then + error("render.draw_entities: filter.tag must be a non-empty string " .. + "(other filter-shapes deferred — see Phase-A Spec A-Q3)") + end + + local list = composition.list_by_tag(filter.tag) + local count = 0 + for _, e in ipairs(list) do + local px = get_num(e, "position.x") + local py = get_num(e, "position.y") + if px ~= nil and py ~= nil then + local atlas = get_str(e, "sprite_atlas") + if atlas ~= nil then + -- Path (1): atlas sub-rect. + local uvx = get_num(e, "sprite_uv.x") + local uvy = get_num(e, "sprite_uv.y") + local uvw = get_num(e, "sprite_uv.w") + local uvh = get_num(e, "sprite_uv.h") + if uvx and uvy and uvw and uvh then + local tex = load_atlas(atlas) + -- position is visual-center: origin = uv center + engine.render.draw_sprite_transform( + tex, px, py, 0, 1, 1, uvw / 2, uvh / 2, + 0xFFFFFFFF, uvx, uvy, uvw, uvh) + count = count + 1 + end + else + local sc = e:get_property("sprite_color") + local sw = get_num(e, "sprite_w") + local sh = get_num(e, "sprite_h") + if type(sc) == "number" and sw and sh then + -- Path (2): colored rect, position = visual-center + engine.render.draw_rect( + px - sw / 2, py - sh / 2, sw, sh, sc) + count = count + 1 + else + -- Path (3): magenta fallback + local fbcol = engine.render.rgb( + FALLBACK_COLOR[1], FALLBACK_COLOR[2], FALLBACK_COLOR[3]) + local s = FALLBACK_RECT_SIZE + engine.render.draw_rect(px - s / 2, py - s / 2, s, s, fbcol) + count = count + 1 + end + end + end + end + return count +end + -- Forward-compat stubs for later slices (kept as comments to avoid table-pollution): --- DEPRECATED-MVP: function M.draw_sprites(...) -- texture-slice -- DEPRECATED-MVP: function M.set_camera(camera) -- lib-core.camera slice -- DEPRECATED-MVP: function M.push_layer(name, order) -- multi-layer slice -- DEPRECATED-MVP: function M.register_pass(...) -- post-processing slice (spec §6) diff --git a/manifest.lib b/manifest.lib index 976044e..dbb9d67 100644 --- a/manifest.lib +++ b/manifest.lib @@ -1 +1 @@ -{"id":"lib-core.render","version":"0.1.0","api_min":"0.1","deps":[{"id":"lib-core.maps","version":"0.5.7"}]} +{"id":"lib-core.render","version":"0.2.0","api_min":"0.1","deps":[{"id":"lib-core.maps","version":"0.5.7"},{"id":"lib-core.composition","version":"0.1.0"}]}