From e3ac27e11c30a8abd3fa14503565f9127a979553 Mon Sep 17 00:00:00 2001 From: martbost Date: Thu, 13 Aug 2026 18:01:50 -0500 Subject: [PATCH] Keep qualification and matrix state live between snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tail added new members but never updated the referrer's directCount, the parent's matrix slots, or the recipient's earnedPol — so a member who qualified mid-day kept showing 1/2 in tree views until the next daily snapshot (hit live: #63 joined under #35 at 22:56 UTC and #35 still displayed unqualified). Registrations now increment the referrer's count and wire the new member into the parent's l/r; payouts bump the recipient's earnedPol. One-time re-snapshot on deploy trues up the stale state. Co-Authored-By: Claude Fable 5 --- chain.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/chain.js b/chain.js index b6e6c7d..4dc72b6 100644 --- a/chain.js +++ b/chain.js @@ -55,6 +55,8 @@ function loadState() { } // income rows saved before desc-classification existed: re-snapshot to backfill if (state.payouts.some(p => p.kind === 'income' && !p.desc)) state.snapshotAt = 0; + // one-time true-up: earlier tail code didn't maintain directCount/matrix slots live + if (!state.migratedLiveCounts) { state.snapshotAt = 0; state.migratedLiveCounts = true; } } function saveState() { try { @@ -221,7 +223,10 @@ async function processRange(fromBlock, toBlock) { if (l.topics[0] === T_REGISTERED) { const id = topicInt(l.topics[1]); if (!state.members[id]) { - state.members[id] = { account: topicAddr(l.topics[2]), referrerId: topicInt(l.topics[3]), tier: wInt(l.data, 0), joinedAt: ts }; + state.members[id] = { account: topicAddr(l.topics[2]), referrerId: topicInt(l.topics[3]), tier: wInt(l.data, 0), joinedAt: ts, level: 1, directCount: 0, earnedPol: 0 }; + // keep the referrer's qualification progress live between snapshots + const ref = state.members[topicInt(l.topics[3])]; + if (ref) ref.directCount = (ref.directCount || 0) + 1; newIds.push(id); emit({ type: 'registered', id, referrerId: topicInt(l.topics[3]), tierName: tierName(wInt(l.data, 0)), tx, ts }); } @@ -249,13 +254,22 @@ async function processRange(fromBlock, toBlock) { if (twinIdx >= 0) state.payouts[twinIdx] = p; else { state.payouts.push(p); state.totals.count++; state.totals.pol = +(state.totals.pol + p.pol).toFixed(6); + const rcpt = state.members[p.toId]; + if (rcpt) rcpt.earnedPol = +((rcpt.earnedPol || 0) + p.pol).toFixed(6); emit({ type: 'payout', kind: p.kind, toId: p.toId, fromId: p.fromId, pol: p.pol, levelName: levelName(p.level), upgrade: p.upgrade, tx, ts }); } } } for (const id of newIds) { - try { const m = await fetchMember(id); if (m) Object.assign(state.members[id], { uplineId: m.uplineId, level: m.level }); } - catch (e) { /* picked up by next snapshot */ } + try { + const m = await fetchMember(id); + if (m) { + Object.assign(state.members[id], { uplineId: m.uplineId, level: m.level }); + // wire the new member into the parent's matrix slots so trees update live + const par = state.members[m.uplineId]; + if (par) { if (!par.l) par.l = id; else if (!par.r && par.l !== id) par.r = id; } + } + } catch (e) { /* picked up by next snapshot */ } } state.payouts.sort((a, b) => (a.ts || 0) - (b.ts || 0)); if (state.payouts.length > KEEP_PAYOUTS) state.payouts = state.payouts.slice(-KEEP_PAYOUTS);