# Conditions

A **condition** decides whether a rule is allowed to fire. The same gate is used by items,
abilities, world events, hooks and quests, so learning it once covers all of them.

A condition is a list of entries plus a match mode: **all** (every entry must hold) or
**any** (one is enough).

## Entry types

- world state flags
- `hasItem`
- character alive / at a location
- stat comparisons
- field comparisons
- time ranges
- abilities
- **`expression`** — write the gate yourself

## When to use an expression entry

The structured entries cover the common cases and are readable in the visual editor. Reach
for `expression` when the gate involves arithmetic or a relationship the entry types cannot
say:

```js
$.field('fever') > $.stat('resolve') * 2
```

## Narrative conditions are quest-only

One entry type is special: **`narrative`** — a plain-English condition judged by the model.

> Player spoke to the mayor about the tides

Every other caller fires synchronously and cannot wait on a judgment, so narrative entries
are **rejected outside quests**. This is not a limitation to work around; it is what keeps
a hook from stalling a turn on a model call.

## Worked example

A door that opens only at night, only if the player has the key, and only once the harbour
has been closed:

- mode: **all**
- time range: `20:00`–`04:00`
- `hasItem`: Lighthouse Key
- world state: `harbour` is `closed`

The same gate as one expression entry:

```js
$.timeBetween('20:00', '04:00') && $.hasItem('Lighthouse Key')
```

...with the world-state check left as a structured entry, because mixing is fine and the
structured version stays readable.

## Remember what null does

A field comparison against a field nobody has **does not match** — `null` loses every
comparison. If "nobody has this yet" should pass the gate, say so explicitly:

```js
!$.hasField('infection') || $.field('infection') < 5
```

See [Missing values](/docs/missing-values).

## Next

[Quests](/docs/quests) — where narrative conditions earn their keep.
