initial: world-overlay v0.1.0 — transient world-space UI primitives
Generic world-space transient UI lib. Spawns text, icon, rect, rect_lines, and composite overlays anchored to entities or fixed world positions. TTL-based expiry with fade-in/hold/fade-out alpha envelope and optional upward rise. Camera-aware world-to-screen transform. Entity-destroyed fallback keeps last_pos snapshot. Five test backdoors for headless testing.
This commit is contained in:
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2026 Calic. All rights reserved.
|
||||
|
||||
This software is part of the Sporel platform — **Tier 1 (Official /
|
||||
Proprietary)** content per the Three-Tier Licensing Model documented in
|
||||
`meta/docs/archive/design/vision.md §Licensing Model` (current source;
|
||||
migration to `meta/docs/architecture/licensing-model.md` pending).
|
||||
|
||||
⚠ **WIP — Legal review required before public launch.** The terms below
|
||||
reflect design intent only; the formalized license framework will be
|
||||
finalized through legal counsel before the first public release. Until
|
||||
then, this notice serves as a placeholder defending the platform owner's
|
||||
rights against unintentional re-licensing.
|
||||
|
||||
No license is granted to copy, modify, distribute, sublicense, or otherwise
|
||||
use this software in any form without prior written permission from the
|
||||
copyright holder.
|
||||
|
||||
References:
|
||||
- Tier 1 (this file): all rights reserved, proprietary, sold/distributed
|
||||
via official channels (Steam, etc.)
|
||||
- Tier 2 (Semi-Commercial Co-Development): bilateral contracts, revenue-
|
||||
share — see vision.md §Licensing Model
|
||||
- Tier 3 (Community Content): CC BY-NC-SA 4.0 + asymmetric CLA — applies
|
||||
to community-uploaded libs/modules/assets, not this repo
|
||||
269
README.md
Normal file
269
README.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# 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
|
||||
|
||||
<!-- topology:start (auto-generated; do not edit) -->
|
||||
```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
|
||||
```
|
||||
<!-- topology:end -->
|
||||
|
||||
## 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
|
||||
```
|
||||
293
init.lua
Normal file
293
init.lua
Normal file
@@ -0,0 +1,293 @@
|
||||
-- =====================================================================
|
||||
-- lib-core.world-overlay v0.1.0 — Generic World-Space Transient UI
|
||||
-- text + icon + rect + composite; entity or fixed anchor; ttl-fade or
|
||||
-- manual-dismiss. Camera-aware. Domain-free — consumed by combat-lib,
|
||||
-- interaction-lib, modules directly. NOT notify-specific.
|
||||
-- =====================================================================
|
||||
|
||||
local M = {}
|
||||
|
||||
-- ---------- state ----------
|
||||
local overlays = {} -- array of overlay-records
|
||||
local next_id = 1
|
||||
|
||||
-- Valid primitive kinds (capability-by-declaration)
|
||||
local VALID_KINDS = {
|
||||
text = true,
|
||||
icon = true,
|
||||
rect = true,
|
||||
rect_lines = true,
|
||||
}
|
||||
|
||||
-- ---------- validation ----------
|
||||
|
||||
local function validate_primitive(spec, ctx_name)
|
||||
if not VALID_KINDS[spec.kind] then
|
||||
error(string.format(
|
||||
"world-overlay.%s: unknown kind '%s' (valid: text, icon, rect, rect_lines)",
|
||||
ctx_name, tostring(spec.kind)))
|
||||
end
|
||||
if spec.kind == "text" and type(spec.text) ~= "string" then
|
||||
error(string.format(
|
||||
"world-overlay.%s: text-kind requires .text string", ctx_name))
|
||||
end
|
||||
if spec.kind == "icon" then
|
||||
if type(spec.atlas) ~= "string" or type(spec.uv) ~= "table" then
|
||||
error(string.format(
|
||||
"world-overlay.%s: icon-kind requires .atlas + .uv", ctx_name))
|
||||
end
|
||||
end
|
||||
if spec.kind == "rect" or spec.kind == "rect_lines" then
|
||||
if type(spec.w) ~= "number" or type(spec.h) ~= "number" then
|
||||
error(string.format(
|
||||
"world-overlay.%s: %s-kind requires .w + .h numbers",
|
||||
ctx_name, spec.kind))
|
||||
end
|
||||
end
|
||||
if spec.kind == "text" or spec.kind == "rect" or spec.kind == "rect_lines" then
|
||||
if spec.color ~= nil and type(spec.color) ~= "number" then
|
||||
error(string.format(
|
||||
"world-overlay.%s: %s-kind .color must be a number (0xRRGGBBAA), got %s",
|
||||
ctx_name, spec.kind, type(spec.color)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ---------- public: spawn ----------
|
||||
|
||||
function M.spawn(spec)
|
||||
if type(spec) ~= "table" then
|
||||
error("world-overlay.spawn: spec must be a table")
|
||||
end
|
||||
if spec.anchor == nil then
|
||||
error("world-overlay.spawn: spec.anchor required (entity or {x, y})")
|
||||
end
|
||||
|
||||
-- Composite vs primitive
|
||||
local is_composite = spec.children ~= nil
|
||||
if is_composite then
|
||||
if type(spec.children) ~= "table" or #spec.children == 0 then
|
||||
error("world-overlay.spawn: children must be a non-empty array")
|
||||
end
|
||||
for i, child in ipairs(spec.children) do
|
||||
validate_primitive(child, string.format("spawn[children[%d]]", i))
|
||||
end
|
||||
else
|
||||
validate_primitive(spec, "spawn")
|
||||
end
|
||||
|
||||
local now = (engine and engine.time and engine.time.now and engine.time.now()) or 0
|
||||
local id = next_id
|
||||
next_id = next_id + 1
|
||||
|
||||
local record = {
|
||||
id = id,
|
||||
spec = spec,
|
||||
spawn_time = now,
|
||||
ttl = spec.ttl,
|
||||
last_pos = nil,
|
||||
}
|
||||
-- Snapshot fixed-anchor at spawn time
|
||||
if type(spec.anchor) == "table" and spec.anchor.x ~= nil then
|
||||
record.last_pos = { x = spec.anchor.x, y = spec.anchor.y }
|
||||
end
|
||||
|
||||
table.insert(overlays, record)
|
||||
return { id = id }
|
||||
end
|
||||
|
||||
-- ---------- public: despawn ----------
|
||||
|
||||
function M.despawn(handle)
|
||||
if type(handle) ~= "table" or not handle.id then return end
|
||||
for i, o in ipairs(overlays) do
|
||||
if o.id == handle.id then
|
||||
table.remove(overlays, i)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- silent idempotent
|
||||
end
|
||||
|
||||
-- ---------- public: update ----------
|
||||
|
||||
function M.update(dt)
|
||||
local now = (engine and engine.time and engine.time.now and engine.time.now()) or 0
|
||||
local kept = {}
|
||||
for _, o in ipairs(overlays) do
|
||||
local a = o.spec.anchor
|
||||
if type(a) == "table" and a.x ~= nil then
|
||||
-- Fixed; already snapshotted at spawn; no update needed
|
||||
elseif a and a.get_property then
|
||||
-- Entity-handle: refresh position; pcall so entity-destroy
|
||||
-- doesn't crash overlay-update.
|
||||
local ok_x, x = pcall(function() return a:get_property("position.x") end)
|
||||
local ok_y, y = pcall(function() return a:get_property("position.y") end)
|
||||
if ok_x and ok_y and type(x) == "number" and type(y) == "number" then
|
||||
o.last_pos = { x = x, y = y }
|
||||
end
|
||||
-- If pcall failed (entity destroyed) → last_pos keeps last snapshot
|
||||
end
|
||||
|
||||
-- TTL expiry
|
||||
if o.ttl then
|
||||
local progress = (now - o.spawn_time) / o.ttl
|
||||
if progress >= 1.0 then
|
||||
-- drop (expired)
|
||||
else
|
||||
table.insert(kept, o)
|
||||
end
|
||||
else
|
||||
-- Manual-dismiss: keep
|
||||
table.insert(kept, o)
|
||||
end
|
||||
end
|
||||
overlays = kept
|
||||
end
|
||||
|
||||
-- ---------- animation + render helpers (module-local) ----------
|
||||
|
||||
local function compute_alpha_rise(o, now)
|
||||
if not o.ttl then return 1.0, 0 end
|
||||
local progress = (now - o.spawn_time) / o.ttl
|
||||
local fade_in = o.spec.fade_in_pct or 0.15
|
||||
local fade_out = o.spec.fade_out_pct or 0.15
|
||||
local rise_px = o.spec.rise_px or 0
|
||||
local alpha
|
||||
if progress < fade_in then
|
||||
alpha = progress / fade_in
|
||||
elseif progress > (1.0 - fade_out) then
|
||||
alpha = (1.0 - progress) / fade_out
|
||||
else
|
||||
alpha = 1.0
|
||||
end
|
||||
alpha = math.max(0.0, math.min(1.0, alpha))
|
||||
return alpha, progress * rise_px
|
||||
end
|
||||
|
||||
local function world_to_screen(world_x, world_y, camera)
|
||||
local zoom = camera.zoom or 1.0
|
||||
local sx = (world_x - camera.target_x) * zoom + camera.offset_x
|
||||
local sy = (world_y - camera.target_y) * zoom + camera.offset_y
|
||||
return sx, sy
|
||||
end
|
||||
|
||||
local function apply_alpha(color_rgba, alpha)
|
||||
local a = math.floor(math.max(0, math.min(1, alpha)) * 255)
|
||||
return (color_rgba & 0xFFFFFF00) | a
|
||||
end
|
||||
|
||||
local function render_primitive(spec, screen_x, screen_y, alpha)
|
||||
if not (engine and engine.render) then return end
|
||||
local off_x = (spec.offset and spec.offset.x) or 0
|
||||
local off_y = (spec.offset and spec.offset.y) or 0
|
||||
local x = screen_x + off_x
|
||||
local y = screen_y + off_y
|
||||
if spec.kind == "text" then
|
||||
local color = apply_alpha(spec.color or 0xFFFFFFFF, alpha)
|
||||
engine.render.draw_text(spec.text, x, y,
|
||||
spec.font_size or 14, color)
|
||||
elseif spec.kind == "icon" then
|
||||
-- pcall around load_texture so headless/test runs degrade
|
||||
-- gracefully to placeholder rect instead of crashing.
|
||||
local ok, tex = pcall(function()
|
||||
return engine.module.load_texture(spec.atlas)
|
||||
end)
|
||||
if ok and tex and engine.render.draw_sprite_transform then
|
||||
engine.render.draw_sprite_transform(tex, x, y, 0, 1, 1, 0, 0,
|
||||
apply_alpha(0xFFFFFFFF, alpha),
|
||||
spec.uv.x, spec.uv.y, spec.uv.w, spec.uv.h)
|
||||
else
|
||||
-- Placeholder rect
|
||||
engine.render.draw_rect(x, y, spec.w or 16, spec.h or 16,
|
||||
apply_alpha(0x808080FF, alpha))
|
||||
end
|
||||
elseif spec.kind == "rect" then
|
||||
engine.render.draw_rect(x, y, spec.w, spec.h,
|
||||
apply_alpha(spec.color or 0xFFFFFFFF, alpha))
|
||||
elseif spec.kind == "rect_lines" then
|
||||
engine.render.draw_rect_lines(x, y, spec.w, spec.h,
|
||||
apply_alpha(spec.color or 0xFFFFFFFF, alpha),
|
||||
spec.thickness or 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- ---------- public: render ----------
|
||||
|
||||
function M.render(camera)
|
||||
if not camera then return end
|
||||
local now = (engine and engine.time and engine.time.now and engine.time.now()) or 0
|
||||
for _, o in ipairs(overlays) do
|
||||
local pos = o.last_pos
|
||||
if pos then
|
||||
local alpha, rise = compute_alpha_rise(o, now)
|
||||
local screen_x, screen_y = world_to_screen(pos.x, pos.y, camera)
|
||||
screen_y = screen_y - rise -- rise = upward motion
|
||||
if o.spec.children then
|
||||
for _, child in ipairs(o.spec.children) do
|
||||
render_primitive(child, screen_x, screen_y, alpha)
|
||||
end
|
||||
else
|
||||
render_primitive(o.spec, screen_x, screen_y, alpha)
|
||||
end
|
||||
end
|
||||
-- pos==nil means entity-anchor that never got position read — skip
|
||||
end
|
||||
end
|
||||
|
||||
-- ---------- public: list ----------
|
||||
|
||||
function M.list()
|
||||
local out = {}
|
||||
for i, o in ipairs(overlays) do out[i] = o.spec end
|
||||
return out
|
||||
end
|
||||
|
||||
-- ---------- test backdoors ----------
|
||||
|
||||
function M._test_clear_all()
|
||||
overlays = {}
|
||||
next_id = 1
|
||||
end
|
||||
|
||||
function M._test_get_world_pos(handle)
|
||||
for _, o in ipairs(overlays) do
|
||||
if o.id == handle.id then return o.last_pos end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function M._test_get_alpha(handle)
|
||||
local now = (engine and engine.time and engine.time.now and engine.time.now()) or 0
|
||||
for _, o in ipairs(overlays) do
|
||||
if o.id == handle.id then
|
||||
local alpha, _ = compute_alpha_rise(o, now)
|
||||
return alpha
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function M._test_child_count(handle)
|
||||
for _, o in ipairs(overlays) do
|
||||
if o.id == handle.id then
|
||||
return o.spec.children and #o.spec.children or 0
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function M._test_overlay_count() return #overlays end
|
||||
|
||||
function M._test_get_rise(handle)
|
||||
local now = (engine and engine.time and engine.time.now and engine.time.now()) or 0
|
||||
for _, o in ipairs(overlays) do
|
||||
if o.id == handle.id then
|
||||
local _, rise = compute_alpha_rise(o, now)
|
||||
return rise
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return M
|
||||
1
manifest.lib
Normal file
1
manifest.lib
Normal file
@@ -0,0 +1 @@
|
||||
{"id":"lib-core.world-overlay","version":"0.1.0","api_min":"0.1","deps":[]}
|
||||
Reference in New Issue
Block a user