Add native gameplay outcomes, recovery verification, and realm diagnostics
Core tests / core (push) Successful in 40s
Core tests / core (push) Successful in 40s
This commit is contained in:
@@ -23,7 +23,8 @@ export function observeInsights(s:State,previous:Cursor|undefined,now:number){
|
||||
}
|
||||
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(e.status==='confirmed'||e.status==='failed')cursor.social.push(e.id);
|
||||
if(previous&&Date.parse(e.at)/1000>=previous.at&&!previous.social.includes(e.id)&&e.status==='failed'&&e.kind==='party'&&!e.id.endsWith(':proposal'))add('social:'+e.id,'partyFailed',e.names.join(' & '),1,'Game rejected party action');
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {State} from './types';
|
||||
import {activityReason,type Evidence} from './realm-insights';
|
||||
export const counterKeys=['quests','abandoned','lootEvents','lootItems','partyKills','rewarded'] as const;
|
||||
export type NativeOutcome={guid:number;name:string}&Record<typeof counterKeys[number],number>;
|
||||
export type OutcomeBaseline={at:number;startedAt:number;rows:NativeOutcome[]};
|
||||
export function outcomeChanges(rows:NativeOutcome[],previous:OutcomeBaseline|undefined,now:number){
|
||||
const events:Evidence[]=[];
|
||||
for(const r of rows){const old=previous?.rows.find(b=>b.guid===r.guid);if(!old)continue;
|
||||
for(const key of counterKeys){const amount=r[key]-old[key];if(amount>0)events.push({id:`outcome:${r.guid}:${key}:${now}:${r[key]}`,at:now,kind:key,name:r.name,amount,detail:({quests:'Quest objectives completed',abandoned:'Quests abandoned',lootEvents:'Loot collections',lootItems:'Items collected',partyKills:'Kills with a nearby bot attacking the same target',rewarded:'New rewarded quest records'})[key]});}
|
||||
}
|
||||
return {baseline:{at:now,startedAt:previous?.startedAt??now,rows},events};
|
||||
}
|
||||
export type Recovery={id:number;guid:number;name:string;at:number;detail:string;status?:'pending'|'resumed'|'no_progress'|'unverified';checkedAt?:number;result?:string};
|
||||
export type Perf={at:number;enabled:boolean;ticks:number;averageTickMs:number;rows:{category:string;name:string;calls:number;totalMs:number;averageMs:number;maxMs:number;perTickMs:number}[]};
|
||||
export type Health={host:string;at:number;memoryAvailableMB:number;memoryTotalMB:number;diskFreeGB:number;cpuPercent:number;modelAvailable?:boolean;runtimeService?:string};
|
||||
export type Attention={key:string;severity:'warning'|'critical';title:string;detail:string;target:string;name?:string};
|
||||
export function attentionItems(s:State,now:number,collectedAt:number|null,outcomeAt:number|null,perf:Perf|null,recoveries:Recovery[],health:Health[]){
|
||||
const items:Attention[]=[];
|
||||
const add=(key:string,severity:Attention['severity'],title:string,detail:string,target:string,name?:string)=>items.push({key,severity,title,detail,target,name});
|
||||
if(!collectedAt||now-collectedAt>180)add('collector','warning','Observations are delayed','The minute collector has not delivered a fresh sample.','System Health');
|
||||
if(!outcomeAt||now-outcomeAt>180)add('outcomes','warning','Gameplay evidence is delayed','Quest and loot counters are unavailable or stale.','Gameplay Results');
|
||||
if(!perf||!perf.enabled||now-perf.at>720)add('profiler','warning','Bot profiler needs attention','A current enabled profiler sample is not available.','Bot Performance');
|
||||
for(const host of ['ubuntu','windows']){const h=health.find(v=>v.host===host);if(!h||now-h.at>180){add('host:'+host,'warning',`${host} telemetry delayed`,'Host resource readings are missing or stale.','System Health');continue;}
|
||||
if(h.memoryAvailableMB<Math.max(256,h.memoryTotalMB*.05))add('memory:'+host,'critical',`${host} memory pressure`,`${Math.round(h.memoryAvailableMB)} MB available. Check host workload before raising bot population.`,'System Health');
|
||||
if(h.diskFreeGB<4)add('disk:'+host,'critical',`${host} disk space low`,`${h.diskFreeGB.toFixed(1)} GB free.`,'System Health');
|
||||
if(h.cpuPercent>90)add('cpu:'+host,'warning',`${host} CPU is busy`,'Latest sample exceeds 90%; this alone does not establish sustained overload.','System Health');
|
||||
if(host==='ubuntu'&&h.modelAvailable===false)add('model','warning','Language model unavailable','Bot conversations may be delayed.','System Health');
|
||||
}
|
||||
const online=s.bots.filter(b=>b.autonomy?.managed&&b.status==='online');
|
||||
if(now-(s.aleRosterAt??0)<120){if(online.length!==50)add('population',online.length>50?'critical':'warning',`${online.length} / 50 bots online`,online.length>50?'The hard cap needs investigation.':'A vacancy may be waiting for its replacement.','Bots');
|
||||
const alliance=online.filter(b=>b.world?.team===0).length,horde=online.filter(b=>b.world?.team===1).length;
|
||||
if(alliance!==25||horde!==25)add('factions','warning','Faction balance differs from target',`${alliance} Alliance · ${horde} Horde · target 25 each.`,'World Monitor');}
|
||||
for(const b of online){const reason=activityReason(b,now);if(['Possible stall','Telemetry delayed'].includes(reason.label))add('bot:'+b.id,'warning',b.name+' · '+reason.label,reason.detail,'Bot Activity',b.name);}
|
||||
const latest=new Map<number,Recovery>();for(const r of [...recoveries].sort((a,b)=>b.at-a.at))if(!latest.has(r.guid))latest.set(r.guid,r);
|
||||
for(const r of latest.values())if(r.status==='no_progress'&&now-r.at<3600&&online.some(b=>b.autonomy?.characterGuid===r.guid&&activityReason(b,now).label==='Possible stall'))add('recovery:'+r.id,'warning',r.name+' · recovery needs follow-up','No native movement or level progress was observed within three minutes.','Recovery Results',r.name);
|
||||
return items.sort((a,b)=>Number(b.severity==='critical')-Number(a.severity==='critical'));
|
||||
}
|
||||
Reference in New Issue
Block a user