Zum Inhalt

Settlement Compute

Datei: core/settlementCompute.ts (299 Zeilen) Typ: Reine Berechnung (keine Side-Effects, keine I/O) Aufgerufen von: cli/run.ts

Kernfunktion

computeSettlementForDay(
  dayId: number,
  pools: PoolInput[]   // 5 Pools
): SettlementComputeResult

Eingabe-Datenstruktur

interface PoolInput {
  pot: bigint;              // potAcc - Regular Pot (60% vom Pot-Anteil)
  rollover: bigint;         // rolloverAcc - 5% Rollover
  jackpotReserve: bigint;   // jackpotReserveAcc - 40% vom Pot-Anteil
  jackpotNoHitStreak: number; // Tage ohne 6er
  winnersByClass: {
    2: WinnerRef[];  // 3 Richtige
    3: WinnerRef[];  // 4 Richtige
    4: WinnerRef[];  // 5 Richtige
    5: WinnerRef[];  // 6 Richtige (Jackpot)
  }
}

Berechnungsalgorithmus

Schritt 1: Gesamtpot berechnen

gesamtpot = pot + rollover

newRollover = gesamtpot * 5%     // Für nächsten Tag
payoutBasis = gesamtpot * 95%    // Verfügbar für Auszahlung

Schritt 2: Klassen-Anteile vom payoutBasis

Klasse Treffer Anteil am payoutBasis
classId 2 3 Richtige 0% (keine Auszahlung)
classId 3 4 Richtige 60%
classId 4 5 Richtige 40%
classId 5 6 Richtige Separate Jackpot-Reserve
// settlementCompute.ts:90-95
const CLASS_SHARE_BP: Record<number, number> = {
  2: 0,      // 3er - kein Gewinn
  3: 6000,   // 4er - 60%
  4: 4000,   // 5er - 40%
  5: 0       // 6er - aus jackpotReserve
};

Schritt 3: Jackpot-Logik

Drei mögliche Szenarien pro Pool:

A) 6er getroffen → gesamte jackpotReserve → an Gewinner
   jackpotHit = true
   jackpotPayout = jackpotReserve

B) Kein 6er, aber noHitStreak >= Trigger-Tage → Redistribution
   jackpotReserve wird 50/50 auf s5 und s4 verteilt
   (zusätzlich zu deren regulären Anteilen)

C) Kein 6er, Streak unter Trigger → Reserve wächst weiter
   jackpotReserve bleibt unverändert
// Pseudocode
if (winners6er.length > 0) {
  // Szenario A: Jackpot getroffen
  jackpotPayout = jackpotReserve;
  jackpotHit = true;
} else if (noHitStreak >= jackpotMissTriggerDays) {
  // Szenario B: Redistribution
  s5bonus = jackpotReserve / 2;
  s4bonus = jackpotReserve - s5bonus; // Dust an s4
  jackpotPayout = jackpotReserve;
} else {
  // Szenario C: Reserve wächst
  jackpotPayout = 0n;
}

Schritt 4: Gleichmäßige Verteilung

// settlementCompute.ts:105-121
function distributeEven(share: bigint, n: number): bigint[] {
  if (n === 0) return [];

  const base = share / BigInt(n);      // Grundbetrag
  const remainder = share % BigInt(n);  // Rest (Dust)

  const amounts = Array(n).fill(base);

  // Ersten `remainder` Gewinnern je +1 (Dust-Handling)
  for (let i = 0; i < Number(remainder); i++) {
    amounts[i] += 1n;
  }

  return amounts;
}

Invariante: sum(amounts) === share - exakt, kein Rundungsverlust.

Ausgabe-Datenstruktur

interface SettlementComputeResult {
  poolPayoutArr: bigint[];         // [5] Regular-Pot-Auszahlungen
  poolJackpotPayoutArr: bigint[];  // [5] Jackpot-Reserve-Auszahlungen
  jackpotHitArr: boolean[];        // [5] Jackpot getroffen?
  totalPayout: bigint;             // Summe aller Auszahlungen
  winnersRaw: WinnerRaw[];         // Pro-Tip-Gewinner
  winnersAgg: WinnerAgg[];         // Pro-Spieler-Aggregation
  poolsAudit: PoolAudit[];         // Audit-Trail pro Pool
}

Audit-Trail

Für jeden Pool wird ein detaillierter Audit-Record erstellt:

{
  "poolIndex": 0,
  "pot": "1500000000",
  "rollover": "200000000",
  "jackpotReserve": "800000000",
  "gesamtpot": "1700000000",
  "payoutBasis": "1615000000",
  "newRollover": "85000000",
  "noHitBefore": 3,
  "noHitAfter": 4,
  "redistribute": false,
  "classShares": {
    "s3": "969000000",
    "s4": "646000000",
    "s5": "0",
    "s6": "0"
  },
  "classWinners": { "2": 0, "3": 12, "4": 3, "5": 0 },
  "poolPaid": "1615000000",
  "jackpotPaid": "0"
}

Charity-Berechnung

Datei: core/charityCompute.ts (11 Zeilen)

function computeCharity(
  operatorFeeTotal: bigint,
  affiliateTotal: bigint,
  charityRateBp: number
): bigint {
  const basis = operatorFeeTotal - affiliateTotal;
  return (basis * BigInt(charityRateBp)) / 10000n;
}

Formel: charity = (feeAcc - affiliateTotal) * charityRateBp / 10000

Zahlenbeispiel

Tag 20500, Pool 0:

Eingabe:
  pot           = 10.000 USDT
  rollover      =  2.000 USDT
  jackpotReserve=  5.000 USDT
  noHitStreak   = 2

Berechnung:
  gesamtpot     = 12.000 USDT
  newRollover   =    600 USDT (5%)
  payoutBasis   = 11.400 USDT (95%)

  s4-Anteil (4er, 60%) = 6.840 USDT
  s3-Anteil (5er, 40%) = 4.560 USDT

  3 Gewinner mit 4 Richtigen:
    6.840 / 3 = 2.280 USDT pro Gewinner

  1 Gewinner mit 5 Richtigen:
    4.560 / 1 = 4.560 USDT

  Kein 6er → jackpotReserve bleibt 5.000 USDT
  noHitStreak: 2 → 3

Ausgabe:
  poolPayout        = 11.400 USDT
  poolJackpotPayout = 0 USDT
  totalPayout       = 11.400 USDT

Invarianten

Datei: core/invariants.ts (18 Zeilen)

// Winners-Merkle-Root darf NIEMALS 0x0 sein
// (Dummy-Leaf wird eingefügt wenn keine Gewinner)
assertMerkleRootNonZero(merkleRoot);

// Affiliate-Root MUSS 0x0 sein wenn affiliateTotal == 0
// Affiliate-Root MUSS non-zero sein wenn affiliateTotal > 0
assertAffiliateRootRule(affiliateRoot, affiliateTotal);