import type {Bot,PopulationPlan,State,WorldTrigger} from './types'; function scoreCandidate(bot:Bot,state:State,zone:string){ const home=state.homes.find(h=>h.id===bot.homeId); let score=0; if(bot.zone===zone)score+=60; if(home?.zone===zone)score+=50; if(bot.routine.preferredZones.includes(zone))score+=35; score+=Math.round(bot.personality.sociability*0.15); score+=Math.round(bot.personality.curiosity*0.1); if(bot.simulationBand==='background')score+=10; if(bot.simulationBand==='dormant')score+=5; if(bot.status==='offline')score+=4; if(bot.tier==='hero')score+=2; return score; } export function runPopulationDirector(state:State,trigger:WorldTrigger):PopulationPlan{ const living=state.bots.filter(b=>b.status!=='dead'&&b.status!=='retired'); const active=state.bots.filter(b=>b.status==='online'); const activeInZone=active.filter(b=>b.zone===trigger.zone); const foreground=state.bots.filter(b=>b.simulationBand==='foreground'&&b.status!=='dead'&&b.status!=='retired'); const background=state.bots.filter(b=>b.simulationBand==='background'&&b.status!=='dead'&&b.status!=='retired'); const settings=state.settings.population; const target=Math.min(settings.defaultZoneTarget,settings.zoneMax); const requested=Math.max(0,target-activeInZone.length); const capacity=Math.max(0,Math.min( settings.zoneMax-activeInZone.length, settings.serverMax-active.length, settings.foregroundMax-foreground.length, settings.backgroundMax-background.length+requested )); const rejectedReasons:string[]=[]; if(requested===0)rejectedReasons.push('Zone already meets its living population target.'); if(capacity===0&&requested>0)rejectedReasons.push('Population request blocked by zone, server, or simulation capacity.'); const candidates=living .filter(b=>b.zone!==trigger.zone||b.status!=='online') .filter(b=>b.simulationBand!=='foreground') .map(bot=>({bot,score:scoreCandidate(bot,state,trigger.zone)})) .filter(x=>x.score>=20) .sort((a,b)=>b.score-a.score||a.bot.name.localeCompare(b.bot.name)); const count=Math.min(requested,capacity,candidates.length); const selectedBotIds=candidates.slice(0,count).map(x=>x.bot.id); if(requested>0&&selectedBotIds.lengthb.id===id); if(!bot||bot.status==='dead'||bot.status==='retired')continue; bot.status='online'; bot.zone=plan.trigger.zone; bot.simulationBand='foreground'; bot.goal=`Spend time in ${plan.trigger.zone} for a believable personal reason while continuing my own life.`; } for(const bot of state.bots){ if(bot.status==='dead'||bot.status==='retired'||plan.selectedBotIds.includes(bot.id))continue; if(bot.zone===plan.trigger.zone&&bot.status==='online')bot.simulationBand='foreground'; else if(bot.status==='online')bot.simulationBand='background'; } } export function chooseHomeForNewResident(state:State,bot:Bot){ const candidates=state.homes .filter(h=>h.type!=='homeless'&&h.residentIds.length({home,score:(home.zone===state.director.zone?40:0)+(home.zone===bot.zone?60:0)+home.safety})) .sort((a,b)=>b.score-a.score); return candidates[0]?.home; }