Engine Builder Mental Model
This guide explains how to think about the Engine Builder: what it builds, how it composes attributes and modifiers, and how conditions, metadata, and evaluation interact.
Core Idea
- The builder constructs an immutable
ItemSpecthat contains:- Attributes: descriptive properties about the item (e.g.,
Rarity,Level,Enchanted). - Modifiers: rules that change numeric metrics (e.g.,
Health,Damage,Speed).
- Attributes: descriptive properties about the item (e.g.,
- The engine evaluates your
ItemSpecby applying valid modifiers to metrics, producing final numbers and a trace of what happened.
Attributes vs Metrics
- Attributes are inputs and context. They do not directly change numbers. They are used by conditions to decide which modifiers apply.
- Metrics are numeric outputs that modifiers change using operations.
Example:
const config = defineConfig({
metrics: ["Health", "Damage", "Speed"] as const,
operations: ["sum", "multiply"] as const,
attributes: [
{
key: "Rarity",
kind: "enum",
values: ["Common", "Rare", "Epic"] as const,
},
{ key: "Level", kind: "number", min: 1, max: 100 },
{ key: "Enchanted", kind: "boolean" },
] as const,
});
Rarity,Level,Enchantedare attributes.Health,Damage,Speedare metrics that modifiers will change.
Builder Phases
- Set attributes (context):
const item = engine
.builder("Epic Sword")
.setAttributes({ Rarity: "Epic", Level: 50, Enchanted: true })
// ...
.build();
- Add modifiers (effects):
const item = engine
.builder()
.increase("Damage")
.by(100) // +100
.multiply("Damage")
.by(1.5) // *1.5
.build();
Conditions (when)
Conditions filter which modifiers apply. A condition set with .when(...) affects all subsequent modifiers until the context is cleared or changed.
const item = engine
.builder()
.set("Rarity", "Epic")
.when({ op: "eq", attr: "Rarity", value: "Epic" })
.multiply("Damage")
.by(1.5) // applies only if Rarity === "Epic"
.build();
Key points:
- Conditions reference attributes, not metrics.
- The builder automatically scopes conditions to modifiers added after
.when(...).
Metadata (with)
Use .with({ priority, stacking, source }) to set metadata for subsequent modifiers:
- priority: higher values apply later in the evaluation (controls application order between modifiers at the same precedence).
- stacking: control uniqueness rules (e.g., unique by
source). - source: identifier for uniqueness or audit.
const item = engine
.builder()
.with({ priority: 100 })
.increase("Damage")
.by(25)
.with({ stacking: "unique", source: "fire-enchant" })
.increase("Damage")
.by(50)
.build();
Grouping
Use .group({ when, with }, (b) => { ... }) to apply a temporary scope for conditions and metadata to multiple modifiers in one place. Outside the group, previous context is restored.
const item = engine
.builder()
.group(
{
when: { op: "eq", attr: "Enchanted", value: true },
with: { priority: 50 },
},
(b) => {
b.increase("Damage").by(20);
b.increase("Speed").by(5);
}
)
.build();
Evaluation Order (mental model)
- Start metrics at a base (typically 0).
- Filter modifiers by conditions (based on attributes).
- Sort modifiers by:
- Operation precedence (e.g., multiply after sum/subtract).
- Priority (higher runs later).
- Apply modifiers in order. Track
before→afterper modifier. - Respect stacking rules (e.g., unique by
sourcekeeps the highest absolute value).
This yields deterministic, predictable results.
Defaults via setAttributes
Prefer initializing attributes from a defaults object:
const defaults = { Rarity: "Epic" as const, Level: 50 };
const epic = engine
.builder("Epic Sword")
.setAttributes(defaults)
.when({ op: "eq", attr: "Rarity", value: "Epic" })
.multiply("Damage")
.by(1.5)
.build();
Putting It Together
const item = engine
.builder("Epic Sword")
.setAttributes({ Rarity: "Epic", Level: 50, Enchanted: true })
.increase("Damage")
.by(100)
.when({ op: "eq", attr: "Rarity", value: "Epic" })
.multiply("Damage")
.by(1.5)
.group(
{
when: { op: "eq", attr: "Enchanted", value: true },
with: { priority: 25 },
},
(b) => {
b.increase("Speed").by(10);
}
)
.build();
const result = engine.evaluate(item);
// result.metrics has final numbers; explainEvaluation(result) shows the trace
See also:
- Concepts: Overview, Items and Attributes
- API: Builder, Evaluation, Conditions