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.
209 lines
12 KiB
Lua
209 lines
12 KiB
Lua
-- =====================================================================
|
||
-- content/stone_age.lua — T1-modder content pack: Stone-Age items + recipes.
|
||
--
|
||
-- PURE CONTENT via the authoring API `c` (see init.lua's content facade):
|
||
-- c.T{ id, name, desc, weight, vol, material={...}, affordance={...},
|
||
-- atlas="tc"|"stone", uv="<sprite-key>", scale? } -- define an item
|
||
-- c.R{ ...crafting.define_recipe def... } -- define a recipe
|
||
-- c.S{ template, x, y } -- place item in world
|
||
--
|
||
-- ROLE-BASED RECIPES (the intent): a recipe slot asks for the CAPABILITY it
|
||
-- needs, and ANY item that offers that capability qualifies — not a specific
|
||
-- item id. Capabilities are expressed as property-constraints (crafting v0.3 /
|
||
-- ADR-0055 + its 2026-07-31 amendment). Two orthogonal axes carry a recipe:
|
||
-- * `affordance.*` — what an item OFFERS / can DO / what role it fills:
|
||
-- cutting / hammering / chopping / leverage / binding /
|
||
-- spinnable / knappable / piercing … (open vocabulary)
|
||
-- * `composition.*`— material makeup (wood / stone / fiber …)
|
||
-- * `weight` — mass discriminator (light stick vs heavy branch)
|
||
-- e.g. a "haft" = { ["affordance.leverage"]=true } → any stick OR branch OR
|
||
-- pole works. The SAME affordance vocabulary is required by both consumed
|
||
-- `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.
|
||
--
|
||
-- Template-id matching ({ template="tree" }) is ALSO valid — it is just the
|
||
-- trivial one-property constraint — and is used where a recipe genuinely wants
|
||
-- one specific item (see rock_pick/rock_pile in init.lua, and chop_tree below).
|
||
-- Role-based is the default; template-id is one of the options, not the norm.
|
||
-- =====================================================================
|
||
return function(c)
|
||
-- ---- items (each tagged with the CAPABILITIES it offers) ---------
|
||
-- NOTE: `rock` (affordance.knappable) and `stick` (affordance.leverage,
|
||
-- weight 0.3) are BASE items defined in init.lua; this pack's role-based
|
||
-- recipes match them by affordance the same as pack items.
|
||
-- World-gathered
|
||
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.",
|
||
weight = 20, vol = 0.05, material = { wood = 1.0 }, -- raw heavy wood; matched by material+weight
|
||
atlas = "tc", uv = "log_pile1" }
|
||
|
||
c.T{ id = "tree", name = "Tree", desc = "A standing tree. Chop it down for logs.",
|
||
weight = 400, vol = 1.0, material = { wood = 1.0 }, -- one concrete item → matched by template-id
|
||
atlas = "tc", uv = "log_pile1", scale = 1.6 }
|
||
|
||
c.T{ id = "plant_fiber", name = "Plant Fiber", desc = "Tough plant fiber. Twist several into cordage.",
|
||
weight = 0.05, vol = 0.0002, material = { fiber = 1.0 },
|
||
affordance = { spinnable = true }, -- can be twisted; cord itself is NOT spinnable
|
||
atlas = "tc", uv = "sack1", scale = 0.6 }
|
||
|
||
-- Crafted
|
||
c.T{ id = "sharp_stone", name = "Sharp Stone", desc = "A knapped stone with a cutting edge.",
|
||
weight = 1, vol = 0.0004, material = { stone = 1.0 },
|
||
affordance = { cutting = true }, -- "blade" role = affords cutting
|
||
atlas = "tc", uv = "spade" }
|
||
|
||
c.T{ id = "cordage", name = "Cordage", desc = "Twisted plant-fiber cord. A binding.",
|
||
weight = 0.1, vol = 0.0003, material = { fiber = 1.0 },
|
||
affordance = { binding = true }, -- "cord" role = affords binding (NOT spinnable → no re-twist)
|
||
atlas = "tc", uv = "sack1" }
|
||
|
||
c.T{ id = "plank", name = "Plank", desc = "A sawn wooden plank.",
|
||
weight = 2, vol = 0.004, material = { wood = 1.0 }, -- output only; light wood (not re-sawable)
|
||
atlas = "tc", uv = "bench1" }
|
||
|
||
c.T{ id = "stone_axe", name = "Stone Axe", desc = "A hafted stone axe. Chops wood.",
|
||
weight = 1.8, vol = 0.0012,
|
||
material = { stone = 0.6, wood = 0.3, fiber = 0.1 },
|
||
affordance = { chopping = true }, atlas = "tc", uv = "spade" }
|
||
|
||
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.",
|
||
weight = 0.3, vol = 0.0005, material = { wood = 1.0 },
|
||
affordance = { piercing = true }, atlas = "tc", uv = "log_pile1", scale = 0.9 }
|
||
|
||
-- ---- 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, 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." }
|
||
|
||
-- cordage: three SPINNABLE fibers (any fiber source) -> cord.
|
||
-- (cordage is binding-but-not-spinnable → you can't re-twist cord.)
|
||
c.R{ id = "twist_cordage",
|
||
inputs = { { match = { ["affordance.spinnable"] = true }, count = 3 } },
|
||
outputs = { { template = "cordage", count = 1 } },
|
||
name = "Twist Cordage",
|
||
description = "Twist three plant fibers into a length of cordage." }
|
||
|
||
-- hammer: a heavy STONE head + a LEVERAGE haft + a BINDING.
|
||
-- head matched the primitive way (material+mass); haft/binding by affordance —
|
||
-- one recipe, both matching styles, one mechanism (ADR-0055).
|
||
c.R{ id = "craft_stone_hammer",
|
||
inputs = {
|
||
{ match = { ["composition.stone"] = ">0.5", weight = ">1" }, count = 1 }, -- head (a stone chunk)
|
||
{ match = { ["affordance.leverage"] = true }, count = 1 }, -- haft (stick OR branch)
|
||
{ match = { ["affordance.binding"] = true }, count = 1 }, -- binding
|
||
},
|
||
outputs = { { template = "stone_hammer", count = 1 } },
|
||
name = "Haft Stone Hammer",
|
||
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, 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.)" }
|
||
|
||
-- break: a HEAVY leverage-thing (branch, not a light stick) -> two sticks.
|
||
c.R{ id = "break_branch",
|
||
inputs = { { match = { ["affordance.leverage"] = true, weight = ">1" }, count = 1 } },
|
||
outputs = { { template = "stick", count = 2 } },
|
||
name = "Break Branch",
|
||
description = "Snap a heavy branch over your knee into two sticks." }
|
||
|
||
-- sharpen: a LIGHT leverage-thing (a stick) + a CUTTING tool -> pointed stick.
|
||
c.R{ id = "sharpen_stick",
|
||
inputs = { { match = { ["affordance.leverage"] = true, weight = "<1" }, count = 1 } },
|
||
tools = { { match = { ["affordance.cutting"] = true } } },
|
||
outputs = { { template = "sharpened_stick", count = 1 } },
|
||
name = "Sharpen Stick",
|
||
description = "Whittle a stick to a point with a cutting edge." }
|
||
|
||
-- chop: THE tree (genuine single item → template-id) + a CHOPPING tool -> logs.
|
||
-- (Multiple tree species would switch this to an affordance/material match.)
|
||
c.R{ id = "chop_tree",
|
||
inputs = { { match = { template = "tree" }, count = 1 } },
|
||
tools = { { match = { ["affordance.chopping"] = true } } },
|
||
outputs = { { template = "log", count = 2 } },
|
||
name = "Chop Tree",
|
||
description = "Fell a tree with a chopping edge — yields two logs." }
|
||
|
||
-- saw: any heavy WOOD (matched by material+mass) + a CUTTING tool -> planks.
|
||
-- A light plank (weight 2) is correctly not re-sawable.
|
||
c.R{ id = "saw_planks",
|
||
inputs = { { match = { ["composition.wood"] = ">0.5", weight = ">5" }, count = 1 } },
|
||
tools = { { match = { ["affordance.cutting"] = true } } },
|
||
outputs = { { template = "plank", count = 4 } },
|
||
name = "Saw Planks",
|
||
description = "Saw a heavy wooden log into four planks (needs a cutting edge)." }
|
||
|
||
-- ---- world spawns (backpack sits at 350,400) ---------------------
|
||
c.S{ template = "log", x = 110, y = 400 }
|
||
c.S{ template = "sharp_stone", x = 110, y = 330 }
|
||
for i = 1, 3 do
|
||
c.S{ template = "plant_fiber", x = 50, y = 300 + (i - 1) * 28 }
|
||
end
|
||
c.S{ template = "branch", x = 50, y = 250 }
|
||
c.S{ template = "branch", x = 85, y = 250 }
|
||
c.S{ template = "tree", x = 200, y = 190 }
|
||
end
|