diff --git a/README.md b/README.md index 654f049..17aff56 100644 --- a/README.md +++ b/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.*` diff --git a/content/stone_age.lua b/content/stone_age.lua index 4a4b666..64598ec 100644 --- a/content/stone_age.lua +++ b/content/stone_age.lua @@ -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 }, - outputs = { { template = "stone_axe", count = 1 } }, + 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", diff --git a/init.lua b/init.lua index e71257d..9e245d3 100644 --- a/init.lua +++ b/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 diff --git a/manifest.module b/manifest.module index 540f85b..05c6da0 100644 --- a/manifest.module +++ b/manifest.module @@ -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"},