- set_tag(entity, tag, present): adds/removes entity from index_by_tag and entity_meta.tags. Idempotent (double-add and double-remove are no-ops). Loud-Error on entity not created by composition. - define_template now accepts an optional container block. Validates kind="list" (only supported value). Loud-Errors individually on each unimplemented constraint field (weight_max, volume_max, grid, accepts_fluid, accepts_gas, restrictions) and on unknown container keys, preventing silent capability mismatches. - create() copies tpl.tags into entity_meta so per-entity set_tag calls do not mutate the shared template tag-set. - manifest.lib version bumped to 0.2.0. - README and header comment updated to reflect new surface and revised deferred-list.
212 lines
7.2 KiB
Markdown
212 lines
7.2 KiB
Markdown
# lib-core.composition
|
|
|
|
Template + Instantiation + Tag-Index over `engine.entity`. Templates define
|
|
property defaults; `create` spawns an `engine.entity` with template
|
|
defaults + per-instance overrides; entities are indexed by template-id
|
|
and tag for cheap reverse-lookup.
|
|
|
|
**Version:** 0.2.0
|
|
**Lib-ID:** lib-core.composition
|
|
**Requires:** (none — uses engine `entity.*`, `domain.contribute` only)
|
|
**Tags:** composition, entity, template, tag-index
|
|
|
|
## Topology
|
|
|
|
<!-- topology:start (auto-generated; do not edit) -->
|
|
```mermaid
|
|
graph LR
|
|
this["lib-core.composition"]
|
|
engine["engine.*"]
|
|
this --> engine
|
|
```
|
|
<!-- topology:end -->
|
|
|
|
## Scope (v0.2.0)
|
|
|
|
Template-Only-Subset + set_tag + container declaration.
|
|
|
|
**Supported:**
|
|
- `define_template{id, properties, tags}`
|
|
- `define_template{..., container={kind="list"}}` — v0.2: `kind="list"` accepted
|
|
- `create{template, properties}`
|
|
- `list_by_template(id)`, `list_by_tag(tag)`
|
|
- `destroy(entity)`
|
|
- `set_tag(entity, tag, present)` — v0.2: runtime tag add/remove
|
|
|
|
**Deferred (Loud-Error if attempted):**
|
|
- `slots` block → Phase D trigger (Composite Items with Sub-Items)
|
|
- `container` constraint fields (`weight_max`, `volume_max`, `grid`,
|
|
`accepts_fluid`, `accepts_gas`, `restrictions`) → Phase F/G trigger
|
|
- `quality`, `condition` → Phase J trigger (Damage / Wear)
|
|
- `parent:` template-inheritance → no consumer yet
|
|
|
|
## API
|
|
|
|
### `composition.define_template(def)`
|
|
|
|
**Syntax:** `composition.define_template({id: string, properties?: table, tags?: array}) -> void`
|
|
|
|
**Example:**
|
|
```lua
|
|
composition.define_template{
|
|
id = "sign",
|
|
properties = {
|
|
sprite_path = "sprites/sign.png",
|
|
text = "default sign text",
|
|
position = {x = 0, y = 0}, -- auto-flattened to position.x + position.y
|
|
},
|
|
tags = {"renderable"},
|
|
}
|
|
```
|
|
|
|
**Description:** Registers a template under `id`. All `properties` are
|
|
auto-declared as `inert` via `domain.contribute` with type inferred
|
|
from the default-value. Nested tables in `properties` are flattened
|
|
to dotted scalar keys (e.g. `position = {x, y}` → declares `position.x`
|
|
and `position.y` as separate `number` properties). Tag-list registers
|
|
the template in the tag-index used by `list_by_tag`.
|
|
|
|
The optional `container` block declares this template as an item container.
|
|
Only `kind = "list"` is supported in v0.2. Any constraint field
|
|
(`weight_max`, `volume_max`, `grid`, `accepts_fluid`, `accepts_gas`,
|
|
`restrictions`) loud-errors at template-load time (Capability-by-Declaration:
|
|
no constraint is silently ignored).
|
|
|
|
**Example with container:**
|
|
```lua
|
|
composition.define_template{
|
|
id = "backpack",
|
|
properties = {
|
|
sprite_atlas = "sprites/items.png",
|
|
sprite_uv = "0 0 32 32",
|
|
position = {x = 0, y = 0},
|
|
},
|
|
tags = {"renderable"},
|
|
container = { kind = "list" },
|
|
}
|
|
```
|
|
|
|
Loud `error(...)` on: missing/non-string `id`, duplicate `id`,
|
|
non-table `properties`, malformed `tags`, attempted `slots` /
|
|
`quality` / `condition` / `parent:` block (each pointing to its
|
|
re-entry phase); `container.kind` other than `"list"`; any
|
|
unimplemented constraint field in `container`.
|
|
|
|
### `composition.create(spec)`
|
|
|
|
**Syntax:** `composition.create({template: string, properties?: table}) -> entity`
|
|
|
|
**Example:**
|
|
```lua
|
|
local sign = composition.create{
|
|
template = "sign",
|
|
properties = {
|
|
position = {x = 144, y = 200},
|
|
text = "Hier steht: Willkommen im Spine.",
|
|
},
|
|
}
|
|
```
|
|
|
|
**Description:** Spawns an `engine.entity`, applies template-defaults
|
|
shallow-overridden by per-instance `properties`. Nested-table
|
|
overrides (like `position = {x, y}`) are flattened the same way as in
|
|
`define_template`. Per-instance properties that weren't declared by
|
|
the template get declared on-the-fly as `inert`. Entity is registered
|
|
in the template-index + tag-index of the template. Returns the
|
|
`engine.entity` handle.
|
|
|
|
Loud `error(...)` on: non-table `spec`, missing/unknown `template`,
|
|
non-table `properties`.
|
|
|
|
### `composition.list_by_template(id)`
|
|
|
|
**Syntax:** `composition.list_by_template(id: string) -> {entity, ...}`
|
|
|
|
**Example:**
|
|
```lua
|
|
for _, sign in ipairs(composition.list_by_template("sign")) do
|
|
engine.print(sign:get_property("text"))
|
|
end
|
|
```
|
|
|
|
**Description:** Returns a shallow-copied list of all currently-alive
|
|
entities created with the given template-id. Empty list if template
|
|
is unknown or has no instances.
|
|
|
|
### `composition.list_by_tag(tag)`
|
|
|
|
**Syntax:** `composition.list_by_tag(tag: string) -> {entity, ...}`
|
|
|
|
**Example:**
|
|
```lua
|
|
for _, e in ipairs(composition.list_by_tag("renderable")) do
|
|
local x = e:get_property("position.x")
|
|
local y = e:get_property("position.y")
|
|
-- ... render
|
|
end
|
|
```
|
|
|
|
**Description:** Returns all currently-alive entities whose template
|
|
declared `tag` in its `tags` array. Used by `lib-core.render`
|
|
`draw_entities{tag="..."}` (see A.2). Empty list if tag has no entities.
|
|
|
|
### `composition.set_tag(entity, tag, present)`
|
|
|
|
**Syntax:** `composition.set_tag(entity, tag: string, present: boolean) -> void`
|
|
|
|
**Example:**
|
|
```lua
|
|
-- Remove from renderable index (e.g. item picked up into inventory)
|
|
composition.set_tag(item_entity, "renderable", false)
|
|
|
|
-- Restore renderable index (e.g. item dropped back into world)
|
|
composition.set_tag(item_entity, "renderable", true)
|
|
```
|
|
|
|
**Description:** Adds or removes `entity` from the tag-index for `tag`.
|
|
`present=true` inserts the entity (idempotent — calling again with the
|
|
same tag is a no-op). `present=false` removes it (idempotent — calling
|
|
again when already absent is a no-op). Both `entity_meta` and
|
|
`index_by_tag` are updated atomically. Loud-Error if `entity` was not
|
|
created by composition (e.g. a raw `entity.create()` handle). Used by
|
|
`lib-core.inventory` `add`/`remove` to flip the `"renderable"` tag
|
|
when items move between world and inventory.
|
|
|
|
### `composition.destroy(entity)`
|
|
|
|
**Syntax:** `composition.destroy(entity) -> void`
|
|
|
|
**Description:** Removes entity from template-index + tag-index +
|
|
destroys via `engine.entity.destroy`. Idempotent. If entity wasn't
|
|
composition-created, falls through to `entity.destroy`.
|
|
|
|
### `composition.list_templates()`
|
|
|
|
**Syntax:** `composition.list_templates() -> {id, ...}`
|
|
|
|
**Description:** Debug/introspection — returns set of declared
|
|
template-ids.
|
|
|
|
## Conventions
|
|
|
|
- **Property-Value-Types:** number, string, boolean (engine §PROPERTY_*
|
|
catalog). Tables are flattened to dotted scalar keys; arrays inside
|
|
`properties` → loud error.
|
|
- **Position-Format:** stored as two scalar properties `position.x`
|
|
and `position.y` (per Phase-A Spec §5 A-Q5 revision 2026-06-09 —
|
|
engine has no PROPERTY_TABLE).
|
|
- **Tag-Index:** maintained in lib, not in engine. Tag-lookup is O(1)
|
|
by tag, list iteration is O(n) per tag.
|
|
- **`composition.reg_id`:** internal book-keeping property on each
|
|
created entity; used by `destroy` to find the meta-record. Do not
|
|
set or read manually.
|
|
|
|
## Future Phases
|
|
|
|
| Phase | Triggers in Template | Spec |
|
|
|---|---|---|
|
|
| B (Items) | `container={kind="list"}` — accepted in v0.2; constraint fields deferred to F/G | inventory-model.md |
|
|
| D (Composites) | `slots = {...}` | composition-model.md (full Slot-System) |
|
|
| F/G (Constraints) | `weight_max`, `volume_max`, `grid`, `accepts_fluid`, `accepts_gas`, `restrictions` | constraint-model.md |
|
|
| J (Damage/Wear) | `quality`, `condition` | damage-model.md |
|