From 31426b07bccb993d38603cf12bd9cb9fac8286a9 Mon Sep 17 00:00:00 2001 From: Calic Date: Sun, 14 Jun 2026 01:45:52 +0200 Subject: [PATCH] =?UTF-8?q?initial:=20world-overlay-test=20v0.1.0=20?= =?UTF-8?q?=E2=80=94=2023=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- LICENSE | 24 +++++ init.lua | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++ manifest.core | 6 ++ manifest.lib | 10 +++ 4 files changed, 278 insertions(+) create mode 100644 LICENSE create mode 100644 init.lua create mode 100644 manifest.core create mode 100644 manifest.lib diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81c9d65 --- /dev/null +++ b/LICENSE @@ -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 diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..09af24f --- /dev/null +++ b/init.lua @@ -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 diff --git a/manifest.core b/manifest.core new file mode 100644 index 0000000..6c4bb84 --- /dev/null +++ b/manifest.core @@ -0,0 +1,6 @@ +{ + "id": "lib-core.world-overlay-test.core", + "kind": "lib", + "version": "0.1.0", + "license": "Proprietary" +} diff --git a/manifest.lib b/manifest.lib new file mode 100644 index 0000000..9873654 --- /dev/null +++ b/manifest.lib @@ -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"} + ] +}