feat(crafting): lib-core.crafting v0.3.0 — property-constraint slots + non-consumed tools + multi-output (ADR-0055); vagrant-skeleton debug-drive hook for headless scenario testing
This commit is contained in:
99
README.md
99
README.md
@@ -1,10 +1,12 @@
|
||||
# lib-core.crafting
|
||||
|
||||
Recipe Registry + Match-Check + Craft Action. Headless data + logic layer.
|
||||
Recipes reference item-template-ids; inputs are consumed from the locale's
|
||||
source containers, outputs are placed into the locale's sink container.
|
||||
A recipe SLOT is a property-constraint over an item's (possibly derived)
|
||||
properties; template-id is the trivial constraint `{template="rock"}`. Inputs
|
||||
are consumed from the locale's source containers, non-consumed tools are
|
||||
presence-checked, and outputs are placed into the locale's sink container.
|
||||
|
||||
**Version:** 0.2.0
|
||||
**Version:** 0.3.0
|
||||
**Lib-ID:** lib-core.crafting
|
||||
**Requires:** `lib-core.composition` 0.3.0, `lib-core.inventory-list` 0.1.0
|
||||
**Tags:** crafting, recipe, registry
|
||||
@@ -20,61 +22,80 @@ graph LR
|
||||
this --> inventory
|
||||
```
|
||||
|
||||
## Scope (v0.2.0)
|
||||
## Scope (v0.3.0 — ADR-0055)
|
||||
|
||||
Solid-Cut: recipe registry + match-check + craft-action + per-recipe
|
||||
discovery hook. Multi-Source / Single-Sink locale (Form 2) plus
|
||||
bw-compat bare-handle locale (Form 1). Count-form recipe-inputs.
|
||||
Property-constraint recipe slots. A recipe slot matches items by a `match`
|
||||
table of property-constraints (AND-combined), evaluated via `get_property`;
|
||||
the pseudo-key `template` maps to `composition.template_of`, so old template-id
|
||||
recipes are the trivial constraint through the SAME match-loop. Adds
|
||||
non-consumed `tools` (presence checks) and multi-output. Multi-Source /
|
||||
Single-Sink locale (Form 2) plus bw-compat bare-handle locale (Form 1).
|
||||
|
||||
**Supported:**
|
||||
- `define_recipe{id, inputs, output, name?, description?, is_known?}`
|
||||
- `list_recipes()`, `get_recipe(id)`
|
||||
- `is_known(recipe_id, ctx)` — per-recipe discovery gating
|
||||
- `can_craft(recipe_id, locale, ctx)` — non-mutating availability check
|
||||
- `craft(recipe_id, locale, ctx)` — mutating action
|
||||
- `locale` parameter accepts either a bare container-entity (Form 1,
|
||||
bw-compat) or an explicit `{sources={...}, sink=...}` table (Form 2)
|
||||
- `define_recipe{id, inputs, tools?, outputs|output, name?, description?, is_known?}`
|
||||
- `inputs` entries: `{template=..., count}` **or** `{match={k=v,...}, count}` (consumed)
|
||||
- `tools` entries: `{template=...}` **or** `{match={...}}` (NON-consumed presence check)
|
||||
- `outputs` array (plural) **or** `output` singular (bw-compat)
|
||||
- Constraint values: exact string/number/bool, or comparison string
|
||||
`">5"` / `">=0.2"` / `"<10"` / `"<=1"` / `"==x"` (numeric; string for `==`)
|
||||
- `list_recipes()`, `get_recipe(id)`, `is_known(recipe_id, ctx)`
|
||||
- `can_craft(recipe_id, locale, ctx)` — non-mutating; `craft(...)` — mutating
|
||||
- `locale`: bare container-entity (Form 1) or `{sources={...}, sink=...}` (Form 2)
|
||||
- Result `error ∈ {unknown_recipe, missing_inputs, missing_tools}`
|
||||
|
||||
**Deferred (Phase D+ / E):**
|
||||
- Workpiece-Model / multi-step crafting / Batch / Time-coupled craft
|
||||
- Tool-Requirements (Hammer-required, Workbench-required)
|
||||
**Deferred (Phase E+):**
|
||||
- Skill-Checks (skill-property-min for recipe)
|
||||
- Property-driven recipes (`composition.iron ≥ 0.8`) — Phase E
|
||||
- Multi-Container-UI (input from A, output to B)
|
||||
- Action-Property-Tags (`crafting.cutting`, `crafting.hammering`) — Domain-Lib substrate
|
||||
- Workpiece-Model / multi-step / Batch / Time-coupled craft
|
||||
- True bipartite input↔item matching (v0.3 uses greedy first-fit — a solvable
|
||||
recipe where one item satisfies two slots can be missed; no stone-age
|
||||
recipe hits this)
|
||||
- `density × volume` derived mass on the material substrate (v0.3 reads
|
||||
whatever property keys the recipe names)
|
||||
|
||||
## API
|
||||
|
||||
### `crafting.define_recipe(def)`
|
||||
|
||||
**Syntax:** `crafting.define_recipe({id, inputs, output, name?, description?, is_known?}) -> void`
|
||||
**Syntax:** `crafting.define_recipe({id, inputs, tools?, outputs|output, name?, description?, is_known?}) -> void`
|
||||
|
||||
**Example:**
|
||||
**Example (template-id, bw-compat):**
|
||||
```lua
|
||||
crafting.define_recipe{
|
||||
id = "rock_pick",
|
||||
inputs = {
|
||||
{ template = "rock", count = 1 },
|
||||
{ template = "stick", count = 1 },
|
||||
},
|
||||
output = { template = "rock_pick", count = 1 },
|
||||
name = "Stone Pick",
|
||||
description = "A pick for mining stone.",
|
||||
is_known = function(ctx) return true end,
|
||||
inputs = { { template = "rock", count = 1 }, { template = "stick", count = 1 } },
|
||||
output = { template = "rock_pick", count = 1 },
|
||||
name = "Stone Pick",
|
||||
}
|
||||
```
|
||||
|
||||
**Description:** Registers a recipe under `id`. `inputs` is a non-empty
|
||||
array of `{template, count}` entries; `output` is a single
|
||||
`{template, count}`. `name` / `description` are optional player-facing
|
||||
display strings. `is_known(ctx) -> bool` is an optional discovery hook;
|
||||
default is `function() return true end`. The hook receives the same `ctx`
|
||||
table the caller passes to `is_known` / `can_craft` / `craft`.
|
||||
**Example (property-constraint + tool + multi-output):**
|
||||
```lua
|
||||
crafting.define_recipe{
|
||||
id = "saw_planks",
|
||||
inputs = { { match = { category = "wood", mass = ">5" }, count = 1 } },
|
||||
tools = { { match = { ["affordance.cutting"] = true } } }, -- NOT consumed
|
||||
outputs = { { template = "plank", count = 4 } },
|
||||
name = "Saw Planks",
|
||||
}
|
||||
```
|
||||
|
||||
**Description:** Registers a recipe under `id`.
|
||||
- `inputs` (required, non-empty): consumed slots. Each entry has a `count` plus
|
||||
either `template = "<id>"` or `match = {key=constraint, ...}`.
|
||||
- `tools` (optional): non-consumed presence checks. Each entry is `{template}`
|
||||
or `{match}` (no count — a matching item need only be present).
|
||||
- `outputs` (array) or `output` (single, bw-compat): items created into the sink.
|
||||
- A `match` constraint value is an exact string/number/bool, or a comparison
|
||||
string `">5"` / `">=0.2"` / `"<10"` / `"<=1"` / `"==x"`. Keys AND-combine.
|
||||
The pseudo-key `template` matches `composition.template_of`; all other keys
|
||||
match `get_property(key)` (read safely — an item lacking the property fails).
|
||||
- `name` / `description` optional display strings; `is_known(ctx) -> bool`
|
||||
optional discovery hook (default `true`).
|
||||
|
||||
Loud `error(...)` on: non-table `def`; missing / non-string / empty `id`;
|
||||
duplicate `id`; non-table / empty `inputs`; any input or output entry
|
||||
that isn't a `{template: string, count: positive int}` table; non-table
|
||||
`output`; non-function `is_known` if provided.
|
||||
duplicate `id`; empty `inputs`; a slot with neither `template` nor `match`;
|
||||
an empty `match`; bad `count`; missing both `outputs` and `output`;
|
||||
non-function `is_known`.
|
||||
|
||||
Error messages follow the pattern `"crafting.define_recipe '<id>': <reason>"`
|
||||
so test-suites can pattern-match.
|
||||
|
||||
Reference in New Issue
Block a user