Fix compute utilization bug and add subscriber saturation cap
CI / build-and-push (push) Successful in 34s
CI / build-and-push (push) Successful in 34s
Three intertwined fixes: 1. Zero-capacity utilization: when inference allocation was 0%, the guard clause returned 0% utilization instead of 100%, so the market system never penalized satisfaction and subscribers never churned. 2. Stale compute in market: restructured tick order so capacity is computed before market runs, giving satisfaction calculations current-tick demand/capacity ratio instead of previous tick's. 3. Subscriber growth: replaced pure compound growth (reached billions in minutes) with logistic saturation curve. Era-based market caps: startup 10K, scaleup 1M, bigtech 20M, agi 100M. Quality and reputation expand the effective cap. Also tuned FLOPS-to-tokens multiplier (10 → 26) for balanced demand/capacity feel across all eras, and added market saturation indicator to the Market page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { useGameStore } from '@/store';
|
||||
import { formatNumber, formatMoney, formatPercent } from '@ai-tycoon/shared';
|
||||
import {
|
||||
formatNumber, formatMoney, formatPercent,
|
||||
MARKET_SIZE_CAP, MARKET_CAP_QUALITY_BONUS, MARKET_CAP_REPUTATION_BONUS,
|
||||
} from '@ai-tycoon/shared';
|
||||
import { Users, Zap, Shield, TrendingUp, Settings2 } from 'lucide-react';
|
||||
|
||||
export function MarketPage() {
|
||||
@@ -10,9 +13,21 @@ export function MarketPage() {
|
||||
const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization);
|
||||
const tokensCapacity = useGameStore((s) => s.compute.tokensPerSecondCapacity);
|
||||
const tokensDemand = useGameStore((s) => s.compute.tokensPerSecondDemand);
|
||||
const currentEra = useGameStore((s) => s.meta.currentEra);
|
||||
const reputationScore = useGameStore((s) => s.reputation.score);
|
||||
const deployedModels = useGameStore((s) => s.models.trainedModels.filter(m => m.isDeployed));
|
||||
const setProductPricing = useGameStore((s) => s.setProductPricing);
|
||||
const setOverloadPolicy = useGameStore((s) => s.setOverloadPolicy);
|
||||
|
||||
const bestQuality = deployedModels.length > 0
|
||||
? Math.max(...deployedModels.map(m => m.benchmarkScore)) / 100
|
||||
: 0;
|
||||
const eraCapBase = MARKET_SIZE_CAP[currentEra] ?? 100_000_000;
|
||||
const effectiveCap = eraCapBase
|
||||
* (1 + bestQuality * MARKET_CAP_QUALITY_BONUS)
|
||||
* (1 + (reputationScore / 100) * MARKET_CAP_REPUTATION_BONUS);
|
||||
const saturation = effectiveCap > 0 ? consumers.totalSubscribers / effectiveCap : 0;
|
||||
|
||||
const chatProduct = productLines.find(p => p.type === 'chat-product');
|
||||
const textApi = productLines.find(p => p.type === 'text-api');
|
||||
|
||||
@@ -20,7 +35,7 @@ export function MarketPage() {
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-2xl font-bold">Market</h2>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Users size={16} className="text-orange-400" />
|
||||
@@ -55,6 +70,22 @@ export function MarketPage() {
|
||||
{formatNumber(tokensDemand)} / {formatNumber(tokensCapacity)} tok/s
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp size={16} className="text-purple-400" />
|
||||
<span className="text-xs text-surface-400 uppercase">Market Saturation</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold font-mono">{formatPercent(saturation)}</div>
|
||||
<div className="text-xs text-surface-400 mt-1">
|
||||
Cap: {formatNumber(effectiveCap)} ({currentEra})
|
||||
</div>
|
||||
<div className="h-1.5 bg-surface-800 rounded-full mt-2 overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${saturation > 0.9 ? 'bg-danger' : saturation > 0.7 ? 'bg-warning' : 'bg-accent'}`}
|
||||
style={{ width: `${Math.min(100, saturation * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
|
||||
Reference in New Issue
Block a user