diff --git a/docs/community/discord-hooks.md b/docs/community/discord-hooks.md index 0b1e0cd7f..23a665e95 100644 --- a/docs/community/discord-hooks.md +++ b/docs/community/discord-hooks.md @@ -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 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. + +## 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. diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index e8e808d52..4016f1496 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -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 (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. 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 (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. 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 (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. Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey. diff --git a/docs/plans/site-ui-3-shots/after-v2/home-1440-dark-fold.jpg b/docs/plans/site-ui-3-shots/after-v2/home-1440-dark-fold.jpg new file mode 100644 index 000000000..e35746433 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-1440-dark-fold.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-1440-dark.jpg b/docs/plans/site-ui-3-shots/after-v2/home-1440-dark.jpg new file mode 100644 index 000000000..334528abc Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-1440-dark.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-1440-light-fold.jpg b/docs/plans/site-ui-3-shots/after-v2/home-1440-light-fold.jpg new file mode 100644 index 000000000..b08b16c70 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-1440-light-fold.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-1440-light.jpg b/docs/plans/site-ui-3-shots/after-v2/home-1440-light.jpg new file mode 100644 index 000000000..35a2525c0 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-1440-light.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-390-dark-fold.jpg b/docs/plans/site-ui-3-shots/after-v2/home-390-dark-fold.jpg new file mode 100644 index 000000000..eaeba06ff Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-390-dark-fold.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-390-dark.jpg b/docs/plans/site-ui-3-shots/after-v2/home-390-dark.jpg new file mode 100644 index 000000000..9f6da3437 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-390-dark.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-390-light-fold.jpg b/docs/plans/site-ui-3-shots/after-v2/home-390-light-fold.jpg new file mode 100644 index 000000000..66ab8e3c4 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-390-light-fold.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-390-light.jpg b/docs/plans/site-ui-3-shots/after-v2/home-390-light.jpg new file mode 100644 index 000000000..7c2ac0d0a Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-390-light.jpg differ diff --git a/docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm b/docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm new file mode 100644 index 000000000..9f570c934 Binary files /dev/null and b/docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm differ diff --git a/site/index.html b/site/index.html index 8d6a61e6f..7bbd8595e 100644 --- a/site/index.html +++ b/site/index.html @@ -58,40 +58,114 @@ @@ -146,26 +220,165 @@
-

A proof-of-work chain for graphics cards, whose miners prove the blocks.

+
GPUs are back · for good
+

Mined by GPUs.
Proven by fire.

+

A proof-of-work chain for graphics cards, whose miners prove the blocks.

- +
+
+
connecting to the devnet observer
+
chain block pendingshards proven pendinglast lock pendingvote keys in 10 min pendinghash rate pendingblocks / s pending
+
+
+ +
+

Connecting to the devnet observer.

+
+
+ mined, pending + mined, included + excluded + shards being proven + proven + locked checkpoint +
+ +
+

One node is read every 2 s. Every square is a real block, released at a calm pace so each step can be seen; the counters read the chain. Chain block is the EVM block number. Shards proven is every shard paid on the chain so far. Last lock is the latest checkpoint the finality rule locked.

+
+
+
+
+
1 / s
blocks, rising to 10
+
~60 s
to a proof at launch, the target. First GPU proof of a block: 1.4 s on an RTX 5090, 4 Oct 2026
+
0
premine
+
4B
IGN hard cap, ever
+
+

100% of emission goes to miners and provers; the protocol carries no fee.

+
+
Network hash rate
pending
one node, read every 2 s
+
Blocks, last 10 min
pending
target 1 a second
+
Latest block
pending
from the chain tip
+
Shards proven and paid
pending
on the chain so far
+
-

Connecting to the devnet observer.

-
+
-
-
Live now
pending

pending vote keys mined a block in the last ten minutes; a card runs several.

-
Proven and paid
pending

shards proven and paid on the chain so far, pending of them in the last ten minutes.

-
The chip model
5x to 9x

per joule for the strongest chip against an RTX 5090 today in our public model, about 2x once the lever now in its gates ships: the numbers.

+
+
The chain, now
+

How the chain proves itself

+

Blocks every second, proven in shards, locked every 30 seconds. First live lock 4 Oct 2026: checkpoint 242, 77.4% of all weight, two hours after genesis.

-

One node is read every 2 s. The chip figures are a cost model, not a measurement.

+
+
+
This hour's program
+

+        

A new program every hour: a chip wired for one program is useless. A programmable chip is met by the latency-shadow work and the price per joule, not by surprise. First live swap 4 Oct 2026: Apple, NVIDIA and AMD kept hashing through it, 0 rejected blocks.

+
+
+
Proofs sold to other chains
+
+
rollup · batch proofat testnet
+
bridge · state proofat testnet
+
rollup · fault proofat testnet
+
IGN burned from jobsphase two
+
+

The same cards sell proofs to rollups and bridges. Jobs are paid on the customer's chain at launch; settlement in IGN with a 10% burn follows the proof bridge in phase two. Live rows arrive with the public testnet. The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes.

+
+
+

Read more in the litepaper

+
+
+ +
+
+
+
Your card
+

What your card does here

+

Measured rates from the bench table, and the time a card alone takes to find a block at the network's hash rate right now. In a pool you are paid your share of every block instead.

+
+
+ + + + + + + + + +
CardHash rateWattsA block alone, right nowMeasured
NVIDIA RTX 5090127.7 MH/s227 Wpending6 Oct 2026, Ember Tune on the three-card Windows rig, 0.15% of rate traded for 84 W
NVIDIA RTX 407028.8 MH/s76 Wpending6 Oct 2026, Ember Tune on the same rig, no rate lost for 30 W
AMD RX 9070 XT17.8 MH/snot loggedpending5 Oct 2026, the eGPU entry of the engineering log, the app's rate
Apple M5 Max26.7 MH/snot loggedpending4 Oct 2026, the first live hourly swap
NVIDIA RTX 4090not yet measuredRun the app and your row joins the bench table
AMD RX 7900 XTXnot yet measuredRun the app and your row joins the bench table
+

The time to a block is the network hash rate divided by the card's rate, recomputed on every reading; it is an expectation, not a promise. Every row names its engineering log entry; nothing here is estimated.

+

Graphics cards only. A new mining program every hour, so no chip is built for it. Your card proves the blocks as well as mining them. Every block pays 80% to its miner and 20% to the provers of that block; nothing to anyone else.

+
+
+ +
+
+
+
For the sceptic
+

Three things to check

+

Igneum states its limits first. Each of these is measured, published and open to anyone who wants to break it.

+
+
+
+
This tab · light client
+
PREVIEW
+
+
+
+
+

Browser checks Igneum

+

A checkpoint certificate, verified in this tab. Voter list from the node.

+
+
+
+
Block proof
not yet
+
Checkpoint
checking
+
Proof system
BLS aggregate, version 1
+
Verified in this tab
checking
+
+ +
+

Every card mines; today's app proves on a 24 GB NVIDIA card and mines and proves on 32 GB. Measured on 6 October 2026 on eleven rented cards with the patched server: every NVIDIA card from 8 GB proves, 10 GB and up mine and prove (the log); ships when the packaging row lands. AMD and Apple cards mine. The chip model is public: the numbers.

+
+
+
Chip economics
+

The chip model is public

+

In our public model the strongest chip reaches 5x to 9x per joule against an RTX 5090 today, about 2x once the lever now in its gates ships. The model, its inputs and what it does not cover are in the engineering log.

+ The numbers +
+
+
Monero's technique, applied to GPUs
+

A program that keeps changing

+

The random program that has kept chips off Monero since 2019 (approximate), rebuilt for graphics cards. The program keeps changing on a schedule fixed at genesis; nobody touches it.

+
+ + + + + + + +
PropertyRandomXIgneum
Who minesCPUsGPUs
Program changesEvery hashEvery hour
Memory2 GB, fixed2 GB, growing (the proposed schedule, fixed at the testnet genesis: 2 GB, doubling at years 4, 12 and 28)
Verified byAny CPUAny CPU
+ Igneum vs RandomX +
+
+
Every criticism
+

What Igneum does not claim

+

Proof lag, chip economics, the market size, and the one rule external review will try hardest to break: the litepaper states the limits, and the ledger carries every criticism, answered or conceded.

+ The limits + The ledger +
+
+

Built on the shoulders: Kaspa's GHOSTDAG and node, Monero's RandomX idea, Chia's class-group VDF, Ethereum's EVM, Succinct's SP1, BLS12-381 and Bitcoin's address format. What changed, why, and how it is measured: the provenance section of the litepaper.

@@ -174,26 +387,155 @@
Igneum Ember · one click

Install. Start. The card mines.

+

Igneum Ember finds your GPU, makes a wallet for you and runs the node, the miner and the prover as one app. The card mines; a 24 GB NVIDIA card proves as well.

-
- Windows v0.3.15 · 62.7 MB - macOS v0.3.15 · 41.9 MB - Linux · HiveOS on the miner page +
+
+ +

Devnet. Coins have no value and the chain may be reset.

+

Public testnet: not yet open; the devnet build is here for people who want to look.

+

Join the Discord: the miners, the releases and the bench numbers as they land. The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes.

+

Read more about the miner. The protocol carries no fee. The Ember software takes an optional 1% dev fee, like other GPU miners, off with one flag. Download only from this domain. Nobody from Igneum will ask for your seed.

+
+

NVIDIA, AMD, Apple silicon

Found by itself. One worker per card, several identities each.

+

A new program every hour, no pause

Compiled ahead and swapped in 0.01 ms on the live devnet, 0 rejected blocks.

+

Signed updates at a safe moment

The card keeps mining through the download. The old version comes back if the new one fails to start.

+

Four pages: Mine, Earnings, Prove, Settings

Every card is one row: rate, watts, MH per watt, pounds a day at your electricity price, a Tune button. The node is one line.

+

Ember Tune, measured

An RTX 5090 from 311 W to 227 W for 0.15% of its rate; an RTX 4070 from 106 W to 76 W for none (the Windows rig, 6 Oct 2026). Every number in the bench table.

+
+
+
+ The Mine page of Igneum Miner 0.3.14 on a three-card PC: 169 MH/s at 532 W, one row per card with its rate, watts, MH per watt, temperature, tune line, Tune button and switch +
Igneum Ember (the app window still says Igneum Miner) 0.3.14 on a Windows rig with an RTX 5090, RTX 4070 and RX 9070 XT, live devnet, 6 Oct 2026.
+
-

Devnet. Coins have no value and the chain may be reset.

-

Public testnet: not yet open; the devnet build is here for people who want to look.

-

Read more about the miner. The protocol carries no fee. The one payment to the project is the Ember software's optional 1% dev fee, like other GPU miners, off with one flag. Download only from this domain. Nobody from Igneum will ask for your seed.

+
+ What you agree to when you run the testnet miner +
+

No value. Testnet IGN cannot be sold, bought or redeemed, now or at mainnet. There is no airdrop, no points scheme and no promise tied to testnet balances. Mainnet starts from an empty genesis.

+

Resets. The chain restarts from a fresh genesis when a consensus rule changes. Every reset is announced at least seven days ahead on this page and in the app. Balances, contracts and history do not carry over. The devnet that runs today resets without notice.

+

What the app sends home. The app version, a random machine id made at install, your operating system, the node version, the hash rate, and the app, node and miner logs (which name the address the card mines to). They go to the project's log intake, a service Igneum runs on Vercel, and are read by the maintainers to find faults. Never your seed phrase, never a key, never a file you did not make with the app. Nothing is sold or shared.

+
+

Wallet set-up for MetaMask: chain id, RPC and the one-click button. The miner software takes an optional 1% fee, off with one flag; the protocol carries no fee to anyone.

+
-
+
-
-

Every criticism, answered or conceded, and what Igneum does not claim.

- The ledger +
+
How it works
+

One chain, three jobs

+
+
+
+ +

Mine

+

Any 4 GB card at launch, 8 GB from about year 4 under the proposed schedule, fixed at the testnet genesis: 2 GB, doubling at years 4, 12 and 28, approximate. 80% of each block to its finder.

+ About the miner +
+
+ +

Prove

+

The same card proves shards and sells proofs to other chains.

+ How proving pays +
+
+ +

Build

+

Ethereum bytecode, with the differences documented. Proofs of any computation from the miners' cards, verified by the chain itself. 20% of priority fees to the contracts that ran, stated at the fee levels that exist.

+ Deploy on Igneum +
+
+

Read more in the litepaper

+
+
+ +
+
+
+
+
Igneum Wallet · desktop
+

Your coins, your wallet

+

Igneum Wallet holds the key the miner makes for you and verifies finality itself. MetaMask works too.

+
+ pending waiting for a block + in a block carried, not yet covered + final, checkpoint 5 under a checkpoint this wallet verified +
+
+

Sealed on your machine

24 words, three typed back, a password. Argon2id and XChaCha20-Poly1305. Nothing leaves.

+

Rewards in one history

Block rewards, proving payouts and transfers. Send with the fee shown. Receive by a QR drawn locally.

+

Reads your own node

When the miner is installed, the wallet uses its node. Nobody else sees your balance.

+
+ +

macOS first, Windows next. Public testnet first. Download only from this domain. Nobody from Igneum will ask for your seed.

+
+
+ The wallet window: a balance in IGN, an example address with Send and Receive, node and finality panels, and a history of block rewards +
Igneum Wallet on a private test network, 5 Oct 2026. The address is an example.
+
+ +
+
+
+
The journey
+

Where Igneum is right now

+
loading
+

Six phases, four public gates, each a measurement published pass or fail. No calendar dates: each phase closes at its gate.

+
+
+

The log below is the engineering log's dated entries, newest first. Day one was 3 October 2026; several entries can share a date because the work is logged as it is run.

+
+

Read more in the litepaper

+
+
+ +
+
+
+
+
Economics
+

Every coin, mined

+

Hard cap of 4 billion IGN, halving every two years for ever.

+
+
+ 80% miners + 20% provers + 0% anyone else in the protocol +
+
+
+
0
premine
+
0
stake, anywhere
+
0
admin keys in consensus
+
90%
of blocks must signal to upgrade
+
+
+

No premine, no stake, no fee to any team in the protocol. Base fee burned. The one payment to the project is the Ember software's optional 1% dev fee, off with one flag. On the devnet today the coinbase's 20% output is burned under the tag igneum-proving-pool-v0, and provers are paid from a separate escrow in the execution state credited with the same 20%.

+

Read more in the litepaper

+
+
+ +
+
+
+

Read what Igneum does not claim.

+

The litepaper states the limits first. Proof lag, chip economics, the market size, and the one rule external review will try hardest to break.

+
+ The litepaper +
+
@@ -257,26 +599,95 @@ - + diff --git a/site/live-steps.js b/site/live-steps.js index efe04dff4..f83007b11 100644 --- a/site/live-steps.js +++ b/site/live-steps.js @@ -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"). - The original step view with real blocks: a lane chart where every square is a block the observer stored, released at a - calm pace (the feed is sampled, the counters elsewhere read the chain), and every square shows its step: mined (an outline, - grey while pending, ember once included), shards being proven (molten quarter cells fill as shards are verified or paid), - proven (ember fill), locked checkpoint (a ring; a dashed "final" line marks that everything to its left is final). - Each transition is animated as it happens (a glow and a ring pulse) and captioned in one line for the newest block that - changed. The wheel is never touched: 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, window: 300 }); then IgneumFeed.start({ subs: [steps], window: 300 }). */ +/* Igneum live steps (6 October 2026). The owner: "I liked it when we could see each step of the algo working", and later, + "can we have a dag animation like we had before? with the shards making up the block, then the final line and the other + bits that looked really cool but brought up to date?" + The lane chart with the original bits, every one driven by the live feed: a real block arrives from the right with a glow + and its chain number under it; its outline is grey while pending and ember once included; as its shards are verified or + paid, particles fly in from the edges and its quarter cells fill molten; when every shard is proven the block turns ember + and its provers are paid (the caption says to how many); a locked checkpoint takes a ring and a ripple and the dashed + "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 + 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) { 'use strict'; var F = root.IgneumFeed; 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 fmt(n) { return n === null || n === undefined ? null : Number(n).toLocaleString('en-GB'); } - function stepOf(b) { - var sh = b.shards || [], done = function (x) { return x.state === 'verified' || x.state === 'paid'; }; - if (b.proven || (sh.length && sh.every(done))) return 'proven'; - if (sh.some(function (x) { return x.state === 'proving' || done(x); })) return 'proving'; - return 'mined'; - } + function isDone(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'; } + function shardKey(b) { return (b.shards || []).map(function (x) { return x.state[0]; }).join(''); } function mount(canvas, opts) { opts = opts || {}; 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; - // 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 - // 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; + var lanes = 5, S, GAP, speed, RELEASE_MS = 2500, blocks = [], byHash = {}, known = {}, queue = [], events = [], sparks = [], ripples = [], laneOf = {}, laneN = 0, last = 0, prevT = 0; + var caption = opts.caption || null, lastChange = 0, latest = null, finality = null, proving = null, seeded = false, lockX = null, lockTarget = null, lockHash = null; function metrics() { S = dims.W < 600 ? 18 : 26; GAP = dims.W < 600 ? S * 2.5 : S * 3.1; speed = GAP / RELEASE_MS; } - metrics(); - root.addEventListener('resize', function () { dims = size(); metrics(); L.kick(); }); - function laneY(l) { return dims.H * 0.14 + l * (dims.H * 0.72 / (lanes - 1)); } + metrics(); 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 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; } - // 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) { 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')); var sh = b.shards || []; 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 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') + (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 (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') : '')); + var done = sh.filter(isDone), np = provers(b), paid = sh.filter(function (x) { return x.state === 'paid'; }).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'); } + else if (done.length) parts.push(done.length + ' of ' + sh.length + ' shards proven' + (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')); } 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('; '); } function setCaption(text, muted) { if (!caption) return; caption.textContent = text; caption.classList.toggle('muted', !!muted); } - function apply(e, src, now, announce) { - var before = e.step + '|' + e.src.color + '|' + e.src.locked + '|' + e.src.final + '|' + (e.src.chain ? 1 : 0); - e.src = src; e.step = stepOf(src); - var after = e.step + '|' + src.color + '|' + src.locked + '|' + src.final + '|' + (src.chain ? 1 : 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 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 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 }; } + // a particle per newly proven shard, from an edge to the block's quarter cell (the original scene's sparks, now real) + 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 }); } + 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) { - var now = performance.now(); finality = d.finality || null; proving = d.proving || null; serverNow = new Date(d.now).getTime(); - if (!seeded) { - // the first reply fills the scene: the newest blocks placed across the width, oldest at the left, so nothing starts empty - 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, ripple: 0, ringed: !!b.locked, born: now }; byHash[b.hash] = e; blocks.push(e); }); - } + var now = performance.now(); finality = d.finality || null; proving = d.proving || null; + if (finality && !finality.active) { lockTarget = null; lockX = null; lockHash = null; } + if (!seeded) { 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); }); } d.blocks.forEach(function (b) { - var e = byHash[b.hash]; - if (e) { apply(e, b, now, true); return; } - e = { hash: b.hash, src: b, step: stepOf(b), x: null, y: null, glow: 0, ripple: 0, ringed: !!b.locked, born: 0 }; - byHash[b.hash] = e; queue.push(e); if (queue.length > 6) { var dropped = queue.shift(); delete byHash[dropped.hash]; } + var e = known[b.hash]; + 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, ringed: !!b.locked, born: 0 }; known[b.hash] = e; queue.push(e); }); - var seen = {}; d.blocks.forEach(function (b) { seen[b.hash] = 1; }); - // a block the feed no longer carries keeps its last words on screen until it leaves at the left + // forget blocks that have left both the feed and the screen + 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) { 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); } 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; blocks.push(e); if (blocks.length > 80) { var old = blocks.shift(); if (!queue.some(function (q) { return q === old; })) delete byHash[old.hash]; } } + 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; + 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) { var W = dims.W, H = dims.H; T = F.theme(); 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 - 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 = 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; }); + 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.015; }); + 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.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 || 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 lock line: everything left of the newest locked block is final - // 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) - var lk = null; if (!finality || finality.active) blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; }); - 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); } + 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(); }); }); + // the final line: drawn only while finality is active; it sweeps in from the right to the newest locked block on screen + if (finality && finality.active) { var lk = null; blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; }); + if (lk) { if (lockHash !== lk.hash) { lockHash = lk.hash; if (lockX === null) lockX = W + 20; } lockTarget = lk.x; lockX += (lockTarget - lockX) * 0.08; + 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([]); + 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) { 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 (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(); } - else { - ctx.fillStyle = T.row; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill(); + else { 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(); - 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 (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; }); + 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(); return { push: push, redraw: L.kick }; diff --git a/tools/ci/ledger-text-check.mjs b/tools/ci/ledger-text-check.mjs index f56b835e2..2e024ae2b 100644 --- a/tools/ci/ledger-text-check.mjs +++ b/tools/ci/ledger-text-check.mjs @@ -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'], ['X2', 'Public testnet: not yet open; the devnet build is here for people who want to look'], ['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': [ ['X3', 'Live rows arrive with the public testnet.'],