Keep qualification and matrix state live between snapshots

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 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-13 18:01:50 -05:00
parent 0a05d97b32
commit e3ac27e11c
+17 -3
View File
@@ -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);