Move revenue after churn and raise price churn cap to prevent exploit
Balance Check / balance-simulation (push) Successful in 2m1s
CI / build-and-push (push) Successful in 2m6s
Balance Check / multi-run-balance (push) Successful in 13m10s

Churned subscribers no longer generate revenue the tick they leave,
and the price churn multiplier cap is raised from 10 to 1000 so
astronomical prices empty the subscriber pool in a single tick.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 22:17:30 -04:00
parent 63e56dc229
commit df01ac8e35
2 changed files with 14 additions and 15 deletions
@@ -108,20 +108,6 @@ export function processConsumerTiers(
tier.conversionRateFromBelow = convRate; tier.conversionRateFromBelow = convRate;
} }
// --- Revenue & token demand ---
let totalUsers = 0;
let subscriptionRevenue = 0;
let totalTokenDemand = 0;
for (const id of CONSUMER_TIER_ORDER) {
const tier = updated.tiers[id];
totalUsers += tier.userCount;
subscriptionRevenue += tier.userCount * (tier.config.price / 86400);
totalTokenDemand += tier.userCount * CONSUMER_TOKENS_PER_SUBSCRIBER;
}
updated.totalUsers = totalUsers;
// --- Serving penalties & serving-based extra churn --- // --- Serving penalties & serving-based extra churn ---
const paidDemand = consumerPaidMetrics.demandTokens; const paidDemand = consumerPaidMetrics.demandTokens;
const freeDemand = consumerFreeMetrics.demandTokens; const freeDemand = consumerFreeMetrics.demandTokens;
@@ -235,6 +221,19 @@ export function processConsumerTiers(
tier.userCount = Math.max(0, tier.userCount - churned); tier.userCount = Math.max(0, tier.userCount - churned);
} }
// --- Revenue & token demand (after all churn — cancelled users don't pay) ---
let totalUsers = 0;
let subscriptionRevenue = 0;
let totalTokenDemand = 0;
for (const id of CONSUMER_TIER_ORDER) {
const tier = updated.tiers[id];
totalUsers += tier.userCount;
subscriptionRevenue += tier.userCount * (tier.config.price / 86400);
totalTokenDemand += tier.userCount * CONSUMER_TOKENS_PER_SUBSCRIBER;
}
updated.totalUsers = totalUsers;
updated.viralCoefficient = modelQuality > 0.5 ? 1 + (modelQuality - 0.5) * 2 : 0; updated.viralCoefficient = modelQuality > 0.5 ? 1 + (modelQuality - 0.5) * 2 : 0;
return { return {
+1 -1
View File
@@ -886,7 +886,7 @@ export const PERCEIVED_VALUE_REPUTATION_RANGE = { min: 0.5, max: 1.0 };
export const PRICE_ELASTICITY_STEEPNESS = 3.0; export const PRICE_ELASTICITY_STEEPNESS = 3.0;
export const PRICE_SATISFACTION_WEIGHT = 0.3; export const PRICE_SATISFACTION_WEIGHT = 0.3;
export const PRICE_CHURN_EXPONENT = 1.5; export const PRICE_CHURN_EXPONENT = 1.5;
export const PRICE_CHURN_MAX_MULTIPLIER = 10.0; export const PRICE_CHURN_MAX_MULTIPLIER = 1000.0;
// --- API Tier Defaults --- // --- API Tier Defaults ---