# 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 ```mermaid graph LR this["lib-core.composition"] engine["engine.*"] this --> engine ``` ## 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 - `get_container(entity)` — v0.2: read back the container block from entity's template **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.get_container(entity)` **Syntax:** `composition.get_container(entity) -> table or nil` **Description:** Returns the `container` block from the entity's template (e.g. `{kind="list"}`), or `nil` if the template declared no `container` block. Loud-Error if `entity` was not created by composition. Used by `lib-core.inventory-list` to validate container entities at runtime. ### `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 |