medieval-kit/my-registry/meta.ts

717 lines
36 KiB
TypeScript

/**
* The SINGLE source of the kit catalogue.
*
* This table is read from two places: `build.ts` when it produces registry.json,
* and the viewer when it builds its sliders and descriptions. The two used to be
* written out by hand separately; at seventeen models that is no longer
* sustainable, and they had already started to drift apart.
*
* The `controls` here are a CONTRACT: every key has to be a config field of the
* model. The viewer checks this through `keyof Config`, so a renamed field is a
* compile error.
*/
export interface ControlSpec {
readonly type: 'number'
readonly label: string
readonly min: number
readonly max: number
readonly step: number
readonly unit?: string
}
export interface ModelMeta {
readonly title: string
readonly description: string
readonly category: string
readonly tags: readonly string[]
readonly controls: Readonly<Record<string, ControlSpec>>
readonly materialSlots: readonly string[]
readonly parts: readonly string[]
}
export const MODEL_META: Readonly<Record<string, ModelMeta>> = {
'wooden-chest': {
title: 'Wooden Chest',
description:
'Six-board medieval chest with iron bands. The lid is hinged and opens on an action.',
category: 'Furniture',
tags: ['medieval', 'lowpoly', 'furniture', 'interactive', 'procedural'],
controls: {
width: { type: 'number', label: 'Width', min: 0.4, max: 1.6, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.28, max: 0.9, step: 0.02, unit: 'm' },
depth: { type: 'number', label: 'Depth', min: 0.24, max: 0.8, step: 0.02, unit: 'm' },
bandCount: { type: 'number', label: 'Band count', min: 0, max: 6, step: 1 },
openAngle: { type: 'number', label: 'Open angle', min: 40, max: 130, step: 2, unit: '°' },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['body', 'lid', 'bands', 'lock'],
},
'wooden-barrel': {
title: 'Wooden Barrel',
description:
'Lowpoly barrel built from separate oak staves, with iron hoops and a recessed head.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.4, max: 2, step: 0.02, unit: 'm' },
radius: { type: 'number', label: 'Radius', min: 0.15, max: 0.9, step: 0.01, unit: 'm' },
taper: { type: 'number', label: 'End taper', min: 0, max: 0.34, step: 0.01 },
staveCount: { type: 'number', label: 'Stave count', min: 6, max: 28, step: 1 },
hoopCount: { type: 'number', label: 'Hoop count', min: 0, max: 8, step: 1 },
rivets: { type: 'number', label: 'Rivets per hoop', min: 0, max: 8, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['staves', 'heads', 'hoops'],
},
'wooden-crate': {
title: 'Wooden Crate',
description:
'Rows of horizontal boards nailed to corner posts, with forged iron straps.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'procedural'],
controls: {
width: { type: 'number', label: 'Width', min: 0.3, max: 1.4, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.25, max: 1.2, step: 0.02, unit: 'm' },
depth: { type: 'number', label: 'Depth', min: 0.3, max: 1.4, step: 0.02, unit: 'm' },
plankRows: { type: 'number', label: 'Board rows', min: 1, max: 6, step: 1 },
strapCount: { type: 'number', label: 'Iron straps', min: 0, max: 4, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['posts', 'planks', 'straps'],
},
'wooden-bucket': {
title: 'Wooden Bucket',
description:
'Tapering oak staves, an iron hoop and a forged handle — a small barrel.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'farm', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.15, max: 0.6, step: 0.01, unit: 'm' },
radius: {
type: 'number', label: 'Rim radius',
min: 0.07, max: 0.3, step: 0.005, unit: 'm',
},
taper: { type: 'number', label: 'Base taper', min: 0, max: 0.45, step: 0.01 },
staveCount: { type: 'number', label: 'Stave count', min: 6, max: 20, step: 1 },
hoopCount: { type: 'number', label: 'Hoop count', min: 0, max: 4, step: 1 },
handle: { type: 'number', label: 'Handle', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['staves', 'base', 'hoops', 'handle'],
},
'trestle-table': {
title: 'Trestle Table',
description:
'Trestle table: the top is not nailed to the legs, it rests on them — so the hall can be cleared.',
category: 'Furniture',
tags: ['medieval', 'lowpoly', 'furniture', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 1, max: 3.2, step: 0.05, unit: 'm' },
width: { type: 'number', label: 'Width', min: 0.5, max: 1.2, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.5, max: 1, step: 0.01, unit: 'm' },
plankCount: { type: 'number', label: 'Top boards', min: 2, max: 7, step: 1 },
splay: { type: 'number', label: 'Leg splay', min: 0, max: 0.45, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['top', 'trestles', 'stretcher'],
},
'wooden-bench': {
title: 'Wooden Bench',
description:
'Medieval bench with splayed legs, its tenons showing through the top of the seat.',
category: 'Furniture',
tags: ['medieval', 'lowpoly', 'furniture', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 0.6, max: 3, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.28, max: 0.7, step: 0.01, unit: 'm' },
width: { type: 'number', label: 'Width', min: 0.18, max: 0.5, step: 0.01, unit: 'm' },
splay: { type: 'number', label: 'Leg splay', min: 0, max: 0.6, step: 0.02 },
inset: { type: 'number', label: 'Leg inset', min: 0.02, max: 0.3, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['seat', 'legs'],
},
'wooden-stool': {
title: 'Wooden Stool',
description:
'Three-legged village stool — three is the count that does not rock on uneven ground.',
category: 'Furniture',
tags: ['medieval', 'lowpoly', 'furniture', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.25, max: 0.9, step: 0.01, unit: 'm' },
seatRadius: {
type: 'number', label: 'Seat radius',
min: 0.1, max: 0.3, step: 0.005, unit: 'm',
},
legCount: { type: 'number', label: 'Legs', min: 3, max: 5, step: 1 },
splay: { type: 'number', label: 'Leg splay', min: 0, max: 0.45, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['seat', 'legs'],
},
'pitch-torch': {
title: 'Pitch Torch',
description:
'Torch wrapped in pitch-soaked cloth. The flame flickers in update(), goes out via actions.setLit.',
category: 'Lighting',
tags: ['medieval', 'lowpoly', 'lighting', 'animated', 'interactive'],
controls: {
length: { type: 'number', label: 'Shaft length', min: 0.3, max: 1.4, step: 0.02, unit: 'm' },
radius: {
type: 'number', label: 'Shaft radius',
min: 0.008, max: 0.05, step: 0.001, unit: 'm',
},
wrapLength: { type: 'number', label: 'Wrap ratio', min: 0.12, max: 0.5, step: 0.01 },
flameHeight: { type: 'number', label: 'Flame height', min: 0.4, max: 3, step: 0.05 },
flicker: { type: 'number', label: 'Flicker', min: 0, max: 2.5, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'char', 'ember'],
parts: ['shaft', 'wrap', 'flame'],
},
'iron-anvil': {
title: 'Iron Anvil',
description:
'Wide base, narrow waist, tapering horn; on top a steel face plate polished by use.',
category: 'Smithy',
tags: ['medieval', 'lowpoly', 'smithy', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.2, max: 0.6, step: 0.01, unit: 'm' },
faceLength: {
type: 'number', label: 'Face length',
min: 0.25, max: 0.8, step: 0.01, unit: 'm',
},
faceWidth: {
type: 'number', label: 'Face width',
min: 0.07, max: 0.25, step: 0.005, unit: 'm',
},
hornReach: { type: 'number', label: 'Horn reach', min: 0.2, max: 0.8, step: 0.02 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['iron', 'steel', 'oak'],
parts: ['base', 'waist', 'body', 'face', 'horn', 'stump'],
},
'cart-wheel': {
title: 'Cart Wheel',
description:
'Four layers from the outside in: iron tyre, wooden felloe, spokes, hub.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'cart', 'structure', 'procedural'],
controls: {
radius: { type: 'number', label: 'Radius', min: 0.25, max: 0.9, step: 0.01, unit: 'm' },
spokeCount: { type: 'number', label: 'Spokes', min: 6, max: 16, step: 1 },
width: { type: 'number', label: 'Thickness', min: 0.04, max: 0.18, step: 0.005, unit: 'm' },
hubLength: { type: 'number', label: 'Hub length', min: 1.2, max: 3.2, step: 0.1 },
tyre: { type: 'number', label: 'Tyre thickness', min: 0.02, max: 0.09, step: 0.005 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['hub', 'spokes', 'felloe', 'tyre'],
},
'log-pile': {
title: 'Log Pile',
description:
'Pile of cut firewood. The end grain is far lighter than the bark; it is what you see first.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'procedural'],
controls: {
rows: { type: 'number', label: 'Rows', min: 1, max: 6, step: 1 },
perRow: { type: 'number', label: 'Logs per row', min: 1, max: 9, step: 1 },
logLength: { type: 'number', label: 'Log length', min: 0.3, max: 1.4, step: 0.02, unit: 'm' },
logRadius: {
type: 'number', label: 'Log radius',
min: 0.03, max: 0.14, step: 0.002, unit: 'm',
},
variation: { type: 'number', label: 'Thickness variation', min: 0, max: 0.5, step: 0.02 },
taperRows: { type: 'number', label: 'Pyramid stack', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['bark', 'ends'],
},
'hay-bale': {
title: 'Hay Bale',
description:
'Hand-tied straw bale — pinched where the ropes bite, with wisps sticking out on every side.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'farm', 'props', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 0.4, max: 1.6, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.2, max: 0.9, step: 0.02, unit: 'm' },
depth: { type: 'number', label: 'Depth', min: 0.2, max: 0.9, step: 0.02, unit: 'm' },
ropeCount: { type: 'number', label: 'Rope count', min: 0, max: 4, step: 1 },
wisps: { type: 'number', label: 'Stray wisps', min: 0, max: 60, step: 2 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['straw', 'cloth'],
parts: ['bale', 'wisps', 'ropes'],
},
'linen-sack': {
title: 'Linen Sack',
description:
'Grain sack tied shut at the mouth with cord. The fill ratio changes the whole silhouette.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'farm', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.2, max: 1, step: 0.01, unit: 'm' },
radius: { type: 'number', label: 'Radius', min: 0.07, max: 0.35, step: 0.005, unit: 'm' },
fill: { type: 'number', label: 'Fill', min: 0.15, max: 1, step: 0.02 },
collar: { type: 'number', label: 'Collar allowance', min: 0.05, max: 0.3, step: 0.01 },
ears: { type: 'number', label: 'Bottom ears', min: 0, max: 6, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['cloth'],
parts: ['body', 'collar', 'cord'],
},
'straw-broom': {
title: 'Straw Broom',
description:
'Besom: hazel shaft, a brush bundle built from three concentric rings, willow bindings.',
category: 'Tools',
tags: ['medieval', 'lowpoly', 'tools', 'household', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 0.7, max: 1.8, step: 0.02, unit: 'm' },
shaftRadius: {
type: 'number', label: 'Shaft thickness',
min: 0.01, max: 0.03, step: 0.001, unit: 'm',
},
headLength: { type: 'number', label: 'Bundle length', min: 0.2, max: 0.55, step: 0.01 },
tieRadius: { type: 'number', label: 'Binding radius', min: 0.03, max: 0.09, step: 0.002, unit: 'm' },
tipRadius: { type: 'number', label: 'Tip radius', min: 0.04, max: 0.26, step: 0.005, unit: 'm' },
bristles: { type: 'number', label: 'Bristle count', min: 8, max: 60, step: 2 },
bindings: { type: 'number', label: 'Binding count', min: 0, max: 5, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'straw', 'cloth'],
parts: ['shaft', 'bristles', 'bindings'],
},
'oak-tankard': {
title: 'Oak Tankard',
description:
'Oak tankard: the barrel at palm scale, in the same stave-and-hoop language.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'tavern', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.08, max: 0.28, step: 0.005, unit: 'm' },
radius: { type: 'number', label: 'Rim radius', min: 0.03, max: 0.1, step: 0.002, unit: 'm' },
taper: { type: 'number', label: 'Base taper', min: 0, max: 0.3, step: 0.01 },
staveCount: { type: 'number', label: 'Stave count', min: 6, max: 18, step: 1 },
hoopCount: { type: 'number', label: 'Hoop count', min: 0, max: 4, step: 1 },
handle: { type: 'number', label: 'Handle', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['staves', 'base', 'hoops', 'handle'],
},
'bronze-bell': {
title: 'Bronze Bell',
description:
'Bronze bell hung from a yoke. The clapper swings with a lag and strikes the rim; strikes are counted.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'animated', 'interactive'],
controls: {
diameter: { type: 'number', label: 'Mouth diameter', min: 0.15, max: 1, step: 0.01, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.15, max: 1.1, step: 0.01, unit: 'm' },
yoke: { type: 'number', label: 'Yoke length', min: 1, max: 2.2, step: 0.05 },
swing: { type: 'number', label: 'Swing', min: 5, max: 60, step: 1, unit: '°' },
damping: { type: 'number', label: 'Damping', min: 0.1, max: 3, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['brass', 'iron', 'oak'],
parts: ['bell', 'clapper', 'yoke', 'frame'],
},
'iron-lantern': {
title: 'Iron Lantern',
description:
'Hexagonal iron cage, glass panels, an oil lamp inside. An enclosed flame flickers more calmly.',
category: 'Lighting',
tags: ['medieval', 'lowpoly', 'lighting', 'animated', 'interactive'],
controls: {
height: { type: 'number', label: 'Height', min: 0.14, max: 0.5, step: 0.01, unit: 'm' },
radius: { type: 'number', label: 'Radius', min: 0.04, max: 0.15, step: 0.005, unit: 'm' },
sides: { type: 'number', label: 'Side count', min: 3, max: 8, step: 1 },
flameHeight: { type: 'number', label: 'Flame height', min: 0.08, max: 0.45, step: 0.01 },
flicker: { type: 'number', label: 'Flicker', min: 0, max: 2.5, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['iron', 'glass', 'char', 'ember'],
parts: ['frame', 'font', 'flame', 'handle'],
},
'tavern-sign': {
title: 'Tavern Sign',
description:
'Wooden board hung by chain from a forged iron bracket. Pushed, it swings long and lazily.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'animated', 'interactive'],
controls: {
width: { type: 'number', label: 'Board width', min: 0.25, max: 1.1, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Board height', min: 0.2, max: 0.8, step: 0.02, unit: 'm' },
reach: { type: 'number', label: 'Bracket reach', min: 0.3, max: 1.2, step: 0.02, unit: 'm' },
drop: { type: 'number', label: 'Chain drop', min: 0.04, max: 0.35, step: 0.01, unit: 'm' },
plankCount: { type: 'number', label: 'Board count', min: 1, max: 6, step: 1 },
damping: { type: 'number', label: 'Damping', min: 0.05, max: 2, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['post', 'bracket', 'board'],
},
'leather-book': {
title: 'Leather Book',
description:
'Leather-bound manuscript with spine bands and clasps. The page block overhangs the cover, wavy.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'tabletop', 'procedural'],
controls: {
width: { type: 'number', label: 'Width', min: 0.08, max: 0.4, step: 0.005, unit: 'm' },
length: { type: 'number', label: 'Length', min: 0.1, max: 0.55, step: 0.005, unit: 'm' },
thickness: { type: 'number', label: 'Thickness', min: 0.02, max: 0.16, step: 0.002, unit: 'm' },
bands: { type: 'number', label: 'Spine bands', min: 0, max: 6, step: 1 },
clasps: { type: 'number', label: 'Clasps', min: 0, max: 3, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['leather', 'cloth', 'brass'],
parts: ['cover', 'pages', 'clasps'],
},
'glass-phial': {
title: 'Glass Phial',
description:
'Blown glass phial with a cork stopper, sealed with wax. Liquid level is computed from the fill.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'alchemy', 'tabletop'],
controls: {
height: { type: 'number', label: 'Height', min: 0.06, max: 0.32, step: 0.005, unit: 'm' },
radius: { type: 'number', label: 'Body radius', min: 0.015, max: 0.08, step: 0.002, unit: 'm' },
neck: { type: 'number', label: 'Neck ratio', min: 0.15, max: 0.55, step: 0.01 },
fill: { type: 'number', label: 'Fill', min: 0, max: 1, step: 0.02 },
hue: { type: 'number', label: 'Liquid hue', min: 0, max: 1, step: 0.01 },
seal: { type: 'number', label: 'Wax seal', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['glass', 'ember', 'oak', 'char'],
parts: ['bottle', 'liquid', 'stopper'],
},
'coin-pouch': {
title: 'Coin Pouch',
description:
'Drawstring leather pouch with silver pennies spilled to one side out of its mouth.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'tabletop', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.05, max: 0.24, step: 0.005, unit: 'm' },
radius: { type: 'number', label: 'Radius', min: 0.02, max: 0.1, step: 0.002, unit: 'm' },
fill: { type: 'number', label: 'Fill', min: 0.15, max: 1, step: 0.02 },
coins: { type: 'number', label: 'Spilled coins', min: 0, max: 30, step: 1 },
coinRadius: { type: 'number', label: 'Coin radius', min: 0.005, max: 0.025, step: 0.001, unit: 'm' },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['leather', 'cloth', 'steel'],
parts: ['pouch', 'cord', 'coins'],
},
'wicker-basket': {
title: 'Wicker Basket',
description:
'Basket woven from willow rods. Horizontal rods pass in front of one upright, behind the next.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'farm', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 0.1, max: 0.5, step: 0.01, unit: 'm' },
radius: { type: 'number', label: 'Rim radius', min: 0.08, max: 0.35, step: 0.005, unit: 'm' },
taper: { type: 'number', label: 'Base taper', min: 0, max: 0.45, step: 0.01 },
stakes: { type: 'number', label: 'Upright rods', min: 6, max: 18, step: 1 },
rows: { type: 'number', label: 'Weave rows', min: 2, max: 14, step: 1 },
produce: { type: 'number', label: 'Produce count', min: 0, max: 24, step: 1 },
hue: { type: 'number', label: 'Produce hue', min: 0, max: 1, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'produce'],
parts: ['weave', 'rim', 'contents'],
},
'wooden-ladder': {
title: 'Wooden Ladder',
description:
'Two rails converging toward the top, with rungs let into them.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural'],
controls: {
height: { type: 'number', label: 'Height', min: 1, max: 5, step: 0.1, unit: 'm' },
width: { type: 'number', label: 'Width', min: 0.25, max: 0.8, step: 0.01, unit: 'm' },
rungCount: { type: 'number', label: 'Rungs', min: 3, max: 18, step: 1 },
taper: { type: 'number', label: 'Taper', min: 0, max: 0.4, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['rails', 'rungs'],
},
'round-shield': {
title: 'Round Shield',
description:
'Planked round shield with a rawhide rim and an iron boss, quartered in paint and leaning on its edge.',
category: 'Arms',
tags: ['medieval', 'lowpoly', 'arms', 'procedural'],
controls: {
radius: { type: 'number', label: 'Radius', min: 0.2, max: 0.6, step: 0.01, unit: 'm' },
planks: { type: 'number', label: 'Planks', min: 3, max: 15, step: 1 },
thickness: { type: 'number', label: 'Board thickness', min: 0.008, max: 0.04, step: 0.002, unit: 'm' },
lean: { type: 'number', label: 'Lean back', min: 0, max: 0.6, step: 0.02, unit: 'rad' },
hue: { type: 'number', label: 'Paint hue', min: 0, max: 1, step: 0.01 },
rivets: { type: 'number', label: 'Boss rivets', min: 0, max: 16, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'leather', 'iron'],
parts: ['boards', 'rim', 'boss'],
},
'market-stall': {
title: 'Market Stall',
description:
'Plank counter on a braced trestle under a linen awning slung between four posts, sagging the way cloth does.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 1, max: 2.6, step: 0.05, unit: 'm' },
depth: { type: 'number', label: 'Depth', min: 0.45, max: 1.1, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Counter height', min: 0.55, max: 1, step: 0.02, unit: 'm' },
awning: { type: 'number', label: 'Awning height', min: 1.4, max: 2.4, step: 0.05, unit: 'm' },
sag: { type: 'number', label: 'Cloth sag', min: 0, max: 0.4, step: 0.02 },
planks: { type: 'number', label: 'Counter boards', min: 2, max: 10, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'cloth'],
parts: ['top', 'trestle', 'posts', 'awning'],
},
'grindstone': {
title: 'Grindstone',
description:
'Treadle grindstone: a sandstone wheel in a splayed timber trestle, with an iron crank and a trough of water it runs through. Turns.',
category: 'Smithy',
tags: ['medieval', 'lowpoly', 'smithy', 'procedural'],
controls: {
diameter: { type: 'number', label: 'Wheel diameter', min: 0.35, max: 0.95, step: 0.02, unit: 'm' },
thickness: { type: 'number', label: 'Wheel thickness', min: 0.05, max: 0.18, step: 0.005, unit: 'm' },
height: { type: 'number', label: 'Axle height', min: 0.4, max: 0.85, step: 0.02, unit: 'm' },
length: { type: 'number', label: 'Frame length', min: 0.7, max: 1.5, step: 0.05, unit: 'm' },
splay: { type: 'number', label: 'Leg splay', min: 0, max: 0.5, step: 0.02 },
water: { type: 'number', label: 'Water level', min: 0, max: 1, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['stone', 'oak', 'iron', 'water'],
parts: ['wheel', 'frame', 'crank', 'water'],
},
'stone-trough': {
title: 'Stone Trough',
description:
'Watering trough cut from one block of weathered limestone, with a heavy rim, a worn basin and a film of standing water.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 0.8, max: 2.6, step: 0.05, unit: 'm' },
width: { type: 'number', label: 'Width', min: 0.35, max: 0.95, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.25, max: 0.7, step: 0.02, unit: 'm' },
wall: { type: 'number', label: 'Wall thickness', min: 0.12, max: 0.34, step: 0.01 },
water: { type: 'number', label: 'Water level', min: 0, max: 1, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['stone', 'water'],
parts: ['block', 'water'],
},
'forge-hearth': {
title: 'Forge Hearth',
description:
"Smith's forge: a raised stone hearth with a chimney back, a bed of embers that can be put out, and a leather bellows.",
category: 'Smithy',
tags: ['medieval', 'lowpoly', 'smithy', 'procedural'],
controls: {
length: { type: 'number', label: 'Hearth length', min: 0.7, max: 1.8, step: 0.02, unit: 'm' },
depth: { type: 'number', label: 'Hearth depth', min: 0.5, max: 1.3, step: 0.02, unit: 'm' },
height: { type: 'number', label: 'Working height', min: 0.5, max: 1.1, step: 0.02, unit: 'm' },
chimney: { type: 'number', label: 'Chimney height', min: 0.3, max: 2, step: 0.05, unit: 'm' },
kerbBlocks: { type: 'number', label: 'Kerb blocks', min: 8, max: 28, step: 1 },
lit: { type: 'number', label: 'Lit', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['stone', 'char', 'ember', 'oak', 'leather', 'iron'],
parts: ['hearth', 'fire', 'chimney', 'bellows'],
},
'vegetables': {
title: 'Vegetables',
description:
'Loose heap mixed from eight kinds: turnip, cabbage, onion, leek, parsnip, carrot, tomato and potato. The first six are the period-correct set.',
category: 'Props',
tags: ['medieval', 'lowpoly', 'props', 'procedural', 'food'],
controls: {
count: { type: 'number', label: 'How many', min: 1, max: 30, step: 1 },
kinds: { type: 'number', label: 'Kinds in the mix', min: 1, max: 8, step: 1 },
lead: { type: 'number', label: 'Leading kind', min: 0, max: 7, step: 1 },
size: { type: 'number', label: 'Vegetable size', min: 0.05, max: 0.2, step: 0.005, unit: 'm' },
spread: { type: 'number', label: 'Heap spread', min: 1, max: 5, step: 0.1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['produce'],
parts: ['bulbs', 'roots'],
},
'iron-cauldron': {
title: 'Iron Cauldron',
description:
'Cauldron slung from an iron tripod over a ring of hearth stones, with embers that can be lit or put out.',
category: 'Lighting',
tags: ['medieval', 'lowpoly', 'hearth', 'procedural'],
controls: {
height: { type: 'number', label: 'Tripod height', min: 0.8, max: 2, step: 0.02, unit: 'm' },
hearthRadius: { type: 'number', label: 'Hearth radius', min: 0.28, max: 0.8, step: 0.01, unit: 'm' },
stones: { type: 'number', label: 'Hearth stones', min: 6, max: 20, step: 1 },
potRadius: { type: 'number', label: 'Cauldron radius', min: 0.12, max: 0.36, step: 0.01, unit: 'm' },
links: { type: 'number', label: 'Chain links', min: 2, max: 8, step: 1 },
lit: { type: 'number', label: 'Lit', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['stone', 'iron', 'char', 'ember'],
parts: ['hearth', 'tripod', 'pot'],
},
'hand-cart': {
title: 'Hand Cart',
description:
'Two-wheeled handcart: planked body on an axle set aft of centre, with shafts that rest on the ground.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural'],
controls: {
bedLength: { type: 'number', label: 'Bed length', min: 0.8, max: 2.1, step: 0.02, unit: 'm' },
bedWidth: { type: 'number', label: 'Bed width', min: 0.4, max: 1.1, step: 0.02, unit: 'm' },
sideHeight: { type: 'number', label: 'Side height', min: 0.1, max: 0.5, step: 0.01, unit: 'm' },
wheelRadius: { type: 'number', label: 'Wheel radius', min: 0.18, max: 0.5, step: 0.01, unit: 'm' },
spokes: { type: 'number', label: 'Spokes', min: 6, max: 14, step: 1 },
shaftLength: { type: 'number', label: 'Shaft length', min: 0.6, max: 1.8, step: 0.02, unit: 'm' },
roll: { type: 'number', label: 'Wheel rotation', min: 0, max: 6.28, step: 0.02, unit: 'rad' },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['wheels', 'bed', 'sides', 'shafts'],
},
'stone-well': {
title: 'Stone Well',
description:
'Village well: a drystone kerb, a timber frame over it, and a windlass with rope and bucket.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural'],
controls: {
radius: { type: 'number', label: 'Kerb radius', min: 0.35, max: 1.1, step: 0.01, unit: 'm' },
wallHeight: { type: 'number', label: 'Kerb height', min: 0.3, max: 1.1, step: 0.02, unit: 'm' },
courses: { type: 'number', label: 'Stone courses', min: 2, max: 7, step: 1 },
blocks: { type: 'number', label: 'Blocks per course', min: 6, max: 18, step: 1 },
frameHeight: { type: 'number', label: 'Frame height', min: 1.2, max: 2.8, step: 0.05, unit: 'm' },
depth: { type: 'number', label: 'Bucket drop', min: 0, max: 1, step: 0.02 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['stone', 'oak', 'iron', 'cloth'],
parts: ['kerb', 'frame', 'windlass', 'bucket'],
},
'post-mill': {
title: 'Post Mill',
description:
'Medieval post mill: the whole timber body turns on one massive post, with four lattice sails and the tail ladder it is turned by.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'procedural', 'landmark'],
controls: {
height: { type: 'number', label: 'Height to ridge', min: 3.5, max: 9, step: 0.1, unit: 'm' },
sailSpan: { type: 'number', label: 'Sail span', min: 4, max: 11, step: 0.1, unit: 'm' },
sailBars: { type: 'number', label: 'Bars per sail', min: 3, max: 14, step: 1 },
sailWidth: { type: 'number', label: 'Sail width', min: 0.08, max: 0.28, step: 0.005 },
ladderRungs: { type: 'number', label: 'Ladder rungs', min: 5, max: 20, step: 1 },
spin: { type: 'number', label: 'Sail angle', min: 0, max: 6.28, step: 0.02, unit: 'rad' },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['trestle', 'body', 'sails', 'ladder'],
},
'wooden-fence': {
title: 'Wooden Fence',
description:
'Mortised riven fence: the rail runs through the post and projects from the far face at the ends.',
category: 'Structure',
tags: ['medieval', 'lowpoly', 'structure', 'farm', 'procedural'],
controls: {
sections: { type: 'number', label: 'Sections', min: 1, max: 5, step: 1 },
sectionLength: { type: 'number', label: 'Section length', min: 1.6, max: 3.4, step: 0.05, unit: 'm' },
height: { type: 'number', label: 'Height', min: 0.8, max: 1.7, step: 0.02, unit: 'm' },
railCount: { type: 'number', label: 'Rails', min: 1, max: 4, step: 1 },
rough: { type: 'number', label: 'Irregularity', min: 0, max: 2, step: 0.05 },
brace: { type: 'number', label: 'Brace', min: 0, max: 1, step: 1 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak'],
parts: ['posts', 'rails'],
},
'wooden-hoe': {
title: 'Wooden Hoe',
description:
'Gooseneck field hoe: a curved forged neck carrying the blade ahead of the shaft axis, dished blade.',
category: 'Tools',
tags: ['medieval', 'lowpoly', 'tools', 'farm', 'procedural'],
controls: {
length: { type: 'number', label: 'Shaft length', min: 0.8, max: 1.7, step: 0.02, unit: 'm' },
shaftRadius: {
type: 'number', label: 'Shaft thickness',
min: 0.012, max: 0.035, step: 0.001, unit: 'm',
},
bladeWidth: {
type: 'number', label: 'Blade width',
min: 0.1, max: 0.3, step: 0.005, unit: 'm',
},
neckSweep: { type: 'number', label: 'Neck sweep', min: 40, max: 150, step: 2, unit: '°' },
dish: { type: 'number', label: 'Dish', min: 0, max: 2, step: 0.05 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron', 'steel'],
parts: ['shaft', 'socket', 'blade'],
},
'wooden-shovel': {
title: 'Wooden Shovel',
description:
'A single-piece dished steel plate that both widens and thins toward the tip.',
category: 'Tools',
tags: ['medieval', 'lowpoly', 'tools', 'farm', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 0.8, max: 1.8, step: 0.02, unit: 'm' },
shaftRadius: {
type: 'number', label: 'Shaft thickness',
min: 0.014, max: 0.035, step: 0.001, unit: 'm',
},
bladeWidth: {
type: 'number', label: 'Blade width',
min: 0.12, max: 0.34, step: 0.005, unit: 'm',
},
bladeLength: { type: 'number', label: 'Blade length', min: 0.18, max: 0.4, step: 0.01 },
dish: { type: 'number', label: 'Scoop depth', min: 0, max: 0.28, step: 0.01 },
bladeAngle: { type: 'number', label: 'Blade angle', min: 0, max: 25, step: 1, unit: 'degrees' },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['shaft', 'socket', 'blade'],
},
'wooden-pitchfork': {
title: 'Wooden Pitchfork',
description:
'Steel tines splaying outward; the gap in the silhouette makes it readable from a distance.',
category: 'Tools',
tags: ['medieval', 'lowpoly', 'tools', 'farm', 'procedural'],
controls: {
length: { type: 'number', label: 'Length', min: 1, max: 2.4, step: 0.02, unit: 'm' },
shaftRadius: {
type: 'number', label: 'Shaft thickness',
min: 0.012, max: 0.035, step: 0.001, unit: 'm',
},
tineCount: { type: 'number', label: 'Tine count', min: 2, max: 6, step: 1 },
spread: { type: 'number', label: 'Tine spread', min: 0, max: 0.4, step: 0.01 },
tineLength: { type: 'number', label: 'Tine length', min: 0.1, max: 0.42, step: 0.01 },
seed: { type: 'number', label: 'Variation seed', min: 1, max: 64, step: 1 },
},
materialSlots: ['oak', 'iron'],
parts: ['shaft', 'socket', 'tines'],
},
}