Phase-A.5 implementation per sporel-meta/docs/superpowers/specs/2026-06-09-phase-A-... Re-Entry from P.0-Actor-Deferral (Trigger 1: Composition-Aktivierung). Slim wrapper: actor.create validates properties.movement_speed > 0, delegates to composition.create. actor.position reads dotted position back as two numbers. actor.move does direct inert-write to position.x + position.y (engine §11 inert-write; Action-routing deferred). DEFERRED in v0.1: Body-Slot-Aggregator, Walk-Capability, Movement-State on entity, Action-mediated move. All have re-entry-trigger notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 lines
3.6 KiB
Lua
87 lines
3.6 KiB
Lua
-- =====================================================================
|
|
-- lib-core.actor v0.1.0 — Position + Movement-Speed wrapper over composition
|
|
-- See: meta/docs/superpowers/specs/2026-06-09-phase-A-inactive-entities-...
|
|
--
|
|
-- v0.1.0 Spine-Cut:
|
|
-- - actor.create(spec) — validates 'properties.movement_speed' is set
|
|
-- (positive number) then delegates to composition.create. Caller is
|
|
-- responsible for composition.define_template{id=<template>, ...}
|
|
-- before invoking; actor doesn't auto-define templates.
|
|
-- - actor.position(handle) -> x, y — convenience read for the
|
|
-- dotted position-properties.
|
|
-- - actor.move(handle, dx, dy) — direct inert-write to position.x /
|
|
-- position.y. dt-scaling is the caller's job (typically
|
|
-- player_control multiplies dx/dy by movement_speed * dt before
|
|
-- calling).
|
|
--
|
|
-- DEFERRED (no consumer yet — wait for re-entry trigger):
|
|
-- - Body-Slot-Aggregator → Phase D / J trigger
|
|
-- - Walk-Capability (per ADR-0002)→ second-actor-consumer trigger
|
|
-- - Movement-State on the entity → kept ephemeral in player_control
|
|
-- - Action-mediated move → when "move" Action exists and
|
|
-- event/effect routing matters
|
|
-- (engine-primitives §11 inert-write
|
|
-- is the v0.1 escape hatch)
|
|
-- =====================================================================
|
|
|
|
local composition = require("lib-core.composition")
|
|
|
|
local M = {}
|
|
|
|
function M.create(spec)
|
|
if type(spec) ~= "table" then
|
|
error("actor.create: expected table, got " .. type(spec))
|
|
end
|
|
if type(spec.properties) ~= "table" then
|
|
error("actor.create: 'properties' table is required " ..
|
|
"(must contain at least 'movement_speed' and 'position')")
|
|
end
|
|
local ms = spec.properties.movement_speed
|
|
if type(ms) ~= "number" or ms <= 0 then
|
|
error("actor.create: 'properties.movement_speed' must be a positive " ..
|
|
"number (got " .. tostring(ms) .. "); actors need a baseline " ..
|
|
"speed; callers typically apply dt-scaling before actor.move")
|
|
end
|
|
-- composition.create handles position-flattening + template-lookup
|
|
-- + tag-index registration. We don't add anything else here in v0.1;
|
|
-- this wrapper exists primarily to enforce the movement_speed
|
|
-- contract and to mark the entity as an "actor" semantically for
|
|
-- future Phase-D extensions.
|
|
return composition.create(spec)
|
|
end
|
|
|
|
function M.position(handle)
|
|
if handle == nil then
|
|
error("actor.position: handle must not be nil")
|
|
end
|
|
local x = handle:get_property("position.x")
|
|
local y = handle:get_property("position.y")
|
|
return x, y
|
|
end
|
|
|
|
-- Direct inert-write to position.x / position.y. Use composition's already-
|
|
-- declared inert properties; no Action-routing in v0.1 (see DEFERRED note).
|
|
function M.move(handle, dx, dy)
|
|
if handle == nil then
|
|
error("actor.move: handle must not be nil")
|
|
end
|
|
if type(dx) ~= "number" or type(dy) ~= "number" then
|
|
error("actor.move: dx and dy must be numbers")
|
|
end
|
|
local x = handle:get_property("position.x")
|
|
local y = handle:get_property("position.y")
|
|
handle:set_property("position.x", x + dx)
|
|
handle:set_property("position.y", y + dy)
|
|
end
|
|
|
|
-- Convenience: read the actor's movement_speed property (in pixels/sec
|
|
-- by convention; caller decides what "pixel" means).
|
|
function M.movement_speed(handle)
|
|
if handle == nil then
|
|
error("actor.movement_speed: handle must not be nil")
|
|
end
|
|
return handle:get_property("movement_speed")
|
|
end
|
|
|
|
return M
|