crafting: actor-precondition (requires) + reward (grants) — Skills half of Phase H (ADR-0056)
lib-core.crafting v0.4.0: two optional domain-free recipe fields.
- `requires`: a match-table evaluated against ctx.actor (not container items) — a hard gate. Failing -> {ok=false, error="requires_unmet", unmet={keys}}; fail-closed when ctx.actor is nil. Same predicate machinery as slots; distinct from is_known (discovery vs lock).
- `grants`: property->number reward, returned as `granted` from craft(); the consuming MODULE applies it to the actor (crafting stays container-scoped). Crafting knows no 'skill' vocabulary (ADR-0001).
vagrant-skeleton v0.21.0: player-actor carries skill.knapping (entity property, Phase-A lesson); knap_sharp_stone grants knapping XP, craft_stone_axe requires knapping>=3; module applies granted; skill-locked recipes show 'Skill too low'. Gate->earn->unlock loop headless-verified (axe locked @0 requires_unmet -> 3 knaps -> unlocks).
Quality-band reserved (Phase J): the requires threshold = the future quality floor (no re-authoring). Docs synced: ADR-0056, crafting-model.md (v0.4.0 + Actor-Precondition section), libraries.md, crafting README, vagrant README.
This commit is contained in:
13
README.md
13
README.md
@@ -8,11 +8,20 @@ callback, foot-body-orientation ("tactical twist"), scale-deformation walk
|
|||||||
cycle, and inheritance-decoupled foot sprites. End-to-end inventory pickup/drop
|
cycle, and inheritance-decoupled foot sprites. End-to-end inventory pickup/drop
|
||||||
demo: pick up a rock into a backpack container, drop it back into the world.
|
demo: pick up a rock into a backpack container, drop it back into the world.
|
||||||
|
|
||||||
**Version:** 0.20.0
|
**Version:** 0.21.0
|
||||||
**Module-ID:** vagrant-skeleton
|
**Module-ID:** vagrant-skeleton
|
||||||
**Requires:** lib-core.input >=0.4.0, lib-core.camera >=0.3.0, lib-core.render >=0.2.0, lib-core.maps >=0.1.2, lib-core.puppet >=0.4.4, lib-core.interaction >=0.1.0, lib-core.composition >=0.3.0, lib-core.actor >=0.1.0, lib-core.inventory-list >=0.1.0, lib-core.panel >=0.2.0, lib-core.inventory-list-display >=0.1.0, lib-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=0.1.0, lib-core.crafting >=0.3.0, lib-core.crafting-display >=0.2.0, lib-asset.prototype-subterrain >=0.2.0, lib-asset.prototype-tc-basics >=0.1.0
|
**Requires:** lib-core.input >=0.4.0, lib-core.camera >=0.3.0, lib-core.render >=0.2.0, lib-core.maps >=0.1.2, lib-core.puppet >=0.4.4, lib-core.interaction >=0.1.0, lib-core.composition >=0.3.0, lib-core.actor >=0.1.0, lib-core.inventory-list >=0.1.0, lib-core.panel >=0.2.0, lib-core.inventory-list-display >=0.1.0, lib-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=0.1.0, lib-core.crafting >=0.4.0, lib-core.crafting-display >=0.2.0, lib-asset.prototype-subterrain >=0.2.0, lib-asset.prototype-tc-basics >=0.1.0
|
||||||
**Tags:** test-chamber, puppet, walking-sim, interaction
|
**Tags:** test-chamber, puppet, walking-sim, interaction
|
||||||
|
|
||||||
|
> **v0.21.0:** **Skill-gating (crafting v0.4.0 / ADR-0056).** Recipes can now gate
|
||||||
|
> on and reward the ACTOR: the player-actor carries `skill.knapping` (an entity
|
||||||
|
> property), `knap_sharp_stone` **grants** knapping-XP, and `craft_stone_axe`
|
||||||
|
> **requires** `skill.knapping>=3`. The module applies the craft's returned
|
||||||
|
> `granted` to the actor. Demonstrates the gate→earn→unlock loop (gate downstream,
|
||||||
|
> grant upstream). Headless-verified: axe locked at skill 0 (`requires_unmet`) →
|
||||||
|
> 3 knaps → axe unlocks. Skill-locked recipes show "Skill too low" instead of a
|
||||||
|
> generic failure.
|
||||||
|
>
|
||||||
> **v0.20.0:** **`form` retired → `affordance` is the one capability axis**
|
> **v0.20.0:** **`form` retired → `affordance` is the one capability axis**
|
||||||
> (ADR-0055 amendment). The `form` role-tag mixed geometry, identity and
|
> (ADR-0055 amendment). The `form` role-tag mixed geometry, identity and
|
||||||
> capability; it is gone. Its functional members folded into `affordance.*`
|
> capability; it is gone. Its functional members folded into `affordance.*`
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
-- `inputs` and non-consumed `tools`; the inputs/tools split (not a separate
|
-- `inputs` and non-consumed `tools`; the inputs/tools split (not a separate
|
||||||
-- axis) says whether the item is used up.
|
-- axis) says whether the item is used up.
|
||||||
--
|
--
|
||||||
|
-- ACTOR-SIDE (crafting v0.4 / ADR-0056): a recipe may also gate on / reward the
|
||||||
|
-- ACTOR, not the ingredients:
|
||||||
|
-- * `requires = { ["skill.knapping"]=">=3" }` — hard gate vs ctx.actor
|
||||||
|
-- * `grants = { ["skill.knapping"]=1 }` — XP given on successful craft
|
||||||
|
-- Same predicate machine; the module applies `grants` to the actor.
|
||||||
|
--
|
||||||
-- `form` was RETIRED (ADR-0055 amendment): a physical-shape axis mixed
|
-- `form` was RETIRED (ADR-0055 amendment): a physical-shape axis mixed
|
||||||
-- geometry, identity and capability. Its functional members became
|
-- geometry, identity and capability. Its functional members became
|
||||||
-- affordances; its identity members (a "tree") stay template-id.
|
-- affordances; its identity members (a "tree") stay template-id.
|
||||||
@@ -86,10 +92,13 @@ return function(c)
|
|||||||
-- ---- recipes (match by CAPABILITY, not by item id) --------------
|
-- ---- recipes (match by CAPABILITY, not by item id) --------------
|
||||||
-- knap: any KNAPPABLE stone + a HAMMERING tool -> a cutting edge.
|
-- knap: any KNAPPABLE stone + a HAMMERING tool -> a cutting edge.
|
||||||
-- (rock is knappable; sharp_stone is NOT → no self-recursion.)
|
-- (rock is knappable; sharp_stone is NOT → no self-recursion.)
|
||||||
|
-- Skill-ungated but GRANTS knapping XP (ADR-0056) — the accessible "grind"
|
||||||
|
-- recipe that bootstraps the gated craft_stone_axe below.
|
||||||
c.R{ id = "knap_sharp_stone",
|
c.R{ id = "knap_sharp_stone",
|
||||||
inputs = { { match = { ["affordance.knappable"] = true }, count = 1 } },
|
inputs = { { match = { ["affordance.knappable"] = true }, count = 1 } },
|
||||||
tools = { { match = { ["affordance.hammering"] = true } } },
|
tools = { { match = { ["affordance.hammering"] = true } } },
|
||||||
outputs = { { template = "sharp_stone", count = 1 } },
|
outputs = { { template = "sharp_stone", count = 1 } },
|
||||||
|
grants = { ["skill.knapping"] = 1 },
|
||||||
name = "Knap Sharp Stone",
|
name = "Knap Sharp Stone",
|
||||||
description = "Strike a stone with a hammerstone to flake off a sharp edge." }
|
description = "Strike a stone with a hammerstone to flake off a sharp edge." }
|
||||||
|
|
||||||
@@ -115,15 +124,18 @@ return function(c)
|
|||||||
description = "Lash a stone to a haft for a pounding tool (enables knapping)." }
|
description = "Lash a stone to a haft for a pounding tool (enables knapping)." }
|
||||||
|
|
||||||
-- axe: a CUTTING head + a LEVERAGE haft + a BINDING.
|
-- axe: a CUTTING head + a LEVERAGE haft + a BINDING.
|
||||||
|
-- Skill-GATED (ADR-0056): needs knapping >=3, earned by knap_sharp_stone.
|
||||||
|
-- Demonstrates the gate+earn+unlock loop (gate downstream, grant upstream).
|
||||||
c.R{ id = "craft_stone_axe",
|
c.R{ id = "craft_stone_axe",
|
||||||
inputs = {
|
inputs = {
|
||||||
{ match = { ["affordance.cutting"] = true }, count = 1 }, -- head (a sharp stone)
|
{ match = { ["affordance.cutting"] = true }, count = 1 }, -- head (a sharp stone)
|
||||||
{ match = { ["affordance.leverage"] = true }, count = 1 }, -- haft
|
{ match = { ["affordance.leverage"] = true }, count = 1 }, -- haft
|
||||||
{ match = { ["affordance.binding"] = true }, count = 1 }, -- binding
|
{ match = { ["affordance.binding"] = true }, count = 1 }, -- binding
|
||||||
},
|
},
|
||||||
outputs = { { template = "stone_axe", count = 1 } },
|
requires = { ["skill.knapping"] = ">=3" },
|
||||||
|
outputs = { { template = "stone_axe", count = 1 } },
|
||||||
name = "Haft Stone Axe",
|
name = "Haft Stone Axe",
|
||||||
description = "Bind a sharp stone to a haft with cordage." }
|
description = "Bind a sharp stone to a haft with cordage. (Needs knapping skill 3.)" }
|
||||||
|
|
||||||
-- break: a HEAVY leverage-thing (branch, not a light stick) -> two sticks.
|
-- break: a HEAVY leverage-thing (branch, not a light stick) -> two sticks.
|
||||||
c.R{ id = "break_branch",
|
c.R{ id = "break_branch",
|
||||||
|
|||||||
26
init.lua
26
init.lua
@@ -239,6 +239,10 @@ composition.define_template{
|
|||||||
properties = {
|
properties = {
|
||||||
position = {x = 0, y = 0},
|
position = {x = 0, y = 0},
|
||||||
movement_speed = 120, -- WALK_SPEED_NORMAL
|
movement_speed = 120, -- WALK_SPEED_NORMAL
|
||||||
|
-- Skill as an actor entity-property (ADR-0056 / Phase-A lesson: NOT a
|
||||||
|
-- Lua-local, so the derivation machinery + get_property reach it).
|
||||||
|
-- Raw XP counter; recipe `requires`/`grants` gate/reward against it.
|
||||||
|
["skill.knapping"] = 0,
|
||||||
},
|
},
|
||||||
tags = {}, -- intentionally untagged: not renderable
|
tags = {}, -- intentionally untagged: not renderable
|
||||||
}
|
}
|
||||||
@@ -492,6 +496,22 @@ local function update_lower_anim(moving, move_x, move_y)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Apply a craft's `granted` rewards (ADR-0056) to the actor: additive numeric
|
||||||
|
-- accumulation (skill XP, etc.). crafting returns the reward; the module owns
|
||||||
|
-- the actor-write (crafting stays container-scoped). Granted keys must be
|
||||||
|
-- declared actor properties (see the vagrant_player template).
|
||||||
|
local function apply_grants(actor_handle, granted)
|
||||||
|
if not granted then return end
|
||||||
|
for key, amount in pairs(granted) do
|
||||||
|
local cur = actor_handle:get_property(key) or 0
|
||||||
|
actor_handle:set_property(key, cur + amount)
|
||||||
|
if CI_FRAME_PERF then
|
||||||
|
engine.print(string.format("vagrant: grant %s +%s -> %s",
|
||||||
|
key, tostring(amount), tostring(cur + amount)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function M.init(ctx)
|
function M.init(ctx)
|
||||||
local aliases = engine.module.asset_aliases()
|
local aliases = engine.module.asset_aliases()
|
||||||
|
|
||||||
@@ -910,6 +930,7 @@ function M.init(ctx)
|
|||||||
local r = crafting.craft(recipe_id, c.container,
|
local r = crafting.craft(recipe_id, c.container,
|
||||||
{ actor = state.actor })
|
{ actor = state.actor })
|
||||||
if r.ok then
|
if r.ok then
|
||||||
|
apply_grants(state.actor, r.granted)
|
||||||
local recipe = crafting.get_recipe(recipe_id)
|
local recipe = crafting.get_recipe(recipe_id)
|
||||||
notify.post{
|
notify.post{
|
||||||
tags = {"craft", "event"},
|
tags = {"craft", "event"},
|
||||||
@@ -923,6 +944,8 @@ function M.init(ctx)
|
|||||||
local msg
|
local msg
|
||||||
if r.error == "missing_inputs" then
|
if r.error == "missing_inputs" then
|
||||||
msg = "Missing inputs"
|
msg = "Missing inputs"
|
||||||
|
elseif r.error == "requires_unmet" then
|
||||||
|
msg = "Skill too low: " .. table.concat(r.unmet or {}, ", ")
|
||||||
else
|
else
|
||||||
msg = "Cannot craft: " .. tostring(r.error)
|
msg = "Cannot craft: " .. tostring(r.error)
|
||||||
end
|
end
|
||||||
@@ -993,6 +1016,7 @@ function M.init(ctx)
|
|||||||
local r = crafting.craft(recipe_id, ctx_inner.locale,
|
local r = crafting.craft(recipe_id, ctx_inner.locale,
|
||||||
{ actor = state.actor, at_workbench = true })
|
{ actor = state.actor, at_workbench = true })
|
||||||
if r.ok then
|
if r.ok then
|
||||||
|
apply_grants(state.actor, r.granted)
|
||||||
local recipe = crafting.get_recipe(recipe_id)
|
local recipe = crafting.get_recipe(recipe_id)
|
||||||
notify.post{
|
notify.post{
|
||||||
tags = {"craft", "event"},
|
tags = {"craft", "event"},
|
||||||
@@ -1007,6 +1031,8 @@ function M.init(ctx)
|
|||||||
local msg
|
local msg
|
||||||
if r.error == "missing_inputs" then
|
if r.error == "missing_inputs" then
|
||||||
msg = "Missing inputs"
|
msg = "Missing inputs"
|
||||||
|
elseif r.error == "requires_unmet" then
|
||||||
|
msg = "Skill too low: " .. table.concat(r.unmet or {}, ", ")
|
||||||
else
|
else
|
||||||
msg = "Cannot craft: " .. tostring(r.error)
|
msg = "Cannot craft: " .. tostring(r.error)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "vagrant-skeleton",
|
"id": "vagrant-skeleton",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"kind": "module",
|
"kind": "module",
|
||||||
"api": "^0.1",
|
"api": "^0.1",
|
||||||
"ci_frames": 60,
|
"ci_frames": 60,
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{"id": "lib-core.puppet", "version": "0.5.0"},
|
{"id": "lib-core.puppet", "version": "0.5.0"},
|
||||||
{"id": "lib-core.interaction", "version": "0.1.0"},
|
{"id": "lib-core.interaction", "version": "0.1.0"},
|
||||||
{"id": "lib-core.inventory-list", "version": "0.1.0"},
|
{"id": "lib-core.inventory-list", "version": "0.1.0"},
|
||||||
{"id": "lib-core.crafting", "version": "0.3.0"},
|
{"id": "lib-core.crafting", "version": "0.4.0"},
|
||||||
{"id": "lib-core.crafting-display", "version": "0.3.0"},
|
{"id": "lib-core.crafting-display", "version": "0.3.0"},
|
||||||
{"id": "lib-asset.prototype-subterrain", "version": "0.2.0"},
|
{"id": "lib-asset.prototype-subterrain", "version": "0.2.0"},
|
||||||
{"id": "lib-asset.prototype-blob-geom", "version": "0.1.0"},
|
{"id": "lib-asset.prototype-blob-geom", "version": "0.1.0"},
|
||||||
|
|||||||
Reference in New Issue
Block a user