crafting v0.5.0 + composition v0.4.0: Quality & Condition (Phase J)
lib-core.composition v0.4.0: un-defer quality/condition as inert numeric properties (loud-error removed); composition stores them without semantics. lib-core.crafting v0.5.0: quality-block (multi-contributor product-quality formula: skill-band with min=requires-floor + named ingredient/tool qualities), optional slot name, tool wear_per_use (condition decrement + wear report). affordance stays boolean. Additive to v0.4.0. vagrant-skeleton v0.22.0: branch quality-band RNG at spawn; stone_hammer condition=1.0; knap+axe quality-blocks; hammer wears out. Headless-verified 20/20. Design: meta/docs/design/2026-08-03-crafting-quality-condition-design.md. Docs synced: crafting-model.md, composition-model.md, libraries.md, READMEs.
This commit is contained in:
16
README.md
16
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`
|
||||
|
||||
@@ -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.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.)" }
|
||||
|
||||
18
init.lua
18
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
|
||||
|
||||
@@ -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"},
|
||||
|
||||
Reference in New Issue
Block a user