Add research money costs, longer research times, era-scaled talent costs, and persona strategy
Research now costs money (drained per-tick) with ~2.5-3.5x longer durations by category. Early-game talent budget costs reduced via era multiplier (startup 0.2x → bigtech 1.0x). New seed-driven PersonaStrategy with 8 axes of variation for meaningful multi-run testing. CI multi-run switched from greedy to persona strategy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { GameState, EconomyState, InfrastructureState } from '@ai-tycoon/shared';
|
||||
import { FINANCIAL_SNAPSHOT_INTERVAL, MAX_FINANCIAL_HISTORY, REGULATION_COMPLIANCE_PER_CAPABILITY } from '@ai-tycoon/shared';
|
||||
import { TECH_TREE } from '../data/techTree';
|
||||
import type { MarketTickResult } from './marketSystem';
|
||||
|
||||
export function processEconomy(
|
||||
@@ -27,7 +28,16 @@ export function processEconomy(
|
||||
const complianceCost = bestCapability > 30 ? bestCapability * REGULATION_COMPLIANCE_PER_CAPABILITY * (1 + eraIdx * 0.5) / 100 : 0;
|
||||
|
||||
const devRelExpenses = state.market.developerEcosystem.devRelSpending;
|
||||
const expenses = infraExpenses + talentExpenses + dataExpenses + complianceCost + devRelExpenses + extraCosts;
|
||||
|
||||
let researchExpenses = 0;
|
||||
if (state.research.activeResearch) {
|
||||
const node = TECH_TREE.find(n => n.id === state.research.activeResearch!.researchId);
|
||||
if (node) {
|
||||
researchExpenses = node.cost.money / node.cost.ticks;
|
||||
}
|
||||
}
|
||||
|
||||
const expenses = infraExpenses + talentExpenses + dataExpenses + complianceCost + devRelExpenses + researchExpenses + extraCosts;
|
||||
|
||||
const money = state.economy.money + revenue - expenses;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ function promoteFromQueue(
|
||||
totalTicks: node.cost.ticks,
|
||||
allocatedResearchers: state.talent.departments.research.headcount,
|
||||
allocatedCompute: node.cost.compute,
|
||||
moneySpent: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -52,6 +53,10 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
|
||||
|
||||
const newProgress = active.progressTicks + speedMultiplier;
|
||||
|
||||
const node = TECH_TREE.find(n => n.id === active.researchId);
|
||||
const moneyPerTick = node ? node.cost.money / node.cost.ticks : 0;
|
||||
const newMoneySpent = (active.moneySpent ?? 0) + moneyPerTick;
|
||||
|
||||
if (newProgress >= active.totalTicks) {
|
||||
const completedResearch = {
|
||||
...state.research,
|
||||
@@ -72,7 +77,7 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
|
||||
return {
|
||||
research: {
|
||||
...state.research,
|
||||
activeResearch: { ...active, progressTicks: newProgress },
|
||||
activeResearch: { ...active, progressTicks: newProgress, moneySpent: newMoneySpent },
|
||||
},
|
||||
researchCompleted: null,
|
||||
};
|
||||
|
||||
@@ -53,8 +53,9 @@ describe('processTalent', () => {
|
||||
expect(result.totalSalaryPerTick).toBe(70);
|
||||
});
|
||||
|
||||
it('adds 1% of department budget per tick', () => {
|
||||
it('adds 1% of department budget per tick scaled by era', () => {
|
||||
const state = createTestState({
|
||||
meta: { currentEra: 'bigtech' },
|
||||
talent: {
|
||||
departments: {
|
||||
research: { id: 'research', headcount: 0, budget: 10_000, effectiveness: 0.5, morale: 0.8 },
|
||||
@@ -66,10 +67,28 @@ describe('processTalent', () => {
|
||||
},
|
||||
});
|
||||
const result = processTalent(state);
|
||||
// 10000 * 0.01 + 5000 * 0.01 = 100 + 50 = 150
|
||||
// bigtech multiplier = 1.0: 10000 * 0.01 + 5000 * 0.01 = 150
|
||||
expect(result.totalSalaryPerTick).toBe(150);
|
||||
});
|
||||
|
||||
it('applies startup era discount to budget costs', () => {
|
||||
const state = createTestState({
|
||||
meta: { currentEra: 'startup' },
|
||||
talent: {
|
||||
departments: {
|
||||
research: { id: 'research', headcount: 0, budget: 10_000, effectiveness: 0.5, morale: 0.8 },
|
||||
engineering: { id: 'engineering', headcount: 0, budget: 5_000, effectiveness: 0.5, morale: 0.8 },
|
||||
operations: { id: 'operations', headcount: 0, budget: 0, effectiveness: 0.5, morale: 0.8 },
|
||||
sales: { id: 'sales', headcount: 0, budget: 0, effectiveness: 0.5, morale: 0.8 },
|
||||
},
|
||||
keyHires: [],
|
||||
},
|
||||
});
|
||||
const result = processTalent(state);
|
||||
// startup multiplier = 0.2: (10000 + 5000) * 0.01 * 0.2 = 30
|
||||
expect(result.totalSalaryPerTick).toBe(30);
|
||||
});
|
||||
|
||||
it('adds key hire salaries to total', () => {
|
||||
const state = createTestState({
|
||||
talent: {
|
||||
@@ -110,6 +129,7 @@ describe('processTalent', () => {
|
||||
|
||||
it('combines headcount salary, budget cost, and key hire salary', () => {
|
||||
const state = createTestState({
|
||||
meta: { currentEra: 'bigtech' },
|
||||
talent: {
|
||||
departments: {
|
||||
research: { id: 'research', headcount: 4, budget: 2_000, effectiveness: 0.5, morale: 0.8 },
|
||||
@@ -133,7 +153,7 @@ describe('processTalent', () => {
|
||||
});
|
||||
const result = processTalent(state);
|
||||
// headcount: (4 + 6) * 5 = 50
|
||||
// budget: 2000 * 0.01 + 3000 * 0.01 = 20 + 30 = 50
|
||||
// budget (bigtech 1.0x): 2000 * 0.01 + 3000 * 0.01 = 50
|
||||
// key hires: 15
|
||||
// total = 50 + 50 + 15 = 115
|
||||
expect(result.totalSalaryPerTick).toBe(115);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { GameState, TalentState } from '@ai-tycoon/shared';
|
||||
import { ERA_BUDGET_COST_MULTIPLIER } from '@ai-tycoon/shared';
|
||||
|
||||
const SALARY_PER_HEADCOUNT_PER_TICK = 5;
|
||||
|
||||
export function processTalent(state: GameState): TalentState {
|
||||
const departments = { ...state.talent.departments };
|
||||
const budgetMultiplier = ERA_BUDGET_COST_MULTIPLIER[state.meta.currentEra] ?? 1.0;
|
||||
|
||||
let totalSalary = 0;
|
||||
for (const [id, dept] of Object.entries(departments)) {
|
||||
totalSalary += dept.headcount * SALARY_PER_HEADCOUNT_PER_TICK;
|
||||
totalSalary += dept.budget * 0.01;
|
||||
totalSalary += dept.budget * 0.01 * budgetMultiplier;
|
||||
}
|
||||
|
||||
for (const hire of state.talent.keyHires) {
|
||||
|
||||
Reference in New Issue
Block a user