The Grammar Doesn’t Know What a Sword Is
Stage 4 builds the physical object. It works bottom-up from geometric primitives, and it is forbidden, as a matter of design law, from ever branching by intent. The spec marks exactly one principle non-negotiable, and it's this one.
Why bottom-up
The obvious way to generate game items is top-down: pick "sword" from a table, then fill in sword-shaped details. The whole game premise dies if you do that. If the generator knows it made a sword, the classification is trivially recoverable and interpretation has nothing to do; the player's job of working out what a thing was only exists if the engine itself never decided. So the grammar produces structures (an elongated form with an edge, socketed to a cylindrical form) and classification happens downstream, by reading the geometry back like an archaeologist would. The ambiguity is native: a long edged thing genuinely might be a weapon, a tool or a ritual object.
The vocabulary
Eight geometric primitives, each with its own parameter set: elongated, cylindrical, flat-broad, hollow-enclosed, ring-form, disc-form, bar-form, sheet-form. Two examples of the parameter space:
- An elongated form has a length band, a cross-section, a taper, an edge count and a point: enough to be a pin, a blade or a shaft, without ever saying which.
- A hollow-enclosed form has a shape, wall thickness, an opening and a base: enough to be a jar, a box or an urn, same rule.
Components join through nine attachment types (inline, perpendicular, socketed, riveted, wrapped, lashed, hinged, threaded, friction-fit), and the attachments matter later: a hinged join is evidence of fine metalwork, and classification will read brooch anatomy off a pin bearing on a hoop.
<object> ::= <component-group>+
<component-group> ::= <primary-component> [<attachment> <component-group>]*
<primary-component> ::= <elongated> | <cylindrical> | <flat-broad>
| <hollow-enclosed> | <ring-form> | <disc-form>
| <bar-form> | <sheet-form>
The BNF's repetition operators are engine-owned: the data module supplies alternatives and weights only, and expandGrammar decides how many groups and how long the attachment chains run, bounded by the complexity budget below.
Grammar rules live in a data file as plain objects; the engine looks them up by symbol. Four production rules cover the MVP.
Culture is a weight modifier
Cultures don't have allowed artefact types. They have tendencies, expressed as weight modifiers over grammar options. Selection works like this: start from the option's authored base weight, add each cultural modifier scaled by the culture's affinity for that material tag, multiply by phase influence, floor at 0.01.
weight = max(0.01,
(baseWeight + Σ affinity(tag) × modifier)
× Π (1 + (multiplier − 1) × phaseAttribute))
The 0.01 floor means nothing is ever impossible: a deeply pacifist culture occasionally produces a blade. The source comment reads, in full, "Because archaeology." Phase influence is calibrated so a high attribute amplifies while a low one merely fails to amplify; "low metallurgy" never drags an option below its base weight.
A phase is a multi-attribute profile, one per culture per period: eight technology axes (metallurgy, ceramics, textiles, leather-working, stone, glass, wood, bone), plus economy, society and aesthetics blocks. A culture can be exceptional metalworkers with crude ceramics, and its artefacts will say so.
Complexity budgets and arrangement patterns
Uncontrolled repetition produces nonsense, so repetition is patterned, and the constraint system encodes two claims about the world rather than two knobs on a generator.
The budget is a claim about people
Object complexity tracks who a culture can afford to specialise: a part-time potter makes simple forms, a workshop tradition supports composite objects with several distinct assemblies. So the budget hangs off society.craftSpecialisation, one number per culture-phase, resolved into a tier that every object draws against:
- Simple (0–0.3): 1–2 component groups, basic patterns only (symmetric, linear).
- Moderate (0.3–0.6): 2–3 groups, most patterns available.
- Sophisticated (0.6–1.0): 3–4 groups, all six patterns, including nesting and branching.
Complexity is a property of the society before it's a property of any object, which is why the budget binds at the culture level and not per artefact.
The patterns are a claim about objects
Real artefacts repeat components deliberately: rivets march in rows, beads circle a rim, sockets stack. Six arrangement patterns cover the MVP (symmetric pairs, radial spreads of 3–12, linear arrays, stacks, nests, branches), and a repeated component must belong to one of them; the accumulation checker rejects an expansion whose repetition fits no pattern, because unpatterned repetition reads as noise where patterned repetition reads as intent. The arrangement group survives normalisation, so downstream feature extraction can tell a radial spread of studs from one stud, and the player can too.
Portability, derived
Generated objects carry a portability band, from pocketable through team-lift to major-effort, and an inspection depth derived from physical dimensions: anything up to 30cm can be held and fully examined, up to 150cm examined in detail, beyond that observed in situ. These are mechanical facts about player handling, and classification rules are banned from reading them; the classification page has the boundary and the test that guards it.
src/lib/engine/generation/grammar.ts · Rules: src/lib/data/grammars/core.ts · Primitives: src/lib/data/grammars/primitives.ts · Spec: doc 05 §5