Governance Metrics¶
The BDPD platform exposes a METRIC_REGISTRY of named per-turn measurements
computed from arena state. Five of these — collectively the governance
metrics — are written for v0.9 onwards to diagnose communication and
sanctioning. This page documents the canonical formulas as implemented
in engine/metrics.js; any paper or notebook citing a different formula
is stale.
Overview¶
| Metric | Range | Activates with | Diagnostic |
|---|---|---|---|
cooperation_index |
\([0, 1]\) | any arena | fraction of players who restrained at or below their share |
lie_score |
\([0, \infty)\) | B1 cheap-talk | mean gap between announced and actual harvest, in harvest units |
sanction_rate |
\([0, 1]\) | C1 sanctioning | fraction of players sanctioned this turn |
announce_frequency |
\([0, 1]\) | B1 cheap-talk | fraction of players who announced |
silent_defection |
\([0, 1]\) | B1 cheap-talk | fraction who skipped announcing and over-harvested |
Each metric returns null when its input data is unavailable (e.g. no
announcements yet, no sanctioning configured). The arena reports null
rather than \(0\) so downstream aggregation distinguishes "absent" from
"empty".
Per-turn state input¶
All five metrics consume the same per-turn state object assembled by
the arena scheduler:
{
harvests: [number, ...], // per-player actual harvest
decisions: [{ announced, actual }, ...],
regen: number | null, // commons regeneration this turn
sanctionCount: number | null, // sanctions fired this turn
nPlayers: number,
}
The sustainable per-capita share is computed as \(\text{share} = \text{regen} / N\).
It is the natural reference for "over-harvest": if every player harvested
exactly share, the commons would be stable. Here \(N\) is the number of
players who acted this turn — silent_defection and sanction_rate use
nPlayers, while cooperation_index divides by the number of harvests
recorded; the two coincide whenever every player harvests each turn.
Formula reference¶
cooperation_index¶
Fraction of players whose actual harvest did not exceed the sustainable
share (with \(\varepsilon = 10^{-6}\) tolerance). \(1.0\) = full restraint;
\(0.0\) = everyone over-harvested. Returns null if regen is unknown or
no one played.
lie_score¶
where \(A = \{i : \text{announced}_i \ne \text{null}\}\). The mean absolute
gap between announced and actual harvest, in harvest units, computed
only over players who announced. Returns null when nobody announced.
Not normalised by share. Earlier drafts of the paper divided by \(\text{share}_i\); the registered implementation does not. The metric is therefore in the same units as
harvest, not in a dimensionless ratio.
sanction_rate¶
Fraction of players sanctioned this turn. Returns null until
sanctioning fires at least once. A value of \(1.0\) means every player
was sanctioned this turn (rare); typical values during enforcement are
\(1/N\) per violation.
Denominator is
nPlayers, notviolations. This choice keeps the metric on the same scale as the other governance metrics (fraction of the population, not fraction of violation events). The trade-off is discussed in the v0.9 design log.
announce_frequency¶
Fraction of players who emitted an intended-harvest announcement this
turn. Returns null only if nPlayers is missing; \(0.0\) is reported
explicitly when nobody announced.
silent_defection¶
Fraction of players who skipped announcing and also over-harvested.
Captures the "real defector" archetype: extracts aggressively while
staying silent. Returns null if regen is unknown or nPlayers is \(0\).
Dynamic threshold. The over-harvest test uses the live
share = regen / N, not a fixed cutoff. A pre-pilot draft of the paper used a constant (\(\text{actual} > 1\)); the registered metric tracks the sustainable share as the engine state evolves.
Aggregation¶
Each metric registers an aggregate reducer used by
governanceSnapshot() when rolling up nested-arena results. All five
governance metrics aggregate by _mean (arithmetic mean over the
non-null values across constituent arenas), so cross-arena reporting
is well-defined whenever at least one arena produced a value.
Reading the metrics in agent code¶
Agents that want to react to the governance signal read the latest snapshot from the observation envelope:
function decide(obs, memory) {
const g = obs.governance ?? {};
const coop = g.cooperation_index ?? null;
if (coop != null && coop < 0.5) {
// Group cooperation is breaking down — pull back.
return obs.myCapacity * 0.05;
}
return obs.myCapacity * 0.15;
}
The governance envelope is identical across nested arenas: an
arena-level meta-agent reads cooperation_index the same way a
world-level enforcer reads it across its child arenas.
Stability of the registry¶
Adding a new metric is a single-file change: register a { compute,
aggregate } pair under a new key in engine/metrics.js's
METRIC_REGISTRY. Engines, agents, and the governanceSnapshot()
helper need not be modified — this is the engine-agnostic invariant
documented in architecture.
The five governance metrics above are stable since v0.9. Future metrics will be appended; existing keys and formulas will not be changed without a versioned migration.