Fix off-by-one in catch-eligibility: catcher must be AT the bought level

The pipeline widget, owner upgrade alerts, and the org routing/leak map all
treated a position as eligible to catch a payment at the buyer's PRE-upgrade
level (level >= depth). Verified against on-chain history: position 6, at
exactly the pre-upgrade level (Ascensus) and qualified, was passed over on
position 8's Fabrica buy - so the contract requires the catcher to be at the
level being BOUGHT (depth+1), matching the "stay one level ahead" doctrine.

- chain.js getOwnerUpgradeNeeds: eligible now pLevel >= depth+1; neededLevel
  reported as the bought level (was one low, so alerts would have stayed
  silent in exactly the situation they exist for)
- chain.js getOrgRouting catcher(): same correction in the routing simulation
- my.js pipeline: needLevel/eligible/summary-text ladder corrected; warnings
  now name the right level

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-18 16:54:07 -05:00
parent b66d3a50e8
commit 9966dc1728
2 changed files with 11 additions and 8 deletions
+5 -5
View File
@@ -199,7 +199,7 @@
if(depth>=1){
if(lvl===depth){
const amt=(up[tier]||[])[depth-1];
if(amt)ready.push({id:n.id,depth,tier,amt,toLevel:LEVELS[depth]||('Level '+(depth+1)),needLevel:depth});
if(amt)ready.push({id:n.id,depth,tier,amt,toLevel:LEVELS[depth]||('Level '+(depth+1)),needLevel:depth+1});
}else if(lvl<depth)building.push({id:n.id,depth,steps:depth-lvl});
else passedCount.n++;
}
@@ -253,8 +253,8 @@
const maxG=Math.min(6,Math.max(2,...Object.keys(gens).map(Number).concat([2])));
for(let g=1;g<=maxG;g++){
const amt=up[g-1]; if(!amt)continue;
const buys=LV[g]||'the top'; const need=LV[g-1];
const have=d.directCount>=2&&myLevel>=g;
const buys=LV[g]||'the top'; const need=LV[g]||'the top';
const have=d.directCount>=2&&myLevel>=g+1;
const cnt=(gens[g]||[]).length;
L.push(`• Layer ${g}${cnt?` (${cnt} ${cnt===1?'person':'people'})`:''}: their ${buys} upgrade pays you ${money(amt)} POL each — needs you at ${need}${have?' ✅ you already are':' ← get here to catch it'}`);
}
@@ -266,11 +266,11 @@
const nextName=LV[myLevel]||'max level';
// shallowest layer you can't yet catch
let gap=0;
for(let g=1;g<=8;g++){ if((gens[g]||[]).length && !(d.directCount>=2&&myLevel>=g)){ gap=g; break; } }
for(let g=1;g<=8;g++){ if((gens[g]||[]).length && !(d.directCount>=2&&myLevel>=g+1)){ gap=g; break; } }
// is anyone in the gap layer already close (at/near their pay-you level)?
const gapClose=gap&&(gens[gap]||[]).some(n=>(n.level||1)>=gap-1);
if(d.directCount<2) L.push(`YOUR NEXT MOVE: get your 2 directs. Until then none of this pipeline can pay you — spillover fills your team but only your own 2 qualify you.`);
else if(gap) L.push(`YOUR NEXT MOVE: your next uncovered layer is layer ${gap} — to catch their ${LV[gap]||'top'} upgrades you'll need to be at ${LV[gap-1]} (${myNext?money(myNext)+' POL':''}). They're still climbing toward it${gapClose?', and getting close — worth upgrading soon':", so no rush — just get there before they do"}. You're already eligible for everything shallower.`);
else if(gap) L.push(`YOUR NEXT MOVE: your next uncovered layer is layer ${gap} — to catch their ${LV[gap]||'top'} upgrades you'll need to be at ${LV[gap]||'the top'} yourself (next step: ${nextName}${myNext?', '+money(myNext)+' POL':''}). They're still climbing toward it${gapClose?', and getting close — worth upgrading soon':", so no rush — just get there before they do"}. You're already eligible for everything shallower.`);
else if(myNext) L.push(`YOUR NEXT MOVE: you're currently eligible for every active layer. As your team goes deeper, upgrade to ${nextName} (${money(myNext)} POL) before that new layer climbs to its pay-you level.`);
L.push('');
L.push(`See it live and get a red alert the moment you'd miss one: rmcircle.team/my/${d.id}`);