Remove events system entirely
CI / build-and-push (push) Successful in 36s

The random events (GPU shortages, regulatory hearings, PR crises, etc.)
added interruption without enough gameplay value. Removed all event
types, definitions (~1800 lines of event data), the event processor,
EventModal UI, store actions, and tick integration. Updated docs to
reflect the removal. Bundle size drops ~47kB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 19:54:44 -04:00
parent 0005e580a7
commit 95f2f97121
16 changed files with 16 additions and 2191 deletions
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1,10 +1,9 @@
export { GameEngine } from './engine';
export { processTick, setEventDefinitions, setAchievementDefinitions } from './tick';
export { processTick, setAchievementDefinitions } from './tick';
export type { TickNotification } from './tick';
export { getAvailableResearch, getResearchNode } from './systems/researchSystem';
export { canRaiseFunding, getNextFundingRound, computeValuation } from './systems/fundingSystem';
export { TECH_TREE } from './data/techTree';
export { INITIAL_RIVALS } from './data/competitors';
export { KEY_HIRE_POOL } from './data/keyHires';
export { EVENT_DEFINITIONS } from './data/events';
export { ACHIEVEMENT_DEFINITIONS } from './data/achievements';
@@ -1,118 +0,0 @@
import type { GameState, EventState, ActiveEvent, EventDefinition, EventCondition } from '@ai-tycoon/shared';
import { uuid } from '@ai-tycoon/shared';
export interface EventTickResult {
events: EventState;
newEvents: ActiveEvent[];
}
export function processEvents(
state: GameState,
definitions: EventDefinition[],
): EventTickResult {
const tick = state.meta.tickCount;
const events = { ...state.events };
const newEvents: ActiveEvent[] = [];
// Remove expired events (auto-choose default)
const stillActive: ActiveEvent[] = [];
for (const event of events.activeEvents) {
if (tick >= event.expiresAtTick) {
events.eventHistory = [
...events.eventHistory,
{
eventId: event.eventId,
instanceId: event.instanceId,
title: event.title,
category: event.category,
tick,
chosenOptionIndex: event.defaultChoiceIndex,
},
];
} else {
stillActive.push(event);
}
}
events.activeEvents = stillActive;
if (events.eventHistory.length > 50) {
events.eventHistory = events.eventHistory.slice(-50);
}
// Only try to fire a new event every 30 ticks, and max 1 active at a time
if (tick % 30 !== 0 || events.activeEvents.length > 0) {
return { events, newEvents };
}
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
const eligible = definitions.filter(def => {
if (!def.eras.some(e => eraOrder.indexOf(e) <= currentEraIdx)) return false;
const occ = events.eventOccurrences[def.id] ?? 0;
if (occ >= def.maxOccurrences) return false;
const cooldownEnd = events.eventCooldowns[def.id] ?? 0;
if (tick < cooldownEnd) return false;
if (def.prerequisites.some(p => !state.research.completedResearch.includes(p))) return false;
if (!def.conditions.every(c => evaluateCondition(state, c))) return false;
return true;
});
if (eligible.length === 0) return { events, newEvents };
const totalWeight = eligible.reduce((s, d) => s + d.weight, 0);
let roll = Math.random() * totalWeight;
let chosen: EventDefinition | null = null;
for (const def of eligible) {
roll -= def.weight;
if (roll <= 0) { chosen = def; break; }
}
if (!chosen) return { events, newEvents };
// Only fire with 30% probability per check to space events out
if (Math.random() > 0.3) return { events, newEvents };
const activeEvent: ActiveEvent = {
eventId: chosen.id,
instanceId: uuid(),
triggeredAtTick: tick,
expiresAtTick: tick + chosen.expiryTicks,
title: chosen.title,
description: chosen.descriptionTemplate,
category: chosen.category,
choices: chosen.choices,
defaultChoiceIndex: chosen.defaultChoiceIndex,
};
events.activeEvents = [...events.activeEvents, activeEvent];
events.eventCooldowns = { ...events.eventCooldowns, [chosen.id]: tick + chosen.cooldownTicks };
events.eventOccurrences = {
...events.eventOccurrences,
[chosen.id]: (events.eventOccurrences[chosen.id] ?? 0) + 1,
};
newEvents.push(activeEvent);
return { events, newEvents };
}
function evaluateCondition(state: GameState, condition: EventCondition): boolean {
const value = getNestedValue(state, condition.field);
if (value === undefined) return false;
switch (condition.operator) {
case 'gt': return value > condition.value;
case 'lt': return value < condition.value;
case 'gte': return value >= condition.value;
case 'lte': return value <= condition.value;
case 'eq': return value === condition.value;
}
}
function getNestedValue(obj: object, path: string): number | undefined {
const parts = path.split('.');
let current: unknown = obj;
for (const part of parts) {
if (current == null || typeof current !== 'object') return undefined;
current = (current as Record<string, unknown>)[part];
}
return typeof current === 'number' ? current : undefined;
}
+1 -21
View File
@@ -1,4 +1,4 @@
import type { GameState, EventDefinition, AchievementDefinition } from '@ai-tycoon/shared';
import type { GameState, AchievementDefinition } from '@ai-tycoon/shared';
import { processEconomy } from './systems/economySystem';
import { processInfrastructure } from './systems/infrastructureSystem';
import { processCompute } from './systems/computeSystem';
@@ -7,7 +7,6 @@ import { processModels } from './systems/modelSystem';
import { processMarket } from './systems/marketSystem';
import { processReputation } from './systems/reputationSystem';
import { processTalent } from './systems/talentSystem';
import { processEvents } from './systems/eventSystem';
import { processCompetitors } from './systems/competitorSystem';
import { processData } from './systems/dataSystem';
import { checkEraTransition } from './systems/eraSystem';
@@ -25,13 +24,8 @@ export interface TickNotification {
type: 'info' | 'success' | 'warning' | 'danger';
}
let cachedEventDefs: EventDefinition[] | null = null;
let cachedAchievementDefs: AchievementDefinition[] | null = null;
export function setEventDefinitions(defs: EventDefinition[]) {
cachedEventDefs = defs;
}
export function setAchievementDefinitions(defs: AchievementDefinition[]) {
cachedAchievementDefs = defs;
}
@@ -88,18 +82,6 @@ export function processTick(state: GameState): Partial<GameState> {
const data = processData(stateWithTalent);
const competitors = processCompetitors(stateWithTalent);
const eventResult = cachedEventDefs
? processEvents(stateWithTalent, cachedEventDefs)
: { events: state.events, newEvents: [] };
for (const evt of eventResult.newEvents) {
notifications.push({
title: evt.title,
message: evt.description,
type: evt.category === 'regulatory' ? 'warning' : 'info',
});
}
const tickCount = state.meta.tickCount + 1;
let meta = {
@@ -137,7 +119,6 @@ export function processTick(state: GameState): Partial<GameState> {
reputation,
data,
competitors,
events: eventResult.events,
achievements: state.achievements,
};
@@ -165,7 +146,6 @@ export function processTick(state: GameState): Partial<GameState> {
reputation,
data,
competitors,
events: eventResult.events,
achievements: achievementResult.achievements,
};
@@ -7,7 +7,6 @@ export const FAST_FORWARD_BATCH_SIZE = 100;
export const AUTO_SAVE_INTERVAL_TICKS = 60;
export const FINANCIAL_SNAPSHOT_INTERVAL = 60;
export const MAX_FINANCIAL_HISTORY = 1000;
export const MAX_EVENT_HISTORY = 50;
export const MAX_REPUTATION_HISTORY = 500;
export const STARTING_MONEY = 50_000;
-1
View File
@@ -9,7 +9,6 @@ export * from './types/competitors';
export * from './types/talent';
export * from './types/data';
export * from './types/reputation';
export * from './types/events';
export * from './types/achievements';
export * from './utils/formatting';
export * from './constants/gameBalance';
-74
View File
@@ -1,74 +0,0 @@
import type { Era } from './gameState';
export interface EventState {
activeEvents: ActiveEvent[];
eventHistory: EventHistoryEntry[];
eventCooldowns: Record<string, number>;
eventOccurrences: Record<string, number>;
}
export interface ActiveEvent {
eventId: string;
instanceId: string;
triggeredAtTick: number;
expiresAtTick: number;
title: string;
description: string;
category: EventCategory;
choices: EventChoice[];
defaultChoiceIndex: number;
}
export type EventCategory = 'industry' | 'regulatory' | 'pr' | 'internal' | 'market';
export interface EventChoice {
label: string;
description: string;
consequences: EventConsequence[];
}
export interface EventConsequence {
type: 'money' | 'reputation' | 'compute' | 'talent' | 'research_speed'
| 'regulation' | 'competitor' | 'unlock' | 'lock' | 'chain_event';
value: number;
target?: string;
delay?: number;
}
export interface EventHistoryEntry {
eventId: string;
instanceId: string;
title: string;
category: EventCategory;
tick: number;
chosenOptionIndex: number;
}
export interface EventDefinition {
id: string;
title: string;
descriptionTemplate: string;
category: EventCategory;
eras: Era[];
weight: number;
cooldownTicks: number;
maxOccurrences: number;
prerequisites: string[];
conditions: EventCondition[];
choices: EventChoice[];
defaultChoiceIndex: number;
expiryTicks: number;
}
export interface EventCondition {
field: string;
operator: 'gt' | 'lt' | 'gte' | 'lte' | 'eq';
value: number;
}
export const INITIAL_EVENTS: EventState = {
activeEvents: [],
eventHistory: [],
eventCooldowns: {},
eventOccurrences: {},
};
-2
View File
@@ -8,7 +8,6 @@ import type { CompetitorState } from './competitors';
import type { TalentState } from './talent';
import type { DataState } from './data';
import type { ReputationState } from './reputation';
import type { EventState } from './events';
import type { AchievementState } from './achievements';
export interface GameState {
@@ -23,7 +22,6 @@ export interface GameState {
talent: TalentState;
data: DataState;
reputation: ReputationState;
events: EventState;
achievements: AchievementState;
}