feat(v0.2.0): draw_entities tag-driven entity-render
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) <noreply@anthropic.com>
This commit is contained in:
53
README.md
53
README.md
@@ -1,11 +1,13 @@
|
|||||||
# lib-core.render
|
# 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
|
**Version:** 0.2.0
|
||||||
**Lib-ID:** lib-core.render
|
**Lib-ID:** lib-core.render
|
||||||
**Requires:** lib-core.maps v>=0.1.1
|
**Requires:** lib-core.maps v>=0.5.7, lib-core.composition v>=0.1.0
|
||||||
**Tags:** render, tiles, draw, colored-quads
|
**Tags:** render, tiles, draw, colored-quads, entities
|
||||||
|
|
||||||
## Topology
|
## Topology
|
||||||
|
|
||||||
@@ -15,6 +17,8 @@ graph LR
|
|||||||
this["lib-core.render"]
|
this["lib-core.render"]
|
||||||
lib_core_maps["lib-core.maps"]
|
lib_core_maps["lib-core.maps"]
|
||||||
this --> lib_core_maps
|
this --> lib_core_maps
|
||||||
|
lib_core_composition["lib-core.composition"]
|
||||||
|
this --> lib_core_composition
|
||||||
engine["engine.*"]
|
engine["engine.*"]
|
||||||
this --> 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).
|
**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
|
## Conventions
|
||||||
|
|
||||||
- Colored-quads only in v0.1.0. Texture-rendering, multi-layer composition, post-processing, parallax, shaders, hot-reload all DEPRECATED-MVP.
|
- 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
|
## 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)
|
### v0.1.0 (P.0)
|
||||||
- Initial release: `draw_map` colored-quad iterator.
|
- Initial release: `draw_map` colored-quad iterator.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- Spec v0.1.0 (P.0): `meta/docs/superpowers/specs/2026-05-09-p0-lib-render-design.md`
|
- 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`
|
- Architecture: `meta/docs/architecture/render-pipeline.md`
|
||||||
- ADR-0001 (engine knows verbs, libs bring nouns)
|
- ADR-0001 (engine knows verbs, libs bring nouns)
|
||||||
- ADR-0031 (pixel-convention: Y-down-positive)
|
- ADR-0031 (pixel-convention: Y-down-positive)
|
||||||
|
|||||||
120
init.lua
120
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
|
-- 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,
|
-- v0.2.0 additions:
|
||||||
-- camera, layers, post-processing, parallax, shaders, hot-reload —
|
-- - draw_entities(filter) — tag-based entity-render via composition.
|
||||||
-- alle DEPRECATED-MVP, kommen in späteren Slices.
|
-- 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
|
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.
|
-- Reads tile-color from TileType-table; fallback to magenta if missing or malformed.
|
||||||
local function tile_color(tile_type)
|
local function tile_color(tile_type)
|
||||||
local c = tile_type.color
|
local c = tile_type.color
|
||||||
@@ -44,8 +73,87 @@ function M.draw_map(map_id)
|
|||||||
return count
|
return count
|
||||||
end
|
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 = "<string>" } — 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):
|
-- 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.set_camera(camera) -- lib-core.camera slice
|
||||||
-- DEPRECATED-MVP: function M.push_layer(name, order) -- multi-layer slice
|
-- DEPRECATED-MVP: function M.push_layer(name, order) -- multi-layer slice
|
||||||
-- DEPRECATED-MVP: function M.register_pass(...) -- post-processing slice (spec §6)
|
-- DEPRECATED-MVP: function M.register_pass(...) -- post-processing slice (spec §6)
|
||||||
|
|||||||
@@ -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"}]}
|
||||||
|
|||||||
Reference in New Issue
Block a user