Add research queue: queue multiple projects, auto-promote on completion, RP refund on dequeue
Balance Check / multi-run-balance (push) Has been cancelled
Balance Check / balance-simulation (push) Has been cancelled
CI / build-and-push (push) Successful in 28s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 08:16:16 -04:00
parent 626ca51041
commit 5885e33531
5 changed files with 164 additions and 20 deletions
@@ -1,4 +1,4 @@
import type { GameState, ResearchState, ComputeState } from '@ai-tycoon/shared';
import type { GameState, ResearchState, ActiveResearch, ComputeState } from '@ai-tycoon/shared';
import { TECH_TREE } from '../data/techTree';
export interface ResearchTickResult {
@@ -6,6 +6,42 @@ export interface ResearchTickResult {
researchCompleted: string | null;
}
function promoteFromQueue(
research: ResearchState,
state: GameState,
): ResearchState {
const queue = research.researchQueue;
if (queue.length === 0) return research;
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
const completed = research.completedResearch;
for (let i = 0; i < queue.length; i++) {
const id = queue[i];
const node = TECH_TREE.find(n => n.id === id);
if (!node) continue;
if (eraOrder.indexOf(node.era) > currentEraIdx) continue;
if (node.prerequisites.some(p => !completed.includes(p))) continue;
const active: ActiveResearch = {
researchId: id,
progressTicks: 0,
totalTicks: node.cost.ticks,
allocatedResearchers: state.talent.departments.research.headcount,
allocatedCompute: node.cost.compute,
};
return {
...research,
activeResearch: active,
researchQueue: [...queue.slice(0, i), ...queue.slice(i + 1)],
};
}
return research;
}
export function processResearch(state: GameState, compute: ComputeState): ResearchTickResult {
const active = state.research.activeResearch;
if (!active) return { research: state.research, researchCompleted: null };
@@ -17,13 +53,18 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
const newProgress = active.progressTicks + speedMultiplier;
if (newProgress >= active.totalTicks) {
const completedResearch = {
...state.research,
completedResearch: [...state.research.completedResearch, active.researchId],
activeResearch: null,
researchPoints: state.research.researchPoints + 1,
};
return {
research: {
...state.research,
completedResearch: [...state.research.completedResearch, active.researchId],
activeResearch: null,
researchPoints: state.research.researchPoints + 1,
},
research: promoteFromQueue(completedResearch, {
...state,
research: completedResearch,
}),
researchCompleted: active.researchId,
};
}
@@ -40,10 +81,12 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
export function getAvailableResearch(state: GameState): typeof TECH_TREE {
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
const queuedIds = new Set(state.research.researchQueue);
return TECH_TREE.filter(node => {
if (state.research.completedResearch.includes(node.id)) return false;
if (state.research.activeResearch?.researchId === node.id) return false;
if (queuedIds.has(node.id)) return false;
if (eraOrder.indexOf(node.era) > currentEraIdx) return false;
if (node.prerequisites.some(p => !state.research.completedResearch.includes(p))) return false;
if (node.cost.researchPoints > state.research.researchPoints) return false;