# Formulas and dials

## Dials are your plugin's tuning numbers

Declare a dial, ship a default, read it in your rules:

```js
$.dial('foodDrainPerHour')
```

Values are namespaced by plugin, so two plugins may both declare `baseRate` and mean
different numbers. `$.dial()` always reads *your* plugin's.

They appear as editable controls in your plugin's panel, which is the point: an author
tuning your system should never have to open its code.

## The engine's dials hold expressions, not coefficients

This is the part that surprises people. `maxHp`, `travelSpeed` and `xpCurve` are not
numbers — they are **formulas** you can rewrite. You are editing the shape of the rule, not
just its constants.

```js
// maxHp, as shipped
50 + $.stat('might') * 1 + $.stat('resolve') * 3 + ($.stat('playerLevel') - 1) * 5
```

A game where toughness comes from Resolve alone writes exactly that:

```js
40 + $.stat('resolve') * 6
```

A game where hit points never grow with level writes that too. Nothing about the shape is
fixed.

> **Image: `docs/formula-editor.png`**
> A formula dial in the editor — the expression box with the shipped `maxHp` formula and its
> live preview.

## Never freeze a dial into a literal

**A plugin reproducing a formula must read the dials, not copy their values.**

```js
$.damage({ amount: 0.4 })                          // wrong
$.damage({ amount: $.dial('starvationPerHour') })  // right
```

Hardcoding today's coefficient is correct right up until an author retunes the dial. Then
you have two copies of a number that no longer agree, the game behaves inconsistently, and
**nothing errors** — the frozen copy is perfectly valid code.

This is the same class of bug as [missing values](/docs/missing-values): silent, and
invisible at the default tuning where both numbers still match.

## Next

[Seams](/docs/seams) — replacing a calculation outright rather than tuning it.
