Batch authoritative snapshots and bound rejected retreat fallback
This commit is contained in:
@@ -34,3 +34,14 @@ test('surface inventory includes every documented ALE class family',()=>{
|
||||
assert.equal(ALE_SURFACE.playerbotClasses.length,7);
|
||||
assert.equal(ALE_SURFACE.modelAccess,'none');
|
||||
});
|
||||
|
||||
test('snapshot batch saves once and does not commit a partial invalid roster',async()=>{
|
||||
const {applyAuthoritativeAleSnapshots}=require('../.test-build/service.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
let state=seed(),writes=0;
|
||||
const store={load:async()=>structuredClone(state),save:async(s,r)=>{assert.equal(r,state.revision);writes++;state=s;}};
|
||||
await applyAuthoritativeAleSnapshots(store,[{roster:true,observedAt:100,characterGuids:[]},{roster:true,observedAt:101,characterGuids:[]}]);
|
||||
assert.equal(writes,1);assert.equal(state.aleRosterAt,101);
|
||||
await assert.rejects(()=>applyAuthoritativeAleSnapshots(store,[{roster:true,observedAt:102,characterGuids:[]},{roster:true,observedAt:103,characterGuids:[-1]}]));
|
||||
assert.equal(writes,1);assert.equal(state.aleRosterAt,101);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ function make(id,name)
|
||||
function p:GetGUIDLow()return self.id end;function p:GetName()return self.name end;function p:GetAccountId()return 10+self.id end
|
||||
function p:IsPlayerbot()return not self.human end;function p:IsDead()return self.dead end;function p:GetBotAI()return self.ai end
|
||||
function p:GetPowerType()return self.powerType or 1 end;function p:GetPowerPct()return self.mana or 100 end;function p:GetAttackers()return self.attackers or {} end;function p:GetHealthPct()return self.hp end;function p:IsInCombat()return self.combat end
|
||||
function p:HasUnitState(state)assert(state==0x00000100);return self.taxi end;function p:IsCasting()return self.cast end;function p:IsRooted()return false end
|
||||
function p:HasUnitState(state)assert(state==0x00000100);return self.taxi end;function p:IsMoving()return self.moving end;function p:GetMovementType()return self.motion or 0 end;function p:MoveExpire()self.expired=true;self.motion=0 end;function p:MoveFleeing(t,ms)self.fleeTarget=t;self.motion=9;self.fleeDuration=ms end;function p:IsCasting()return self.cast end;function p:IsRooted()return false end
|
||||
function p:GetX()return self.x or 0 end;function p:GetY()return 0 end
|
||||
function p:GetUnfriendlyUnitsInRange()return self.friendly and {} or self.selected and {self.selected} or {} end;function p:GetSelection()return self.selected end;function p:StopSpellCast()self.cast=false;self.interrupted=true end
|
||||
function p:AttackStop()self.attackStopped=true end;function p:MoveStop()self.waited=true end
|
||||
@@ -69,3 +69,7 @@ print('Cohesion suppresses competing work, keeps catch-up hysteresis, yields to
|
||||
l=new();l.execute("A.strategies={stay=true};step(0);assert(not A.strategies.stay,'Fully recovered autonomous resident must not remain in stay')")
|
||||
l=new();l.execute("group();B.distance=50;step(0);step(40);assert(B.master==nil,'No-progress regroup must release its owned master');assert(not AzerothAISurvivalOverride(B));step(30);assert(B.master==nil,'Backoff must prevent immediate retry')")
|
||||
print('Recovered stay cleared and non-progressing regroup bounded')
|
||||
|
||||
l=new();l.execute("A.combat=true;A.hp=20;A.attackers={B};function A.ai:DoSpecificAction(s)table.insert(calls,s);return false end;step(0);assert(A.fleeTarget==B and A.fleeDuration==8000);step(8);assert(A.expired);A.fleeTarget=nil;step(8);assert(A.fleeTarget==nil,'Native fallback must honor cooldown')")
|
||||
l=new();l.execute("A.combat=true;A.hp=20;A.controlled=true;A.attackers={B};function A.ai:DoSpecificAction()return false end;step(0);assert(A.fleeTarget==nil)")
|
||||
print('Rejected flee fallback is bounded, cooled down, and preserves human control')
|
||||
|
||||
Reference in New Issue
Block a user