initial: actor v0.1.0 — composition-wrapper + movement-speed-contract

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>
This commit is contained in:
Axel Meyer
2026-06-09 13:57:20 +00:00
commit e627f0097f
4 changed files with 243 additions and 0 deletions

132
README.md Normal file
View File

@@ -0,0 +1,132 @@
# lib-core.actor
Thin wrapper over `lib-core.composition` that enforces a baseline-speed
contract for "actor" entities. Provides convenience helpers for
position-read + position-write (inert-mediated). Doesn't track any
ephemeral movement-state (vel/dir) — that stays in `player_control`
or whatever drives the actor each frame.
**Version:** 0.1.0
**Lib-ID:** lib-core.actor
**Requires:** lib-core.composition v>=0.1.0
**Tags:** actor, entity, movement
## Topology
<!-- topology:start (auto-generated; do not edit) -->
```mermaid
graph LR
this["lib-core.actor"]
lib_core_composition["lib-core.composition"]
this --> lib_core_composition
engine["engine.*"]
this --> engine
```
<!-- topology:end -->
## Scope (v0.1.0 — Spine)
Re-Entry from P.0-Actor-Deferral (Trigger 1: Composition-Aktivierung).
v0.1 is a minimal slice; intentional non-goals:
- **No Body-Slot-Aggregator.** Phase D / J trigger.
- **No Walk-Capability** (per ADR-0002 capabilities-as-derived).
Waits for a second actor-consumer that needs capability-routing.
- **No Movement-State on the entity.** vel / dir / direction-flags are
ephemeral; kept in `player_control` (or whatever driver). Persisted
ones come with a re-entry-trigger.
- **No Action-mediated move.** v0.1 uses engine-§11 inert-write directly.
Action-routing is the "right" path for game-state-weighty mutations
but waits until a "move" Action is registered and event/effect
routing matters.
## API
### `actor.create(spec)`
**Syntax:** `actor.create({template: string, properties: table}) -> entity`
**Example:**
```lua
-- Caller defines a template via composition first:
composition.define_template{
id = "player",
properties = {
position = {x = 0, y = 0},
movement_speed = 200,
sprite_color = engine.render.rgb(200, 60, 80),
sprite_w = 24,
sprite_h = 24,
},
tags = {"renderable", "player"},
}
-- Then actor.create instantiates:
local p = actor.create{
template = "player",
properties = {
position = {x = 400, y = 300},
movement_speed = 200,
},
}
```
**Description:** Validates that `properties.movement_speed` is a
positive number, then delegates to `composition.create`. Loud-Error
if `movement_speed` is missing, non-number, or non-positive.
The caller is responsible for `composition.define_template{id=...}`
before invoking. Actor doesn't auto-define templates.
### `actor.position(handle)`
**Syntax:** `actor.position(handle) -> x, y`
**Description:** Returns the actor's current position as two numbers
(x, y). Reads via `entity:get_property("position.x"/"position.y")`.
Loud-Error if `handle` is nil.
### `actor.move(handle, dx, dy)`
**Syntax:** `actor.move(handle, dx: number, dy: number) -> void`
**Example:**
```lua
-- Typical caller (player_control update-loop):
local speed = actor.movement_speed(player)
local move_dx, move_dy = compute_input_direction()
actor.move(player, move_dx * speed * dt, move_dy * speed * dt)
```
**Description:** Adds (dx, dy) to the actor's current position via
direct inert-write. Caller does dt-scaling and any speed-multiplication
upstream. v0.1 uses engine §11 inert-write — Action-routing is a future
re-entry trigger (see Scope above).
### `actor.movement_speed(handle)`
**Syntax:** `actor.movement_speed(handle) -> number`
**Description:** Convenience read of the actor's `movement_speed`
property (in pixels/sec by convention; caller decides the unit).
## Conventions
- **Position-Format**: composite-table at authoring-time
(`properties = { position = {x, y} }`), flattened by composition to
scalar properties `position.x` and `position.y`. See Phase-A Spec §5
A-Q5 revision (2026-06-09).
- **movement_speed**: pixels per second. dt-scaling applied at the
caller, NOT by `actor.move`.
## References
- Spec: `meta/docs/superpowers/specs/2026-06-09-phase-A-inactive-entities-composition-actor-reentry-design.md`
- Origin: `meta/docs/superpowers/specs/2026-05-10-p0-actor-deferral.md`
(Trigger 1: Composition-Aktivierung)
- Architecture: `meta/docs/architecture/composition-model.md` (Actor as
Item-with-Body-Slot in Phase D; v0.1 is composition-only wrapper)
- ADR-0001 (engine knows verbs, libs bring nouns)
- ADR-0002 (capabilities as derived properties — Walk-Capability deferred)
- ADR-0010 (read/write asymmetry — v0.1 inert-write escape hatch)
- ADR-0038 (API-Doc-Convention)