Merge site-ui-3 13fafe3338: the home page restored and polished for the miner (the step scene as the hero, the network strip, the measured card picker, the Discord line). The owner, 6 October 2026: "Ok new page approved".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
@ -80,3 +80,7 @@ Dry run against the live API at 18:51 UTC rendered all six posts (pulse 641 char
|
||||||
from the release plan. Finality was paused at render time (`finality.active` false, last lock 6842 at about 18:42Z, the newest
|
from the release plan. Finality was paused at render time (`finality.active` false, last lock 6842 at about 18:42Z, the newest
|
||||||
checkpoints at 55% of total weight): the watcher's first condition, seen in dry run. Not yet run: a live post (no credentials
|
checkpoints at 55% of total weight): the watcher's first condition, seen in dry run. Not yet run: a live post (no credentials
|
||||||
file yet), the install on the box.
|
file yet), the install on the box.
|
||||||
|
|
||||||
|
## The server invite
|
||||||
|
|
||||||
|
The standing invite is https://discord.gg/beCy8G5ZR (created 6 October 2026, main; the vanity names discord.gg/igneum and discord.gg/igneumnetwork were free on 3 October and are not yet claimed). The home page's join line and any public text use this URL; nobody asks for it again.
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,8 @@ Evidence: design doc, decisions table row "Who are you?". Journey: `site/journey
|
||||||
|
|
||||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Governance, "There are no admin keys in consensus"; `site/index.html`, tile "admin keys in consensus". Was: Conceded, wording fix needed.
|
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Governance, "There are no admin keys in consensus"; `site/index.html`, tile "admin keys in consensus". Was: Conceded, wording fix needed.
|
||||||
|
|
||||||
|
Status: Conceded, stated (6 October 2026, later that night): the home page was restored to its fuller shape on the owner's word ("revert it but polish it") and carries the tile "admin keys in consensus" again, beside the litepaper's Governance sentence.
|
||||||
|
|
||||||
Status: Conceded, stated (6 October 2026, night): the home page was redrawn as one statement, the live scene, three facts and the downloads, so the tile "admin keys in consensus" is no longer on `site/index.html`; the sentence stands on `site/litepaper.html`, Governance ("There are no admin keys in consensus"). The 5 October line below is the history.
|
Status: Conceded, stated (6 October 2026, night): the home page was redrawn as one statement, the live scene, three facts and the downloads, so the tile "admin keys in consensus" is no longer on `site/index.html`; the sentence stands on `site/litepaper.html`, Governance ("There are no admin keys in consensus"). The 5 October line below is the history.
|
||||||
|
|
||||||
Answer: Correct. Consensus has no admin keys. The genesis apps are contracts and each will have an upgrade policy; the bridge's is the one that matters, and if it starts as a committee bridge (E7) it has keys by definition. The development fund contract named in the quote no longer exists: the fund was removed on 3 October 2026. The litepaper's sentence must be scoped to consensus and each genesis contract must publish its key policy before launch.
|
Answer: Correct. Consensus has no admin keys. The genesis apps are contracts and each will have an upgrade policy; the bridge's is the one that matters, and if it starts as a committee bridge (E7) it has keys by definition. The development fund contract named in the quote no longer exists: the fund was removed on 3 October 2026. The litepaper's sentence must be scoped to consensus and each genesis contract must publish its key policy before launch.
|
||||||
|
|
@ -689,6 +691,8 @@ Evidence: litepaper "For miners", hardware paragraph. Wording: overclaims list,
|
||||||
|
|
||||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Mining, "no chip publicly shipped, approximate" and "Monero is precedent, not proof"; `site/index.html`, hero, "a chip gains too little to take your place". Was: Conceded, label needed.
|
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Mining, "no chip publicly shipped, approximate" and "Monero is precedent, not proof"; `site/index.html`, hero, "a chip gains too little to take your place". Was: Conceded, label needed.
|
||||||
|
|
||||||
|
Status: Conceded, stated (6 October 2026, later that night): the restored home page carries the RandomX paragraph again, "since 2019 (approximate)", beside the litepaper.
|
||||||
|
|
||||||
Status: Conceded, stated (6 October 2026, night): the home page no longer carries the RandomX paragraph; "since 2019 (approximate)" and "precedent, not proof" stand on `site/litepaper.html` (vs RandomX, Mining). The 5 October line below is the history.
|
Status: Conceded, stated (6 October 2026, night): the home page no longer carries the RandomX paragraph; "since 2019 (approximate)" and "precedent, not proof" stand on `site/litepaper.html` (vs RandomX, Mining). The 5 October line below is the history.
|
||||||
|
|
||||||
Answer: True. "No chip publicly shipped, approximate" is the defensible phrasing. The argument from Monero is precedent, not proof, and the bounty exists because precedent is not proof.
|
Answer: True. "No chip publicly shipped, approximate" is the defensible phrasing. The argument from Monero is precedent, not proof, and the bounty exists because precedent is not proof.
|
||||||
|
|
@ -958,6 +962,8 @@ Sweep (5 October 2026, evening): stated in part. `site/partials/footer.html` on
|
||||||
|
|
||||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Roadmap phase 6, and `site/journey.json` phase 6, "No listing is arranged, promised or sought by the project". Was: Conceded, fix now.
|
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Roadmap phase 6, and `site/journey.json` phase 6, "No listing is arranged, promised or sought by the project". Was: Conceded, fix now.
|
||||||
|
|
||||||
|
Status: Conceded, stated (6 October 2026, later that night): the restored home page shows the journey again; phase 6 reads "No listing is arranged, promised or sought by the project" there and in the litepaper's roadmap.
|
||||||
|
|
||||||
Status: Conceded, stated (6 October 2026, night): the home page's journey is no longer shown (the inlined feed remains in the page source); the sentence "No listing is arranged, promised or sought by the project" stands on `site/litepaper.html` Roadmap phase 6 and in `site/journey.json`. The 5 October line below is the history.
|
Status: Conceded, stated (6 October 2026, night): the home page's journey is no longer shown (the inlined feed remains in the page source); the sentence "No listing is arranged, promised or sought by the project" stands on `site/litepaper.html` Roadmap phase 6 and in `site/journey.json`. The 5 October line below is the history.
|
||||||
|
|
||||||
Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey.
|
Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey.
|
||||||
|
|
|
||||||
BIN
docs/plans/site-ui-3-shots/after-v2/home-1440-dark-fold.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-1440-dark.jpg
Normal file
|
After Width: | Height: | Size: 871 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-1440-light-fold.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-1440-light.jpg
Normal file
|
After Width: | Height: | Size: 876 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-390-dark-fold.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-390-dark.jpg
Normal file
|
After Width: | Height: | Size: 699 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-390-light-fold.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-390-light.jpg
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm
Normal file
541
site/index.html
|
|
@ -1,48 +1,45 @@
|
||||||
/* Igneum live steps (6 October 2026, the owner: "I liked it when we could see each step of the algo working").
|
/* Igneum live steps (6 October 2026). The owner: "I liked it when we could see each step of the algo working", and later,
|
||||||
The original step view with real blocks: a lane chart where every square is a block the observer stored, released at a
|
"can we have a dag animation like we had before? with the shards making up the block, then the final line and the other
|
||||||
calm pace (the feed is sampled, the counters elsewhere read the chain), and every square shows its step: mined (an outline,
|
bits that looked really cool but brought up to date?"
|
||||||
grey while pending, ember once included), shards being proven (molten quarter cells fill as shards are verified or paid),
|
The lane chart with the original bits, every one driven by the live feed: a real block arrives from the right with a glow
|
||||||
proven (ember fill), locked checkpoint (a ring; a dashed "final" line marks that everything to its left is final).
|
and its chain number under it; its outline is grey while pending and ember once included; as its shards are verified or
|
||||||
Each transition is animated as it happens (a glow and a ring pulse) and captioned in one line for the newest block that
|
paid, particles fly in from the edges and its quarter cells fill molten; when every shard is proven the block turns ember
|
||||||
changed. The wheel is never touched: the page scrolls through the scene. Light and dark from the CSS tokens; a still frame
|
and its provers are paid (the caption says to how many); a locked checkpoint takes a ring and a ripple and the dashed
|
||||||
under reduced motion. Needs /scenes/feed.js (IgneumFeed) on the page.
|
"final" line sweeps in from the right to it, drawn only while finality is active. A proof or a lock that lands on a block
|
||||||
Usage: var steps = IgneumSteps.mount(canvas, { caption: el, window: 300 }); then IgneumFeed.start({ subs: [steps], window: 300 }). */
|
which has already left the screen re-enters from the right as its own event, so every step is seen when it happens. The
|
||||||
|
stream is sampled (one block every 2.5 s, a crossing of about 34 s); the counters elsewhere read the chain. No wheel
|
||||||
|
handler: the page scrolls through the scene. Light and dark from the CSS tokens; a still frame under reduced motion.
|
||||||
|
Needs /scenes/feed.js (IgneumFeed) on the page.
|
||||||
|
Usage: var steps = IgneumSteps.mount(canvas, { caption: el }); then IgneumFeed.start({ subs: [steps], window: 300 }). */
|
||||||
(function (root) {
|
(function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var F = root.IgneumFeed;
|
var F = root.IgneumFeed;
|
||||||
function pad(n) { return String(n).padStart(2, '0'); }
|
function pad(n) { return String(n).padStart(2, '0'); }
|
||||||
function hms(ms) { var d = new Date(ms); return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); }
|
function hms(ms) { var d = new Date(ms); return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); }
|
||||||
function fmt(n) { return n === null || n === undefined ? null : Number(n).toLocaleString('en-GB'); }
|
function fmt(n) { return n === null || n === undefined ? null : Number(n).toLocaleString('en-GB'); }
|
||||||
function stepOf(b) {
|
function isDone(x) { return x.state === 'verified' || x.state === 'paid'; }
|
||||||
var sh = b.shards || [], done = function (x) { return x.state === 'verified' || x.state === 'paid'; };
|
function stepOf(b) { var sh = b.shards || []; if (b.proven || (sh.length && sh.every(isDone))) return 'proven'; if (sh.some(function (x) { return x.state === 'proving' || isDone(x); })) return 'proving'; return 'mined'; }
|
||||||
if (b.proven || (sh.length && sh.every(done))) return 'proven';
|
function shardKey(b) { return (b.shards || []).map(function (x) { return x.state[0]; }).join(''); }
|
||||||
if (sh.some(function (x) { return x.state === 'proving' || done(x); })) return 'proving';
|
|
||||||
return 'mined';
|
|
||||||
}
|
|
||||||
function mount(canvas, opts) {
|
function mount(canvas, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
var ctx = canvas.getContext('2d'), size = F.sizer(canvas, ctx), dims = size(), T = F.theme();
|
var ctx = canvas.getContext('2d'), size = F.sizer(canvas, ctx), dims = size(), T = F.theme();
|
||||||
var lanes = 5, S, GAP, speed, blocks = [], byHash = {}, queue = [], laneOf = {}, laneN = 0, last = 0, prevT = 0;
|
var lanes = 5, S, GAP, speed, RELEASE_MS = 2500, blocks = [], byHash = {}, known = {}, queue = [], events = [], sparks = [], ripples = [], laneOf = {}, laneN = 0, last = 0, prevT = 0;
|
||||||
// one block released every 2.5 s and a crossing of about 34 s at 1440: the chain merges a pending block in about 10 s, so a
|
var caption = opts.caption || null, lastChange = 0, latest = null, finality = null, proving = null, seeded = false, lockX = null, lockTarget = null, lockHash = null;
|
||||||
// square is still on screen when its step changes (the original pace of 10 s lost most transitions off the left edge)
|
|
||||||
var RELEASE_MS = 2500;
|
|
||||||
var caption = opts.caption || null, lastChange = 0, latest = null, finality = null, proving = null, serverNow = 0;
|
|
||||||
function metrics() { S = dims.W < 600 ? 18 : 26; GAP = dims.W < 600 ? S * 2.5 : S * 3.1; speed = GAP / RELEASE_MS; }
|
function metrics() { S = dims.W < 600 ? 18 : 26; GAP = dims.W < 600 ? S * 2.5 : S * 3.1; speed = GAP / RELEASE_MS; }
|
||||||
metrics();
|
metrics(); root.addEventListener('resize', function () { dims = size(); metrics(); L.kick(); });
|
||||||
root.addEventListener('resize', function () { dims = size(); metrics(); L.kick(); });
|
function laneY(l) { return dims.H * 0.14 + l * (dims.H * 0.70 / (lanes - 1)); }
|
||||||
function laneY(l) { return dims.H * 0.14 + l * (dims.H * 0.72 / (lanes - 1)); }
|
|
||||||
function laneFor(m) { if (!(m in laneOf)) laneOf[m] = laneN++ % lanes; return laneOf[m]; }
|
function laneFor(m) { if (!(m in laneOf)) laneOf[m] = laneN++ % lanes; return laneOf[m]; }
|
||||||
function cpIndexOf(hash) { var cps = (finality && finality.checkpoints) || []; for (var i = cps.length - 1; i >= 0; i--) if (cps[i].hash === hash) return cps[i].index; return null; }
|
function cpIndexOf(hash) { var cps = (finality && finality.checkpoints) || []; for (var i = cps.length - 1; i >= 0; i--) if (cps[i].hash === hash) return cps[i].index; return null; }
|
||||||
// the record of a block for the caption: its words with their times
|
function provers(b) { var set = {}; (b.shards || []).forEach(function (x) { if (x.prover) set[x.prover] = 1; }); return Object.keys(set).length; }
|
||||||
function describe(e) {
|
function describe(e) {
|
||||||
var b = e.src, parts = [], name = b.number !== null && b.number !== undefined ? 'block ' + fmt(b.number) : 'block ' + b.hash.slice(0, 8);
|
var b = e.src, parts = [], name = b.number !== null && b.number !== undefined ? 'block ' + fmt(b.number) : 'block ' + b.hash.slice(0, 8);
|
||||||
parts.push('mined ' + hms(b.ts) + (b.color === 'red' ? ', excluded' : b.chain ? ', on the selected chain' : b.color === 'blue' ? ', included' : ', pending'));
|
parts.push('mined ' + hms(b.ts) + (b.color === 'red' ? ', excluded' : b.chain ? ', on the selected chain' : b.color === 'blue' ? ', included' : ', pending'));
|
||||||
var sh = b.shards || [];
|
var sh = b.shards || [];
|
||||||
if (sh.length) {
|
if (sh.length) {
|
||||||
var proving = sh.filter(function (x) { return x.state === 'proving'; }), done = sh.filter(function (x) { return x.state === 'verified' || x.state === 'paid'; });
|
var done = sh.filter(isDone), np = provers(b), paid = sh.filter(function (x) { return x.state === 'paid'; }).length;
|
||||||
var provers = {}; sh.forEach(function (x) { if (x.prover) provers[x.prover] = 1; }); var np = Object.keys(provers).length;
|
if (e.step === 'proven') { var lag = Math.max.apply(null, sh.map(function (x) { return x.lag || 0; })); parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proven in ' + (lag > 0 ? lag : Math.max(1, Math.round((Date.now() - b.ts) / 1000))) + ' s'); if (paid) parts.push(np ? np + (np === 1 ? ' prover paid' : ' provers paid') : 'provers paid'); }
|
||||||
if (e.step === 'proven') { var lag = Math.max.apply(null, sh.map(function (x) { return x.lag || 0; })); parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : '')); parts.push('proven in ' + (lag > 0 ? lag : Math.max(1, Math.round((Date.now() - b.ts) / 1000))) + ' s'); }
|
else if (done.length) parts.push(done.length + ' of ' + sh.length + ' shards proven' + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
|
||||||
else if (proving.length || done.length) parts.push((done.length ? done.length + ' of ' + sh.length + ' shards proven' : sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proving') + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
|
else if (sh.some(function (x) { return x.state === 'proving'; })) parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proving' + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
|
||||||
else parts.push(sh.length + (sh.length === 1 ? ' shard planned' : ' shards planned'));
|
else parts.push(sh.length + (sh.length === 1 ? ' shard planned' : ' shards planned'));
|
||||||
}
|
}
|
||||||
if (b.locked) { var idx = cpIndexOf(b.hash); parts.push('locked at checkpoint' + (idx !== null ? ' ' + fmt(idx) : '')); }
|
if (b.locked) { var idx = cpIndexOf(b.hash); parts.push('locked at checkpoint' + (idx !== null ? ' ' + fmt(idx) : '')); }
|
||||||
|
|
@ -50,64 +47,75 @@
|
||||||
return name + ': ' + parts.join('; ');
|
return name + ': ' + parts.join('; ');
|
||||||
}
|
}
|
||||||
function setCaption(text, muted) { if (!caption) return; caption.textContent = text; caption.classList.toggle('muted', !!muted); }
|
function setCaption(text, muted) { if (!caption) return; caption.textContent = text; caption.classList.toggle('muted', !!muted); }
|
||||||
function apply(e, src, now, announce) {
|
function rr(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }
|
||||||
var before = e.step + '|' + e.src.color + '|' + e.src.locked + '|' + e.src.final + '|' + (e.src.chain ? 1 : 0);
|
function cellOf(b, i) { var h = S / 2, q = S / 2 - 3; return { x: b.x - h + 3 + (i % 2) * q + (q - 1) / 2, y: b.y - h + 3 + Math.floor(i / 2) * q + (q - 1) / 2 }; }
|
||||||
e.src = src; e.step = stepOf(src);
|
// a particle per newly proven shard, from an edge to the block's quarter cell (the original scene's sparks, now real)
|
||||||
var after = e.step + '|' + src.color + '|' + src.locked + '|' + src.final + '|' + (src.chain ? 1 : 0);
|
function sparkFor(e, i) { var fromTop = Math.random() < 0.5; sparks.push({ x: Math.random() * dims.W, y: fromTop ? -6 : dims.H + 6, b: e, i: i, p: 0 }); }
|
||||||
if (announce && after !== before) { e.glow = 1; if (src.locked && !e.ringed) { e.ringed = true; e.ripple = 1; } lastChange = now; latest = e; setCaption(describe(e)); if (opts.onChange) opts.onChange(e, before, after); }
|
function announce(e, now, reason) { e.glow = 1; lastChange = now; latest = e; setCaption(describe(e)); if (opts.onChange) opts.onChange(e, reason); }
|
||||||
|
function apply(e, src, now) {
|
||||||
|
var before = e.step + '|' + e.src.color + '|' + e.src.locked + '|' + (e.src.chain ? 1 : 0) + '|' + shardKey(e.src);
|
||||||
|
var oldSh = e.src.shards || []; e.src = src; e.step = stepOf(src);
|
||||||
|
var after = e.step + '|' + src.color + '|' + src.locked + '|' + (src.chain ? 1 : 0) + '|' + shardKey(src);
|
||||||
|
if (after === before) return false;
|
||||||
|
(src.shards || []).forEach(function (x, i) { if (i < 4 && isDone(x) && !(oldSh[i] && isDone(oldSh[i])) && e.x !== null) sparkFor(e, i); });
|
||||||
|
if (src.locked && !e.ringed) { e.ringed = true; if (e.x !== null) { ripples.push({ x: e.x, y: e.y, r: S, a: 1 }); ripples.push({ x: e.x, y: e.y, r: S * 0.5, a: 1 }); } }
|
||||||
|
announce(e, now, after.split('|')[0]); return true;
|
||||||
}
|
}
|
||||||
var seeded = false;
|
|
||||||
function push(d) {
|
function push(d) {
|
||||||
var now = performance.now(); finality = d.finality || null; proving = d.proving || null; serverNow = new Date(d.now).getTime();
|
var now = performance.now(); finality = d.finality || null; proving = d.proving || null;
|
||||||
if (!seeded) {
|
if (finality && !finality.active) { lockTarget = null; lockX = null; lockHash = null; }
|
||||||
// the first reply fills the scene: the newest blocks placed across the width, oldest at the left, so nothing starts empty
|
if (!seeded) { seeded = true; var n = Math.ceil(dims.W / GAP) + 1, recent = d.blocks.slice(-n);
|
||||||
seeded = true; var n = Math.ceil(dims.W / GAP) + 1, recent = d.blocks.slice(-n);
|
recent.forEach(function (b, i) { var e = { hash: b.hash, src: b, step: stepOf(b), x: dims.W - S - (recent.length - 1 - i) * GAP, y: laneY(laneFor(b.miner || '?')), glow: 0, ringed: !!b.locked, born: now }; byHash[b.hash] = e; known[b.hash] = e; blocks.push(e); }); }
|
||||||
recent.forEach(function (b, i) { var e = { hash: b.hash, src: b, step: stepOf(b), x: dims.W - S - (recent.length - 1 - i) * GAP, y: laneY(laneFor(b.miner || '?')), glow: 0, ripple: 0, ringed: !!b.locked, born: now }; byHash[b.hash] = e; blocks.push(e); });
|
|
||||||
}
|
|
||||||
d.blocks.forEach(function (b) {
|
d.blocks.forEach(function (b) {
|
||||||
var e = byHash[b.hash];
|
var e = known[b.hash];
|
||||||
if (e) { apply(e, b, now, true); return; }
|
if (e) { var changed = apply(e, b, now); if (changed && e.x === null && blocks.indexOf(e) < 0 && (e.step === 'proven' || b.locked)) { e.event = true; queue.push(e); } return; }
|
||||||
e = { hash: b.hash, src: b, step: stepOf(b), x: null, y: null, glow: 0, ripple: 0, ringed: !!b.locked, born: 0 };
|
e = { hash: b.hash, src: b, step: stepOf(b), x: null, y: null, glow: 0, ringed: !!b.locked, born: 0 }; known[b.hash] = e; queue.push(e);
|
||||||
byHash[b.hash] = e; queue.push(e); if (queue.length > 6) { var dropped = queue.shift(); delete byHash[dropped.hash]; }
|
|
||||||
});
|
});
|
||||||
var seen = {}; d.blocks.forEach(function (b) { seen[b.hash] = 1; });
|
// forget blocks that have left both the feed and the screen
|
||||||
// a block the feed no longer carries keeps its last words on screen until it leaves at the left
|
var inFeed = {}; d.blocks.forEach(function (b) { inFeed[b.hash] = 1; });
|
||||||
|
Object.keys(known).forEach(function (h) { var e = known[h]; if (!inFeed[h] && blocks.indexOf(e) < 0 && queue.indexOf(e) < 0) delete known[h]; });
|
||||||
|
if (queue.length > 12) queue = queue.filter(function (e) { return e.event; }).concat(queue.filter(function (e) { return !e.event; }).slice(-6));
|
||||||
if (!latest || now - lastChange > 60000) {
|
if (!latest || now - lastChange > 60000) {
|
||||||
var why = finality && finality.supported && !finality.active ? 'finality paused: under two thirds of the weight is signing, so no checkpoint locks' : proving && proving.supported && proving.active && !(proving.shards_paid_10m > 0) ? 'no proof landed in the last 10 minutes; proofs land about ' + (proving.median_proof_lag_s ? Math.round(proving.median_proof_lag_s) : 380) + ' s behind the tip' : 'no block changed step in the last minute';
|
var why = finality && finality.supported && !finality.active ? 'finality paused: under two thirds of the weight is signing, so no checkpoint locks' : proving && proving.supported && proving.active && !(proving.shards_paid_10m > 0) ? 'no proof landed in the last 10 minutes; proofs land about ' + (proving.median_proof_lag_s ? Math.round(proving.median_proof_lag_s) : 380) + ' s behind the tip' : 'no block changed step in the last minute';
|
||||||
setCaption('Blocks arrive every second. ' + why.charAt(0).toUpperCase() + why.slice(1) + '.', true);
|
setCaption('Blocks arrive every second. ' + why.charAt(0).toUpperCase() + why.slice(1) + '.', true);
|
||||||
}
|
}
|
||||||
L.kick();
|
L.kick();
|
||||||
}
|
}
|
||||||
function rr(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }
|
function spawn(e, t) { e.x = dims.W + S; e.y = laneY(laneFor(e.src.miner || '?')); e.born = t; e.glow = 1; blocks.push(e); byHash[e.hash] = e;
|
||||||
function spawn(e, t) { e.x = dims.W + S; e.y = laneY(laneFor(e.src.miner || '?')); e.born = t; blocks.push(e); if (blocks.length > 80) { var old = blocks.shift(); if (!queue.some(function (q) { return q === old; })) delete byHash[old.hash]; } }
|
if (e.event) { (e.src.shards || []).forEach(function (x, i) { if (i < 4 && isDone(x)) sparkFor(e, i); }); if (e.src.locked) { ripples.push({ x: e.x, y: e.y, r: S, a: 1 }); } }
|
||||||
|
if (blocks.length > 80) { var old = blocks.shift(); delete byHash[old.hash]; } }
|
||||||
function draw(t) {
|
function draw(t) {
|
||||||
var W = dims.W, H = dims.H; T = F.theme();
|
var W = dims.W, H = dims.H; T = F.theme();
|
||||||
var dt = prevT ? Math.min(50, t - prevT) : 16; prevT = t;
|
var dt = prevT ? Math.min(50, t - prevT) : 16; prevT = t;
|
||||||
if (queue.length && t - last > RELEASE_MS) { spawn(queue.pop(), t); queue.length = 0; last = t; } // the newest waiting block; the rest are sampled out
|
if (queue.length && t - last > RELEASE_MS) { var ev = queue.findIndex(function (e) { return e.event; }); spawn(ev >= 0 ? queue.splice(ev, 1)[0] : queue.pop(), t); if (ev < 0) queue = queue.filter(function (e) { return e.event; }); last = t; }
|
||||||
blocks.forEach(function (b) { b.x -= speed * dt; if (b.glow > 0) b.glow -= 0.02; if (b.ripple > 0) b.ripple -= 0.012; });
|
blocks.forEach(function (b) { b.x -= speed * dt; if (b.glow > 0) b.glow -= 0.015; });
|
||||||
blocks = blocks.filter(function (b) { if (b.x < -S * 2) { if (byHash[b.hash] === b && queue.indexOf(b) < 0) delete byHash[b.hash]; return false; } return true; });
|
blocks = blocks.filter(function (b) { if (b.x < -S * 2) { b.x = null; delete byHash[b.hash]; return false; } return true; });
|
||||||
|
sparks.forEach(function (sp) { sp.p += 0.035; }); sparks = sparks.filter(function (sp) { return sp.p < 1 && blocks.indexOf(sp.b) >= 0; });
|
||||||
|
ripples.forEach(function (r) { r.r += 2.2; r.a -= 0.012; r.x -= speed * dt; }); ripples = ripples.filter(function (r) { return r.a > 0; });
|
||||||
ctx.clearRect(0, 0, W, H);
|
ctx.clearRect(0, 0, W, H);
|
||||||
ctx.strokeStyle = F.rgba(T.line, 0.9); ctx.lineWidth = 1; for (var l = 0; l < lanes; l++) { ctx.beginPath(); ctx.moveTo(0, laneY(l)); ctx.lineTo(W, laneY(l)); ctx.stroke(); }
|
ctx.strokeStyle = F.rgba(T.line, 0.9); ctx.lineWidth = 1; for (var l = 0; l < lanes; l++) { ctx.beginPath(); ctx.moveTo(0, laneY(l)); ctx.lineTo(W, laneY(l)); ctx.stroke(); }
|
||||||
// parent edges between blocks on screen
|
blocks.forEach(function (b) { (b.src.parents || []).forEach(function (h) { var q = byHash[h]; if (!q || q.x === null) return; var both = b.step === 'proven' && q.step === 'proven'; ctx.strokeStyle = both ? F.rgba(T.ember, 0.45) : F.rgba(T.ash, 0.22); ctx.lineWidth = both ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(b.x, b.y); var mx = (b.x + q.x) / 2; ctx.bezierCurveTo(mx, b.y, mx, q.y, q.x, q.y); ctx.stroke(); }); });
|
||||||
blocks.forEach(function (b) { (b.src.parents || []).forEach(function (h) { var q = byHash[h]; if (!q || q.x === null || blocks.indexOf(q) < 0) return; var both = b.step === 'proven' && q.step === 'proven'; ctx.strokeStyle = both ? F.rgba(T.ember, 0.45) : F.rgba(T.ash, 0.22); ctx.lineWidth = both ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(b.x, b.y); var mx = (b.x + q.x) / 2; ctx.bezierCurveTo(mx, b.y, mx, q.y, q.x, q.y); ctx.stroke(); }); });
|
// the final line: drawn only while finality is active; it sweeps in from the right to the newest locked block on screen
|
||||||
// the lock line: everything left of the newest locked block is final
|
if (finality && finality.active) { var lk = null; blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; });
|
||||||
// the lock line and the word final are drawn only while finality is active (the owner's rule: no "final" anywhere while finality is paused)
|
if (lk) { if (lockHash !== lk.hash) { lockHash = lk.hash; if (lockX === null) lockX = W + 20; } lockTarget = lk.x; lockX += (lockTarget - lockX) * 0.08;
|
||||||
var lk = null; if (!finality || finality.active) blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; });
|
ctx.strokeStyle = F.rgba(T.ember, 0.55); ctx.lineWidth = 1.5; ctx.setLineDash([5, 6]); ctx.beginPath(); ctx.moveTo(lockX, 6); ctx.lineTo(lockX, H - 6); ctx.stroke(); ctx.setLineDash([]);
|
||||||
if (lk) { ctx.strokeStyle = F.rgba(T.ember, 0.55); ctx.lineWidth = 1.5; ctx.setLineDash([5, 6]); ctx.beginPath(); ctx.moveTo(lk.x, 6); ctx.lineTo(lk.x, H - 6); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = T.ash; ctx.font = '500 ' + Math.max(10, Math.round(S * 0.42)) + 'px IBM Plex Mono, monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'alphabetic'; ctx.fillText('final', lk.x - 8, 14); }
|
ctx.fillStyle = T.ash; ctx.font = '500 ' + Math.max(10, Math.round(S * 0.42)) + 'px IBM Plex Mono, monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'alphabetic'; ctx.fillText('final', lockX - 8, 14); } }
|
||||||
blocks.forEach(function (b) {
|
blocks.forEach(function (b) {
|
||||||
var h = S / 2, w = b.step, src = b.src, ex = src.color === 'red';
|
var h = S / 2, w = b.step, src = b.src, ex = src.color === 'red';
|
||||||
if (b.glow > 0) { var g = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, S * 1.6); g.addColorStop(0, F.rgba(T.ember, 0.45 * b.glow)); g.addColorStop(1, F.rgba(T.ember, 0)); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.6, 0, Math.PI * 2); ctx.fill(); }
|
if (b.glow > 0) { var g = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, S * 1.6); g.addColorStop(0, F.rgba(T.ember, 0.45 * b.glow)); g.addColorStop(1, F.rgba(T.ember, 0)); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.6, 0, Math.PI * 2); ctx.fill(); }
|
||||||
if (ex) ctx.globalAlpha = 0.45;
|
if (ex) ctx.globalAlpha = 0.45;
|
||||||
if (w === 'proven') { ctx.fillStyle = T.ember; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill(); }
|
if (w === 'proven') { ctx.fillStyle = T.ember; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill(); }
|
||||||
else {
|
else { ctx.fillStyle = T.row; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill();
|
||||||
ctx.fillStyle = T.row; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill();
|
|
||||||
ctx.strokeStyle = w === 'proving' ? F.rgba(T.molten, 0.9) : (src.chain || src.color === 'blue') ? F.rgba(T.ember, 0.8) : F.rgba(T.ash, 0.7); ctx.lineWidth = 2; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.stroke();
|
ctx.strokeStyle = w === 'proving' ? F.rgba(T.molten, 0.9) : (src.chain || src.color === 'blue') ? F.rgba(T.ember, 0.8) : F.rgba(T.ash, 0.7); ctx.lineWidth = 2; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.stroke();
|
||||||
if (w === 'proving') { var sh = src.shards || [], n = Math.min(4, sh.length), q = S / 2 - 3; for (var i = 0; i < n; i++) { var st = sh[i].state; if (st === 'verified' || st === 'paid' || st === 'proving') { ctx.fillStyle = st === 'proving' ? F.rgba(T.molten, 0.45) : T.molten; var qx = b.x - h + 3 + (i % 2) * q, qy = b.y - h + 3 + Math.floor(i / 2) * q; rr(qx, qy, q - 1, q - 1, 2); ctx.fill(); } } }
|
var sh = src.shards || [], n = Math.min(4, sh.length), q = S / 2 - 3;
|
||||||
}
|
for (var i = 0; i < n; i++) { var st = sh[i].state; if (st === 'verified' || st === 'paid' || st === 'proving') { ctx.fillStyle = st === 'proving' ? F.rgba(T.molten, 0.45) : T.molten; var qx = b.x - h + 3 + (i % 2) * q, qy = b.y - h + 3 + Math.floor(i / 2) * q; rr(qx, qy, q - 1, q - 1, 2); ctx.fill(); } } }
|
||||||
if (src.locked) { var pulse = 0.8 + 0.2 * Math.sin(t / 350); ctx.strokeStyle = F.rgba(T.ember, pulse); ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.15, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = T.bone; ctx.beginPath(); ctx.arc(b.x, b.y, S * 0.16, 0, Math.PI * 2); ctx.fill(); }
|
if (src.locked) { var pulse = 0.8 + 0.2 * Math.sin(t / 350); ctx.strokeStyle = F.rgba(T.ember, pulse); ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.15, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = T.bone; ctx.beginPath(); ctx.arc(b.x, b.y, S * 0.16, 0, Math.PI * 2); ctx.fill(); }
|
||||||
if (b.ripple > 0) { ctx.strokeStyle = F.rgba(T.ember, b.ripple); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(b.x, b.y, S * (1.3 + (1 - b.ripple) * 2), 0, Math.PI * 2); ctx.stroke(); }
|
// the chain number under the block (the short hash off the chain), at the wide size only
|
||||||
|
if (S >= 26) { ctx.fillStyle = F.rgba(T.ash, ex ? 0.6 : 0.9); ctx.font = '400 10px IBM Plex Mono, monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText(src.number !== null && src.number !== undefined ? fmt(src.number) : src.hash.slice(0, 6), b.x, b.y + h + 5); }
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
});
|
});
|
||||||
|
sparks.forEach(function (sp) { var c = cellOf(sp.b, sp.i), e = 1 - Math.pow(1 - sp.p, 3), sx = sp.x + (c.x - sp.x) * e, sy = sp.y + (c.y - sp.y) * e; ctx.strokeStyle = F.rgba(T.molten, 0.5 * (1 - sp.p)); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(sp.x + (c.x - sp.x) * Math.max(0, e - 0.15), sp.y + (c.y - sp.y) * Math.max(0, e - 0.15)); ctx.lineTo(sx, sy); ctx.stroke(); ctx.fillStyle = T.molten; ctx.beginPath(); ctx.arc(sx, sy, 2.2, 0, Math.PI * 2); ctx.fill(); });
|
||||||
|
ripples.forEach(function (r) { ctx.strokeStyle = F.rgba(T.ember, r.a); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(r.x, r.y, r.r, 0, Math.PI * 2); ctx.stroke(); });
|
||||||
}
|
}
|
||||||
var L = F.loop(draw, canvas); L.kick();
|
var L = F.loop(draw, canvas); L.kick();
|
||||||
return { push: push, redraw: L.kick };
|
return { push: push, redraw: L.kick };
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ const REQUIRED = {
|
||||||
['E5', 'The one payment to the project is the Ember software\u2019s optional 1% dev fee', 'The one payment to the project is the Ember software\'s optional 1% dev fee'],
|
['E5', 'The one payment to the project is the Ember software\u2019s optional 1% dev fee', 'The one payment to the project is the Ember software\'s optional 1% dev fee'],
|
||||||
['X2', 'Public testnet: not yet open; the devnet build is here for people who want to look'],
|
['X2', 'Public testnet: not yet open; the devnet build is here for people who want to look'],
|
||||||
['X7', 'hello@igneum.network'],
|
['X7', 'hello@igneum.network'],
|
||||||
|
// restored with the home page on 6 October 2026 (the owner: "revert it but polish it")
|
||||||
|
['G4', 'admin keys in consensus'],
|
||||||
|
['C2', 'since 2019 (approximate)'],
|
||||||
|
['X8', 'No listing is arranged, promised or sought by the project'],
|
||||||
|
['X31', 'The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes.'],
|
||||||
],
|
],
|
||||||
'litepaper.html': [
|
'litepaper.html': [
|
||||||
['X3', 'Live rows arrive with the public testnet.'],
|
['X3', 'Live rows arrive with the public testnet.'],
|
||||||
|
|
|
||||||