# Budgets and tiers

# Budgets and tiers

Realistic physics is the one part of a game that can eat any amount of time you give it. The budget system makes the cost a *decision*: each hardware class has a **tier** with a share of the frame, and a **governor** watches the step and takes detail away in an order you choose, then gives it back.

## What it is for

A game that targets the console at 640×480 and a desktop at 4K cannot ship one set of numbers. Tiers give you the numbers; the governor keeps a bad scene from dropping the frame rate by simplifying physics instead of stuttering.

## The tier table

Each tier is a set of numbers: how long the physics step may take, and how much of each expensive thing a game should allow. `TIERS` is the table.

| Tier | `stepMs` | `maxPieces` | `fluidParticles` | `clothParticles` | `flowSources` | `flowBoxCells` |
|---|---|---|---|---|---|---|
| `desktop` | 6 | 200 | 400 | 600 | 32 | 2000 |
| `console` | 5 | 100 | 200 | 300 | 16 | 1000 |
| `mobile` | 4 | 60 | 120 | 200 | 12 | 600 |
| `low` | 3 | 30 | 60 | 100 | 8 | 300 |

The numbers are a starting point that you replace with measurements from your own device (see Measuring). `stepMs` is the share of a frame the whole physics step may take. The rest are the numbers *you* hand to the capabilities that take such a budget (`strength.maxPieces`, the particle counts of liquids and cloth); the host does not apply them on its own. `flowSources` is `FlowField.maxSources` (`applyFlowTier(field, TIERS[tier])`); `flowBoxCells` is the cells of flow boxes solved per physics step (measured 0.9 µs per cell per step on the dev machine, 30 % of `stepMs`); a box stepped every n-th step may have n times as many (`flowTierBudget(tier, every)` gives the `maxCells` to pass to the box).

```ts
import { TIERS } from '@ferret/physics';

const tier = TIERS.console;
host.config('strength').set('world', 'maxPieces', tier.maxPieces);   // hand the tier's number to the capability
```

## The governor

```ts
import { Governor } from '@ferret/physics';

host.governor = new Governor(host, {
  tier: 'console',
  window: 30, down: 2, up: 4,
  rules: [{ name: 'lighter gravity work', set: [{ capability: 'gravity', key: 'scale', value: 0.5 }] }],
});
world.events.on('physics:degrade', e => console.log(e.rule));
```

How it decides: it averages the step time over a **window** of steps (30). A window over `stepMs` counts as over; a window under 60 % of it counts as under. `down` windows over in a row (2) apply the next rule; `up` windows under in a row (4) revert the last one. The gap between the two thresholds is the dead band that stops it from flapping.

The host calls `governor.observe(ms)` with the time of every step once you set `host.governor`. Without one, nothing is ever given up.

## Rules

A rule is one step down: values to set at world scope and capabilities to switch off. The defaults (`defaultRules`) are:

1. **fewer pieces**: `strength.maxPieces` to half the tier's;
2. **fewest pieces, no particle liquid**: a fifth of the pieces and `fluid` off;
3. **no cloth, no water surface**: `stretch` and `surface` off.

A rule that names a capability the host does not have is skipped, so one list serves every game. Write your own in the order *least visible first*: shorten effect lifetimes, drop far-away detail, then switch whole features off.

```ts
host.governor = new Governor(host, {
  tier: 'mobile',
  rules: [
    { name: 'fewer debris', set: [{ capability: 'strength', key: 'maxPieces', value: 20 }] },
    { name: 'no fire spread', disable: ['combustion'] },
    { name: 'no liquids', disable: ['fluid', 'surface'] },
  ],
});
```

A capability that something else `requires` cannot be switched off while that one is on; the governor leaves it on and moves on.

## Reacting in the game

`physics:degrade` is emitted with `{ level, rule, meanMs, budgetMs }` on every step down *and* up (a revert is named `back: <rule>`). Log it in development; in a release show nothing, or a small "reduced effects" mark.

## Measuring

- `host.lastStepMs`: wall time of the last step; `physics.info` shows it.
- `tools/bench` runs the physics scenes in Node, Chromium and WebKit and prints the table. Run it on the target device and update `TIERS` with what it says.
- Count what costs: bodies awake, contacts per step, particles, cloth nodes, pieces alive. The capability guides list the cost of each.

## Debugging

| Symptom | Cause |
|---|---|
| The governor never acts | no rule applies (names not registered) or the step is under budget; check `physics.info` |
| It flaps up and down | rules that are too coarse: split a big step into two smaller rules |
| A rule has no effect | the value is overridden at a narrower scope (zone, entity): the governor sets the **world** scope |
| Pieces count is ignored | `maxPieces` is read by the `strength` capability only |

## Reference

`Governor`, `GovernorOptions`, `DegradeRule`, `TIERS`, `Tier`, `TierBudget`, `defaultRules`.
