Add social feed, cooperative parties, guild goals, and operations recovery

This commit is contained in:
Codex
2026-09-22 17:43:07 -04:00
parent 5bee808492
commit 46935c791e
17 changed files with 477 additions and 12 deletions
+10
View File
@@ -1,3 +1,4 @@
import {community,observePresence,applyCooperation,socialEntry} from './community';
import { runGM } from './gm';
import { actionSchema } from './validation';
import { connectors } from './connectors';
@@ -72,6 +73,7 @@ function observedHardcoreDeath(s:State,b:Bot,observation:Observation){
export async function applyAuthoritativeAleEvent(store:StateStore,kind:string,payload:Record<string,unknown>){
let s=await store.load();
const revision=s.revision;
if(kind==='player.snapshot'&&payload.cooperation){applyCooperation(s,payload);await store.save(s,revision);return {matched:true,cooperation:true};}
// Complete world snapshots own presence once available; historical events
// must never bring disconnected residents back online.
if(kind==='player.snapshot' && payload.roster===true){
@@ -151,6 +153,7 @@ export async function applyAuthoritativeAleEvent(store:StateStore,kind:string,pa
}
if(b.status==='retired')return {matched:true,botId:b.id,retired:true};
if(kind==='player.snapshot')observePresence(s,b,payload);
b.level=level;
if(zone)b.zone=zone;
@@ -231,6 +234,13 @@ export async function execute(store:StateStore,input:unknown,config:RuntimeConfi
if(a.type==='gm'){
const id=crypto.randomUUID();s.operations[id]={started:Date.now(),botId:'gm'};event(s,'gm',`${a.live?'LIVE':'SIMULATED'} command requested: ${a.command}`);await store.save(s,revision);
try{const result=await runGM(a.command,a.live,a.confirmed,config);delete s.operations[id];event(s,'gm',`${a.live?'LIVE':'SIMULATED'} ${result.command}: ${result.output}`);await store.save(s,s.revision);return result;}catch(e){delete s.operations[id];event(s,'error',`GM command failed: ${e instanceof Error?e.message:'Unknown error'}`);await store.save(s,s.revision);throw e;}
}else if(a.type==='retentionPolicy'){
s.opsRetentionDays=a.days;
}else if(a.type==='communityPolicy'){
Object.assign(community(s),{partiesEnabled:a.partiesEnabled,guildsEnabled:a.guildsEnabled});
}else if(a.type==='guildGoal'){
const g=community(s).guilds.find(g=>g.id===a.guildId);if(!g)throw new Error('Guild not observed');g.goal=a.goal;g.progress=0;
socialEntry(s,{id:crypto.randomUUID(),at:new Date().toISOString(),kind:'guild',names:[g.name],text:'Guild goal changed to '+a.goal,status:'confirmed'});
}else if(a.type==='director'){
s.director={experience:a.experience,zone:a.zone,playerName:a.playerName,brief:a.brief};
for(const b of s.bots.filter(b=>b.status!=='retired'&&b.status!=='dead')) b.goal=`${a.experience} in ${a.zone}${a.playerName?' around '+a.playerName:''}: ${a.brief}`.slice(0,200);