diff --git a/public/assets/readme/profile-dark.png b/public/assets/readme/profile-dark.png
index ff78d51..3ff3339 100644
Binary files a/public/assets/readme/profile-dark.png and b/public/assets/readme/profile-dark.png differ
diff --git a/public/assets/readme/profile-light.png b/public/assets/readme/profile-light.png
index 4329d8e..7a963d2 100644
Binary files a/public/assets/readme/profile-light.png and b/public/assets/readme/profile-light.png differ
diff --git a/public/assets/readme/profile-narrow-dark.png b/public/assets/readme/profile-narrow-dark.png
index 46cfd06..8298791 100644
Binary files a/public/assets/readme/profile-narrow-dark.png and b/public/assets/readme/profile-narrow-dark.png differ
diff --git a/public/assets/readme/profile-narrow-light.png b/public/assets/readme/profile-narrow-light.png
index f288ea7..3933e94 100644
Binary files a/public/assets/readme/profile-narrow-light.png and b/public/assets/readme/profile-narrow-light.png differ
diff --git a/public/charts/flight-orbit-2-dark.svg b/public/charts/flight-orbit-2-dark.svg
new file mode 100644
index 0000000..3a61e02
--- /dev/null
+++ b/public/charts/flight-orbit-2-dark.svg
@@ -0,0 +1,61 @@
+
diff --git a/public/charts/flight-orbit-2-light.svg b/public/charts/flight-orbit-2-light.svg
new file mode 100644
index 0000000..dcbbcd2
--- /dev/null
+++ b/public/charts/flight-orbit-2-light.svg
@@ -0,0 +1,61 @@
+
diff --git a/scripts/charts/flight-orbit-2.mjs b/scripts/charts/flight-orbit-2.mjs
new file mode 100644
index 0000000..f845dec
--- /dev/null
+++ b/scripts/charts/flight-orbit-2.mjs
@@ -0,0 +1,279 @@
+// Draws public/charts/flight-orbit-2-{light,dark}.svg from the event logs,
+// summaries and checkpoints the satellite sent down for run 2 of experiment
+// exp4051 (OPS-SAT PRETTY, 22 October 2026):
+// opssat/results/2026-10-22-ops-sat-pretty-on-board-run2 in mruspace/flight.
+// Same theme, series colors and axis break as flight-orbit.mjs (№ 006). Three
+// lanes: segment 1 (shrinking quorum, lifecycle faults), segment 2 (fixed TMR,
+// the same faults) and segment 3 (shrinking quorum, killed and resumed).
+//
+// Run: node scripts/charts/flight-orbit-2.mjs [path to the flight repo, default ../flight]
+import { readFileSync, writeFileSync } from 'node:fs';
+import { join } from 'node:path';
+
+const FLIGHT = process.argv[2] ?? '../flight';
+const DIR = join(FLIGHT, 'opssat/results/2026-10-22-ops-sat-pretty-on-board-run2');
+const FLATSAT = join(FLIGHT, 'opssat/results/2026-10-21-tu-graz-flatsat-run2');
+
+function events(file) {
+ const rows = readFileSync(join(DIR, file), 'utf8').trim().split('\n').slice(1);
+ return rows.map((r) => {
+ const [tick, event, ...detail] = r.split(',');
+ return { tick: Number(tick), event, detail: detail.join(',') };
+ });
+}
+function fields(file) {
+ const text = readFileSync(join(DIR, file), 'utf8');
+ const out = {};
+ for (const m of text.matchAll(/(\w+)=(\S+)/g)) out[m[1]] = m[2];
+ return out;
+}
+
+const shrink = { ev: events('quorum-shrink.csv'), s: fields('summary-shrink.txt') };
+const tmr = { ev: events('quorum-tmr.csv'), s: fields('summary-tmr.txt') };
+const restart = { ev: events('quorum-restart.csv'), s: fields('summary-restart.txt') };
+const atKill = fields('checkpoint-restart-at-kill.txt');
+const final = fields('checkpoint-restart.txt');
+const find = (run, event, detail) =>
+ run.ev.find((e) => e.event === event && (detail === undefined || e.detail === detail));
+const n = (x) => Number(x);
+
+// The numbers the chart shows, each from the files.
+const D = {
+ injected: find(shrink, 'sensor_injected').tick, // 20
+ outvoted: find(shrink, 'outvoted').tick, // 30
+ hang: find(shrink, 'hang').tick, // 60
+ stuck: find(shrink, 'stuck').tick, // 120
+ retired: find(shrink, 'retired', '1').tick, // 120
+ scan: find(shrink, 'sensor_upset').tick, // 250
+ stuckbit: find(shrink, 'stuckbit').tick, // 1280
+ halted: n(shrink.s.halted_at), // 1281
+ shrinkLast: n(shrink.s.last_useful), // 1278
+ shrinkUseful: n(shrink.s.useful), // 698
+ shrinkWrong: n(shrink.s.wrong), // 0
+ kats: n(shrink.s.kat_tests), // 21
+ tmrUseful: n(tmr.s.useful), // 119
+ tmrLastUseful: n(tmr.s.last_useful), // 119
+ tmrDetected: n(tmr.s.detected), // 631
+ tmrWrong: n(tmr.s.wrong), // 0
+ tmrEnd: find(tmr, 'stopped').tick - 1, // 750
+ rStuck: find(restart, 'stuck').tick, // 200
+ rRetired: find(restart, 'retired').tick, // 202
+ killedAt: n(atKill.tick), // 900
+ resumedAt: find(restart, 'resumed').tick, // 900
+ rEnd: find(restart, 'stopped').tick - 1, // 1750
+ rUseful: n(restart.s.useful), // 1750
+ rWrong: n(restart.s.wrong), // 0
+};
+
+// What the write-up says, checked against the files.
+const fail = (msg) => {
+ throw new Error(msg);
+};
+for (const e of ['outvoted', 'hang', 'stuck', 'sensor_injected', 'sensor_upset']) {
+ if (find(tmr, e)?.tick !== find(shrink, e).tick) fail(`segments 1 and 2 do not share the ${e} tick`);
+}
+if (readFileSync(join(DIR, 'quorum-shrink.csv'), 'utf8') !== readFileSync(join(FLATSAT, 'quorum-shrink.csv'), 'utf8')) {
+ fail('the segment 1 event log differs from the FlatSat');
+}
+if (restart.ev.filter((e) => e.event === 'resumed').length !== 1 || n(restart.s.resumes) !== 1) {
+ fail('segment 3 did not resume exactly once');
+}
+if (n(atKill.resumes) !== 0) fail('segment 3 did not start from tick 0');
+if (D.resumedAt !== D.killedAt) fail('the resume tick is not the checkpoint tick');
+if (atKill.alive !== '1,0,1' || final.alive !== '1,0,1') fail('replica 1 is not retired across the kill');
+if (D.shrinkWrong + D.tmrWrong + D.rWrong !== 0) fail('a segment delivered a wrong result');
+if (D.halted !== D.stuckbit + 1) fail('the halt is not on the tick after the stuck bit');
+
+const THEMES = {
+ light: {
+ bg: '#fafaf8',
+ ink: '#16161a',
+ soft: '#44444c',
+ faint: '#6f6f78',
+ rule: '#e3e3dd',
+ mru: '#2a78d6',
+ tmr: '#eb6834',
+ },
+ dark: {
+ bg: '#16161a',
+ ink: '#ecece8',
+ soft: '#c0c0bc',
+ faint: '#8f8f8a',
+ rule: '#2e2e33',
+ mru: '#3987e5',
+ tmr: '#d95926',
+ },
+};
+const SERIF = "Charter,'Source Serif 4',Georgia,serif";
+const DISPLAY = "Futura,Jost,'Century Gothic',sans-serif";
+const W = 1200;
+const H = 676;
+const M = 56;
+const fmt = (x) => x.toLocaleString('en-US');
+
+// Plot box. Left panel: ticks 0 to 300. Right panel: 300 to 1,800.
+const PX0 = M + 24;
+const BREAK0 = 520;
+const BREAK1 = 548;
+const PX1 = W - M - 170;
+const LEFT_MAX = 300;
+const END = 1800;
+const sx = (tick) =>
+ tick <= LEFT_MAX
+ ? PX0 + ((BREAK0 - PX0) * tick) / LEFT_MAX
+ : BREAK1 + ((PX1 - BREAK1) * (tick - LEFT_MAX)) / (END - LEFT_MAX);
+const LANE_Y = { shrink: 338, tmr: 446, restart: 554 };
+const BAR = 26;
+const PY0 = 214;
+const PY1 = LANE_Y.restart + BAR / 2 + 44;
+
+function svg(theme) {
+ const t = THEMES[theme];
+ const s = [];
+ const add = (x) => s.push(x);
+ const halo = `paint-order="stroke" stroke="${t.bg}" stroke-width="6" stroke-linejoin="round"`;
+ add(`');
+ return s.join('\n') + '\n';
+}
+
+for (const theme of ['light', 'dark']) {
+ writeFileSync(`public/charts/flight-orbit-2-${theme}.svg`, svg(theme));
+}
+console.log('wrote public/charts/flight-orbit-2-{light,dark}.svg', D);
diff --git a/scripts/readme/profile.json b/scripts/readme/profile.json
index 22158bf..d2f6579 100644
--- a/scripts/readme/profile.json
+++ b/scripts/readme/profile.json
@@ -5,8 +5,8 @@
"Mru Flight is the version for spacecraft. Mru Field is the version for equipment on Earth that nobody can reach in time, such as deep-ocean sensors."
],
"status": {
- "label": "Status · prototype, ran in orbit; second run confirmed",
- "text": "The decision core, a fault-injection demo and F´ components run on general-purpose processors. On 18 October 2026 the demo ran in orbit on ESA's OPS-SAT PRETTY: three replicas on one processor. ESA confirmed a second run on 20 October."
+ "label": "Status · prototype, ran in orbit twice",
+ "text": "The decision core, a fault-injection demo and F´ components run on general-purpose processors. The demo ran in orbit on ESA's OPS-SAT PRETTY on 18 and 22 October 2026: three replicas on one processor, down to one and a safe halt, with 0 wrong results."
},
"repos": [
{
diff --git a/scripts/readme/profile.sha256 b/scripts/readme/profile.sha256
index a17a584..85126cc 100644
--- a/scripts/readme/profile.sha256
+++ b/scripts/readme/profile.sha256
@@ -1 +1 @@
-1b6c465fc3d25b044aacf9e16303d23937634d83c7cd4ada9a7269034380b1ef
+abade3c7a0f86a0055ec9bbc47e3c9702432731f555f291d6b6514b7d1c1504a
diff --git a/src/components/Chart.astro b/src/components/Chart.astro
index 3a0d4cd..21555de 100644
--- a/src/components/Chart.astro
+++ b/src/components/Chart.astro
@@ -4,13 +4,21 @@
// scripts/charts/flight-bench.mjs). Shown as
: the SVGs carry their own