diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index af1bb188b..6be2790c0 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -258,7 +258,21 @@ var View = (function () { function shortHex(h, head, tail) { h = String(h || ''); head = head || 8; tail = tail || 6; return h.length > head + tail + 2 ? h.slice(0, head) + '…' + h.slice(-tail) : h; } var kindWord = Notices.kindWord; // hot-plug (src/hotplug.rs): a removed card's row hides after five minutes (gone); a faulty one has no switch - function shownCards(cards) { return (cards || []).filter(function (c) { return !c.gone; }); } + // The list is ordered by performance (the project lead, 6 October 2026): usable cards first, then by the measured rate since the + // start (5 MH/s buckets so the order does not flicker), then discrete, external and Apple before integrated, then + // memory; removed and unusable cards last. Ties keep the detection order. + function perfRank(c) { + var usable = !(c.removed_at > 0) && !c.problem ? 0 : 1; + var integrated = c.kind === 'integrated' ? 1 : 0; + var bucket = -Math.floor(((c.enabled ? (c.hash_avg || c.hash_now || 0) : 0)) / 5); + return [usable, integrated, bucket, -(c.vram_mb || 0)]; + } + function shownCards(cards) { + return (cards || []).map(function (c, i) { return { c: c, i: i, r: perfRank(c) }; }).sort(function (a, b) { + for (var k = 0; k < a.r.length; k++) { if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]; } + return a.i - b.i; + }).map(function (x) { return x.c; }).filter(function (c) { return !c.gone; }); + } // listed, driver fine, not unplugged: a worker can run on it function present(c) { return !(c.removed_at > 0) && !c.problem; } // the tooltip on a card's name: what the tool calls it (gfx1201), its device index, the OpenCL platform, the PCI address @@ -919,7 +933,7 @@ if (typeof document !== 'undefined') (function () { if (s.detecting) { det.hidden = false; $('detect-text').textContent = 'asking the graphics cards to report in'; $('btn-cards-next').disabled = true; $('cards-sub').textContent = 'Asking the graphics cards to report in.'; cardsRendered = ''; return; } det.hidden = true; var cards = shownCards(s.mining.cards); - var sig = cards.map(function (c) { return c.key + ':' + (c.problem || '') + ':' + (c.removed_at > 0 ? 'r' : '') + ':' + (c.enabled ? 'on' : 'off'); }).join('|'); + var sig = cards.map(function (c) { return c.key + ':' + (c.problem || '') + ':' + (c.removed_at > 0 ? 'r' : '') + ':' + (c.enabled ? 'on' : 'off'); }).join('|'); // cards is already in performance order, so a reorder changes the signature if (sig !== cardsRendered) { cardsRendered = sig; renderCardRows(list, cards); } if (!cards.length) { $('cards-sub').textContent = 'No GPU this app can drive was found.'; diff --git a/app/igneum-app/ui/view.test.mjs b/app/igneum-app/ui/view.test.mjs index e14a05ce1..21af9f898 100644 --- a/app/igneum-app/ui/view.test.mjs +++ b/app/igneum-app/ui/view.test.mjs @@ -172,6 +172,24 @@ test('hot-plug (src/hotplug.rs): a removed card and a faulty card are shown as s // a row that is gone (five minutes after removal) is not shown at all; the big button counts only present cards assert.deepEqual(V.shownCards([card(), card({ key: 'x', gone: true })]).map((c) => c.key), ['nvidia:0:RTX 5090']); assert.equal(V.present(card()), true); + // performance order (6 October 2026): PC 1 detects 5090, integrated AMD, 9070 XT; the list shows the 5090, the 9070 XT, + // then the integrated card, whatever the detection order and whatever the integrated card's rate + const pc1 = [ + card({ key: 'nvidia:0:RTX 5090', hash_avg: 122 }), + card({ key: 'amd:gfx1036', name: 'AMD Radeon(TM) Graphics', vendor: 'amd', kind: 'integrated', vram_mb: 512, hash_avg: 2.1, enabled: true }), + card({ key: 'amd:gfx1201', name: 'AMD Radeon RX 9070 XT', vendor: 'amd', kind: 'discrete', vram_mb: 16384, hash_avg: 18.2 }), + ]; + assert.deepEqual(V.shownCards(pc1).map((c) => c.key), ['nvidia:0:RTX 5090', 'amd:gfx1201', 'amd:gfx1036']); + // before any rate (first start): discrete by memory, integrated last; a removed card last of all; ties keep detection order + const fresh = [ + card({ key: 'amd:gfx1036', kind: 'integrated', vram_mb: 512, hash_avg: 0, hash_now: 0, state: 'off' }), + card({ key: 'amd:gfx1201', kind: 'discrete', vram_mb: 16384, hash_avg: 0, hash_now: 0, state: 'waiting' }), + card({ key: 'nvidia:0:RTX 5090', hash_avg: 0, hash_now: 0, state: 'waiting' }), + card({ key: 'nvidia:1:RTX 5090', hash_avg: 0, hash_now: 0, state: 'waiting', removed_at: 5 }), + ]; + assert.deepEqual(V.shownCards(fresh).map((c) => c.key), ['nvidia:0:RTX 5090', 'amd:gfx1201', 'amd:gfx1036', 'nvidia:1:RTX 5090']); + // a small rate change does not reorder (5 MH/s buckets): 122 and 124 sort as equal and keep detection order + assert.deepEqual(V.shownCards([card({ key: 'a', hash_avg: 122 }), card({ key: 'b', hash_avg: 124 })]).map((c) => c.key), ['a', 'b']); assert.equal(V.present(gone), false); assert.equal(V.present(bad), false); const t = V.toggle({ state: 'mining', paused: false, cards: [gone, bad] }, { synced: true }, {});