Files
calic d862a250c4 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.
2026-08-03 09:14:56 +00:00

209 lines
12 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =====================================================================
-- 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