Add Week 4 social features, regulation, and safety tradeoffs

Leaderboard page with category tabs and score submission, shareable
company stats card with clipboard copy, dynamic regulation system
(compliance costs scale with capability and era, regulatory standing
tracks safety research), 6 geopolitical events (export controls, energy
crisis, natural disaster, AI safety summit, immigration policy, data
sovereignty), safety-capability tradeoff (safety score affects benchmark,
low safety triggers incidents with reputation damage), and enhanced
event consequence handling for regulation and talent types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 18:02:30 -04:00
parent 8a8b49d934
commit 0ff8a32b95
12 changed files with 532 additions and 14 deletions
+11 -1
View File
@@ -23,7 +23,7 @@ import {
import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
| 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'settings';
| 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'settings';
interface UIState {
activePage: ActivePage;
@@ -252,18 +252,28 @@ export const useGameStore = create<Store>()(
let money = s.economy.money;
let reputation = { ...s.reputation };
let talent = { ...s.talent };
const consequences = choice.consequences;
for (const c of consequences) {
switch (c.type) {
case 'money': money += c.value; break;
case 'reputation': reputation = { ...reputation, score: Math.min(100, Math.max(0, reputation.score + c.value)), publicPerception: Math.min(100, Math.max(0, reputation.publicPerception + c.value)) }; break;
case 'regulation': reputation = { ...reputation, regulatoryStanding: Math.min(100, Math.max(0, reputation.regulatoryStanding + c.value)) }; break;
case 'talent': {
const dept = c.target as keyof typeof talent.departments | undefined;
if (dept && talent.departments[dept]) {
talent = { ...talent, departments: { ...talent.departments, [dept]: { ...talent.departments[dept], headcount: Math.max(0, talent.departments[dept].headcount + c.value) } } };
}
break;
}
}
}
return {
economy: { ...s.economy, money: Math.max(0, money) },
reputation,
talent,
events: {
...s.events,
activeEvents: s.events.activeEvents.filter(e => e.instanceId !== instanceId),