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
|
||||
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
|
||||
**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
|
||||
|
||||
> **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**
|
||||
> (ADR-0055 amendment). The `form` role-tag mixed geometry, identity and
|
||||
> 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
|
||||
-- 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
|
||||
-- geometry, identity and capability. Its functional members became
|
||||
-- affordances; its identity members (a "tree") stay template-id.
|
||||
@@ -86,10 +92,13 @@ return function(c)
|
||||
-- ---- recipes (match by CAPABILITY, not by item id) --------------
|
||||
-- knap: any KNAPPABLE stone + a HAMMERING tool -> a cutting edge.
|
||||
-- (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",
|
||||
inputs = { { match = { ["affordance.knappable"] = true }, count = 1 } },
|
||||
tools = { { match = { ["affordance.hammering"] = true } } },
|
||||
outputs = { { template = "sharp_stone", count = 1 } },
|
||||
grants = { ["skill.knapping"] = 1 },
|
||||
name = "Knap Sharp Stone",
|
||||
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)." }
|
||||
|
||||
-- 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",
|
||||
inputs = {
|
||||
{ match = { ["affordance.cutting"] = true }, count = 1 }, -- head (a sharp stone)
|
||||
{ match = { ["affordance.leverage"] = true }, count = 1 }, -- haft
|
||||
{ match = { ["affordance.binding"] = true }, count = 1 }, -- binding
|
||||
},
|
||||
requires = { ["skill.knapping"] = ">=3" },
|
||||
outputs = { { template = "stone_axe", count = 1 } },
|
||||
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.
|
||||
c.R{ id = "break_branch",
|
||||
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "vagrant-skeleton",
|
||||
"version": "0.20.0",
|
||||
"version": "0.21.0",
|
||||
"kind": "module",
|
||||
"api": "^0.1",
|
||||
"ci_frames": 60,
|
||||
@@ -26,7 +26,7 @@
|
||||
{"id": "lib-core.puppet", "version": "0.5.0"},
|
||||
{"id": "lib-core.interaction", "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-asset.prototype-subterrain", "version": "0.2.0"},
|
||||
{"id": "lib-asset.prototype-blob-geom", "version": "0.1.0"},
|
||||
|
||||
Reference in New Issue
Block a user