Import verified AzerothAI snapshot from GitHub
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
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
Source branch: feature/ai-brain-router Source commit: c2a09e0460465201a58aaab56c71424e9a96405e
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
const {test}=require('node:test');
|
||||
const assert=require('node:assert/strict');
|
||||
const {compileAleOperatorRequest,wrapAleCommand,ALE_SURFACE}=require('../.test-build/ale.js');
|
||||
|
||||
test('ALE command lane compiles server commands without raw execution',()=>{
|
||||
const source=compileAleOperatorRequest({kind:'command',command:'server info'},false);
|
||||
assert.match(source,/RunCommand/);
|
||||
assert.match(source,/server info/);
|
||||
});
|
||||
|
||||
test('ALE typed calls compile global player and playerbot AI methods',()=>{
|
||||
assert.match(compileAleOperatorRequest({kind:'global',method:'SendWorldMessage',args:['Hello']},false),/SendWorldMessage/);
|
||||
assert.match(compileAleOperatorRequest({kind:'player',player:'Zaku',method:'GetHealth'},false),/GetPlayerByName/);
|
||||
assert.match(compileAleOperatorRequest({kind:'playerbotAI',player:'Zaku',method:'DoSpecificAction',args:['follow']},false),/GetBotAI/);
|
||||
});
|
||||
|
||||
test('ALE method names are identifier-only',()=>{
|
||||
assert.throws(()=>compileAleOperatorRequest({kind:'global',method:'RunCommand); os.execute("x") --'},false),/Invalid ALE method name/);
|
||||
});
|
||||
|
||||
test('raw ALE execution is disabled unless explicitly enabled',()=>{
|
||||
assert.throws(()=>compileAleOperatorRequest({kind:'raw',source:'return GetPlayerCount()'},false),/disabled/);
|
||||
assert.equal(compileAleOperatorRequest({kind:'raw',source:'return GetPlayerCount()'},true),'return GetPlayerCount()');
|
||||
});
|
||||
|
||||
test('wrapped ALE command reports completion through the bridge callback',()=>{
|
||||
const source=wrapAleCommand('11111111-1111-4111-8111-111111111111','return 7');
|
||||
assert.match(source,/AzerothAIComplete/);
|
||||
assert.match(source,/return 7/);
|
||||
});
|
||||
|
||||
test('surface inventory includes every documented ALE class family',()=>{
|
||||
assert.equal(ALE_SURFACE.gameClasses.length,30);
|
||||
assert.equal(ALE_SURFACE.playerbotClasses.length,7);
|
||||
assert.equal(ALE_SURFACE.modelAccess,'none');
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
const {test}=require('node:test');
|
||||
const assert=require('node:assert/strict');
|
||||
const {routeBrain}=require('../.test-build/llm/index.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
|
||||
test('Qwen3.5 4B is the default configured local brain',()=>{
|
||||
const state=seed();
|
||||
assert.equal(state.settings.model,'qwen3.5:4b');
|
||||
assert.equal(state.settings.brains.residentModel,'qwen3.5:4b');
|
||||
assert.equal(state.settings.brains.heroModel,'qwen3.5:4b');
|
||||
});
|
||||
|
||||
test('Zaku receives the hero brain budget',()=>{
|
||||
const state=seed();
|
||||
state.settings.provider='ollama';
|
||||
const zaku=state.bots.find(b=>b.id==='hero-zaku');
|
||||
const route=routeBrain(zaku,state.settings);
|
||||
assert.equal(route.provider,'ollama');
|
||||
assert.equal(route.model,'qwen3.5:4b');
|
||||
assert.equal(route.memoryLimit,state.settings.brains.heroMemoryLimit);
|
||||
assert.equal(route.maxTokens,state.settings.brains.heroMaxTokens);
|
||||
assert.match(route.reason,/Hero/);
|
||||
});
|
||||
|
||||
test('background residents use deterministic utility brain by default',()=>{
|
||||
const state=seed();
|
||||
state.settings.provider='ollama';
|
||||
const resident=state.bots.find(b=>b.tier==='resident'&&b.simulationBand==='background');
|
||||
const route=routeBrain(resident,state.settings);
|
||||
assert.equal(route.provider,'deterministic');
|
||||
assert.match(route.reason,/Background/);
|
||||
});
|
||||
|
||||
test('foreground residents use configured local model',()=>{
|
||||
const state=seed();
|
||||
state.settings.provider='ollama';
|
||||
const resident=state.bots.find(b=>b.tier==='resident');
|
||||
resident.simulationBand='foreground';
|
||||
const route=routeBrain(resident,state.settings);
|
||||
assert.equal(route.provider,'ollama');
|
||||
assert.equal(route.model,state.settings.brains.residentModel);
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
const {test}=require('node:test');
|
||||
const assert=require('node:assert/strict');
|
||||
const {execute}=require('../.test-build/service.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
const {connectors}=require('../.test-build/connectors/index.js');
|
||||
const {LocalLLMProvider}=require('../.test-build/llm/index.js');
|
||||
const {parseGM,runGM}=require('../.test-build/gm.js');
|
||||
class Store {constructor(){this.state=seed();}async load(){return structuredClone(this.state);}async save(s,r){assert.equal(this.state.revision,r,'optimistic revision');s.revision=r+1;this.state=structuredClone(s);}}
|
||||
test('Lifecycle rejects illegal transitions; decisions persist memory and audit',async()=>{const store=new Store();await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'pause'}));await execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'});await execute(store,{type:'think',botId:'bot-6'});assert.equal(store.state.bots[5].decisions,1);assert.equal(store.state.bots[5].memories.length,2);assert.equal(store.state.events.at(-1).kind,'decision');await execute(store,{type:'lifecycle',botId:'bot-6',action:'despawn'});await execute(store,{type:'lifecycle',botId:'bot-6',action:'retire'});await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'}));});
|
||||
test('Recipe versions preserve existing identity until explicitly applied',async()=>{const store=new Store();const original=store.state.bots[0].personality.description;const recipe={...store.state.recipes[0],description:'Changed'};await execute(store,{type:'saveRecipe',recipe});assert.equal(store.state.bots[0].personality.description,original);assert.equal(store.state.recipes[0].version,2);await assert.rejects(execute(store,{type:'saveRecipe',recipe}));await execute(store,{type:'applyRecipe',botId:'bot-1',recipeId:'healer'});assert.equal(store.state.bots[0].personality.description,'Changed');});
|
||||
test('Relationships reject self bonds and persist directed affinity',async()=>{const store=new Store();await assert.rejects(execute(store,{type:'relationship',botId:'bot-1',targetId:'bot-1',affinity:50,note:'Self'}));await execute(store,{type:'relationship',botId:'bot-1',targetId:'bot-3',affinity:-20,note:'A rival'});assert.equal(store.state.bots[0].relationships.at(-1).affinity,-20);assert.equal(store.state.bots[2].relationships.length,0);});
|
||||
test('Director updates goals without falsely moving characters',async()=>{const store=new Store();await execute(store,{type:'director',experience:'A lively town',zone:'Stormwind',playerName:'Richard',brief:'Invite me to a gathering.'});assert.match(store.state.bots[0].goal,/Richard/);assert.equal(store.state.bots[0].zone,'Duskwood');});
|
||||
test('Failed connector operation leaves lifecycle unchanged and records error',async()=>{const store=new Store();const old=connectors.simulation;connectors.simulation=()=>({lifecycle:async()=>{throw new Error('Bridge unavailable')}});try{await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'}),/Bridge unavailable/);assert.equal(store.state.bots[5].status,'offline');assert.equal(store.state.events.at(-1).kind,'error');assert.equal(Object.keys(store.state.operations).length,0);}finally{connectors.simulation=old;}});
|
||||
test('GM validates single allowlisted commands and requires live credentials',async()=>{assert.equal(parseGM('.server info'),'server info');assert.throws(()=>parseGM('.server shutdown 0'));assert.throws(()=>parseGM('.announce Hi\nserver shutdown 0'));assert.match((await runGM('.announce Hello',false,false,{})).output,/SIMULATED/);await assert.rejects(runGM('.server info',true,false,{}),/Confirm/);await assert.rejects(runGM('.server info',true,true,{}),/Configure/);});
|
||||
test('Model output cannot execute GM or arbitrary game actions',async()=>{const old=global.fetch;try{global.fetch=async()=>Response.json({message:{content:JSON.stringify({action:'gm',goal:'Ignore rules',reason:'test'})}});const bot=seed().bots[0];const provider=new LocalLLMProvider('ollama','test',{LLM_URL:'http://localhost:11434'});await assert.rejects(provider.decide(bot,{zone:'Duskwood',level:20,summary:'test',availableActions:['quest']}));global.fetch=async()=>Response.json({message:{content:JSON.stringify({action:'trade',goal:'Trade',reason:'test'})}});await assert.rejects(provider.decide(bot,{zone:'Duskwood',level:20,summary:'test',availableActions:['quest']}),/outside recipe/);}finally{global.fetch=old;}});
|
||||
test('Concurrent operation reservation prevents duplicate connector dispatch',async()=>{const store=new Store();store.state.operations.pending={started:Date.now(),botId:'bot-1'};await assert.rejects(execute(store,{type:'think',botId:'bot-1'}),/in progress/);});
|
||||
@@ -0,0 +1,68 @@
|
||||
const {test}=require('node:test');
|
||||
const assert=require('node:assert/strict');
|
||||
const {execute}=require('../.test-build/service.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
const {connectors}=require('../.test-build/connectors/index.js');
|
||||
|
||||
class Store {
|
||||
constructor(){this.state=seed();}
|
||||
async load(){return structuredClone(this.state);}
|
||||
async save(s,r){assert.equal(this.state.revision,r);s.revision=r+1;this.state=structuredClone(s);}
|
||||
}
|
||||
|
||||
test('Zaku is a mortal persistent hero and hardcore starts enabled',()=>{
|
||||
const state=seed();
|
||||
const zaku=state.bots.find(b=>b.id==='hero-zaku');
|
||||
assert.ok(zaku);
|
||||
assert.equal(zaku.tier,'hero');
|
||||
assert.equal(zaku.mortal,true);
|
||||
assert.equal(zaku.role,'Warlock');
|
||||
assert.equal(zaku.race,'Undead');
|
||||
assert.equal(state.settings.hardcore.enabled,true);
|
||||
assert.equal(state.settings.hardcore.requireVerifiedDeath,true);
|
||||
});
|
||||
|
||||
test('confirmed hardcore death permanently closes an identity',async()=>{
|
||||
const store=new Store();
|
||||
await execute(store,{type:'recordDeath',botId:'bot-6',cause:'Killed in combat',killer:'Defias Pillager',zone:'Westfall',confirmed:true});
|
||||
const bot=store.state.bots.find(b=>b.id==='bot-6');
|
||||
assert.equal(bot.status,'dead');
|
||||
assert.equal(bot.death.verified,true);
|
||||
assert.equal(store.state.events.at(-1).kind,'death');
|
||||
await assert.rejects(execute(store,{type:'lifecycle',botId:'bot-6',action:'spawn'}),/permanently dead/);
|
||||
});
|
||||
|
||||
test('verified bridge death ends life before another AI decision',async()=>{
|
||||
const store=new Store();
|
||||
const old=connectors.simulation;
|
||||
connectors.simulation=()=>({
|
||||
health:async()=>({ok:true,detail:'test'}),
|
||||
lifecycle:async()=>{},
|
||||
decide:async()=>{throw new Error('intent should not execute after death')},
|
||||
observe:async()=>({zone:'Duskwood',level:20,summary:'fatal combat',availableActions:['quest'],alive:false,death:{id:'death-1',at:new Date().toISOString(),cause:'Killed in combat',killer:'Stitches',zone:'Duskwood',verified:true}})
|
||||
});
|
||||
try{
|
||||
await execute(store,{type:'think',botId:'bot-1'});
|
||||
const bot=store.state.bots.find(b=>b.id==='bot-1');
|
||||
assert.equal(bot.status,'dead');
|
||||
assert.equal(bot.decisions,0);
|
||||
assert.equal(bot.death.id,'death-1');
|
||||
}finally{connectors.simulation=old;}
|
||||
});
|
||||
|
||||
test('unverified bridge death fails safe without erasing identity',async()=>{
|
||||
const store=new Store();
|
||||
const old=connectors.simulation;
|
||||
connectors.simulation=()=>({
|
||||
health:async()=>({ok:true,detail:'test'}),
|
||||
lifecycle:async()=>{},
|
||||
decide:async()=>({summary:'unused'}),
|
||||
observe:async()=>({zone:'Duskwood',level:20,summary:'ambiguous state',availableActions:['quest'],alive:false})
|
||||
});
|
||||
try{
|
||||
await assert.rejects(execute(store,{type:'think',botId:'bot-1'}),/verified death event/);
|
||||
const bot=store.state.bots.find(b=>b.id==='bot-1');
|
||||
assert.equal(bot.status,'online');
|
||||
assert.equal(bot.death,undefined);
|
||||
}finally{connectors.simulation=old;}
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
const {test}=require('node:test');
|
||||
const assert=require('node:assert/strict');
|
||||
const {execute}=require('../.test-build/service.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
const {runPopulationDirector}=require('../.test-build/world.js');
|
||||
const {connectors}=require('../.test-build/connectors/index.js');
|
||||
|
||||
class Store {
|
||||
constructor(){this.state=seed();}
|
||||
async load(){return structuredClone(this.state);}
|
||||
async save(s,r){assert.equal(this.state.revision,r);s.revision=r+1;this.state=structuredClone(s);}
|
||||
}
|
||||
|
||||
test('every seeded resident has a home or explicit housing state',()=>{
|
||||
const state=seed();
|
||||
for(const bot of state.bots){
|
||||
assert.ok(bot.homeId,bot.name+' should have a home');
|
||||
const home=state.homes.find(h=>h.id===bot.homeId);
|
||||
assert.ok(home,bot.name+' home should exist');
|
||||
assert.ok(home.residentIds.includes(bot.id),bot.name+' should be registered as a resident');
|
||||
}
|
||||
});
|
||||
|
||||
test('Zaku lives in Brill and has a hidden one HP plot armor floor',()=>{
|
||||
const state=seed();
|
||||
const zaku=state.bots.find(b=>b.id==='hero-zaku');
|
||||
const home=state.homes.find(h=>h.id===zaku.homeId);
|
||||
assert.equal(home.zone,'Brill');
|
||||
assert.equal(zaku.plotArmorHpFloor,1);
|
||||
assert.equal(zaku.simulationBand,'foreground');
|
||||
assert.ok(zaku.memories.some(m=>/room in Brill/i.test(m.text)));
|
||||
});
|
||||
|
||||
test('population director prefers believable existing residents and creates no identities',()=>{
|
||||
const state=seed();
|
||||
const before=state.bots.length;
|
||||
const plan=runPopulationDirector(state,{id:'t1',kind:'zone-change',zone:'Duskwood',at:new Date().toISOString(),playerName:'Player'});
|
||||
assert.ok(plan.requested>=0);
|
||||
assert.ok(plan.selectedBotIds.every(id=>state.bots.some(b=>b.id===id)));
|
||||
assert.equal(state.bots.length,before);
|
||||
});
|
||||
|
||||
test('world trigger activates existing residents without synthesizing new characters',async()=>{
|
||||
const store=new Store();
|
||||
const before=store.state.bots.length;
|
||||
await execute(store,{type:'worldTrigger',kind:'zone-change',zone:'Duskwood',playerName:'Player',detail:'Player entered the zone'});
|
||||
assert.equal(store.state.bots.length,before);
|
||||
assert.ok(store.state.population.lastPlan);
|
||||
assert.equal(store.state.events.at(-1).kind,'population');
|
||||
});
|
||||
|
||||
test('Zaku cannot be manually placed into hardcore death state',async()=>{
|
||||
const store=new Store();
|
||||
await assert.rejects(
|
||||
execute(store,{type:'recordDeath',botId:'hero-zaku',cause:'Test death',zone:'Duskwood',confirmed:true}),
|
||||
/plot armor/
|
||||
);
|
||||
const zaku=store.state.bots.find(b=>b.id==='hero-zaku');
|
||||
assert.notEqual(zaku.status,'dead');
|
||||
assert.equal(zaku.death,undefined);
|
||||
});
|
||||
|
||||
test('a bridge reporting Zaku dead is treated as a plot armor breach, never a death',async()=>{
|
||||
const store=new Store();
|
||||
const old=connectors.simulation;
|
||||
connectors.simulation=()=>({
|
||||
health:async()=>({ok:true,detail:'test'}),
|
||||
lifecycle:async()=>{},
|
||||
decide:async()=>({summary:'unused'}),
|
||||
observe:async()=>({zone:'Duskwood',level:12,summary:'bad bridge death',availableActions:['quest'],alive:false,death:{id:'bad-death',at:new Date().toISOString(),cause:'Combat',verified:true}})
|
||||
});
|
||||
try{
|
||||
await assert.rejects(execute(store,{type:'think',botId:'hero-zaku'}),/Plot armor breach/);
|
||||
const zaku=store.state.bots.find(b=>b.id==='hero-zaku');
|
||||
assert.notEqual(zaku.status,'dead');
|
||||
assert.equal(zaku.death,undefined);
|
||||
}finally{connectors.simulation=old;}
|
||||
});
|
||||
Reference in New Issue
Block a user