initial: world-overlay-test v0.1.0 — 23 assertions
Tests all spec-required behaviors: spawn/list, invalid-kind and missing-anchor validation, ttl expiry via engine.time monkey-patch, despawn idempotency, entity-anchor position tracking, fixed-anchor snapshot, composite child count, alpha envelope (mid-hold and fade-out phases), entity-destroyed last_pos fallback, and rise_px formula verification. 23 pass, 0 fail.
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
|
||||
238
init.lua
Normal file
238
init.lua
Normal file
@@ -0,0 +1,238 @@
|
||||
-- lib-core.world-overlay-test v0.1.0 — 23 assertions across 11 spec items
|
||||
-- Exercises: spawn/list, kind-validation, anchor-validation, ttl expiry,
|
||||
-- despawn idempotency, entity-anchor position tracking, fixed-anchor snapshot,
|
||||
-- composite child count, alpha envelope (mid-hold + fade-out), entity-destroyed
|
||||
-- fallback, rise_px math verification.
|
||||
|
||||
local world_overlay = require("lib-core.world-overlay")
|
||||
local composition = require("lib-core.composition")
|
||||
local T = engine.test
|
||||
|
||||
local M = {}
|
||||
|
||||
-- ---------- fixture: anchor entity template ----------
|
||||
|
||||
composition.define_template{
|
||||
id = "test_anchor_entity",
|
||||
properties = {
|
||||
position = {x = 100, y = 200},
|
||||
},
|
||||
tags = {},
|
||||
}
|
||||
|
||||
-- Helper: reset all state between groups
|
||||
local function setup()
|
||||
world_overlay._test_clear_all()
|
||||
end
|
||||
|
||||
function M.run_tests(_ctx)
|
||||
|
||||
-- ==================================================================
|
||||
-- 1. spawn text-primitive with ttl + fixed anchor → handle + list()
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local h1 = world_overlay.spawn{
|
||||
kind = "text",
|
||||
text = "hello",
|
||||
color = 0xFFFFFFFF,
|
||||
anchor = {x = 10, y = 20},
|
||||
ttl = 2.0,
|
||||
}
|
||||
T.assert(type(h1) == "table" and type(h1.id) == "number",
|
||||
"spawn returns handle with numeric id")
|
||||
local listed = world_overlay.list()
|
||||
T.equals(#listed, 1, "list() contains 1 overlay after spawn")
|
||||
|
||||
-- ==================================================================
|
||||
-- 2. spawn with invalid kind → loud-error
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local ok_bad_kind = pcall(world_overlay.spawn, {
|
||||
kind = "triangle",
|
||||
anchor = {x = 0, y = 0},
|
||||
})
|
||||
T.assert(not ok_bad_kind, "spawn with unknown kind raises an error")
|
||||
|
||||
-- ==================================================================
|
||||
-- 3. spawn without anchor → loud-error
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local ok_no_anchor = pcall(world_overlay.spawn, {
|
||||
kind = "rect",
|
||||
w = 10, h = 10,
|
||||
})
|
||||
T.assert(not ok_no_anchor, "spawn without anchor raises an error")
|
||||
|
||||
-- ==================================================================
|
||||
-- 4. spawn with ttl, update past ttl → list() empty
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local saved_time_4 = engine.time
|
||||
engine.time = { now = function() return 0.0 end }
|
||||
world_overlay.spawn{
|
||||
kind = "rect",
|
||||
w = 8, h = 8,
|
||||
color = 0xFFFFFFFF,
|
||||
anchor = {x = 0, y = 0},
|
||||
ttl = 1.0,
|
||||
}
|
||||
T.equals(world_overlay._test_overlay_count(), 1,
|
||||
"overlay present before ttl expiry")
|
||||
engine.time = { now = function() return 5.0 end }
|
||||
world_overlay.update(0)
|
||||
engine.time = saved_time_4
|
||||
T.equals(world_overlay._test_overlay_count(), 0,
|
||||
"overlay expired after update past ttl")
|
||||
|
||||
-- ==================================================================
|
||||
-- 5. despawn(handle) removes; second despawn is idempotent (no error)
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local h5 = world_overlay.spawn{
|
||||
kind = "text",
|
||||
text = "bye",
|
||||
color = 0xFFFFFFFF,
|
||||
anchor = {x = 0, y = 0},
|
||||
}
|
||||
world_overlay.despawn(h5)
|
||||
T.equals(world_overlay._test_overlay_count(), 0, "despawn removes overlay")
|
||||
local ok_second_despawn = pcall(world_overlay.despawn, h5)
|
||||
T.assert(ok_second_despawn, "second despawn is idempotent (no error)")
|
||||
|
||||
-- ==================================================================
|
||||
-- 6. entity-anchor → _test_get_world_pos reads from entity after update
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local ent6 = composition.create{ template = "test_anchor_entity" }
|
||||
local h6 = world_overlay.spawn{
|
||||
kind = "text",
|
||||
text = "entity-follow",
|
||||
color = 0xFFFFFFFF,
|
||||
anchor = ent6,
|
||||
}
|
||||
world_overlay.update(0)
|
||||
local pos6 = world_overlay._test_get_world_pos(h6)
|
||||
T.assert(pos6 ~= nil, "entity-anchor: _test_get_world_pos is non-nil after update")
|
||||
T.equals(pos6.x, 100, "entity-anchor: position.x matches template default (100)")
|
||||
T.equals(pos6.y, 200, "entity-anchor: position.y matches template default (200)")
|
||||
composition.destroy(ent6)
|
||||
|
||||
-- ==================================================================
|
||||
-- 7. fixed-anchor → _test_get_world_pos is static across updates
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local h7 = world_overlay.spawn{
|
||||
kind = "rect",
|
||||
w = 4, h = 4,
|
||||
anchor = {x = 55, y = 77},
|
||||
}
|
||||
local pos7a = world_overlay._test_get_world_pos(h7)
|
||||
world_overlay.update(0)
|
||||
world_overlay.update(0)
|
||||
local pos7b = world_overlay._test_get_world_pos(h7)
|
||||
T.assert(pos7a ~= nil and pos7b ~= nil, "fixed-anchor: pos is non-nil before and after updates")
|
||||
T.equals(pos7b.x, 55, "fixed-anchor: x remains 55 after updates")
|
||||
T.equals(pos7b.y, 77, "fixed-anchor: y remains 77 after updates")
|
||||
|
||||
-- ==================================================================
|
||||
-- 8. composite spawn → _test_child_count returns correct count
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local h8 = world_overlay.spawn{
|
||||
anchor = {x = 0, y = 0},
|
||||
ttl = 2.0,
|
||||
children = {
|
||||
{ kind = "rect", w = 16, h = 16, color = 0xFFFFFFFF },
|
||||
{ kind = "rect_lines", w = 16, h = 16, color = 0xFF0000FF },
|
||||
{ kind = "text", text = "+loot", color = 0xFFFF00FF },
|
||||
},
|
||||
}
|
||||
T.assert(world_overlay._test_child_count(h8) == 3,
|
||||
"composite overlay has child count 3")
|
||||
|
||||
-- ==================================================================
|
||||
-- 9. alpha envelope: mid-hold ≈ 1.0; fade-out phase < 1.0
|
||||
-- ==================================================================
|
||||
setup()
|
||||
-- spawn_time=0, ttl=2.0, default fade_in=0.15 (0.3s), fade_out=0.15 (0.3s)
|
||||
-- mid-hold: progress=0.5 → alpha should be 1.0
|
||||
-- fade-out: progress=0.9 → alpha < 1.0
|
||||
local saved_time_9 = engine.time
|
||||
engine.time = { now = function() return 0.0 end }
|
||||
local h9 = world_overlay.spawn{
|
||||
kind = "rect",
|
||||
w = 8, h = 8,
|
||||
anchor = {x = 0, y = 0},
|
||||
ttl = 2.0,
|
||||
}
|
||||
|
||||
-- Mid-hold: now = 1.0 → progress = 0.5 → in hold zone
|
||||
engine.time = { now = function() return 1.0 end }
|
||||
local alpha_mid = world_overlay._test_get_alpha(h9)
|
||||
T.assert(alpha_mid ~= nil and alpha_mid >= 0.99,
|
||||
"alpha is ~1.0 in mid-hold phase (progress=0.5)")
|
||||
|
||||
-- Fade-out: now = 1.9 → progress = 0.95 → in fade-out zone
|
||||
engine.time = { now = function() return 1.9 end }
|
||||
local alpha_fade = world_overlay._test_get_alpha(h9)
|
||||
T.assert(alpha_fade ~= nil and alpha_fade < 1.0,
|
||||
"alpha < 1.0 in fade-out phase (progress=0.95)")
|
||||
|
||||
engine.time = saved_time_9
|
||||
|
||||
-- ==================================================================
|
||||
-- 10. entity destroyed mid-flight: last_pos falls back to snapshot
|
||||
-- ==================================================================
|
||||
setup()
|
||||
local ent10 = composition.create{
|
||||
template = "test_anchor_entity",
|
||||
properties = { position = {x = 300, y = 400} },
|
||||
}
|
||||
local h10 = world_overlay.spawn{
|
||||
kind = "text",
|
||||
text = "dmg",
|
||||
color = 0xFF0000FF,
|
||||
anchor = ent10,
|
||||
ttl = 5.0,
|
||||
}
|
||||
-- First update — position snapshot recorded
|
||||
world_overlay.update(0)
|
||||
local pos_before = world_overlay._test_get_world_pos(h10)
|
||||
T.assert(pos_before ~= nil, "entity-anchor pos snapshotted before destroy")
|
||||
|
||||
-- Destroy entity; next update should not crash and pos should be preserved
|
||||
composition.destroy(ent10)
|
||||
local ok_update = pcall(world_overlay.update, 0)
|
||||
T.assert(ok_update, "update does not crash when entity-anchor is destroyed")
|
||||
local pos_after = world_overlay._test_get_world_pos(h10)
|
||||
T.assert(pos_after ~= nil, "last_pos preserved after entity destroy")
|
||||
T.equals(pos_after.x, pos_before.x, "last_pos.x unchanged after entity destroy")
|
||||
|
||||
-- ==================================================================
|
||||
-- 11. rise_px math: at 50% progress, rise = 0.5 * rise_px
|
||||
-- ==================================================================
|
||||
setup()
|
||||
-- spawn at t=0; ttl=2.0; rise_px=48
|
||||
-- at t=1.0 → progress=0.5 → expected rise_offset = 0.5 * 48 = 24 px
|
||||
local saved_time_11 = engine.time
|
||||
engine.time = { now = function() return 0.0 end }
|
||||
local h11 = world_overlay.spawn{
|
||||
kind = "rect",
|
||||
w = 8, h = 8,
|
||||
anchor = {x = 0, y = 0},
|
||||
ttl = 2.0,
|
||||
rise_px = 48,
|
||||
}
|
||||
-- At progress=0.5, alpha should be 1.0 (hold zone) and rise = 24 px
|
||||
engine.time = { now = function() return 1.0 end }
|
||||
local alpha_11 = world_overlay._test_get_alpha(h11)
|
||||
T.assert(alpha_11 ~= nil and alpha_11 >= 0.99,
|
||||
"rise_px test: alpha=1.0 at progress=0.5 confirms hold zone")
|
||||
local rise_11 = world_overlay._test_get_rise(h11)
|
||||
T.assert(rise_11 ~= nil and math.abs(rise_11 - 24.0) < 0.01,
|
||||
"rise at 50% progress is rise_px * 0.5 (expected 24, got " .. tostring(rise_11) .. ")")
|
||||
engine.time = saved_time_11
|
||||
|
||||
end
|
||||
|
||||
return M
|
||||
6
manifest.core
Normal file
6
manifest.core
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "lib-core.world-overlay-test.core",
|
||||
"kind": "lib",
|
||||
"version": "0.1.0",
|
||||
"license": "Proprietary"
|
||||
}
|
||||
10
manifest.lib
Normal file
10
manifest.lib
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "lib-core.world-overlay-test",
|
||||
"role": "test",
|
||||
"version": "0.1.0",
|
||||
"api_min": "0.1",
|
||||
"deps": [
|
||||
{"id": "lib-core.world-overlay", "version": "0.1.0"},
|
||||
{"id": "lib-core.composition", "version": "0.2.0"}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user