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:
26
init.lua
26
init.lua
@@ -239,6 +239,10 @@ composition.define_template{
|
||||
properties = {
|
||||
position = {x = 0, y = 0},
|
||||
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
|
||||
}
|
||||
@@ -492,6 +496,22 @@ local function update_lower_anim(moving, move_x, move_y)
|
||||
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)
|
||||
local aliases = engine.module.asset_aliases()
|
||||
|
||||
@@ -910,6 +930,7 @@ function M.init(ctx)
|
||||
local r = crafting.craft(recipe_id, c.container,
|
||||
{ actor = state.actor })
|
||||
if r.ok then
|
||||
apply_grants(state.actor, r.granted)
|
||||
local recipe = crafting.get_recipe(recipe_id)
|
||||
notify.post{
|
||||
tags = {"craft", "event"},
|
||||
@@ -923,6 +944,8 @@ function M.init(ctx)
|
||||
local msg
|
||||
if r.error == "missing_inputs" then
|
||||
msg = "Missing inputs"
|
||||
elseif r.error == "requires_unmet" then
|
||||
msg = "Skill too low: " .. table.concat(r.unmet or {}, ", ")
|
||||
else
|
||||
msg = "Cannot craft: " .. tostring(r.error)
|
||||
end
|
||||
@@ -993,6 +1016,7 @@ function M.init(ctx)
|
||||
local r = crafting.craft(recipe_id, ctx_inner.locale,
|
||||
{ actor = state.actor, at_workbench = true })
|
||||
if r.ok then
|
||||
apply_grants(state.actor, r.granted)
|
||||
local recipe = crafting.get_recipe(recipe_id)
|
||||
notify.post{
|
||||
tags = {"craft", "event"},
|
||||
@@ -1007,6 +1031,8 @@ function M.init(ctx)
|
||||
local msg
|
||||
if r.error == "missing_inputs" then
|
||||
msg = "Missing inputs"
|
||||
elseif r.error == "requires_unmet" then
|
||||
msg = "Skill too low: " .. table.concat(r.unmet or {}, ", ")
|
||||
else
|
||||
msg = "Cannot craft: " .. tostring(r.error)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user