Files
Axel Meyer a48cfb9abf crafting v0.5.0 + composition v0.4.0: Quality & Condition (Phase J)
lib-core.composition v0.4.0: un-defer quality/condition as inert numeric
properties (loud-error removed); composition stores them without semantics.

lib-core.crafting v0.5.0: quality-block (multi-contributor product-quality
formula: skill-band with min=requires-floor + named ingredient/tool qualities),
optional slot name, tool wear_per_use (condition decrement + wear report).
affordance stays boolean. Additive to v0.4.0.

vagrant-skeleton v0.22.0: branch quality-band RNG at spawn; stone_hammer
condition=1.0; knap+axe quality-blocks; hammer wears out. Headless-verified 20/20.

Design: meta/docs/design/2026-08-03-crafting-quality-condition-design.md.
Docs synced: crafting-model.md, composition-model.md, libraries.md, READMEs.
2026-08-03 09:14:54 +00:00

8.8 KiB

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.4.0 Lib-ID: lib-core.composition Requires: (none — uses engine entity.*, domain.contribute only) Tags: composition, entity, template, tag-index

Topology

graph LR
  this["lib-core.composition"]
  engine["engine.*"]
  this --> engine

Scope (v0.4.0)

Template-Only-Subset + set_tag + container declaration + template-id reverse-lookup.

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)
  • template_of(entity) — v0.3: read back the template-id an entity was created from
  • 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

Supported (v0.4.0 — Phase J):

  • quality, condition — accepted as ordinary inert numeric properties (template default or per-instance override). composition stores them and knows NO semantics (no clamp/default/decay/RNG); producers set the values (crafting quality-formula, wear-decrement, module acquisition roll).

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
  • parent: template-inheritance → no consumer yet

API

composition.define_template(def)

Syntax: composition.define_template({id: string, properties?: table, tags?: array}) -> void

Example:

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:

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:

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:

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:

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.template_of(entity)

Syntax: composition.template_of(entity) -> string or nil

Example:

local sign = composition.create{ template = "sign" }
local tpl_id = composition.template_of(sign)
-- tpl_id == "sign"

local raw = entity.create()
local none = composition.template_of(raw)
-- none == nil  (raw entities are not composition-managed)

Description: Returns the template-id this entity was created from, or nil if the entity is not composition-managed (e.g. a raw entity.create() handle, or an entity whose composition meta-record has already been cleared via destroy). Loud-Error if entity is nil. Used by crafting / inventory / recipe code that needs to recover the template-id without round-tripping through tags.

composition.set_tag(entity, tag, present)

Syntax: composition.set_tag(entity, tag: string, present: boolean) -> void

Example:

-- 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