The table below shows the payoff matrix for two drivers at an intersection. Go earns a big reward if the other waits but causes a crash if both go; Wait is safe but slow. A traffic light acts as mediator and recommends each driver an action.
<p class="hint">Two drivers reach an intersection. A <strong>traffic-light mediator</strong>
privately recommends each driver an action. Click <em>Run mediator</em> to draw a joint
recommendation, then <em>Check equilibrium</em> to verify that neither driver gains by
deviating.</p>
<div class="payoff-wrap">
<table class="payoff" id="payoffTable">
<thead>
<tr><th></th><th>Driver B: Go</th><th>Driver B: Wait</th></tr>
</thead>
<tbody>
<tr>
<th>Driver A: Go</th>
<td id="c-gg" class="cell">(−5, −5)</td>
<td id="c-gw" class="cell">(4, 0)</td>
</tr>
<tr>
<th>Driver A: Wait</th>
<td id="c-wg" class="cell">(0, 4)</td>
<td id="c-ww" class="cell">(1, 1)</td>
</tr>
</tbody>
</table>
</div>
<div class="signal-row" id="signalRow" style="display:none">
<div class="signal-box" id="sigA"></div>
<div class="signal-box" id="sigB"></div>
</div>
<div class="status" id="status">Press <em>Run mediator</em> to begin.</div>
<div class="btns">
<button id="btnRun" type="button">Run mediator</button>
<button id="btnCheck" type="button" disabled>Check equilibrium</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div class="detail" id="detail"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.payoff-wrap { overflow-x: auto; margin-bottom: .8rem; }
.payoff { border-collapse: collapse; font-size: .92rem; min-width: 300px; }
.payoff th { background: #e8eef3; color: #1d3557; padding: .45rem .7rem;
text-align: center; font-weight: 700; border: 1px solid #cdd9e3; }
.payoff td { padding: .45rem .7rem; text-align: center; border: 1px solid #cdd9e3;
transition: background .25s; }
.cell.highlight { background: #d4ecd4; font-weight: 700; }
.signal-row { display: flex; gap: 1rem; margin: .6rem 0; flex-wrap: wrap; }
.signal-box { border-radius: 10px; padding: .55rem 1rem; font-size: .92rem;
font-weight: 600; border: 1px solid #cdd9e3; background: #f5f7fa;
min-width: 180px; opacity: 0; transition: opacity .4s; }
.signal-box.shown { opacity: 1; }
.signal-box.go { background: #d4ecd4; border-color: #5aab6e; color: #1a5c2a; }
.signal-box.wait { background: #fde8c8; border-color: #d4843a; color: #7a430a; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.5em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .4; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
.detail { font-size: .84rem; color: #555; margin-top: .7rem; line-height: 1.6; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ── Correlated equilibrium demo: traffic intersection ─────────────────────
// Payoffs (row=A, col=B): Go/Go=(-5,-5), Go/Wait=(4,0), Wait/Go=(0,4), Wait/Wait=(1,1)
// Mediator distribution p:
// p(Go,Go)=0, p(Go,Wait)=0.5, p(Wait,Go)=0.5, p(Wait,Wait)=0
//
// Correlated equilibrium condition (for each player and each recommendation):
// E[u_i | rec_i=a] >= E[u_i | rec_i=a, play b] for all alternative b.
//
// For Driver A recommended "Go":
// Only outcome with rec_A=Go is (Go,Wait) with prob 0.5, so B=Wait for sure.
// E[u_A | play Go, B=Wait] = 4
// E[u_A | play Wait, B=Wait] = 1 => Go is better. Condition holds.
//
// For Driver A recommended "Wait":
// Only outcome with rec_A=Wait is (Wait,Go), so B=Go for sure.
// E[u_A | play Wait, B=Go] = 0
// E[u_A | play Go, B=Go] = -5 => Wait is better. Condition holds.
//
// By symmetry same holds for Driver B. So this IS a correlated equilibrium.
const payoff = {
GG: [-5, -5], GW: [4, 0],
WG: [0, 4], WW: [1, 1]
};
// Mediator distribution: equal weight on (Go,Wait) and (Wait,Go)
const dist = [
{ a: 'Go', b: 'Wait', prob: 0.5 },
{ a: 'Wait', b: 'Go', prob: 0.5 }
];
let currentRec = null;
function sampleMediator() {
const r = Math.random();
let cum = 0;
for (const entry of dist) { cum += entry.prob; if (r < cum) return entry; }
return dist[dist.length - 1];
}
function key(a, b) {
return (a[0] + b[0]); // 'GG','GW','WG','WW'
}
// Highlight the recommended cell in the payoff table
function highlightCell(a, b) {
['c-gg','c-gw','c-wg','c-ww'].forEach(id => {
document.getElementById(id).classList.remove('highlight');
});
const cellId = 'c-' + a[0].toLowerCase() + b[0].toLowerCase();
const el = document.getElementById(cellId);
if (el) el.classList.add('highlight');
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg;
s.className = 'status ' + (cls || '');
}
function setDetail(html) {
document.getElementById('detail').innerHTML = html;
}
// ── No-deviation check ──────────────────────────────────────────────────
// For each conditional recommendation (a | rec_i=a), check that
/* u_i(a, E[b|rec_i=a]) >= u_i(alt, E[b|rec_i=a]) for all alternatives alt.
Since distribution has point masses (given rec_A=Go, B=Wait with certainty),
we compute the expected payoff exactly. */
function conditionalExpectedPayoffs(player, recAction) {
// Filter distribution entries where this player received recAction
const relevant = dist.filter(e => (player === 'A' ? e.a : e.b) === recAction);
const totalProb = relevant.reduce((s, e) => s + e.prob, 0);
if (totalProb === 0) return null;
const actions = ['Go', 'Wait'];
const expected = {};
for (const alt of actions) {
let sum = 0;
for (const e of relevant) {
const a = player === 'A' ? alt : e.a;
const b = player === 'A' ? e.b : alt;
sum += (e.prob / totalProb) * payoff[key(a, b)][player === 'A' ? 0 : 1];
}
expected[alt] = sum;
}
return expected;
}
function checkEquilibrium(rec) {
const lines = [];
let isEq = true;
for (const player of ['A', 'B']) {
const recAction = player === 'A' ? rec.a : rec.b;
const exp = conditionalExpectedPayoffs(player, recAction);
if (!exp) continue;
const altActions = ['Go', 'Wait'].filter(x => x !== recAction);
for (const alt of altActions) {
const gain = exp[alt] - exp[recAction];
if (gain > 1e-9) {
isEq = false;
lines.push(fmt(`Driver %player% recommended <b>%recAction%</b>: deviating to <b>%alt%</b> gains %gain% — NOT in equilibrium.`, { player: player, recAction: recAction, alt: alt, gain: gain.toFixed(2) }));
} else {
lines.push(fmt(`Driver %player% recommended <b>%recAction%</b>: following gives %v%, deviating to <b>%alt%</b> gives %v2% — no benefit from deviating. ✓`, { player: player, recAction: recAction, v: exp[recAction].toFixed(2), alt: alt, v2: exp[alt].toFixed(2) }));
}
}
}
return { isEq, lines };
}
// ── UI ──────────────────────────────────────────────────────────────────
document.getElementById('btnRun').addEventListener('click', () => {
currentRec = sampleMediator();
const sigA = document.getElementById('sigA');
const sigB = document.getElementById('sigB');
const row = document.getElementById('signalRow');
sigA.textContent = '';
sigB.textContent = '';
sigA.className = 'signal-box';
sigB.className = 'signal-box';
row.style.display = 'flex';
setStatus(`Mediator is drawing a recommendation…`, 'info');
setDetail('');
document.getElementById('btnCheck').disabled = true;
setTimeout(() => {
sigA.textContent = fmt(`🚦 Driver A: %a%`, { a: currentRec.a });
sigA.className = 'signal-box shown ' + currentRec.a.toLowerCase();
setTimeout(() => {
sigB.textContent = fmt(`🚦 Driver B: %b%`, { b: currentRec.b });
sigB.className = 'signal-box shown ' + currentRec.b.toLowerCase();
highlightCell(currentRec.a, currentRec.b);
setStatus(fmt(`Recommendation drawn: A→%a%, B→%b%. Now check the equilibrium condition.`, { a: currentRec.a, b: currentRec.b }), 'info');
document.getElementById('btnCheck').disabled = false;
}, 500);
}, 400);
});
document.getElementById('btnCheck').addEventListener('click', () => {
if (!currentRec) return;
const { isEq, lines } = checkEquilibrium(currentRec);
if (isEq) {
setStatus(`✓ Correlated equilibrium condition holds — no driver benefits from deviating.`, 'ok');
} else {
setStatus(`✗ Equilibrium condition violated.`, 'bad');
}
setDetail(lines.join('<br>'));
console.log(`Recommendation:`, currentRec, `| isEq:`, isEq);
console.log(`Distribution:`, dist);
});
document.getElementById('btnReset').addEventListener('click', () => {
currentRec = null;
document.getElementById('signalRow').style.display = 'none';
['c-gg','c-gw','c-wg','c-ww'].forEach(id =>
document.getElementById(id).classList.remove('highlight'));
setStatus(`Press <em>Run mediator</em> to begin.`);
setDetail('');
document.getElementById('btnCheck').disabled = true;
});
// Verify at load time that the distribution IS a correlated equilibrium
// for BOTH recommendations (Go and Wait) for both players:
(function selfTest() {
let allOk = true;
for (const player of ['A', 'B']) {
for (const recAction of ['Go', 'Wait']) {
const exp = conditionalExpectedPayoffs(player, recAction);
if (!exp) continue;
const alts = ['Go', 'Wait'].filter(x => x !== recAction);
for (const alt of alts) {
const gain = exp[alt] - exp[recAction];
if (gain > 1e-9) {
allOk = false;
console.error(`CE violated for player`, player, `rec`, recAction, `alt`, alt, `gain`, gain);
}
}
}
}
if (allOk) console.log(`Self-test: distribution IS a correlated equilibrium. ✓`);
})();
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ── Correlated equilibrium demo: traffic intersection ─────────────────────
// Payoffs (row=A, col=B): Go/Go=(-5,-5), Go/Wait=(4,0), Wait/Go=(0,4), Wait/Wait=(1,1)
// Mediator distribution p:
// p(Go,Go)=0, p(Go,Wait)=0.5, p(Wait,Go)=0.5, p(Wait,Wait)=0
//
// Correlated equilibrium condition (for each player and each recommendation):
// E[u_i | rec_i=a] >= E[u_i | rec_i=a, play b] for all alternative b.
//
// For Driver A recommended "Go":
// Only outcome with rec_A=Go is (Go,Wait) with prob 0.5, so B=Wait for sure.
// E[u_A | play Go, B=Wait] = 4
// E[u_A | play Wait, B=Wait] = 1 => Go is better. Condition holds.
//
// For Driver A recommended "Wait":
// Only outcome with rec_A=Wait is (Wait,Go), so B=Go for sure.
// E[u_A | play Wait, B=Go] = 0
// E[u_A | play Go, B=Go] = -5 => Wait is better. Condition holds.
//
// By symmetry same holds for Driver B. So this IS a correlated equilibrium.
const payoff = {
GG: [-5, -5], GW: [4, 0],
WG: [0, 4], WW: [1, 1]
};
// Mediator distribution: equal weight on (Go,Wait) and (Wait,Go)
const dist = [
{ a: 'Go', b: 'Wait', prob: 0.5 },
{ a: 'Wait', b: 'Go', prob: 0.5 }
];
let currentRec = null;
function sampleMediator() {
const r = Math.random();
let cum = 0;
for (const entry of dist) { cum += entry.prob; if (r < cum) return entry; }
return dist[dist.length - 1];
}
function key(a, b) {
return (a[0] + b[0]); // 'GG','GW','WG','WW'
}
// Highlight the recommended cell in the payoff table
function highlightCell(a, b) {
['c-gg','c-gw','c-wg','c-ww'].forEach(id => {
document.getElementById(id).classList.remove('highlight');
});
const cellId = 'c-' + a[0].toLowerCase() + b[0].toLowerCase();
const el = document.getElementById(cellId);
if (el) el.classList.add('highlight');
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg;
s.className = 'status ' + (cls || '');
}
function setDetail(html) {
document.getElementById('detail').innerHTML = html;
}
// ── No-deviation check ──────────────────────────────────────────────────
// For each conditional recommendation (a | rec_i=a), check that
/* u_i(a, E[b|rec_i=a]) >= u_i(alt, E[b|rec_i=a]) for all alternatives alt.
Since distribution has point masses (given rec_A=Go, B=Wait with certainty),
we compute the expected payoff exactly. */
function conditionalExpectedPayoffs(player, recAction) {
// Filter distribution entries where this player received recAction
const relevant = dist.filter(e => (player === 'A' ? e.a : e.b) === recAction);
const totalProb = relevant.reduce((s, e) => s + e.prob, 0);
if (totalProb === 0) return null;
const actions = ['Go', 'Wait'];
const expected = {};
for (const alt of actions) {
let sum = 0;
for (const e of relevant) {
const a = player === 'A' ? alt : e.a;
const b = player === 'A' ? e.b : alt;
sum += (e.prob / totalProb) * payoff[key(a, b)][player === 'A' ? 0 : 1];
}
expected[alt] = sum;
}
return expected;
}
function checkEquilibrium(rec) {
const lines = [];
let isEq = true;
for (const player of ['A', 'B']) {
const recAction = player === 'A' ? rec.a : rec.b;
const exp = conditionalExpectedPayoffs(player, recAction);
if (!exp) continue;
const altActions = ['Go', 'Wait'].filter(x => x !== recAction);
for (const alt of altActions) {
const gain = exp[alt] - exp[recAction];
if (gain > 1e-9) {
isEq = false;
lines.push(fmt(`Driver %player% recommended <b>%recAction%</b>: deviating to <b>%alt%</b> gains %gain% — NOT in equilibrium.`, { player: player, recAction: recAction, alt: alt, gain: gain.toFixed(2) }));
} else {
lines.push(fmt(`Driver %player% recommended <b>%recAction%</b>: following gives %v%, deviating to <b>%alt%</b> gives %v2% — no benefit from deviating. ✓`, { player: player, recAction: recAction, v: exp[recAction].toFixed(2), alt: alt, v2: exp[alt].toFixed(2) }));
}
}
}
return { isEq, lines };
}
// ── UI ──────────────────────────────────────────────────────────────────
document.getElementById('btnRun').addEventListener('click', () => {
currentRec = sampleMediator();
const sigA = document.getElementById('sigA');
const sigB = document.getElementById('sigB');
const row = document.getElementById('signalRow');
sigA.textContent = '';
sigB.textContent = '';
sigA.className = 'signal-box';
sigB.className = 'signal-box';
row.style.display = 'flex';
setStatus(`Mediator is drawing a recommendation…`, 'info');
setDetail('');
document.getElementById('btnCheck').disabled = true;
setTimeout(() => {
sigA.textContent = fmt(`🚦 Driver A: %a%`, { a: currentRec.a });
sigA.className = 'signal-box shown ' + currentRec.a.toLowerCase();
setTimeout(() => {
sigB.textContent = fmt(`🚦 Driver B: %b%`, { b: currentRec.b });
sigB.className = 'signal-box shown ' + currentRec.b.toLowerCase();
highlightCell(currentRec.a, currentRec.b);
setStatus(fmt(`Recommendation drawn: A→%a%, B→%b%. Now check the equilibrium condition.`, { a: currentRec.a, b: currentRec.b }), 'info');
document.getElementById('btnCheck').disabled = false;
}, 500);
}, 400);
});
document.getElementById('btnCheck').addEventListener('click', () => {
if (!currentRec) return;
const { isEq, lines } = checkEquilibrium(currentRec);
if (isEq) {
setStatus(`✓ Correlated equilibrium condition holds — no driver benefits from deviating.`, 'ok');
} else {
setStatus(`✗ Equilibrium condition violated.`, 'bad');
}
setDetail(lines.join('<br>'));
console.log(`Recommendation:`, currentRec, `| isEq:`, isEq);
console.log(`Distribution:`, dist);
});
document.getElementById('btnReset').addEventListener('click', () => {
currentRec = null;
document.getElementById('signalRow').style.display = 'none';
['c-gg','c-gw','c-wg','c-ww'].forEach(id =>
document.getElementById(id).classList.remove('highlight'));
setStatus(`Press <em>Run mediator</em> to begin.`);
setDetail('');
document.getElementById('btnCheck').disabled = true;
});
// Verify at load time that the distribution IS a correlated equilibrium
// for BOTH recommendations (Go and Wait) for both players:
(function selfTest() {
let allOk = true;
for (const player of ['A', 'B']) {
for (const recAction of ['Go', 'Wait']) {
const exp = conditionalExpectedPayoffs(player, recAction);
if (!exp) continue;
const alts = ['Go', 'Wait'].filter(x => x !== recAction);
for (const alt of alts) {
const gain = exp[alt] - exp[recAction];
if (gain > 1e-9) {
allOk = false;
console.error(`CE violated for player`, player, `rec`, recAction, `alt`, alt, `gain`, gain);
}
}
}
}
if (allOk) console.log(`Self-test: distribution IS a correlated equilibrium. ✓`);
})();
Notice: the mediator never recommends (Go, Go) — that would be a crash. It splits time between (Go, Wait) and (Wait, Go), so on average each driver gets through half the time. Neither driver wants to deviate because the recommendation already accounts for what the other driver is doing.
Comments
Loading comments...