FERRETGame engine over Babylon.js
Ferret

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.

TierstepMsmaxPiecesfluidParticlesclothParticlesflowSourcesflowBoxCells
desktop6200400600322000
console5100200300161000
mobile46012020012600
low330601008300

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).

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#

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.

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#

SymptomCause
The governor never actsno rule applies (names not registered) or the step is under budget; check physics.info
It flaps up and downrules that are too coarse: split a big step into two smaller rules
A rule has no effectthe value is overridden at a narrower scope (zone, entity): the governor sets the world scope
Pieces count is ignoredmaxPieces is read by the strength capability only

Reference#

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