Four-tab panel with resource manipulation, time controls, state inspection, and event triggers to accelerate testing across all game systems. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import { useState } from 'react';
|
||||
import { useGameStore } from '@/store';
|
||||
import type { FundingRoundType } from '@ai-tycoon/shared';
|
||||
|
||||
function DevButton({ onClick, children, variant = 'default' }: {
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
variant?: 'default' | 'success' | 'danger' | 'warning';
|
||||
}) {
|
||||
const cls = {
|
||||
default: 'bg-surface-800 hover:bg-surface-700 border-surface-600 text-surface-300',
|
||||
success: 'bg-emerald-500/20 hover:bg-emerald-500/30 border-emerald-500/50 text-emerald-400',
|
||||
danger: 'bg-red-500/20 hover:bg-red-500/30 border-red-500/50 text-red-400',
|
||||
warning: 'bg-amber-500/20 hover:bg-amber-500/30 border-amber-500/50 text-amber-400',
|
||||
}[variant];
|
||||
return (
|
||||
<button onClick={onClick} className={`px-2 py-1 rounded text-xs border ${cls}`}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function triggerSafetyIncident() {
|
||||
useGameStore.setState((s) => ({
|
||||
reputation: {
|
||||
...s.reputation,
|
||||
safetyRecord: Math.max(0, s.reputation.safetyRecord - 15),
|
||||
publicPerception: Math.max(0, s.reputation.publicPerception - 7.5),
|
||||
},
|
||||
}));
|
||||
useGameStore.getState().addNotification({
|
||||
title: '[DEV] Safety Incident',
|
||||
message: 'Manually triggered safety incident. Safety -15, Public -7.5.',
|
||||
type: 'danger',
|
||||
tick: useGameStore.getState().meta.tickCount,
|
||||
});
|
||||
}
|
||||
|
||||
function triggerRackFailure(count: number) {
|
||||
useGameStore.setState((s) => {
|
||||
let remaining = count;
|
||||
const newClusters = s.infrastructure.clusters.map((cluster) => ({
|
||||
...cluster,
|
||||
campuses: cluster.campuses.map((campus) => ({
|
||||
...campus,
|
||||
dataCenters: campus.dataCenters.map((dc) => {
|
||||
if (remaining <= 0 || dc.computeRacksOnline <= 0) return dc;
|
||||
const toFail = Math.min(remaining, dc.computeRacksOnline);
|
||||
remaining -= toFail;
|
||||
return {
|
||||
...dc,
|
||||
computeRacksOnline: dc.computeRacksOnline - toFail,
|
||||
computeRacksFailed: dc.computeRacksFailed + toFail,
|
||||
};
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
return { infrastructure: { ...s.infrastructure, clusters: newClusters } };
|
||||
});
|
||||
useGameStore.getState().addNotification({
|
||||
title: '[DEV] Rack Failure',
|
||||
message: `Manually failed ${count} racks across data centers.`,
|
||||
type: 'warning',
|
||||
tick: useGameStore.getState().meta.tickCount,
|
||||
});
|
||||
}
|
||||
|
||||
function resetRackFailures() {
|
||||
useGameStore.setState((s) => {
|
||||
const newClusters = s.infrastructure.clusters.map((cluster) => ({
|
||||
...cluster,
|
||||
campuses: cluster.campuses.map((campus) => ({
|
||||
...campus,
|
||||
dataCenters: campus.dataCenters.map((dc) => ({
|
||||
...dc,
|
||||
computeRacksOnline: dc.computeRacksOnline + dc.computeRacksFailed,
|
||||
computeRacksFailed: 0,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
return { infrastructure: { ...s.infrastructure, clusters: newClusters } };
|
||||
});
|
||||
}
|
||||
|
||||
function triggerMarketBoom(multiplier: number) {
|
||||
useGameStore.setState((s) => ({
|
||||
market: {
|
||||
...s.market,
|
||||
consumers: {
|
||||
...s.market.consumers,
|
||||
totalSubscribers: Math.round(s.market.consumers.totalSubscribers * multiplier),
|
||||
},
|
||||
},
|
||||
}));
|
||||
useGameStore.getState().addNotification({
|
||||
title: '[DEV] Market Boom',
|
||||
message: `Subscribers multiplied by ${multiplier}x.`,
|
||||
type: 'success',
|
||||
tick: useGameStore.getState().meta.tickCount,
|
||||
});
|
||||
}
|
||||
|
||||
function forceFunding(roundType: FundingRoundType) {
|
||||
useGameStore.getState().raiseFunding(roundType);
|
||||
useGameStore.getState().addNotification({
|
||||
title: '[DEV] Funding',
|
||||
message: `Force-raised ${roundType} funding round.`,
|
||||
type: 'success',
|
||||
tick: useGameStore.getState().meta.tickCount,
|
||||
});
|
||||
}
|
||||
|
||||
export function EventTriggersTab() {
|
||||
const [failCount, setFailCount] = useState('10');
|
||||
const [boomMultiplier, setBoomMultiplier] = useState('2');
|
||||
|
||||
const completedRounds = useGameStore((s) => s.economy.funding.completedRounds);
|
||||
const totalFailedRacks = useGameStore((s) =>
|
||||
s.infrastructure.clusters.reduce((sum, cl) =>
|
||||
sum + cl.campuses.reduce((s2, ca) =>
|
||||
s2 + ca.dataCenters.reduce((s3, dc) => s3 + dc.computeRacksFailed, 0), 0), 0));
|
||||
|
||||
const fundingRounds: { type: FundingRoundType; label: string }[] = [
|
||||
{ type: 'seed', label: 'Seed ($500K)' },
|
||||
{ type: 'seriesA', label: 'A ($2M)' },
|
||||
{ type: 'seriesB', label: 'B ($10M)' },
|
||||
{ type: 'seriesC', label: 'C ($50M)' },
|
||||
{ type: 'seriesD', label: 'D ($200M)' },
|
||||
{ type: 'ipo', label: 'IPO ($1B)' },
|
||||
];
|
||||
|
||||
const completedTypes = new Set(completedRounds.map((r) => r.type));
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">Reputation Events</div>
|
||||
<DevButton onClick={triggerSafetyIncident} variant="danger">Trigger Safety Incident</DevButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">
|
||||
Infrastructure Events {totalFailedRacks > 0 && <span className="text-red-400">({totalFailedRacks} failed)</span>}
|
||||
</div>
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<input
|
||||
type="number"
|
||||
value={failCount}
|
||||
onChange={(e) => setFailCount(e.target.value)}
|
||||
className="bg-surface-800 border border-surface-600 rounded px-2 py-1 text-xs text-surface-100 w-16"
|
||||
min={1}
|
||||
/>
|
||||
<DevButton onClick={() => triggerRackFailure(Number(failCount) || 10)} variant="danger">Fail Racks</DevButton>
|
||||
<DevButton onClick={resetRackFailures} variant="success">Reset All Failures</DevButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">Market Events</div>
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<DevButton onClick={() => triggerMarketBoom(Number(boomMultiplier) || 2)} variant="warning">Market Boom</DevButton>
|
||||
<span className="text-xs text-surface-400">x</span>
|
||||
<input
|
||||
type="number"
|
||||
value={boomMultiplier}
|
||||
onChange={(e) => setBoomMultiplier(e.target.value)}
|
||||
className="bg-surface-800 border border-surface-600 rounded px-2 py-1 text-xs text-surface-100 w-12"
|
||||
min={1}
|
||||
step={0.5}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">Force Funding</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{fundingRounds.map(({ type, label }) => (
|
||||
<DevButton
|
||||
key={type}
|
||||
onClick={() => forceFunding(type)}
|
||||
variant={completedTypes.has(type) ? 'default' : 'success'}
|
||||
>
|
||||
{label} {completedTypes.has(type) && '✓'}
|
||||
</DevButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user