diff --git a/README.md b/README.md index 17aff56..919463d 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,23 @@ 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.21.0 +**Version:** 0.22.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.4.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.4.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.5.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.22.0:** **Quality & Condition (crafting v0.5.0 / Phase J).** Items now carry +> a `quality` (build-grade) and tools a `condition` (wear). A storm-broken `branch` +> rolls its `quality` in [0.5,1.0] at spawn (producer sets quality; the RNG lives in +> the module, never Core). `knap_sharp_stone` computes the edge's quality from +> skill-band + rock grade + hammer `quality×condition`, and **wears** the hammer 0.2 +> per knap (5 knaps → a fully-worn hammer stops qualifying — the recipe asks for +> `condition > 0`). `craft_stone_axe`'s quality reads skill (floor = the `requires` +> gate 3) + head + haft, so a mediocre branch caps the axe even at max skill. +> `affordance.*` stays boolean; quality/condition are orthogonal scalars. Headless- +> verified (20/20): formula values, hammer wear-out, mediocre-haft cap, neutral-1.0 +> default for un-graded items. +> > **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` diff --git a/content/stone_age.lua b/content/stone_age.lua index 64598ec..0058c8d 100644 --- a/content/stone_age.lua +++ b/content/stone_age.lua @@ -45,6 +45,10 @@ return function(c) c.T{ id = "branch", name = "Branch", desc = "A heavy springy branch. Snap it into sticks.", weight = 1.5, vol = 0.006, material = { wood = 1.0 }, affordance = { leverage = true }, -- a haft candidate, but heavy + -- Phase J (design 2026-08-03 §5): a storm-broken branch is of variable + -- grade — the world rolls its `quality` in [0.5,1.0] at spawn. Used as a + -- haft, this variance flows into the axe's quality (craft_stone_axe). + quality_band = { min = 0.5, max = 1.0 }, atlas = "tc", uv = "log_pile1", scale = 0.85 } c.T{ id = "log", name = "Log", desc = "A heavy wooden log. Saw it into planks.", @@ -83,6 +87,10 @@ return function(c) c.T{ id = "stone_hammer", name = "Stone Hammer", desc = "A stone lashed to a haft. Pounds and knaps.", weight = 1.5, vol = 0.001, material = { stone = 0.6, wood = 0.3, fiber = 0.1 }, + -- Phase J: a tool tracks `condition` (fresh = 1.0). Each knap wears it + -- (knap_sharp_stone.tools.wear_per_use); at condition 0 it stops + -- qualifying (the recipe's tool match asks for condition > 0). + condition = 1.0, affordance = { hammering = true }, atlas = "tc", uv = "spade" } c.T{ id = "sharpened_stick", name = "Sharpened Stick", desc = "A stick whittled to a point. A crude spear.", @@ -95,10 +103,20 @@ return function(c) -- 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 } } }, + inputs = { { match = { ["affordance.knappable"] = true }, count = 1, name = "stone" } }, + -- Phase J: the hammer must have condition > 0, and wears 0.2 per knap → + -- a fresh hammer lasts 5 knaps, then stops qualifying (missing_tools). + tools = { { match = { ["affordance.hammering"] = true, condition = ">0" }, + name = "hammer", wear_per_use = 0.2 } }, outputs = { { template = "sharp_stone", count = 1 } }, grants = { ["skill.knapping"] = 1 }, + -- Product quality = skill (0..5 band, min 0 since ungated) + stone grade + -- + hammer grade×condition. A worn hammer makes worse edges. + quality = { contributors = { + { skill = "skill.knapping", max = 5, weight = 0.3 }, + { ingredient = "stone", weight = 0.4 }, + { tool = "hammer", weight = 0.3 }, + } }, name = "Knap Sharp Stone", description = "Strike a stone with a hammerstone to flake off a sharp edge." } @@ -128,11 +146,19 @@ return function(c) -- 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 + { match = { ["affordance.cutting"] = true }, count = 1, name = "head" }, -- a sharp stone + { match = { ["affordance.leverage"] = true }, count = 1, name = "haft" }, -- stick OR branch + { match = { ["affordance.binding"] = true }, count = 1 }, -- binding }, requires = { ["skill.knapping"] = ">=3" }, + -- Product quality = skill (band min 3 = the requires-floor, no re-author, + -- max 5) + head grade (the knapped edge) + haft grade (raw-branch variance). + -- A mediocre branch caps the axe even at max skill. + quality = { contributors = { + { skill = "skill.knapping", max = 5, weight = 0.4 }, + { ingredient = "head", weight = 0.3 }, + { ingredient = "haft", weight = 0.3 }, + } }, outputs = { { template = "stone_axe", count = 1 } }, name = "Haft Stone Axe", description = "Bind a sharp stone to a haft with cordage. (Needs knapping skill 3.)" } diff --git a/init.lua b/init.lua index 9e245d3..ff5f83f 100644 --- a/init.lua +++ b/init.lua @@ -654,6 +654,11 @@ function M.init(ctx) rock_pick = { atlas = tc_atlas_png, uv = tc_sprites.spade }, rock_pile = { atlas = stone_atlas_png, uv = stone_uvs.slot_13_solid }, } + -- Acquisition quality-bands (Phase J / design 2026-08-03 §5): template-id -> + -- {min,max}. content.S rolls a per-instance `quality` within the band when a + -- raw resource is spawned — the PRODUCER (the world) sets quality; the RNG + -- lives here in the module, never in Core. + local QUALITY_BANDS = {} local content = {} function content.T(d) local a = ATLAS[d.atlas] or error("content.T '" .. tostring(d.id) .. "': unknown atlas '" .. tostring(d.atlas) .. "'") @@ -676,12 +681,23 @@ function M.init(ctx) -- into template-id / material+weight. for m, v in pairs(d.material or {}) do props["composition." .. m] = v end for k, v in pairs(d.affordance or {}) do props["affordance." .. k] = v end + -- Phase J: a tool may declare a default `condition` (fresh = 1.0); a raw + -- resource may declare a `quality_band` rolled at spawn (see content.S). + if d.condition ~= nil then props.condition = d.condition end + if d.quality_band ~= nil then QUALITY_BANDS[d.id] = d.quality_band end composition.define_template{ id = d.id, properties = props, tags = { "renderable", "item" } } if d.icon ~= false then craft_icons[d.id] = { atlas = a.png, uv = uv } end end function content.R(def) crafting.define_recipe(def) end function content.S(d) - local e = composition.create{ template = d.template, properties = { position = { x = d.x, y = d.y } } } + local props = { position = { x = d.x, y = d.y } } + local band = QUALITY_BANDS[d.template] + if band then + -- Acquisition RNG (design 2026-08-03 §5): the world (producer) sets + -- quality; a storm-broken resource rolls within its band. + props.quality = band.min + math.random() * (band.max - band.min) + end + local e = composition.create{ template = d.template, properties = props } register_pickup(e) return e end diff --git a/manifest.module b/manifest.module index 05c6da0..8c1a3af 100644 --- a/manifest.module +++ b/manifest.module @@ -1,6 +1,6 @@ { "id": "vagrant-skeleton", - "version": "0.21.0", + "version": "0.22.0", "kind": "module", "api": "^0.1", "ci_frames": 60, @@ -15,7 +15,7 @@ {"id": "lib-core.input", "version": "0.4.0"}, {"id": "lib-core.camera", "version": "0.3.0"}, {"id": "lib-core.render", "version": "0.3.0"}, - {"id": "lib-core.composition", "version": "0.3.0"}, + {"id": "lib-core.composition", "version": "0.4.0"}, {"id": "lib-core.actor", "version": "0.1.0"}, {"id": "lib-core.panel", "version": "0.4.0"}, {"id": "lib-core.inventory-list-display", "version": "0.2.0"}, @@ -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.4.0"}, + {"id": "lib-core.crafting", "version": "0.5.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"},