Add nightly realm reports and observed bot activity explanations
Core tests / core (push) Successful in 59s
Core tests / core (push) Successful in 59s
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import type {Bot,State} from './types';
|
||||
export const DAY=86400,BOUNDARY=6*3600;
|
||||
export const reportWindow=(at:number)=>Math.floor((at-BOUNDARY)/DAY)*DAY+BOUNDARY;
|
||||
export type Evidence={id:string;at:number;kind:string;name:string;amount:number;detail:string};
|
||||
export type Progress={level:number;status:string;seenOnline:boolean;x?:number;y?:number;map?:number;health?:number;lastProgressAt?:number};
|
||||
export type Cursor={at:number;startedAt:number;bots:Record<string,Progress>;bonds:string[];guilds:Record<string,{goal:string;progress:number}>;social:string[]};
|
||||
export function observeInsights(s:State,previous:Cursor|undefined,now:number){
|
||||
const events:Evidence[]=[];const cursor:Cursor={at:now,startedAt:previous?.startedAt??now,bots:{},bonds:[],guilds:{},social:[]};
|
||||
const add=(id:string,kind:string,name:string,amount:number,detail:string,at=now)=>events.push({id,at,kind,name,amount,detail});
|
||||
const managed=s.bots.filter(b=>b.autonomy?.managed),ids=new Set(managed.map(b=>b.id));
|
||||
for(const b of managed){
|
||||
const old=previous?.bots[b.id],w=b.world;
|
||||
const fresh=!!w&&now-w.at<120;
|
||||
const moved=fresh&&old?.x!==undefined&&(old.map!==w!.mapId||Math.hypot(w!.x-old.x,w!.y-(old.y??w!.y))>=3);
|
||||
const advanced=!!old&&(b.level>old.level||(fresh&&old.health!==undefined&&w!.healthPct!==undefined&&w!.healthPct!==old.health));
|
||||
cursor.bots[b.id]={level:b.level,status:b.status,seenOnline:!!old?.seenOnline||b.status==='online',x:fresh?w!.x:old?.x,y:fresh?w!.y:old?.y,map:fresh?w!.mapId:old?.map,health:fresh?w!.healthPct:old?.health,lastProgressAt:moved||advanced?now:old?.lastProgressAt};
|
||||
if(previous){
|
||||
if(old&&b.level>old.level)add(`level:${b.id}:${b.level}`,'levels',b.name,b.level-old.level,`Level ${old.level} → ${b.level}`);
|
||||
if(b.status==='dead'&&old?.status!=='dead'&&b.death?.verified)add(`death:${b.id}`,'deaths',b.name,1,`Level ${b.death.level} · ${b.death.zone}`);
|
||||
if(b.status==='online'&&!old?.seenOnline)add(`arrival:${b.id}`,'arrivals',b.name,1,`First observed online at level ${b.level}`);
|
||||
}
|
||||
for(const r of b.relationships){if(r.affinity<=0||!ids.has(r.targetId))continue;const key=[b.id,r.targetId].sort().join(':');if(cursor.bonds.includes(key))continue;cursor.bonds.push(key);if(previous&&!previous.bonds.includes(key))add('bond:'+key,'bonds',`${b.name} & ${managed.find(x=>x.id===r.targetId)?.name}`,1,'First observed positive bond');}
|
||||
}
|
||||
for(const g of s.community?.guilds??[]){const old=previous?.guilds[g.id];cursor.guilds[g.id]={goal:g.goal,progress:g.progress};if(old&&old.goal===g.goal&&g.progress>old.progress)add(`guild:${g.id}:${g.goal}:${g.progress}`,'guild',g.name,g.progress-old.progress,`${g.goal} goal: ${g.progress} / ${g.target}`);}
|
||||
for(const e of s.community?.feed??[]){
|
||||
if(e.status==='confirmed')cursor.social.push(e.id);
|
||||
if(!previous||previous.social.includes(e.id)||e.status!=='confirmed'||e.id.endsWith(':proposal'))continue;
|
||||
const at=Date.parse(e.at)/1000;if(!Number.isFinite(at)||at<previous.startedAt||at>now+60)continue;
|
||||
if(e.kind==='conversation'&&e.id.startsWith('social-')&&e.id.endsWith(':reply'))add('social:'+e.id,'conversations',e.names.join(' & '),1,'Completed bot-to-bot exchange',at);
|
||||
if(e.kind==='party'||e.kind==='recruit')add('social:'+e.id,e.kind==='party'?'parties':'recruits',e.names.join(' & '),1,e.kind==='party'?'Confirmed party invitation':'Confirmed guild admission',at);
|
||||
}
|
||||
return {cursor,events};
|
||||
}
|
||||
export function activityReason(b:Bot,now:number){
|
||||
const w=b.world,action=b.lastDecision?.action;
|
||||
if(b.status!=='online')return {label:b.status,detail:'This resident is not currently online.'};
|
||||
if(!w||now-w.at>120)return {label:'Telemetry delayed',detail:'Current activity cannot be confirmed from stale observations.'};
|
||||
if(w.inCombat)return {label:'In combat',detail:'The game reports active combat. Recovery is skipped.'};
|
||||
if(w.casting)return {label:'Casting',detail:'The game reports a spell cast. Recovery is skipped.'};
|
||||
if(w.taxi)return {label:'Traveling by taxi',detail:'The game reports taxi travel. Recovery is skipped.'};
|
||||
if(w.humanControlled)return {label:'Following player control',detail:'A human master controls this bot. Recovery is skipped.'};
|
||||
const idle=now-(w.stationarySince??w.at);
|
||||
if(idle<90)return {label:'Recently moving',detail:`Movement observed recently; intended activity: ${action??'not recorded'}.`};
|
||||
if(action==='rest'||action==='socialize'||action==='trade')return {label:`Intends to ${action}`,detail:'Stationary activity may be intentional. This is an AI intention, not proof of the action completing; recovery is skipped.'};
|
||||
if(idle>=600&&(action==='quest'||action==='explore'))return {label:'Possible stall',detail:w.movementType===7?'Flight controller remains active; native recovery checks whether it is stale.':'No recent movement while intending to travel. The native guard checks progress, safety and cooldown before recovery.'};
|
||||
return {label:w.groupId?'Grouped / stationary':'Briefly stationary',detail:w.groupId?'May be waiting for companions; waiting is inferred, not confirmed.':'May be choosing a target or pausing between actions. No stall is established.'};
|
||||
}
|
||||
export function summarize(events:Evidence[],start:number,end:number,startedAt:number,samples:number){
|
||||
const totals:Record<string,number>={levels:0,deaths:0,arrivals:0,conversations:0,bonds:0,parties:0,recruits:0,guild:0,recoveries:0};
|
||||
for(const e of events)totals[e.kind]=(totals[e.kind]??0)+e.amount;
|
||||
return {start,end,observedSince:Math.max(start,startedAt),partial:startedAt>start,samples,totals,highlights:events.filter(e=>['levels','deaths','arrivals','guild','bonds'].includes(e.kind)).slice(-40).reverse()};
|
||||
}
|
||||
Reference in New Issue
Block a user