Below the sender holds three data packets. A parity packet is computed as the XOR of all three. Together they form a coded block of four packets. The channel randomly drops one packet — click Drop a packet to simulate a loss.
<!-- FEC demo — sender packs 3 data + 1 XOR parity; receiver recovers any one loss -->
<p class="hint">The sender transmits 3 data packets plus 1 XOR parity packet. Drop one packet to simulate a lossy channel, then recover the missing data without a retransmission.</p>
<div class="block" id="sender-block">
<div class="block-label">Sender (4 packets transmitted)</div>
<div class="packets" id="sender-packets"></div>
</div>
<div class="arrow-row">
<div class="arrow-label">→ channel (may drop 1) →</div>
<div class="channel-line" id="channel-line"></div>
</div>
<div class="block" id="receiver-block">
<div class="block-label">Receiver</div>
<div class="packets" id="receiver-packets"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-drop" type="button">Drop a packet</button>
<button id="btn-recover" type="button" disabled>Recover it</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Visual styles for the FEC packet demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.block { background: #f0f4f8; border: 1px solid #cdd9e3; border-radius: 10px; padding: .5rem .7rem .6rem; margin-bottom: .3rem; }
.block-label { font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: #5a7088; margin-bottom: .4rem; }
.packets { display: flex; gap: .4rem; flex-wrap: wrap; }
.pkt { width: 72px; height: 56px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; font: 700 13px ui-monospace, monospace; border: 2px solid transparent; transition: all .25s; position: relative; }
.pkt .pkt-label { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; opacity: .7; }
.pkt .pkt-val { font-size: 1rem; margin-top: .15rem; }
.pkt.data { background: #dbeafe; border-color: #93c5fd; color: #1e3a5f; }
.pkt.parity { background: #ede9fe; border-color: #c4b5fd; color: #3b0764; }
.pkt.dropped { background: #fee2e2; border-color: #fca5a5; color: #7f1d1d; opacity: .7; }
.pkt.recovered { background: #d1fae5; border-color: #6ee7b7; color: #064e3b; }
.pkt.missing { background: #f3f4f6; border-color: #d1d5db; color: #9ca3af; border-style: dashed; }
.arrow-row { display: flex; flex-direction: column; align-items: center; margin: .3rem 0; }
.arrow-label { font-size: .7rem; color: #888; margin-bottom: .1rem; }
.channel-line { width: 90%; height: 2px; background: #adb1b8; position: relative; }
.channel-line::after { content: ''; position: absolute; right: 0; top: -4px; border: 5px solid transparent; border-left-color: #adb1b8; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .5rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem; 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; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Forward Error Correction demo — XOR parity scheme
// Three 8-bit data packets
const DATA = [
{ id: 'A', val: 0b10110101 },
{ id: 'B', val: 0b01101110 },
{ id: 'C', val: 0b11001010 }
];
// Parity = XOR of all data packets; any one can be recovered from the other three
const parityVal = DATA.reduce((acc, p) => acc ^ p.val, 0);
const PACKETS = [
...DATA.map(p => ({ id: p.id, val: p.val, type: 'data' })),
{ id: 'P', val: parityVal, type: 'parity' }
];
// Format a byte value as a hex string for display
const hex = v => '0x' + v.toString(16).toUpperCase().padStart(2, '0');
let droppedIdx = null;
let phase = 'idle'; // idle | dropped | recovered
function setStatus(txt, cls) {
const el = document.getElementById('status');
el.textContent = txt;
el.className = 'status ' + (cls || '');
}
// Render sender's packet row (always shows all 4 packets)
function renderSender() {
const cont = document.getElementById('sender-packets');
cont.innerHTML = '';
PACKETS.forEach((pkt, i) => {
const d = document.createElement('div');
d.className = 'pkt ' + pkt.type;
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">${hex(pkt.val)}</span>`;
cont.appendChild(d);
});
}
function renderReceiver(dropped, recovered) {
const cont = document.getElementById('receiver-packets');
cont.innerHTML = '';
PACKETS.forEach((pkt, i) => {
const d = document.createElement('div');
if (i === dropped && recovered === null) {
// Dropped packet shows as a dashed placeholder
d.className = 'pkt missing';
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">(lost)</span>`;
} else if (i === dropped && recovered !== null) {
d.className = 'pkt recovered';
d.innerHTML = `<span class="pkt-label">Recovered ${pkt.id}</span><span class="pkt-val">${hex(recovered)}</span>`;
} else {
d.className = 'pkt ' + pkt.type;
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">${hex(pkt.val)}</span>`;
}
cont.appendChild(d);
});
}
function dropPacket() {
// Pick a random packet to simulate channel loss
droppedIdx = Math.floor(Math.random() * PACKETS.length);
phase = 'dropped';
renderReceiver(droppedIdx, null);
const pkt = PACKETS[droppedIdx];
setStatus(i18nFmt('Packet %id% was lost in transit. XOR the other 3 to recover it.', { id: pkt.id }), 'bad');
document.getElementById('btn-drop').disabled = true;
document.getElementById('btn-recover').disabled = false;
}
function recoverPacket() {
// XOR all received packets to reconstruct the missing one (works for data AND parity)
const knownVals = PACKETS.filter((_, i) => i !== droppedIdx).map(p => p.val);
const recovered = knownVals.reduce((acc, v) => acc ^ v, 0);
const expected = PACKETS[droppedIdx].val;
phase = 'recovered';
renderReceiver(droppedIdx, recovered);
if (recovered === expected) {
setStatus(i18nFmt('Recovered packet %id% = %val% — matches original. No retransmission needed!', { id: PACKETS[droppedIdx].id, val: hex(recovered) }), 'ok');
} else {
setStatus('Recovery mismatch — this should not happen.', 'bad');
}
document.getElementById('btn-recover').disabled = true;
}
function reset() {
droppedIdx = null;
phase = 'idle';
renderReceiver(null, null);
setStatus('Click "Drop a packet" to simulate a channel loss.', 'info');
document.getElementById('btn-drop').disabled = false;
document.getElementById('btn-recover').disabled = true;
}
document.getElementById('btn-drop').addEventListener('click', dropPacket);
document.getElementById('btn-recover').addEventListener('click', recoverPacket);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initial render
renderSender();
reset();
<!-- FEC demo — sender packs 3 data + 1 XOR parity; receiver recovers any one loss -->
<p class="hint">The sender transmits 3 data packets plus 1 XOR parity packet. Drop one packet to simulate a lossy channel, then recover the missing data without a retransmission.</p>
<div class="block" id="sender-block">
<div class="block-label">Sender (4 packets transmitted)</div>
<div class="packets" id="sender-packets"></div>
</div>
<div class="arrow-row">
<div class="arrow-label">→ channel (may drop 1) →</div>
<div class="channel-line" id="channel-line"></div>
</div>
<div class="block" id="receiver-block">
<div class="block-label">Receiver</div>
<div class="packets" id="receiver-packets"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-drop" type="button">Drop a packet</button>
<button id="btn-recover" type="button" disabled>Recover it</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Visual styles for the FEC packet demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.block { background: #f0f4f8; border: 1px solid #cdd9e3; border-radius: 10px; padding: .5rem .7rem .6rem; margin-bottom: .3rem; }
.block-label { font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: #5a7088; margin-bottom: .4rem; }
.packets { display: flex; gap: .4rem; flex-wrap: wrap; }
.pkt { width: 72px; height: 56px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; font: 700 13px ui-monospace, monospace; border: 2px solid transparent; transition: all .25s; position: relative; }
.pkt .pkt-label { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; opacity: .7; }
.pkt .pkt-val { font-size: 1rem; margin-top: .15rem; }
.pkt.data { background: #dbeafe; border-color: #93c5fd; color: #1e3a5f; }
.pkt.parity { background: #ede9fe; border-color: #c4b5fd; color: #3b0764; }
.pkt.dropped { background: #fee2e2; border-color: #fca5a5; color: #7f1d1d; opacity: .7; }
.pkt.recovered { background: #d1fae5; border-color: #6ee7b7; color: #064e3b; }
.pkt.missing { background: #f3f4f6; border-color: #d1d5db; color: #9ca3af; border-style: dashed; }
.arrow-row { display: flex; flex-direction: column; align-items: center; margin: .3rem 0; }
.arrow-label { font-size: .7rem; color: #888; margin-bottom: .1rem; }
.channel-line { width: 90%; height: 2px; background: #adb1b8; position: relative; }
.channel-line::after { content: ''; position: absolute; right: 0; top: -4px; border: 5px solid transparent; border-left-color: #adb1b8; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .5rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem; 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; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Forward Error Correction demo — XOR parity scheme
// Three 8-bit data packets
const DATA = [
{ id: 'A', val: 0b10110101 },
{ id: 'B', val: 0b01101110 },
{ id: 'C', val: 0b11001010 }
];
// Parity = XOR of all data packets; any one can be recovered from the other three
const parityVal = DATA.reduce((acc, p) => acc ^ p.val, 0);
const PACKETS = [
...DATA.map(p => ({ id: p.id, val: p.val, type: 'data' })),
{ id: 'P', val: parityVal, type: 'parity' }
];
// Format a byte value as a hex string for display
const hex = v => '0x' + v.toString(16).toUpperCase().padStart(2, '0');
let droppedIdx = null;
let phase = 'idle'; // idle | dropped | recovered
function setStatus(txt, cls) {
const el = document.getElementById('status');
el.textContent = txt;
el.className = 'status ' + (cls || '');
}
// Render sender's packet row (always shows all 4 packets)
function renderSender() {
const cont = document.getElementById('sender-packets');
cont.innerHTML = '';
PACKETS.forEach((pkt, i) => {
const d = document.createElement('div');
d.className = 'pkt ' + pkt.type;
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">${hex(pkt.val)}</span>`;
cont.appendChild(d);
});
}
function renderReceiver(dropped, recovered) {
const cont = document.getElementById('receiver-packets');
cont.innerHTML = '';
PACKETS.forEach((pkt, i) => {
const d = document.createElement('div');
if (i === dropped && recovered === null) {
// Dropped packet shows as a dashed placeholder
d.className = 'pkt missing';
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">(lost)</span>`;
} else if (i === dropped && recovered !== null) {
d.className = 'pkt recovered';
d.innerHTML = `<span class="pkt-label">Recovered ${pkt.id}</span><span class="pkt-val">${hex(recovered)}</span>`;
} else {
d.className = 'pkt ' + pkt.type;
d.innerHTML = `<span class="pkt-label">${pkt.type === 'parity' ? 'Parity' : 'Data'} ${pkt.id}</span><span class="pkt-val">${hex(pkt.val)}</span>`;
}
cont.appendChild(d);
});
}
function dropPacket() {
// Pick a random packet to simulate channel loss
droppedIdx = Math.floor(Math.random() * PACKETS.length);
phase = 'dropped';
renderReceiver(droppedIdx, null);
const pkt = PACKETS[droppedIdx];
setStatus(i18nFmt('Packet %id% was lost in transit. XOR the other 3 to recover it.', { id: pkt.id }), 'bad');
document.getElementById('btn-drop').disabled = true;
document.getElementById('btn-recover').disabled = false;
}
function recoverPacket() {
// XOR all received packets to reconstruct the missing one (works for data AND parity)
const knownVals = PACKETS.filter((_, i) => i !== droppedIdx).map(p => p.val);
const recovered = knownVals.reduce((acc, v) => acc ^ v, 0);
const expected = PACKETS[droppedIdx].val;
phase = 'recovered';
renderReceiver(droppedIdx, recovered);
if (recovered === expected) {
setStatus(i18nFmt('Recovered packet %id% = %val% — matches original. No retransmission needed!', { id: PACKETS[droppedIdx].id, val: hex(recovered) }), 'ok');
} else {
setStatus('Recovery mismatch — this should not happen.', 'bad');
}
document.getElementById('btn-recover').disabled = true;
}
function reset() {
droppedIdx = null;
phase = 'idle';
renderReceiver(null, null);
setStatus('Click "Drop a packet" to simulate a channel loss.', 'info');
document.getElementById('btn-drop').disabled = false;
document.getElementById('btn-recover').disabled = true;
}
document.getElementById('btn-drop').addEventListener('click', dropPacket);
document.getElementById('btn-recover').addEventListener('click', recoverPacket);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initial render
renderSender();
reset();
Notice what happened: the receiver got only three of the four packets, yet it reconstructed the missing one by XOR-ing the others — no retransmission needed. The asymmetry is the point: sending one extra packet costs 25 % extra bandwidth but eliminates the latency of a round-trip that could be hundreds of milliseconds on a satellite link or a deep-space channel.
Comments
Loading comments...