# lib-core.world-overlay Generic world-space transient UI layer. Spawns short-lived overlays (text, icons, rects, composites) anchored to world entities or fixed world-space positions. Camera-aware: converts world coordinates to screen coordinates each frame. Domain-free — intended for combat-lib (damage numbers), interaction-lib (mouseover prompts), and module-level pickup confirmations. **Version:** 0.1.0 **Lib-ID:** lib-core.world-overlay **Requires:** engine.render (draw_text, draw_rect, draw_rect_lines, draw_sprite_transform), engine.entity (get_property via entity handles), engine.time (optional, for TTL/animation) **Tags:** world-overlay, ui, hud, transient, world-space, animation, camera ## Topology ```mermaid graph LR this["lib-core.world-overlay"] engine_render["engine.render.*"] engine_entity["engine.entity (via handle.get_property)"] engine_time["engine.time.now (optional)"] this --> engine_render this -.->|entity-anchor| engine_entity this -.->|optional| engine_time ``` ## Scope (v0.1.0) v0.1 ships: - **Content types:** text, icon, rect, rect_lines, composite (children array) - **Lifecycle:** TTL with automatic expiry OR manual-dismiss (no TTL) - **Anchor modes:** entity-follow (reads `position.x`/`position.y` from entity each frame) or fixed-position (world-space `{x, y}` snapshotted at spawn) - **Animation:** fade-in / hold / fade-out alpha envelope + optional upward rise (pixels over TTL duration) - **Camera transform:** world → screen conversion via camera handle each render **Intentional non-goals (deferred):** - Predicate-based lifecycle (despawn when condition becomes true) - Auto-layout for composite children (stacking, centering) - World-space speech-bubble tails and pointer geometry - Per-primitive animation curves (easing functions) ## Spec Schema ### Primitive spec ```lua { kind = "text" | "icon" | "rect" | "rect_lines", anchor = entity_handle | {x = number, y = number}, -- kind-specific fields: -- text: text (string), color (RGBA u32), font_size (number) -- icon: atlas (string), uv = {x,y,w,h}, w (number), h (number) -- rect: w (number), h (number), color (RGBA u32) -- rect_lines: w (number), h (number), color (RGBA u32), thickness (number) -- optional for all: offset = {x = number, y = number}, -- screen-space offset from anchor ttl = number, -- seconds; nil = manual-dismiss fade_in_pct = number, -- fraction of ttl for fade-in (default 0.15) fade_out_pct = number, -- fraction of ttl for fade-out (default 0.15) rise_px = number, -- upward screen-px movement over ttl (default 0) } ``` ### Composite spec ```lua { anchor = entity_handle | {x = number, y = number}, children = { primitive_spec, ... }, -- non-empty array; no anchor on children -- optional lifecycle fields same as primitive (ttl, fade_in_pct, etc.) } ``` Children inherit the composite's anchor position and animation envelope. Each child may have its own `offset` for relative positioning. ## Anchor Modes ### Entity-follow Pass an engine entity handle as `anchor`. Each `update(dt)` call reads `position.x` and `position.y` from the entity via `get_property`. If the entity is destroyed between frames, `pcall` absorbs the error and the overlay keeps rendering at the last known position until it expires or is manually despawned. ```lua local overlay = world_overlay.spawn{ kind = "text", text = "-42", color = 0xFF4040FF, anchor = some_entity, -- entity handle ttl = 1.2, } ``` ### Fixed-position Pass `{x = number, y = number}` as `anchor`. The world-space position is snapshotted at spawn time and never updated. ```lua local overlay = world_overlay.spawn{ kind = "rect", w = 32, h = 32, color = 0x00FF00AA, anchor = {x = 512, y = 300}, ttl = 0.8, } ``` ## Animation Model TTL overlays follow a three-phase alpha envelope: ``` alpha 1 | _______________ | / \ | / \ 0 |_____/___________________\____ ^ ^ fade_in_pct * ttl (1 - fade_out_pct) * ttl ``` - **fade-in phase** `[0, fade_in_pct)`: alpha ramps 0 → 1 - **hold phase** `[fade_in_pct, 1-fade_out_pct]`: alpha = 1.0 - **fade-out phase** `(1-fade_out_pct, 1.0]`: alpha ramps 1 → 0 `rise_px` moves the overlay upward by `progress * rise_px` screen pixels over the full TTL duration. At 50% TTL with `rise_px = 48`, the overlay is 24 px above its anchor screen position. Overlays without TTL (`ttl = nil`) always render at alpha 1.0, no rise. ## Camera Handle Convention `M.render(camera)` expects: ```lua camera = { target_x = number, -- world-space center of view (x) target_y = number, -- world-space center of view (y) zoom = number, -- pixels-per-world-unit scale (default 1.0) offset_x = number, -- screen-space offset of camera center (x) offset_y = number, -- screen-space offset of camera center (y) } ``` World-to-screen transform: ``` screen_x = (world_x - camera.target_x) * zoom + camera.offset_x screen_y = (world_y - camera.target_y) * zoom + camera.offset_y ``` ## API ### `world_overlay.spawn(spec) -> handle` **Syntax:** `world_overlay.spawn(spec: table) -> { id: number }` Validates spec, records anchor position (fixed) or defers to first `update` (entity), and inserts overlay into the active list. Returns an opaque handle for `despawn`. Loud-error on missing anchor, unknown kind, or missing kind-required fields. --- ### `world_overlay.despawn(handle)` **Syntax:** `world_overlay.despawn(handle: table) -> void` Removes the overlay identified by `handle`. Idempotent — calling with an already-expired or non-existent handle is a no-op. --- ### `world_overlay.update(dt)` **Syntax:** `world_overlay.update(dt: number) -> void` Must be called each game-update frame. Refreshes entity-anchor positions, expires overlays whose TTL has elapsed. --- ### `world_overlay.render(camera)` **Syntax:** `world_overlay.render(camera: table) -> void` Must be called each render frame (inside the engine render phase). Converts world positions to screen coordinates using the camera handle, computes alpha + rise, and draws all active overlays. No-op if `camera` is nil. --- ### `world_overlay.list() -> spec[]` **Syntax:** `world_overlay.list() -> table` Returns an array of spec tables for all currently active overlays. Intended for debug/introspection. ## Glue-Pattern ### Combat-style damage number ```lua local world_overlay = require("lib-core.world-overlay") -- In combat hit handler: local function show_damage(entity, amount) world_overlay.spawn{ kind = "text", text = tostring(amount), color = 0xFF3030FF, font_size = 18, anchor = entity, ttl = 1.2, rise_px = 32, fade_out_pct = 0.4, } end -- Module update/render wiring: function M.update(dt) world_overlay.update(dt) end function M.render() -- draw world first world_overlay.render(camera) end ``` ### Pickup confirmation (fixed position + composite) ```lua local world_overlay = require("lib-core.world-overlay") local function show_pickup(world_x, world_y, icon_atlas, icon_uv, label) world_overlay.spawn{ anchor = {x = world_x, y = world_y}, ttl = 1.5, rise_px = 20, children = { { kind = "icon", atlas = icon_atlas, uv = icon_uv, w = 16, h = 16, offset = {x = -8, y = -8}, }, { kind = "text", text = label, color = 0xFFFFAAFF, font_size = 14, offset = {x = 12, y = -4}, }, }, } end ```