import React, { useState, useEffect, useRef } from 'react'; import { Heart, Flame, Droplets, Thermometer, Zap, CloudRain, Sun, Moon, CloudLightning, Apple, Skull, AlertTriangle, Cloud, Target, CloudFog, Waves, Snowflake, Brain, Sword, Ghost, Trophy, RefreshCcw, Fish, Axe, Tent, Navigation, Crosshair, ListTodo, TrendingUp, ShieldAlert, Eye } from 'lucide-react'; const TICK_RATE = 1000; const WORLD_WIDTH = 6000; const WORLD_HEIGHT = 3000; // ─── Runtime backend config ───────────────────────────────────────────────── // Token is kept server-side; the frontend calls /api/infer and reads /api/config. const ZONES = { OCEAN: { baseEndX: 400, color1: '#094b65', color2: '#20a4c0' }, ISLAND: { startX: 400, endX: 1200, color: '#e6d3a8' }, JUNGLE: { startX: 1200, endX: 2800, color: '#569e40' }, VILLAGE: { startX: 2800, endX: 4500, color: '#7a6a4f' }, OCEAN_GULF: { startX: 4500, endX: 5200, color1: '#094b65', color2: '#20a4c0' }, SAFE_HAVEN: { startX: 5200, endX: 6000, color: '#447a32' } }; const clamp = (val, min, max) => Math.min(Math.max(val, min), max); const CHALLENGE_DB = [ { category: '🟢 Survival', tasks: [ { name: "Survive 1 Full Day", type: 'SURVIVE_1_DAY', timeLimit: 96 }, { name: "Find Food Immediate", type: 'FIND_FOOD', timeLimit: 40 }, { name: "Build Basic Shelter", type: 'BUILD_BASIC', timeLimit: 80 } ]}, { category: '🟡 Predator', tasks: [ { name: "Escape Detection", type: 'ESCAPE_PRED', timeLimit: 30, trigger: 'SPAWN_LION' }, { name: "Survive 2 Predators", type: 'SURVIVE_2_PRED', timeLimit: 60, trigger: 'SPAWN_2_LIONS' }, { name: "Hunt Boar w/ Weapon", type: 'HUNT_WEAPON', timeLimit: 90 } ]}, { category: '🌊 Disaster', tasks: [ { name: "Survive Flood Evac", type: 'SURVIVE_FLOOD', timeLimit: 120, trigger: 'FLASH_FLOOD' }, { name: "Survive Storm", type: 'SURVIVE_STORM', timeLimit: 60, trigger: 'STORM_COLLAPSE' }, { name: "Navigate Thick Fog", type: 'NAVIGATE_FOG', timeLimit: 45, trigger: 'THICK_FOG' } ]}, { category: '🔴 Strategic', tasks: [ { name: "Reach Eastern Ruins", type: 'REACH_RUINS', timeLimit: 150 }, { name: "Build Improved Camp", type: 'IMPROVED_SHELTER', timeLimit: 120 }, { name: "Craft Before Attack", type: 'CRAFT_BEFORE_PREDATOR', timeLimit: 60, trigger: 'SPAWN_LION_FAR' } ]}, { category: '⚡ God-Level', tasks: [ { name: "Storm + Hunted (2m)", type: 'STORM_AND_HUNTED', timeLimit: 120, trigger: 'STORM_AND_LIONS' }, { name: "Flood + 2 Lions", type: 'LIONS_AND_FLOOD', timeLimit: 90, trigger: 'FLOOD_AND_LIONS' }, { name: "Survive No Food", type: 'SURVIVE_NO_FOOD', timeLimit: 90 } ]} ]; const generateWorld = () => { const trees = []; const buildings = []; const animals = []; const lions = []; const panthers = []; const crocodiles = []; const hippos = []; const rocks = []; const lilyPads = []; for (let i = 0; i < 250; i++) trees.push({ id: i, x: 1250 + Math.random() * 1450, y: 50 + Math.random() * 2900, radius: 25 + Math.random() * 20, type: 'pine', wood: 3, food: Math.random() > 0.6 ? 2 : 0 }); for (let i = 0; i < 80; i++) trees.push({ id: i + 1000, x: 450 + Math.random() * 650, y: 50 + Math.random() * 2900, radius: 20 + Math.random() * 15, type: 'palm', wood: 1, food: Math.random() > 0.8 ? 1 : 0 }); for (let i = 0; i < 100; i++) trees.push({ id: i + 2000, x: 5250 + Math.random() * 700, y: 50 + Math.random() * 2900, radius: 30 + Math.random() * 25, type: 'pine', wood: 4, food: 2 }); for (let i = 0; i < 25; i++) buildings.push({ id: i, x: 2900 + Math.random() * 1500, y: 100 + Math.random() * 2700, w: 160 + Math.random() * 140, h: 160 + Math.random() * 140, hasSecret: true }); for (let i = 0; i < 40; i++) animals.push({ id: i, x: 1300 + Math.random() * 2500, y: 100 + Math.random() * 2800, vx: 0, vy: 0, angle: 0, state: 'idle', type: 'boar', meat: 10 }); for (let i = 0; i < 10; i++) hippos.push({ id: i+200, x: 400 + Math.random() * 400, y: 100 + Math.random() * 2800, vx: 0, vy: 0, angle: 0, state: 'idle', type: 'hippo', meat: 25 }); for (let i = 0; i < 6; i++) lions.push({ id: i, x: 1500 + Math.random() * 1200, y: 200 + Math.random() * 2600, angle: 0, state: 'idle', type: 'lion' }); for (let i = 0; i < 4; i++) panthers.push({ id: i+50, x: 2000 + Math.random() * 1500, y: 200 + Math.random() * 2600, angle: 0, state: 'idle', type: 'panther' }); for (let i = 0; i < 12; i++) crocodiles.push({ id: i, x: 100 + Math.random() * 250, y: 200 + Math.random() * 2600, angle: 0, state: 'hidden' }); for (let i = 0; i < 100; i++) rocks.push({ id: i, x: 350 + Math.random() * 5500, y: Math.random() * 3000, size: 10 + Math.random() * 25, stone: 1 }); for (let i = 0; i < 40; i++) lilyPads.push({ id: i, x: 150 + Math.random() * 200, y: Math.random() * 3000, size: 8 + Math.random() * 10, angle: Math.random() * Math.PI * 2 }); return { trees, buildings, animals, lions, crocodiles, rocks, lilyPads, fires: [], baseCamp: null }; }; let WORLD_OBJECTS = generateWorld(); const buildSurvivalLessons = (pastDeaths) => { if (!pastDeaths || pastDeaths.length === 0) return []; const lessons = []; pastDeaths.forEach(d => { const text = d.toLowerCase(); if (text.includes('starvation')) lessons.push('CRITICAL: Prioritize food — starvation has killed me before.'); if (text.includes('dehydration')) lessons.push('CRITICAL: Prioritize water — dehydration has killed me before.'); if (text.includes('hypothermia')) lessons.push('CRITICAL: Seek shelter at night — hypothermia has killed me before.'); if (text.includes('heatstroke')) lessons.push('CRITICAL: Find shade/water during heatwaves — heatstroke has killed me before.'); if (text.includes('lion') || text.includes('mauled') || text.includes('panther')) lessons.push('CRITICAL: Craft a spear/bow before exploring. FLEE when predators are near until armed.'); if (text.includes('crocodile')) lessons.push('CRITICAL: Avoid water edges without a boat — crocodiles are deadly.'); if (text.includes('flood')) lessons.push('CRITICAL: During floods, evacuate eastward IMMEDIATELY.'); }); return [...new Set(lessons)]; }; const getAdaptiveStrategy = (memory) => { if (!memory || memory.evolutionLevel <= 1) return 'basic'; if (memory.pastDeaths.length >= 5) return 'veteran'; if (memory.pastDeaths.length >= 3) return 'cautious'; return 'experienced'; }; const getChallengeAction = (s) => { const c = s.activeChallenge; if (!c) return null; let nearThreat = false; let closestThreat = null; let minThreatD = Infinity; WORLD_OBJECTS.lions.forEach(l => { const d = Math.hypot(s.player.x - l.x, s.player.y - l.y); if (d < 500) { nearThreat = true; if (d < minThreatD) { minThreatD = d; closestThreat = l; } } }); switch (c.type) { case 'SURVIVE_1_DAY': if (nearThreat) return 'FLEE'; if (s.hunger < 40) return s.ai.inventory.spear || s.ai.inventory.bow ? 'HUNT' : 'FORAGE'; if (s.thirst < 40) return 'GET_WATER'; if (s.wood < 3 && s.stone < 2) return 'FORAGE'; if (!s.ai.inventory.spear && s.wood >= 3 && s.stone >= 2) return 'CRAFT_SPEAR'; return 'WANDER'; case 'FIND_FOOD': if (nearThreat) return 'FLEE'; if (s.ai.inventory.spear || s.ai.inventory.bow) return 'HUNT'; if (s.wood >= 3 && s.stone >= 2) return 'CRAFT_SPEAR'; if (WORLD_OBJECTS.animals.length > 0) return 'HUNT'; return 'FORAGE'; case 'BUILD_BASIC': if (nearThreat) return 'FLEE'; if (s.ai.baseCamp.level >= 1) return 'WANDER'; if (s.wood >= 5) return 'BUILD_CAMP'; return 'FORAGE'; case 'ESCAPE_PRED': return nearThreat ? 'FLEE' : 'WANDER'; case 'SURVIVE_2_PRED': if (nearThreat) { return (s.ai.inventory.spear || s.ai.inventory.bow) ? 'FIGHT' : 'FLEE'; } if (!s.ai.inventory.spear && s.wood >= 3 && s.stone >= 2) return 'CRAFT_SPEAR'; return 'WANDER'; case 'HUNT_WEAPON': if (nearThreat) return 'FLEE'; if (!s.ai.inventory.spear && !s.ai.inventory.bow) { if (s.wood >= 5 && s.stone >= 1) return 'CRAFT_BOW'; if (s.wood >= 3 && s.stone >= 2) return 'CRAFT_SPEAR'; return 'FORAGE'; } return 'HUNT'; case 'SURVIVE_FLOOD': if (s.player.x < ZONES.VILLAGE.startX - 100) return 'EVACUATE'; if (s.ai.baseCamp.level === 0 && s.wood >= 5) return 'BUILD_CAMP'; if (s.ai.baseCamp.level === 0) return 'FORAGE'; return 'SEEK_SHELTER'; case 'SURVIVE_STORM': if (s.ai.baseCamp.level > 0) return 'SEEK_SHELTER'; if (s.wood >= 5) return 'BUILD_CAMP'; return 'FORAGE'; case 'NAVIGATE_FOG': if (nearThreat) return 'FLEE'; return 'EVACUATE'; case 'REACH_RUINS': if (nearThreat) return 'FLEE'; return 'EVACUATE'; case 'IMPROVED_SHELTER': if (nearThreat) return 'FLEE'; if (s.ai.baseCamp.level === 0) { if (s.wood >= 5) return 'BUILD_CAMP'; return 'FORAGE'; } if (s.ai.baseCamp.level === 1) { if (s.wood >= 10 && s.stone >= 5) return 'UPGRADE_CAMP'; return 'FORAGE'; } return 'WANDER'; case 'CRAFT_BEFORE_PREDATOR': if (nearThreat && minThreatD < 300) return 'FLEE'; if (!s.ai.inventory.spear && !s.ai.inventory.bow) { if (s.wood >= 5 && s.stone >= 1) return 'CRAFT_BOW'; if (s.wood >= 3 && s.stone >= 2) return 'CRAFT_SPEAR'; return 'FORAGE'; } return 'WANDER'; case 'STORM_AND_HUNTED': if (nearThreat) return (s.ai.inventory.spear || s.ai.inventory.bow) ? 'FIGHT' : 'FLEE'; if (s.ai.baseCamp.level === 0 && s.wood >= 5) return 'BUILD_CAMP'; if (s.ai.baseCamp.level === 0) return 'FORAGE'; return 'SEEK_SHELTER'; case 'LIONS_AND_FLOOD': if (nearThreat) return (s.ai.inventory.spear || s.ai.inventory.bow) ? 'FIGHT' : 'FLEE'; if (s.player.x < ZONES.VILLAGE.startX - 100) return 'EVACUATE'; if (s.ai.baseCamp.level === 0 && s.wood >= 5) return 'BUILD_CAMP'; if (s.ai.baseCamp.level === 0) return 'FORAGE'; return 'SEEK_SHELTER'; case 'SURVIVE_NO_FOOD': if (nearThreat) return 'FLEE'; if (s.thirst < 50) return 'GET_WATER'; if (s.ai.baseCamp.level === 0 && s.wood >= 5) return 'BUILD_CAMP'; if (s.ai.baseCamp.level === 0) return 'FORAGE'; return 'SEEK_SHELTER'; default: return null; } }; export default function App() { const gameRef = useRef({ started: false, gameMode: 'evolution', generation: 1, isAlive: true, causeOfDeath: '', player: { x: 1000, y: 1500, radius: 14, baseSpeed: 7.5, speedMult: 1, vx: 0, vy: 0, angle: 0 }, camera: { x: 0, y: 0, shake: 0, zoom: 1.2 }, ai: { state: 'IDLE', target: null, actionDelay: 0, sneaking: false, message: 'Booting Neural Net...', fear: 0, panic: false, inventory: { spear: false, fishingRod: false, boat: false, bow: false }, baseCamp: { x: null, y: null, level: 0 }, memory: { evolutionLevel: 1, pastDeaths: [], totalGenerations: 0, challengesWon: 0 }, llmAction: 'WANDER', llmThinking: false, llmTimer: 0, consecutiveFailures: 0 }, health: 100, hunger: 100, thirst: 100, stamina: 100, temp: 25, wetness: 0, wood: 0, food: 0, water: 0, stone: 0, shelterStatus: 0, day: 1, time: 8, activeEvents: [], eventTimer: 60, dynamicWaterLevel: 400, activeChallenge: null, completedChallenges: 0, logs: [] }); const [hudState, setHudState] = useState(null); const [showMemoryLog, setShowMemoryLog] = useState(false); const [brainConfig, setBrainConfig] = useState({ model: 'loading...', hasToken: false, localPipeline: false }); const canvasRef = useRef(null); useEffect(() => { const fetchConfig = async () => { try { const response = await fetch('/api/config'); if (!response.ok) return; const data = await response.json(); setBrainConfig({ model: data.model || 'unknown', hasToken: Boolean(data.hasToken), localPipeline: Boolean(data.localPipeline), }); } catch (error) { console.warn('Failed to load backend config:', error); } }; fetchConfig(); }, []); const addLog = (message, type = 'info') => { gameRef.current.logs = [{ id: Date.now(), message, type, time: Date.now() }]; }; const startGame = (mode) => { WORLD_OBJECTS = generateWorld(); gameRef.current = { ...gameRef.current, started: true, gameMode: mode, generation: 1, isAlive: true, causeOfDeath: '', player: { x: 1000, y: 1500, radius: 14, baseSpeed: 7.5, speedMult: 1, vx: 0, vy: 0, angle: 0 }, health: 100, hunger: 100, thirst: 100, stamina: 100, temp: 25, wetness: 0, wood: 0, food: 0, water: 0, stone: 0, day: 1, time: 8, activeEvents: [], activeChallenge: null, completedChallenges: 0, dynamicWaterLevel: ZONES.OCEAN.baseEndX, ai: { state: 'IDLE', target: null, actionDelay: 0, sneaking: false, message: 'Analyzing world...', fear: 0, panic: false, inventory: { spear: false, fishingRod: false, boat: false, bow: false }, baseCamp: { x: null, y: null, level: 0 }, memory: { evolutionLevel: 1, pastDeaths: [], totalGenerations: 0, challengesWon: 0 }, llmAction: 'WANDER', llmThinking: false, llmTimer: 0, consecutiveFailures: 0 }, logs: [] }; addLog(`SUBJECT-01 ONLINE. [MODE: ${mode.toUpperCase()}]`, "system"); setHudState({ ...gameRef.current }); }; const evolveAndRespawn = () => { const retainedMemory = gameRef.current.ai.memory; const retainedChallenges = gameRef.current.completedChallenges; const nextGen = gameRef.current.generation + 1; retainedMemory.totalGenerations = nextGen - 1; retainedMemory.challengesWon = (retainedMemory.challengesWon || 0) + retainedChallenges; const speedBonus = Math.min(retainedMemory.evolutionLevel * 0.15, 1.5); WORLD_OBJECTS = generateWorld(); gameRef.current = { ...gameRef.current, isAlive: true, causeOfDeath: '', generation: nextGen, player: { x: 1000, y: 1500, radius: 14, baseSpeed: 7.5 + speedBonus, speedMult: 1, vx: 0, vy: 0, angle: 0 }, health: 100, hunger: 100, thirst: 100, stamina: 100, temp: 25, wetness: 0, wood: 0, food: 0, water: 0, stone: 0, day: 1, time: 8, activeEvents: [], activeChallenge: null, completedChallenges: 0, dynamicWaterLevel: ZONES.OCEAN.baseEndX, ai: { state: 'IDLE', target: null, actionDelay: 0, sneaking: false, message: `Gen ${nextGen}: Learning from ${retainedMemory.pastDeaths.length} past deaths...`, fear: 0, panic: false, inventory: { spear: false, fishingRod: false, boat: false, bow: false }, baseCamp: { x: null, y: null, level: 0 }, memory: retainedMemory, llmAction: 'WANDER', llmThinking: false, llmTimer: 0, consecutiveFailures: 0 }, logs: [] }; addLog(`GEN ${nextGen} DEPLOYED. ${retainedMemory.pastDeaths.length} DEATH PATTERNS LOADED.`, "system"); setHudState({ ...gameRef.current }); }; const toggleEvent = (eventName) => { const s = gameRef.current; if (!s.isAlive) return; if (eventName === 'Clear') { s.activeEvents = []; s.dynamicWaterLevel = ZONES.OCEAN.baseEndX; addLog("GOD ACTION: Cleared all disasters.", 'info'); } else { if (s.activeEvents.includes(eventName)) s.activeEvents = s.activeEvents.filter(e => e !== eventName); else { s.activeEvents.push(eventName); if (eventName === 'Storm Collapse') WORLD_OBJECTS.fires = []; } addLog(`GOD ACTION: Toggled ${eventName.toUpperCase()}`, 'danger'); } setHudState({ ...s }); }; const godAmbush = () => { const s = gameRef.current; if (!s.isAlive) return; WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 200, y: s.player.y + 200, angle: 0, state: 'chasing', type: 'panther' }); WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x - 200, y: s.player.y - 200, angle: 0, state: 'chasing', type: 'lion' }); if (!s.activeEvents.includes('Thick Fog')) s.activeEvents.push('Thick Fog'); addLog("GOD ACTION: Predator Ambush Triggered!", 'danger'); setHudState({ ...s }); }; const godSmite = () => { const s = gameRef.current; if (!s.isAlive) return; s.health -= 35; s.ai.fear += 50; addLog("GOD ACTION: Lightning Strike!", 'danger'); setHudState({ ...s }); }; const godStarve = () => { const s = gameRef.current; if (!s.isAlive) return; s.hunger = 0; addLog("GOD ACTION: Induced Starvation!", 'danger'); setHudState({ ...s }); }; const godBless = () => { const s = gameRef.current; if (!s.isAlive) return; s.food += 15; s.wood += 15; s.stone += 10; addLog("GOD ACTION: Care package dropped.", 'success'); setHudState({ ...s }); }; const assignAdvancedChallenge = (chal) => { const s = gameRef.current; if (!s.isAlive) return; if (chal.trigger === 'SPAWN_LION') WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 400, y: s.player.y + 300, angle: 0, state: 'chasing', type: 'lion' }); if (chal.trigger === 'SPAWN_2_LIONS') { WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 400, y: s.player.y + 300, angle: 0, state: 'chasing', type: 'lion' }); WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x - 400, y: s.player.y - 300, angle: 0, state: 'chasing', type: 'panther' }); } if (chal.trigger === 'FLASH_FLOOD' && !s.activeEvents.includes('Flash Flood')) s.activeEvents.push('Flash Flood'); if (chal.trigger === 'STORM_COLLAPSE' && !s.activeEvents.includes('Storm Collapse')) s.activeEvents.push('Storm Collapse'); if (chal.trigger === 'THICK_FOG' && !s.activeEvents.includes('Thick Fog')) s.activeEvents.push('Thick Fog'); if (chal.trigger === 'STORM_AND_LIONS') { if (!s.activeEvents.includes('Storm Collapse')) s.activeEvents.push('Storm Collapse'); WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 500, y: s.player.y + 300, angle: 0, state: 'chasing', type: 'lion' }); } if (chal.trigger === 'FLOOD_AND_LIONS') { if (!s.activeEvents.includes('Flash Flood')) s.activeEvents.push('Flash Flood'); WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 400, y: s.player.y + 300, angle: 0, state: 'chasing', type: 'lion' }); } if (chal.trigger === 'SPAWN_LION_FAR') WORLD_OBJECTS.lions.push({ id: Math.random(), x: s.player.x + 900, y: s.player.y + 700, angle: 0, state: 'stalking', type: 'panther' }); s.activeChallenge = { ...chal, maxTime: chal.timeLimit, startDay: s.day, startFood: s.food, startX: s.player.x, progress: '' }; s.ai.target = null; s.ai.actionDelay = 0; s.ai.llmTimer = 20; addLog(`CHALLENGE INITIATED: ${chal.name}`, 'warning'); setHudState({ ...s }); }; const manageChallenges = (s) => { if (!s.activeChallenge) return; const c = s.activeChallenge; c.timeLimit -= 1; let success = false; let failed = false; let progressMsg = ''; switch (c.type) { case 'SURVIVE_1_DAY': progressMsg = `Day ${s.day}/${c.startDay + 1} — HP: ${Math.floor(s.health)}`; if (s.day > c.startDay && s.health > 20) success = true; break; case 'FIND_FOOD': progressMsg = `Food: ${s.food} (need > ${c.startFood})`; if (s.food > c.startFood) success = true; break; case 'BUILD_BASIC': progressMsg = `Camp level: ${s.ai.baseCamp.level}/1 | Wood: ${s.wood}/5`; if (s.ai.baseCamp.level >= 1) success = true; break; case 'ESCAPE_PRED': { let safe = true; WORLD_OBJECTS.lions.forEach(l => { if (Math.hypot(s.player.x - l.x, s.player.y - l.y) < 500) safe = false; }); progressMsg = safe ? 'Safe distance maintained!' : 'Predator too close!'; if (safe && c.timeLimit < (c.maxTime - 10)) success = true; break; } case 'SURVIVE_2_PRED': progressMsg = `HP: ${Math.floor(s.health)} | Time: ${c.timeLimit}s`; if (c.timeLimit <= 0 && s.isAlive) success = true; break; case 'HUNT_WEAPON': progressMsg = `Weapon: ${s.ai.inventory.spear ? 'Spear✓' : s.ai.inventory.bow ? 'Bow✓' : 'None'} | Food: ${s.food}/${c.startFood + 5}`; if ((s.ai.inventory.spear || s.ai.inventory.bow) && s.food > c.startFood + 5) success = true; break; case 'SURVIVE_FLOOD': progressMsg = `X: ${Math.floor(s.player.x)} (need > ${ZONES.VILLAGE.startX}) | Camp: ${s.ai.baseCamp.level}`; if (s.player.x > ZONES.VILLAGE.startX && s.isAlive && c.timeLimit < (c.maxTime - 5)) success = true; break; case 'SURVIVE_STORM': progressMsg = `HP: ${Math.floor(s.health)} | Shelter: ${s.shelterStatus === 100 ? 'Yes' : 'No'} | Time: ${c.timeLimit}s`; if (c.timeLimit <= 0 && s.isAlive) success = true; break; case 'NAVIGATE_FOG': progressMsg = `Moved: ${Math.floor(Math.abs(s.player.x - c.startX))}px / 600px`; if (Math.abs(s.player.x - c.startX) > 600 && s.isAlive) success = true; break; case 'REACH_RUINS': progressMsg = `X: ${Math.floor(s.player.x)} / ${ZONES.VILLAGE.startX}`; if (s.player.x > ZONES.VILLAGE.startX) success = true; break; case 'IMPROVED_SHELTER': progressMsg = `Camp level: ${s.ai.baseCamp.level}/2 | Wood: ${s.wood} Stone: ${s.stone}`; if (s.ai.baseCamp.level >= 2) success = true; break; case 'CRAFT_BEFORE_PREDATOR': progressMsg = `Weapon: ${s.ai.inventory.spear ? 'Spear✓' : s.ai.inventory.bow ? 'Bow✓' : 'None'} | Time: ${c.timeLimit}s`; if (s.ai.inventory.spear || s.ai.inventory.bow) success = true; break; case 'STORM_AND_HUNTED': progressMsg = `HP: ${Math.floor(s.health)} | Fear: ${Math.floor(s.ai.fear)} | Time: ${c.timeLimit}s`; if (c.timeLimit <= 0 && s.isAlive) success = true; break; case 'LIONS_AND_FLOOD': progressMsg = `X: ${Math.floor(s.player.x)} | Camp: ${s.ai.baseCamp.level} | HP: ${Math.floor(s.health)}`; if ((s.player.x > ZONES.VILLAGE.startX || s.shelterStatus === 100) && s.isAlive && c.timeLimit < (c.maxTime - 5)) success = true; break; case 'SURVIVE_NO_FOOD': progressMsg = `HP: ${Math.floor(s.health)} | Time: ${c.timeLimit}s (don't eat!)`; if (s.food > c.startFood + 3) failed = true; if (c.timeLimit <= 0 && s.isAlive) success = true; break; default: break; } c.progress = progressMsg; if (!s.isAlive) failed = true; if (success) { s.completedChallenges += 1; s.ai.memory.evolutionLevel += 2; s.ai.fear = clamp(s.ai.fear - 40, 0, 100); s.player.speedMult = 1.35; addLog(`✓ CHALLENGE COMPLETE: ${c.name}!`, "success"); s.activeChallenge = null; s.ai.llmTimer = 20; } else if (c.timeLimit <= 0 || failed) { s.ai.fear = clamp(s.ai.fear + 30, 0, 100); s.player.speedMult = 0.85; addLog(`✗ CHALLENGE FAILED: ${c.name}`, "danger"); s.activeChallenge = null; s.ai.llmTimer = 20; } if (s.player.speedMult > 1) s.player.speedMult = Math.max(1, s.player.speedMult - 0.01); if (s.player.speedMult < 1) s.player.speedMult = Math.min(1, s.player.speedMult + 0.01); }; // ─── BACKEND INFERENCE CALL (via /api/infer) ───────────────────────────── const callClaudeAPI = async (s) => { s.ai.llmThinking = true; const isNight = s.time < 6 || s.time > 18; let predatorNear = false; WORLD_OBJECTS.lions.concat(WORLD_OBJECTS.crocodiles).forEach(t => { if (Math.hypot(s.player.x - t.x, s.player.y - t.y) < 400) predatorNear = true; }); const survivalLessons = buildSurvivalLessons(s.ai.memory.pastDeaths); const strategy = getAdaptiveStrategy(s.ai.memory); const genNumber = s.generation; const challengeContext = s.activeChallenge ? `\nACTIVE CHALLENGE: "${s.activeChallenge.name}" (type: ${s.activeChallenge.type}) — ${s.activeChallenge.timeLimit}s remaining.\nChallenge progress: ${s.activeChallenge.progress || 'just started'}.\nPrioritize completing this challenge above all else!` : ''; // Build the prompt in instruction format compatible with most HF instruct models const prompt = `[INST] You are the survival instinct AI (Generation ${genNumber}) of Subject-01. You have died ${s.ai.memory.pastDeaths.length} times. STRATEGY LEVEL: ${strategy.toUpperCase()} ${survivalLessons.length > 0 ? `\nLESSONS FROM PAST DEATHS:\n${survivalLessons.map((l, i) => `${i + 1}. ${l}`).join('\n')}` : '\nNo prior deaths — explore and gather resources.'} ${challengeContext} Current status: HP:${Math.floor(s.health)}, Hunger:${Math.floor(s.hunger)}, Thirst:${Math.floor(s.thirst)}, Fear:${Math.floor(s.ai.fear)}/100. Resources: Wood:${s.wood}, Stone:${s.stone}, Food:${s.food}, Water:${s.water}. Equipped: Spear:${s.ai.inventory.spear}, Bow:${s.ai.inventory.bow}, Rod:${s.ai.inventory.fishingRod}, Boat:${s.ai.inventory.boat}. Camp Level: ${s.ai.baseCamp.level}. Position X: ${Math.floor(s.player.x)}. Environment: ${isNight ? 'Night' : 'Day'}, Events: ${s.activeEvents.join(', ') || 'None'}. Predator Nearby: ${predatorNear ? 'YES - HIGH DANGER' : 'No'}. ${s.activeChallenge ? `ACTIVE CHALLENGE: ${s.activeChallenge.name} (${s.activeChallenge.type}) — ${s.activeChallenge.timeLimit}s left` : 'No active challenge.'} Valid Actions: FORAGE, HUNT, FISH, GET_WATER, SEEK_SHELTER, BUILD_CAMP, UPGRADE_CAMP, CRAFT_SPEAR, CRAFT_BOW, CRAFT_ROD, CRAFT_BOAT, EVACUATE, FIGHT, FLEE, WANDER. Respond ONLY with a raw JSON object — no markdown, no extra text. Example: {"action":"FORAGE","thought":"Need wood and resources"} [/INST]`; try { const response = await fetch('/api/infer', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt }), }); if (!response.ok) { const err = await response.json().catch(() => ({})); throw new Error(err?.error || `HTTP ${response.status}`); } const data = await response.json(); // HF Inference API returns: [{generated_text: "..."}] const rawText = Array.isArray(data) ? (data[0]?.generated_text || '') : (data?.generated_text || ''); // Strip any markdown fences and trim const cleaned = rawText .replace(/```json|```/gi, '') .replace(/^[^{]*/, '') // drop anything before first { .replace(/}[^}]*$/, '}') // drop anything after last } .trim(); const result = JSON.parse(cleaned); // Challenge override still takes priority const challengeOverride = getChallengeAction(s); if (challengeOverride && s.activeChallenge) { s.ai.llmAction = challengeOverride; s.ai.message = result.thought || 'Challenge focus...'; } else { s.ai.llmAction = result.action || 'WANDER'; s.ai.message = result.thought || 'Analyzing...'; } s.ai.consecutiveFailures = 0; } catch (err) { console.error('HuggingFace API error:', err.message); s.ai.consecutiveFailures = (s.ai.consecutiveFailures || 0) + 1; // Smart fallback — identical to original const challengeOverride = getChallengeAction(s); if (challengeOverride && s.activeChallenge) { s.ai.llmAction = challengeOverride; s.ai.message = `Offline. Challenge: ${s.activeChallenge.name.slice(0, 20)}...`; } else { let nearThreat = false; WORLD_OBJECTS.lions.forEach(l => { if (Math.hypot(s.player.x - l.x, s.player.y - l.y) < 400) nearThreat = true; }); if (nearThreat) { s.ai.llmAction = s.ai.inventory.spear || s.ai.inventory.bow ? 'FIGHT' : 'FLEE'; s.ai.message = 'Offline. Reacting to predator.'; } else if (s.hunger < 30) { s.ai.llmAction = s.ai.inventory.spear ? 'HUNT' : 'FORAGE'; s.ai.message = 'Offline. Finding food.'; } else if (s.thirst < 30) { s.ai.llmAction = 'GET_WATER'; s.ai.message = 'Offline. Finding water.'; } else { s.ai.llmAction = 'WANDER'; s.ai.message = 'Offline. Patrolling.'; } } } finally { s.ai.llmThinking = false; s.ai.llmTimer = 0; } }; // ───────────────────────────────────────────────────────────────────────── const executeLLMLogic = (s) => { if (s.ai.actionDelay > 0) { s.ai.actionDelay -= 1; return; } const isNight = s.time < 6 || s.time > 18; const visionRadius = s.activeEvents.includes('Thick Fog') ? 250 : Infinity; const isFlooded = s.activeEvents.includes('Flash Flood'); const waterLevel = isFlooded ? ZONES.JUNGLE.startX + 600 : ZONES.OCEAN.baseEndX; if (isFlooded && s.dynamicWaterLevel < ZONES.OCEAN_GULF.endX) s.dynamicWaterLevel += 2; else if (!isFlooded && s.dynamicWaterLevel > ZONES.OCEAN.baseEndX) s.dynamicWaterLevel -= 1; let fearIncrease = 0; if (isNight) fearIncrease += 0.5; if (isFlooded && s.player.x < s.dynamicWaterLevel) fearIncrease += 3; if (s.health < 40) fearIncrease += 3; let nearThreat = false; let closestThreat = null; let minThreatD = Infinity; WORLD_OBJECTS.lions.forEach(l => { let d = Math.hypot(s.player.x - l.x, s.player.y - l.y); if (d < 500) { fearIncrease += 5; nearThreat = true; if (d < minThreatD) { minThreatD = d; closestThreat = l; } }}); WORLD_OBJECTS.crocodiles.forEach(c => { let d = Math.hypot(s.player.x - c.x, s.player.y - c.y); if (d < 400) { fearIncrease += 5; nearThreat = true; if (d < minThreatD) { minThreatD = d; closestThreat = c; } }}); s.ai.fear = (!nearThreat && s.health > 50 && !isFlooded && !isNight) ? clamp(s.ai.fear - 2, 0, 100) : clamp(s.ai.fear + fearIncrease, 0, 100); if (s.ai.fear > 90 && Math.random() < 0.15 && !s.ai.panic) { s.ai.panic = true; s.ai.message = "PANIC ATTACK!"; } else if (s.ai.fear < 60) s.ai.panic = false; if (s.activeChallenge && !s.ai.panic) { const override = getChallengeAction(s); if (override) { s.ai.llmAction = override; } } if (s.gameMode === 'evolution') { s.ai.llmTimer += 1; if (s.ai.llmTimer > 5 && !s.ai.llmThinking && !s.ai.target) { callClaudeAPI(s); } } if (s.ai.panic || (isFlooded && s.player.x < waterLevel && !s.ai.inventory.boat)) { s.ai.state = 'FLEEING'; s.ai.sneaking = false; s.ai.target = { x: ZONES.VILLAGE.startX + 400, y: s.player.y + (Math.random() * 400 - 200), type: 'evasion' }; return; } s.ai.sneaking = nearThreat && s.ai.state !== 'FLEEING' && !s.ai.panic; if (s.ai.target && ['combat', 'evasion', 'shelter', 'fish', 'upgrade_base'].includes(s.ai.target.type)) return; const action = s.ai.llmAction; s.ai.state = action; switch (action) { case 'FORAGE': { let fClosest = null; let fMinD = Infinity; WORLD_OBJECTS.trees.forEach(t => { if (t.wood > 0) { let d = Math.hypot(s.player.x - t.x, s.player.y - t.y); if (d < fMinD && d < visionRadius) { fMinD = d; fClosest = { x: t.x, y: t.y, type: 'tree', id: t.id }; } }}); WORLD_OBJECTS.rocks.forEach(r => { if (r.stone > 0) { let d = Math.hypot(s.player.x - r.x, s.player.y - r.y); if (d < fMinD && d < visionRadius) { fMinD = d; fClosest = { x: r.x, y: r.y, type: 'stone', id: r.id }; } }}); if (fClosest) s.ai.target = fClosest; break; } case 'HUNT': { let hClosest = null; let hMinD = Infinity; WORLD_OBJECTS.animals.forEach(a => { let d = Math.hypot(s.player.x - a.x, s.player.y - a.y); if (d < hMinD && d < visionRadius) { hMinD = d; hClosest = { x: a.x, y: a.y, type: 'animal', id: a.id }; }}); if (hClosest) s.ai.target = hClosest; break; } case 'FISH': if (s.ai.inventory.fishingRod) { let waterTargetX = s.player.x >= ZONES.SAFE_HAVEN.startX ? ZONES.SAFE_HAVEN.startX - 40 : s.dynamicWaterLevel - 40; s.ai.target = { x: waterTargetX, y: s.player.y + (Math.random() * 200 - 100), type: 'fish' }; } break; case 'GET_WATER': { let wTargetX = s.player.x >= ZONES.SAFE_HAVEN.startX ? ZONES.SAFE_HAVEN.startX - 30 : s.dynamicWaterLevel - 30; s.ai.target = { x: wTargetX, y: s.player.y, type: 'water' }; break; } case 'SEEK_SHELTER': if (s.ai.baseCamp.level > 0) { s.ai.target = { x: s.ai.baseCamp.x, y: s.ai.baseCamp.y, type: 'shelter' }; } else { let rClosest = null; let rMinD = Infinity; WORLD_OBJECTS.buildings.forEach(b => { let cx = b.x + b.w / 2; let cy = b.y + b.h / 2; let d = Math.hypot(s.player.x - cx, s.player.y - cy); if (d < rMinD && d < visionRadius) { rMinD = d; rClosest = { x: cx, y: cy, type: 'shelter' }; }}); if (rClosest) s.ai.target = rClosest; } break; case 'BUILD_CAMP': if (s.wood >= 5 && s.ai.baseCamp.level === 0) { s.ai.baseCamp = { x: s.player.x, y: s.player.y, level: 1 }; s.wood -= 5; s.ai.actionDelay = 4; WORLD_OBJECTS.baseCamp = s.ai.baseCamp; addLog("Established a Base Camp.", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } else if (s.wood < 5) { s.ai.llmAction = 'FORAGE'; } break; case 'UPGRADE_CAMP': if (s.ai.baseCamp.level === 1 && s.wood >= 10 && s.stone >= 5) { s.ai.target = { x: s.ai.baseCamp.x, y: s.ai.baseCamp.y, type: 'upgrade_base' }; } else if (s.ai.baseCamp.level === 2 && s.wood >= 25 && s.stone >= 15) { s.ai.target = { x: s.ai.baseCamp.x, y: s.ai.baseCamp.y, type: 'upgrade_base' }; } else { s.ai.llmAction = 'FORAGE'; } break; case 'CRAFT_SPEAR': if (s.wood >= 3 && s.stone >= 2) { s.wood -= 3; s.stone -= 2; s.ai.inventory.spear = true; s.ai.actionDelay = 3; addLog("Crafted Spear.", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } else { s.ai.llmAction = 'FORAGE'; } break; case 'CRAFT_BOW': if (s.wood >= 5 && s.stone >= 1) { s.wood -= 5; s.stone -= 1; s.ai.inventory.bow = true; s.ai.actionDelay = 3; addLog("Crafted Bow.", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } else { s.ai.llmAction = 'FORAGE'; } break; case 'CRAFT_ROD': if (s.wood >= 2) { s.wood -= 2; s.ai.inventory.fishingRod = true; s.ai.actionDelay = 2; addLog("Crafted Fishing Rod.", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } else { s.ai.llmAction = 'FORAGE'; } break; case 'CRAFT_BOAT': if (s.wood >= 10) { s.wood -= 10; s.ai.inventory.boat = true; s.ai.actionDelay = 5; addLog("Crafted Boat.", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } else { s.ai.llmAction = 'FORAGE'; } break; case 'EVACUATE': s.ai.target = { x: Math.min(s.player.x + 800, ZONES.SAFE_HAVEN.startX + 200), y: clamp(s.player.y + (Math.random() * 200 - 100), 100, WORLD_HEIGHT - 100), type: 'evasion' }; break; case 'FIGHT': if (closestThreat) s.ai.target = { x: closestThreat.x, y: closestThreat.y, type: 'combat', ref: closestThreat }; else if (nearThreat) s.ai.llmAction = 'FLEE'; break; case 'FLEE': if (closestThreat) s.ai.target = { x: clamp(s.player.x + (s.player.x - closestThreat.x) * 2, 50, WORLD_WIDTH - 50), y: clamp(s.player.y + (s.player.y - closestThreat.y) * 2, 50, WORLD_HEIGHT - 50), type: 'evasion' }; break; case 'WANDER': default: if (!s.ai.target || Math.random() < 0.1) s.ai.target = { x: clamp(s.player.x + (Math.random() * 800 - 400), 500, WORLD_WIDTH - 100), y: clamp(s.player.y + (Math.random() * 800 - 400), 100, WORLD_HEIGHT - 100), type: 'wander' }; break; } if (s.ai.target && s.ai.target.type === 'upgrade_base') { if (Math.hypot(s.player.x - s.ai.target.x, s.player.y - s.ai.target.y) < 30) { if (s.ai.baseCamp.level === 1) { s.wood -= 10; s.stone -= 5; s.ai.baseCamp.level = 2; addLog("Shelter Upgraded (Level 2).", "success"); } else if (s.ai.baseCamp.level === 2) { s.wood -= 25; s.stone -= 15; s.ai.baseCamp.level = 3; addLog("Shelter Upgraded to Lake Deck (Level 3).", "success"); } WORLD_OBJECTS.baseCamp = s.ai.baseCamp; s.ai.actionDelay = 5; s.ai.target = null; s.ai.llmAction = 'WANDER'; } } }; useEffect(() => { if (!gameRef.current.started || !gameRef.current.isAlive) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); let animationId; let frameCount = 0; const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; window.addEventListener('resize', resize); resize(); const render = () => { const s = gameRef.current; if (!s.isAlive) return; frameCount++; const isNearDeath = s.health < 25; const timeScale = isNearDeath ? 0.4 : 1.0; const isFlooded = s.activeEvents.includes('Flash Flood'); const isSailing = s.ai.state === 'SAILING' || (s.ai.inventory.boat && s.player.x < s.dynamicWaterLevel); const isFishing = s.ai.target && s.ai.target.type === 'fish' && Math.hypot(s.player.x - s.ai.target.x, s.player.y - s.ai.target.y) < 15; WORLD_OBJECTS.animals.forEach(anim => { if (Math.random() < 0.02 * timeScale) { anim.angle = Math.random() * Math.PI * 2; anim.vx = Math.cos(anim.angle); anim.vy = Math.sin(anim.angle); } anim.x = clamp(anim.x + anim.vx * timeScale, ZONES.JUNGLE.startX, WORLD_WIDTH - 100); anim.y = clamp(anim.y + anim.vy * timeScale, 100, WORLD_HEIGHT - 100); }); WORLD_OBJECTS.lions.forEach(lion => { let distToPlayer = Math.hypot(s.player.x - lion.x, s.player.y - lion.y); let inVisionCone = distToPlayer < 100 || (distToPlayer < 400 && !s.ai.sneaking && !isSailing); if (inVisionCone && s.isAlive) lion.state = distToPlayer > 200 ? 'stalking' : 'chasing'; else if (distToPlayer > 600) lion.state = 'idle'; if (lion.state === 'chasing' || lion.state === 'stalking') { lion.angle = Math.atan2(s.player.y - lion.y, s.player.x - lion.x); let speed = lion.state === 'chasing' ? (lion.type === 'panther' ? 9.5 : 8.5) : 3; lion.x += Math.cos(lion.angle) * speed * timeScale; lion.y += Math.sin(lion.angle) * speed * timeScale; if (distToPlayer < 40 && s.isAlive && !isSailing) { if (s.ai.state === 'FIGHT') { WORLD_OBJECTS.lions = WORLD_OBJECTS.lions.filter(l => l !== lion); s.food += 20; s.ai.actionDelay = 5; addLog(`Defeated ${lion.type}!`, "success"); } else { s.health -= 40; s.ai.fear = 100; s.ai.panic = true; lion.x -= Math.cos(lion.angle) * 100; addLog(`Mauled by ${lion.type}!`, "danger"); } } } else { if (Math.random() < 0.01 * timeScale) lion.angle = Math.random() * Math.PI * 2; lion.x += Math.cos(lion.angle) * timeScale; lion.y += Math.sin(lion.angle) * timeScale; } lion.x = clamp(lion.x, 100, WORLD_WIDTH - 100); lion.y = clamp(lion.y, 100, WORLD_HEIGHT - 100); }); WORLD_OBJECTS.crocodiles.forEach(croc => { let distToPlayer = Math.hypot(s.player.x - croc.x, s.player.y - croc.y); if (distToPlayer < 300 && s.player.x < s.dynamicWaterLevel && !isSailing) { croc.state = 'attacking'; croc.angle = Math.atan2(s.player.y - croc.y, s.player.x - croc.x); croc.x += Math.cos(croc.angle) * 7 * timeScale; croc.y += Math.sin(croc.angle) * 7 * timeScale; if (distToPlayer < 40 && s.isAlive) { s.health -= 25; s.ai.fear += 40; croc.x -= Math.cos(croc.angle) * 150; addLog("Crocodile ambush!", "danger"); } } else { croc.state = 'hidden'; if (Math.random() < 0.01 * timeScale) croc.angle = Math.random() * Math.PI * 2; croc.x += Math.cos(croc.angle) * 0.5 * timeScale; croc.y += Math.sin(croc.angle) * 0.5 * timeScale; } croc.x = clamp(croc.x, 0, s.dynamicWaterLevel - 50); croc.y = clamp(croc.y, 100, WORLD_HEIGHT - 100); }); let actualSpeed = s.player.baseSpeed * s.player.speedMult * timeScale; if (s.player.x < s.dynamicWaterLevel && !isSailing) actualSpeed *= 0.35; if (s.ai.sneaking) actualSpeed *= 0.4; if (s.ai.panic) actualSpeed *= 1.3; if (isSailing) actualSpeed = 9 * timeScale; if (s.ai.target && s.ai.actionDelay <= 0 && s.stamina > 5 && !isFishing) { let dx = s.ai.target.x - s.player.x; let dy = s.ai.target.y - s.player.y; let dist = Math.hypot(dx, dy); let reachDist = ['animal', 'combat'].includes(s.ai.target.type) ? (s.ai.inventory.bow ? 120 : 50) : 20; if (dist > reachDist) { s.player.vx = (dx / dist) * actualSpeed; s.player.vy = (dy / dist) * actualSpeed; s.player.angle = Math.atan2(dy, dx); s.stamina -= (actualSpeed < 3 ? 0.08 : 0.02) * timeScale; } else { s.player.vx = 0; s.player.vy = 0; if (s.ai.target.type === 'water') { s.water += 3; s.ai.actionDelay = 2; s.ai.target = null; } else if (s.ai.target.type === 'fish') { s.ai.actionDelay = 6; s.ai.message = "Casting line..."; } else if (s.ai.target.type === 'tree') { let t = WORLD_OBJECTS.trees.find(x => x.id === s.ai.target.id); if (t) { s.wood += t.wood; t.wood = 0; s.ai.actionDelay = 2; } s.ai.target = null; } else if (s.ai.target.type === 'stone') { let r = WORLD_OBJECTS.rocks.find(x => x.id === s.ai.target.id); if (r) { s.stone += r.stone; r.stone = 0; s.ai.actionDelay = 2; } s.ai.target = null; } else if (s.ai.target.type === 'animal') { let animIdx = WORLD_OBJECTS.animals.findIndex(a => a.id === s.ai.target.id); if (animIdx !== -1) { s.food += s.ai.inventory.spear || s.ai.inventory.bow ? WORLD_OBJECTS.animals[animIdx].meat : 3; WORLD_OBJECTS.animals.splice(animIdx, 1); s.ai.actionDelay = 2; if (s.ai.inventory.bow) addLog("Shot animal with Bow.", 'success'); else if (s.ai.inventory.spear) addLog("Hunted animal with Spear.", 'success'); else addLog("Hunted animal unarmed.", 'info'); } s.ai.target = null; } else if (s.ai.target.type === 'secret') { let b = s.ai.target.ref; if (b && b.hasSecret) { b.hasSecret = false; s.food += 5; s.stone += 5; s.ai.memory.evolutionLevel += 1; addLog("Found ancient secrets!", 'success'); } s.ai.target = null; } else if (!['combat', 'shelter'].includes(s.ai.target.type)) { s.ai.target = null; } } } else { s.player.vx = 0; s.player.vy = 0; } if (isFishing && s.ai.actionDelay <= 0) { s.food += 15; addLog("Caught a massive fish!", "success"); s.ai.target = null; s.ai.llmAction = 'WANDER'; } s.player.x = clamp(s.player.x + s.player.vx, 20, WORLD_WIDTH - 20); s.player.y = clamp(s.player.y + s.player.vy, 20, WORLD_HEIGHT - 20); if (isNearDeath) s.camera.zoom += (1.4 - s.camera.zoom) * 0.05; else s.camera.zoom += (1.1 - s.camera.zoom) * 0.05; s.camera.shake = s.ai.panic || s.ai.state === 'FIGHT' ? (Math.random() - 0.5) * 15 : 0; s.camera.x += (s.player.x - (canvas.width / 2) / s.camera.zoom - s.camera.x) * 0.1; s.camera.y += (s.player.y - (canvas.height / 2) / s.camera.zoom - s.camera.y) * 0.1; s.shelterStatus = 0; for (const b of WORLD_OBJECTS.buildings) { if (s.player.x > b.x && s.player.x < b.x + b.w && s.player.y > b.y && s.player.y < b.y + b.h) { s.shelterStatus = 100; break; } } if (s.ai.baseCamp.level > 0 && Math.hypot(s.player.x - s.ai.baseCamp.x, s.player.y - s.ai.baseCamp.y) < 80) s.shelterStatus = 100; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.scale(s.camera.zoom, s.camera.zoom); ctx.translate(-s.camera.x + s.camera.shake, -s.camera.y + s.camera.shake); const oceanGrad = ctx.createLinearGradient(0, 0, s.dynamicWaterLevel, 0); oceanGrad.addColorStop(0, ZONES.OCEAN.color1); oceanGrad.addColorStop(1, ZONES.OCEAN.color2); ctx.fillStyle = oceanGrad; ctx.fillRect(0, 0, s.dynamicWaterLevel, WORLD_HEIGHT); ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; ctx.lineWidth = 4; for (let w = 0; w < 3; w++) { ctx.beginPath(); for (let y = 0; y < WORLD_HEIGHT; y += 30) { let waveX = s.dynamicWaterLevel - 15 - (w * 25) + Math.sin(frameCount * 0.04 + y * 0.03 + w) * 12; if (y === 0) ctx.moveTo(waveX, y); else ctx.quadraticCurveTo(waveX + 20, y - 15, waveX, y); } ctx.stroke(); } WORLD_OBJECTS.lilyPads.forEach(lp => { if (lp.x < s.dynamicWaterLevel - 30) { ctx.save(); ctx.translate(lp.x, lp.y + Math.sin(frameCount * 0.02 + lp.id) * 2); ctx.rotate(lp.angle); ctx.fillStyle = '#4caf50'; ctx.beginPath(); ctx.arc(0, 0, lp.size, 0.3, Math.PI * 2 - 0.3); ctx.fill(); ctx.fillStyle = '#ff9999'; ctx.beginPath(); ctx.arc(0, 0, lp.size * 0.3, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } }); if (s.dynamicWaterLevel < ZONES.ISLAND.endX) { ctx.fillStyle = ZONES.ISLAND.color; ctx.fillRect(s.dynamicWaterLevel, 0, ZONES.ISLAND.endX - s.dynamicWaterLevel, WORLD_HEIGHT); } if (s.dynamicWaterLevel < ZONES.JUNGLE.endX) { ctx.fillStyle = ZONES.JUNGLE.color; ctx.fillRect(Math.max(s.dynamicWaterLevel, ZONES.JUNGLE.startX), 0, ZONES.JUNGLE.endX - Math.max(s.dynamicWaterLevel, ZONES.JUNGLE.startX), WORLD_HEIGHT); } ctx.fillStyle = ZONES.VILLAGE.color; ctx.fillRect(ZONES.VILLAGE.startX, 0, ZONES.VILLAGE.endX - ZONES.VILLAGE.startX, WORLD_HEIGHT); const gulfGrad = ctx.createLinearGradient(ZONES.OCEAN_GULF.startX, 0, ZONES.OCEAN_GULF.endX, 0); gulfGrad.addColorStop(0, ZONES.OCEAN_GULF.color1); gulfGrad.addColorStop(1, ZONES.OCEAN_GULF.color2); ctx.fillStyle = gulfGrad; ctx.fillRect(ZONES.OCEAN_GULF.startX, 0, ZONES.OCEAN_GULF.endX - ZONES.OCEAN_GULF.startX, WORLD_HEIGHT); ctx.fillStyle = ZONES.SAFE_HAVEN.color; ctx.fillRect(ZONES.SAFE_HAVEN.startX, 0, ZONES.SAFE_HAVEN.endX - ZONES.SAFE_HAVEN.startX, WORLD_HEIGHT); if (s.activeEvents.includes('Flash Flood')) { ctx.fillStyle = 'rgba(100, 180, 220, 0.2)'; ctx.fillRect(s.dynamicWaterLevel, 0, WORLD_WIDTH - s.dynamicWaterLevel, WORLD_HEIGHT); } const shX = 18; const shY = 18; WORLD_OBJECTS.trees.forEach(t => { ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.beginPath(); ctx.ellipse(t.x + shX, t.y + shY, t.radius * 1.2, t.radius * 0.8, 0, 0, Math.PI * 2); ctx.fill(); }); WORLD_OBJECTS.buildings.forEach(b => { ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.fillRect(b.x + shX, b.y + shY, b.w, b.h); }); WORLD_OBJECTS.rocks.forEach(r => { ctx.fillStyle = '#8f979c'; ctx.beginPath(); ctx.ellipse(r.x, r.y, r.size, r.size * 0.8, 0, 0, Math.PI * 2); ctx.fill(); if (r.stone > 0) { ctx.fillStyle = '#bac1c4'; ctx.beginPath(); ctx.ellipse(r.x - 3, r.y - 3, r.size * 0.6, r.size * 0.4, 0, 0, Math.PI * 2); ctx.fill(); } }); if (WORLD_OBJECTS.baseCamp && WORLD_OBJECTS.baseCamp.level > 0) { const bc = WORLD_OBJECTS.baseCamp; ctx.save(); ctx.translate(bc.x, bc.y); if (bc.level === 1) { ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(-20, -10, 50, 40); ctx.fillStyle = '#5c8a4c'; ctx.beginPath(); ctx.moveTo(-30, 20); ctx.lineTo(0, -30); ctx.lineTo(30, 20); ctx.fill(); ctx.fillStyle = '#3a5430'; ctx.beginPath(); ctx.moveTo(0, -30); ctx.lineTo(30, 20); ctx.lineTo(0, 20); ctx.fill(); ctx.strokeStyle = '#4a3320'; ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(0, -30); ctx.lineTo(0, 20); ctx.stroke(); } else if (bc.level === 2) { ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(-35, -25, 80, 80); ctx.fillStyle = '#8f5c38'; ctx.fillRect(-40, -40, 80, 80); ctx.fillStyle = '#664228'; ctx.fillRect(-40, 30, 80, 10); ctx.fillStyle = '#a6754b'; ctx.fillRect(-45, -45, 90, 45); ctx.fillStyle = '#825a38'; ctx.fillRect(-45, 0, 90, 35); } else if (bc.level === 3) { ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(-55, -20, 130, 110); ctx.fillStyle = '#3d2616'; ctx.fillRect(-55, 60, 10, 30); ctx.fillRect(55, 60, 10, 30); ctx.fillRect(55, -40, 10, 30); ctx.fillStyle = '#c78f5a'; ctx.fillRect(-60, -50, 130, 110); ctx.strokeStyle = '#a37143'; ctx.lineWidth = 2; for (let p = -50; p < 70; p += 15) { ctx.beginPath(); ctx.moveTo(p, -50); ctx.lineTo(p, 60); ctx.stroke(); } ctx.fillStyle = '#8f6138'; ctx.fillRect(-60, 60, 130, 15); ctx.fillRect(70, -50, 15, 110); ctx.fillStyle = '#7a4e2a'; ctx.fillRect(-50, -40, 75, 65); ctx.fillStyle = '#a66d3a'; ctx.fillRect(-55, -45, 85, 45); ctx.fillStyle = '#4a2f18'; ctx.fillRect(-25, -10, 25, 35); ctx.fillStyle = '#c49a6c'; ctx.beginPath(); ctx.arc(-5, 10, 3, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#b37c49'; ctx.fillRect(-25, 75, 45, 12); ctx.fillRect(-25, 92, 45, 12); } ctx.restore(); } WORLD_OBJECTS.crocodiles.forEach(croc => { ctx.save(); ctx.translate(croc.x, croc.y); ctx.rotate(croc.angle); if (croc.state === 'hidden') { ctx.fillStyle = '#1e3323'; ctx.beginPath(); ctx.ellipse(12, -6, 4, 3, 0, 0, Math.PI * 2); ctx.ellipse(12, 6, 4, 3, 0, 0, Math.PI * 2); ctx.fill(); } else { ctx.fillStyle = '#36593e'; ctx.beginPath(); ctx.ellipse(-10, 0, 35, 14, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#213826'; ctx.beginPath(); ctx.ellipse(-30, 0, 20, 6, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#1a2e20'; ctx.beginPath(); ctx.moveTo(15, -10); ctx.lineTo(40, -14); ctx.lineTo(25, 0); ctx.fill(); ctx.beginPath(); ctx.moveTo(15, 10); ctx.lineTo(40, 14); ctx.lineTo(25, 0); ctx.fill(); } ctx.restore(); }); WORLD_OBJECTS.buildings.forEach(b => { ctx.fillStyle = '#c49566'; ctx.fillRect(b.x, b.y, b.w, b.h); ctx.fillStyle = '#8f6541'; ctx.fillRect(b.x, b.y, b.w, 15); ctx.fillRect(b.x, b.y, 15, b.h); if (b.hasSecret) { ctx.fillStyle = '#ffea00'; ctx.fillRect(b.x + b.w / 2 - 12, b.y + b.h / 2 - 12, 24, 24); } if (!(s.player.x > b.x && s.player.x < b.x + b.w && s.player.y > b.y && s.player.y < b.y + b.h)) { ctx.fillStyle = '#a6774e'; ctx.fillRect(b.x - 5, b.y - 5, b.w + 10, b.h + 10); } }); WORLD_OBJECTS.animals.forEach(anim => { ctx.save(); ctx.translate(anim.x, anim.y); ctx.rotate(anim.angle); if (anim.type === 'hippo') { ctx.fillStyle = '#6a707a'; ctx.beginPath(); ctx.ellipse(0, 0, 25, 18, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#555a63'; ctx.beginPath(); ctx.ellipse(20, 0, 16, 14, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#ffcccc'; ctx.beginPath(); ctx.arc(32, -6, 2, 0, Math.PI * 2); ctx.arc(32, 6, 2, 0, Math.PI * 2); ctx.fill(); } else { ctx.fillStyle = '#6b4c3a'; ctx.beginPath(); ctx.ellipse(0, 0, 16, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#e8dcc5'; ctx.beginPath(); ctx.moveTo(16, -4); ctx.lineTo(20, -6); ctx.lineTo(16, -2); ctx.fill(); ctx.beginPath(); ctx.moveTo(16, 4); ctx.lineTo(20, 6); ctx.lineTo(16, 2); ctx.fill(); } ctx.restore(); }); WORLD_OBJECTS.lions.forEach(lion => { ctx.save(); ctx.translate(lion.x, lion.y); ctx.rotate(lion.angle); let legOffset = lion.state === 'chasing' ? Math.sin(frameCount * 0.6) * 8 : 0; if (lion.type === 'panther') { ctx.fillStyle = '#222'; ctx.beginPath(); ctx.ellipse(-10, -6 + legOffset, 5, 4, 0, 0, Math.PI * 2); ctx.ellipse(10, 6 - legOffset, 5, 4, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#111'; ctx.beginPath(); ctx.ellipse(-2, 0, 20, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(-20, 0, 15, 3, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.ellipse(14, 0, 8, 7, 0, 0, Math.PI * 2); ctx.fill(); } else { ctx.fillStyle = '#b8860b'; ctx.beginPath(); ctx.ellipse(-10, -8 + legOffset, 6, 4, 0, 0, Math.PI * 2); ctx.ellipse(10, 8 - legOffset, 6, 4, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#6e3c15'; ctx.beginPath(); ctx.ellipse(10, 0, 18, 16, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#DAA520'; ctx.beginPath(); ctx.ellipse(-4, 0, 24, 14, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(14, 0, 10, 9, 0, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); }); WORLD_OBJECTS.trees.forEach(t => { ctx.fillStyle = '#523a25'; ctx.fillRect(t.x - 5, t.y, 10, 18); if (t.wood > 0) { if (t.type === 'pine') { ctx.fillStyle = '#315e21'; ctx.beginPath(); ctx.moveTo(t.x, t.y - 20); ctx.lineTo(t.x - t.radius * 1.1, t.y + 15); ctx.lineTo(t.x + t.radius * 1.1, t.y + 15); ctx.fill(); ctx.fillStyle = '#427d2c'; ctx.beginPath(); ctx.moveTo(t.x, t.y - 40); ctx.lineTo(t.x - t.radius * 0.8, t.y - 5); ctx.lineTo(t.x + t.radius * 0.8, t.y - 5); ctx.fill(); ctx.fillStyle = '#56a638'; ctx.beginPath(); ctx.moveTo(t.x, t.y - 60); ctx.lineTo(t.x - t.radius * 0.5, t.y - 25); ctx.lineTo(t.x + t.radius * 0.5, t.y - 25); ctx.fill(); } else { ctx.fillStyle = '#63c43f'; for (let i = 0; i < 5; i++) { ctx.beginPath(); ctx.ellipse(t.x + Math.cos(i) * t.radius * 0.7, t.y - 20 + Math.sin(i) * t.radius * 0.7, t.radius, t.radius * 0.4, i, 0, Math.PI * 2); ctx.fill(); } } } else { ctx.fillStyle = '#e0c294'; ctx.beginPath(); ctx.ellipse(t.x, t.y + 10, t.radius * 0.4, t.radius * 0.25, 0, 0, Math.PI * 2); ctx.fill(); } }); ctx.save(); ctx.translate(s.player.x, s.player.y); ctx.rotate(s.player.angle); if (isSailing) { ctx.fillStyle = '#9e734c'; ctx.fillRect(-30, -25, 60, 50); ctx.strokeStyle = '#785536'; ctx.lineWidth = 3; for (let p = -15; p < 25; p += 15) { ctx.beginPath(); ctx.moveTo(-30, p); ctx.lineTo(30, p); ctx.stroke(); } } let bob = (s.player.vx !== 0 || s.player.vy !== 0) ? Math.sin(frameCount * 0.5) * 3 : 0; ctx.fillStyle = '#614835'; ctx.beginPath(); ctx.ellipse(-8, 0, 6, 12, 0, 0, Math.PI * 2); ctx.fill(); if (s.ai.inventory.bow && !isFishing && s.ai.state !== 'FORAGE') { ctx.strokeStyle = '#5c4033'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(20, 0, 15, -Math.PI / 2.5, Math.PI / 2.5); ctx.stroke(); ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(25, -14); ctx.lineTo(10, 0); ctx.lineTo(25, 14); ctx.stroke(); } else if (s.ai.inventory.spear && !isFishing) { ctx.strokeStyle = '#5c4033'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(0, 16); ctx.lineTo(35, 16); ctx.stroke(); ctx.fillStyle = '#bcc4c7'; ctx.beginPath(); ctx.moveTo(35, 13); ctx.lineTo(48, 16); ctx.lineTo(35, 19); ctx.fill(); } ctx.fillStyle = '#2980b9'; ctx.beginPath(); ctx.ellipse(0, 0, 14, 18, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#f1c27d'; ctx.beginPath(); ctx.ellipse(5 + bob * 0.5, 0, 10, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(10, -14 + bob, 5, 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(10, 14 - bob, 5, 5, 0, 0, Math.PI * 2); ctx.fill(); if (isFishing) { ctx.strokeStyle = '#8f6640'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(10, 14); ctx.lineTo(45, 35); ctx.stroke(); ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(45, 35); ctx.lineTo(100, 0); ctx.stroke(); ctx.fillStyle = '#ff3333'; ctx.beginPath(); ctx.arc(100, 0 + Math.sin(frameCount * 0.1) * 4, 4, 0, Math.PI * 2); ctx.fill(); } if (s.ai.panic) { ctx.fillStyle = '#73c2fb'; ctx.beginPath(); ctx.arc(0, -15, 3, 0, Math.PI * 2); ctx.arc(-5, 15, 2, 0, Math.PI * 2); ctx.fill(); } if (s.ai.sneaking) { ctx.strokeStyle = 'rgba(255,255,255,0.6)'; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.arc(0, 0, 30, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]); } ctx.restore(); ctx.globalCompositeOperation = 'lighter'; WORLD_OBJECTS.fires.forEach((f, index) => { if (f.fuel <= 0) { WORLD_OBJECTS.fires.splice(index, 1); return; } const gradient = ctx.createRadialGradient(f.x, f.y, 10, f.x, f.y, 300 + Math.random() * 30); gradient.addColorStop(0, 'rgba(255, 160, 0, 0.8)'); gradient.addColorStop(1, 'rgba(255, 20, 0, 0)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(f.x, f.y, 350, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#fffb00'; ctx.beginPath(); ctx.arc(f.x, f.y, 8 + Math.random() * 4, 0, Math.PI * 2); ctx.fill(); }); ctx.globalCompositeOperation = 'source-over'; ctx.restore(); const isNight2 = s.time < 6 || s.time > 18; let darkAlpha = isNight2 ? 0.85 : 0; if (s.activeEvents.includes('Storm Collapse')) darkAlpha = Math.max(darkAlpha, 0.65); if (darkAlpha > 0) { ctx.fillStyle = `rgba(8, 12, 25, ${darkAlpha})`; ctx.fillRect(0, 0, canvas.width, canvas.height); } if (s.activeEvents.includes('Heatwave')) { ctx.fillStyle = `rgba(255, 90, 0, 0.18)`; ctx.fillRect(0, 0, canvas.width, canvas.height); } if (s.activeEvents.includes('Night Freeze')) { ctx.fillStyle = `rgba(160, 230, 255, 0.35)`; ctx.fillRect(0, 0, canvas.width, canvas.height); const frostGrad = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, canvas.height * 0.3, canvas.width / 2, canvas.height / 2, canvas.height); frostGrad.addColorStop(0, 'rgba(255,255,255,0)'); frostGrad.addColorStop(1, 'rgba(255,255,255,0.7)'); ctx.fillStyle = frostGrad; ctx.fillRect(0, 0, canvas.width, canvas.height); } if (s.activeEvents.includes('Thick Fog')) { const fogGrad = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 150, canvas.width / 2, canvas.height / 2, 600); fogGrad.addColorStop(0, 'rgba(240, 245, 255, 0)'); fogGrad.addColorStop(1, 'rgba(240, 245, 255, 0.95)'); ctx.fillStyle = fogGrad; ctx.fillRect(0, 0, canvas.width, canvas.height); } if (s.activeEvents.includes('Storm Collapse')) { ctx.strokeStyle = 'rgba(220, 230, 255, 0.8)'; ctx.lineWidth = 2.5; ctx.beginPath(); for (let i = 0; i < 250; i++) { let rx = Math.random() * canvas.width; let ry = (frameCount * 40 + i * 25) % canvas.height; ctx.moveTo(rx, ry); ctx.lineTo(rx - 20, ry + 50); } ctx.stroke(); if (Math.random() < 0.05) { ctx.fillStyle = 'rgba(255, 255, 255, 0.85)'; ctx.fillRect(0, 0, canvas.width, canvas.height); } } if (isNearDeath) { ctx.fillStyle = `rgba(220, 0, 0, ${0.2 + Math.sin(frameCount * 0.15) * 0.2})`; ctx.fillRect(0, 0, canvas.width, canvas.height); const deathGrad = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, canvas.height * 0.3, canvas.width / 2, canvas.height / 2, canvas.height); deathGrad.addColorStop(0, 'rgba(0,0,0,0)'); deathGrad.addColorStop(1, 'rgba(120,0,0,0.9)'); ctx.fillStyle = deathGrad; ctx.fillRect(0, 0, canvas.width, canvas.height); } animationId = requestAnimationFrame(render); }; render(); return () => { cancelAnimationFrame(animationId); window.removeEventListener('resize', resize); }; }, [gameRef.current.started, gameRef.current.isAlive]); useEffect(() => { if (!gameRef.current.started || !gameRef.current.isAlive) return; const interval = setInterval(() => { const s = gameRef.current; if (!s.isAlive) return; if (!s.ai.llmThinking) executeLLMLogic(s); manageChallenges(s); s.time += 0.25; if (s.time >= 24) { s.time = 0; s.day += 1; addLog(`Day ${s.day} begins.`, 'system'); } s.eventTimer -= 1; if (s.eventTimer <= 0 && !s.activeChallenge) { s.activeEvents = []; s.eventTimer = Math.floor(Math.random() * 40) + 60; addLog("Atmosphere stabilizing.", "info"); } else if (s.eventTimer <= 0) { s.eventTimer = 30; } s.hunger = clamp(s.hunger - (s.ai.panic ? 1.0 : 0.5), 0, 100); let thirstDrain = s.activeEvents.includes('Heatwave') ? 2.5 : 0.8; s.thirst = clamp(s.thirst - thirstDrain, 0, 100); let targetTemp = 25; if (s.time < 6 || s.time > 18) targetTemp -= 5; if (s.activeEvents.includes('Storm Collapse')) targetTemp -= 20; if (s.activeEvents.includes('Night Freeze')) targetTemp = -15; if (s.activeEvents.includes('Heatwave')) targetTemp += 25; WORLD_OBJECTS.fires.forEach(f => { if (Math.hypot(s.player.x - f.x, s.player.y - f.y) < 300) { targetTemp += 40; f.fuel -= (s.activeEvents.includes('Storm Collapse') && s.shelterStatus === 0 ? 8 : 1); } }); targetTemp -= (s.wetness / 8); if (s.shelterStatus === 100 && s.activeEvents.length > 0) targetTemp += 20; if (s.temp < targetTemp) s.temp = clamp(s.temp + 1.0, -20, 60); else if (s.temp > targetTemp) s.temp = clamp(s.temp - 1.5, -20, 60); if (s.activeEvents.includes('Flash Flood') && s.player.x < s.dynamicWaterLevel) s.wetness = 100; else if (s.activeEvents.includes('Storm Collapse') && s.shelterStatus === 0) s.wetness = clamp(s.wetness + 15, 0, 100); else s.wetness = clamp(s.wetness - 2, 0, 100); if (s.ai.state === 'IDLE' || s.ai.sneaking) s.stamina = clamp(s.stamina + 2, 0, 100); let healthDrain = 0; if (s.hunger <= 0) healthDrain += 2.0; if (s.thirst <= 0) healthDrain += 3.0; if (s.temp < 10) healthDrain += 3.0; if (s.temp > 45) healthDrain += 3.0; if (healthDrain > 0) { s.health -= healthDrain; } else if (s.hunger > 50 && s.thirst > 50 && s.temp > 15 && s.temp < 35 && s.ai.fear < 50) { s.health = clamp(s.health + 0.5, 0, 100); } if (s.health < 1 && s.isAlive) { s.health = 0; s.isAlive = false; if (s.hunger <= 0) s.causeOfDeath = "Starvation"; else if (s.thirst <= 0) s.causeOfDeath = "Dehydration"; else if (s.temp < 10) s.causeOfDeath = "Hypothermia"; else if (s.temp > 45) s.causeOfDeath = "Heatstroke"; else s.causeOfDeath = "System Failure"; if (s.gameMode === 'evolution') { const deathRecord = `${s.causeOfDeath} on Day ${s.day} (Gen ${s.generation})`; s.ai.memory.pastDeaths.push(deathRecord); if (s.ai.memory.pastDeaths.length > 6) s.ai.memory.pastDeaths.shift(); s.ai.memory.evolutionLevel = clamp(s.ai.memory.evolutionLevel + 1, 1, 20); } addLog("SUBJECT TERMINATED.", "danger"); } setHudState({ ...s, logs: [...s.logs], ai: { ...s.ai } }); }, TICK_RATE); return () => clearInterval(interval); }, [gameRef.current.started, gameRef.current.isAlive]); const formatTime = (t) => `${Math.floor(t).toString().padStart(2, '0')}:${Math.floor((t - Math.floor(t)) * 60).toString().padStart(2, '0')}`; const CircularStat = ({ icon: Icon, value, color, warn }) => { const radius = 20; const circumference = 2 * Math.PI * radius; const strokeDashoffset = circumference - (clamp(value, 0, 100) / 100) * circumference; return (
); }; const HotbarSlot = ({ icon: Icon, count, active, label }) => (
{count !== undefined && {count}} {label && {label}}
); if (!gameRef.current.started) { return (

EVOLUTIONARY AI

Initialize Subject-01 into the high-fidelity 2.5D simulation. Powered by HuggingFace AI with persistent memory across 6 generations.

); } if (!hudState) return null; const survivalLessons = buildSurvivalLessons(hudState.ai.memory.pastDeaths); return (
{!hudState.isAlive && (

SIMULATION ENDED

Cause of Death{hudState.causeOfDeath}
Days Survived{hudState.day}
Challenges Completed{hudState.completedChallenges}
GenerationGEN-{hudState.generation}
{hudState.ai.memory.pastDeaths.length > 0 && (

Death Memory ({hudState.ai.memory.pastDeaths.length}/6)

{hudState.ai.memory.pastDeaths.map((d, i) => (
#{i + 1} {d}
))}
)}
{hudState.gameMode === 'evolution' ? ( ) : ( )}
)} {hudState.isAlive && ( <>
0 ? 'text-yellow-400' : 'text-blue-400'}`} size={24}/>
Subject-01 Gen-{hudState.generation} {hudState.ai.llmThinking ? '(Thinking...)' : hudState.ai.consecutiveFailures > 0 ? '(Offline)' : ''} {hudState.ai.panic ? 'FULL PANIC' : hudState.ai.sneaking ? 'SNEAKING' : hudState.ai.state.replace('_', ' ')}
"{hudState.ai.message}"
{hudState.gameMode === 'evolution' && hudState.ai.memory.pastDeaths.length > 0 && ( )} {showMemoryLog && hudState.ai.memory.pastDeaths.length > 0 && (

Neural Memory Log

{hudState.ai.memory.pastDeaths.map((d, i) => (
#{i + 1} {d}
))} {survivalLessons.length > 0 && (

Behavior Adaptations

{survivalLessons.slice(0, 3).map((l, i) => (
{l.slice(0, 80)}...
))}
)}
)}
Generation GEN-{hudState.generation}
Day {hudState.day} {hudState.time < 6 || hudState.time > 18 ? : } {formatTime(hudState.time)}
Evo Level {hudState.ai.memory.evolutionLevel}
Challenge Master
{CHALLENGE_DB.map((cat, idx) => (
{cat.category} {cat.tasks.map((task, tIdx) => ( ))}
))}
God Controls
{['Flash Flood', 'Storm Collapse', 'Thick Fog', 'Heatwave', 'Night Freeze'].map(ev => ( ))}
80} />
0} /> 0} /> 0} />
{hudState.activeChallenge && (
Active Challenge
{hudState.activeChallenge.name}
{hudState.activeChallenge.progress && (
{hudState.activeChallenge.progress}
)}
)} )} {hudState.logs.length > 0 && hudState.logs[0].time && (Date.now() - hudState.logs[0].time < 4000) && (
{hudState.logs[0].message}
)}
); }