Files
Richard Clark 5f0fc86cf9
Core tests / core (push) Successful in 9m57s
Full verification / application (push) Failing after 5m16s
Full verification / local-brain (push) Failing after 6m15s
Realm simulation / simulated-live-world (push) Failing after 5m32s
WoW integration lab / real-worldserver (push) Canceled after 17m23s
Import verified AzerothAI snapshot from GitHub
Source branch: feature/ai-brain-router
Source commit: c2a09e0460465201a58aaab56c71424e9a96405e
2026-09-22 13:12:54 -04:00

85 lines
3.5 KiB
TypeScript

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.length<Math.min(requested,capacity))rejectedReasons.push('Not enough believable existing residents were available; no synthetic identities were injected.');
return {
id:crypto.randomUUID(),
trigger,
target,
activeBefore:activeInZone.length,
requested,
capacity,
selectedBotIds,
rejectedReasons,
createdAt:new Date().toISOString()
};
}
export function applySimulationPopulationPlan(state:State,plan:PopulationPlan){
for(const id of plan.selectedBotIds){
const bot=state.bots.find(b=>b.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<h.capacity)
.map(home=>({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;
}