Files
Calic 31426b07bc 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.
2026-06-14 01:49:25 +02:00

239 lines
9.4 KiB
Lua

-- 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