BatBrain -- a central nervous system for consult scripts

I think I've found and solved a serious bug regarding BatBrain's predictive ability. I decided to test out some advanced BatBrain functionality with this code:

Code:
while(monster_stat("hp") > 0) {
	sort opts by -to_profit(value);
	sort opts by -dmg_dealt(value.dmg);
	if(dmg_dealt(opts[0].dmg) < 1) break;
	enqueue(opts[0]);
}

It looked to me that monster_stat("hp") is modified by adj which means that each enqueued action should reduce monsters current HP. However this goes into an infinite loop which debugging reveals occurs because adj.dmg is zero and monster_stat("hp") does not change.

A bit of experimentation revealed the problem at line 992. Right before that oneround(a) returned the damage that my attack will do. Then line 992 changed it to 0, presumably because to_spread() returns 0 if dmg<=0. I believe line 992 should be this:

Code:
   temp.dmg = to_spread(dmg_dealt(temp.dmg),"physical", -1);          // monster hp is stored in dmg[none]
 
I'm having problems fighting anything. Oddly enough, the error is
Code:
Bad monster value: "demon spirit of new wave"     (BatBrain.ash, line 255)
Consult script     'SmartStasis.ash' not found.
You're on your     own, partner.
I'm running bumcheekascend, and I do have the latest version of Batbrain and SmartStasis. Didn't kolmafia just change something with this monster's name? Could that have broken something?
 
Last edited:
I think I've found and solved a serious bug regarding BatBrain's predictive ability. I decided to test out some advanced BatBrain functionality with this code:

Code:
while(monster_stat("hp") > 0) {
	sort opts by -to_profit(value);
	sort opts by -dmg_dealt(value.dmg);
	if(dmg_dealt(opts[0].dmg) < 1) break;
	enqueue(opts[0]);
}

It looked to me that monster_stat("hp") is modified by adj which means that each enqueued action should reduce monsters current HP. However this goes into an infinite loop which debugging reveals occurs because adj.dmg is zero and monster_stat("hp") does not change.

A bit of experimentation revealed the problem at line 992. Right before that oneround(a) returned the damage that my attack will do. Then line 992 changed it to 0, presumably because to_spread() returns 0 if dmg<=0. I believe line 992 should be this:

Code:
   temp.dmg = to_spread(dmg_dealt(temp.dmg),"physical", -1);          // monster hp is stored in dmg[none]

Oh thank you for tracking that down. I was trying to improve my spamattack script to use predictive calculations and enqueue and remove skills but it never worked and I couldn't figure out why. I always thought I was doing something incredibly wrong...

I'm having problems fighting Lobsterfrogmen. Oddly enough, the error is
Code:
Bad monster value: "demon spirit of new wave"     (BatBrain.ash, line 255)
Consult script     'SmartStasis.ash' not found.
You're on your     own, partner.
I'm running bumcheekascend, and I do have the latest version of Batbrain and SmartStasis.

Chnage that monster name in the script to "the Demon of New Wave" instead and it shoudl work. A recent correction to Mafia broke that.
 
@Winterbay: Pretty sure that didn't work because ternary syntax should be

(condition ? value1 : value2)

where the values are the same type. Not

(condition ? command1 : command2)

with arbitrary commands.

Thanks for posting that code though! I think I'll add this to build_skillz() since a variable for the elemental bonus already exists there.

Yeah I was a bit unsure of where to add that, but as the mine-crab sets its -hp in addopt I though these other modifications might go there as well.
Also, glad to be of service :)

On the ternary things, it is very possible that is the case, but I feel that there is no difference between
Code:
((total_spell_damage() > 10) ? a.hp -= 19 : a.hp -= 0);
and
Code:
((total_spell_damage() > 10) ? print("Yes") : print("No"));
which obviously does work. At least in my mind, but then Mafia's .ash is the first time I've ever run into ternary operators so I may be confused here :)
 
which obviously does work. At least in my mind, but then Mafia's .ash is the first time I've ever run into ternary operators so I may be confused here :)

Think of a ternary operator as a variable that can have one of two possible values. If the condition is true then it has the first value, but if the condition is false then it can have the second value. It is not an excuse to run assignment operators since they do not have a return value.

In the second example it "works" because print() has a return value and both options have a boolean value. If you tried to run different commands with different return types it would fail.
 
Last edited:
Ahh right. Thanks. I thought of ternary operators as a compressed if-statement which is obviously not the case apart form certain specific circumstances.
 
@Winterbay: You had the right idea as far as ternary operators' conventional use -- assigning different values to a variable depending on a condition. However, they are usually used when it's time to specify the actual value, not when it's time to assign the value. In other words,

((total_spell_damage() > 10) ? a.hp -= 19 : a.hp -= 0);

would rather be

a.hp -= (total_spell_damage() > 10 ? 19 : 0);

Likewise

((total_spell_damage() > 10) ? print("Yes") : print("No"));

should be

print(total_spell_damage() > 10 ? "Yes" : "No");

@Bale: I'm not sure whether it qualifies as "serious", but it is an icky bug, and a good catch. Will make your fix in the next update.
 
Right. I think I've found an error in my code. The code was based on the wiki which said "splashback damage is max(x,20)-2 to max(x,20) where x is how much you exceed the spell damage cap". It should be min(x,20) since otherwise it would scale from 20 and up when you got x above 20 butinstead it stops at 20. My code says 19 since I misinterpreted the tests I did... (i.e. I thought it was 19+/-1)
 
I'm aware of that post. It sounded like the poster thought that changing them to | had fixed the issue so I thought I should point out that the issue was still there.
I think I found why though I'm curious as to why this is happening.
Code:
ash split_string("1|1|1","|")

Returned: aggregate string [6]
0 =>
1 => 1
2 => |
3 => 1
4 => |
5 => 1

> ash split_string("1,1,1",",")

Returned: aggregate string [3]
0 => 1
1 => 1
2 => 1
It seems that using a pipe is causing errors.
 
The second parameter of split_string() is treated as a regular expression. Zarqon was probably caught off-guard, and didn't have his RegEx Tongs on.
Code:
> ash split_string("1|1|1","[|]")

Returned: aggregate string [3]
0 => 1
1 => 1
2 => 1
 
Yup, that little change to line 220 eliminated the error.
Code:
      case "stats": string[int] sts = split_string(bittles.group(2),"[|]");
Edit: This is something else that's been bugging me for a while:
Code:
> zlib verbosity = 10

Previous value of verbosity: 6
Changed to 10.

[7354] Giant's Castle
Encounter: Possibility Giant
Round 0: fluxxdog wins initiative!

> finishhim

1 MP costs 8.0μ.
1 HP costs 6.6666665μ.
chaos butterfly (20.0 @ +77.0): 124μ * 35.4% = 43.896
plot hole (20.0 @ +77.0): 128μ * 35.4% = 45.312
probability potion (30.0 @ +77.0): 128μ * 53.1% = 67.967995
Value of stat gain: 319.45μ
Factoring in Scarysauce: (6) damage, retal
Profit per round: ActionProfitDamageOtherbase; Stocking Mimic; enthroned Adorable Seal Larva (144.24μ)144.35μ20.74 (-6.95 μ/dmg)Att: -6.86 (-0.02 DPR) Def: -6.86 HP: 21.59 MP: 21.59
Parsed round number: 1
Building options...
Evaluating '45*loc(chasm)'...
Evaluating 'max(5,floor(0.1*588.0))+25'...
Evaluating '-max(5,floor(0.1*588.0))'...
Evaluating 'floor(50+10*2.0^0.7)'...
Evaluating '192.5*zone(sea)'...
Evaluating '0.25*141.0*(zone(sea)-1)'...
Evaluating 'min(100,ceil(5*sqrt(min(369.0,161.0))))'...
Evaluating 'min(50,ceil(5*sqrt(min(320.0,161.0))))'...
Evaluating 'min(120.0+3,10)+floor(sqrt(max(120.0-7,0)))'...
Options built! (158 actions)
Building custom actions...
Custom actions built! (0 actions)
Stasis action chosen: skill 7014
Top of the stasis loop.
Executing macro: scrollwhendone; sub batround; if haseffect 264 || haseffect 284; abort "BatBrain abort: poisoned"; endif; endsub; sub batsub1; skill 7014; call batround; endsub; call batsub1; repeat hasskill 7014 && (!hpbelow 588.0 && !mpbelow 480.0 && !pastround 28); if hpbelow 3; abort "BatBrain abort: Danger, Will Robinson"; endif; 
Round 1: fluxxdog executes a macro!
Round 1: fluxxdog casts FIRE BLACK BOTTLE-ROCKET!
Round 2: possibility giant drops 13 attack power.
Round 2: possibility giant drops 12 defense.
Round 2: Snuggle Buster tosses you a clump of nuggets. I don't know where they came from, and I'm not going to ask.
You acquire an item: cold nuggets
Round 2: Sweet Sock mimics a red-and-white striped ATM, and dispenses a little extra Meat.
You gain 599 Meat.
Parsed round number: 2
Look! You found 1 cold nuggets (230μ)!
Building options...
Evaluating '45*loc(chasm)'...
Evaluating 'max(5,floor(0.1*588.0))+25'...
Evaluating '-max(5,floor(0.1*588.0))'...
Evaluating 'floor(50+10*2.0^0.7)'...
Evaluating '192.5*zone(sea)'...
Evaluating '0.25*128.0*(zone(sea)-1)'...
Evaluating 'min(100,ceil(5*sqrt(min(369.0,161.0))))'...
Evaluating 'min(50,ceil(5*sqrt(min(320.0,161.0))))'...
Evaluating 'min(120.0+3,10)+floor(sqrt(max(120.0-7,0)))'...
Options built! (153 actions)
Stasis action chosen: use 3114
Top of the stasis loop.
Executing macro: scrollwhendone; sub batround; if haseffect 264 || haseffect 284; abort "BatBrain abort: poisoned"; endif; endsub; sub batsub1; use 3114; call batround; endsub; call batsub1; repeat hascombatitem 3114 && (!hpbelow 588.0 && !mpbelow 475.0 && !pastround 28); if hpbelow 3; abort "BatBrain abort: Danger, Will Robinson"; endif; 
Round 2: fluxxdog executes a macro!
Round 2: fluxxdog uses the Miniborg Destroy-O-Bot!
Round 3: possibility giant takes 26 damage.
Round 3: possibility giant drops 3 attack power.
Round 3: possibility giant drops 5 defense.
Round 3: Sweet Sock cranks up the throttle on his little helicopter. Vroom!
Round 3: Sweet Sock mimics a red-and-white striped ATM, and dispenses a little extra Meat.
You gain 538 Meat.
Round 3: fluxxdog uses the Miniborg Destroy-O-Bot!
Round 4: possibility giant takes 25 damage.
Round 4: possibility giant drops 3 attack power.
Round 4: possibility giant drops 4 defense.
Round 4: fluxxdog uses the Miniborg Destroy-O-Bot!
Round 5: possibility giant takes 23 damage.
Round 5: possibility giant drops 4 attack power.
Round 5: possibility giant drops 5 defense.
Round 5: fluxxdog uses the Miniborg Destroy-O-Bot!
Round 6: possibility giant takes 22 damage.
Round 6: possibility giant drops 5 attack power.
Round 6: possibility giant drops 5 defense.
Round 6: fluxxdog uses the Miniborg Destroy-O-Bot!
Round 7: possibility giant takes 24 damage.
Round 7: possibility giant drops 4 attack power.
Round 7: possibility giant drops 4 defense.
Round 7: Sweet Sock mimics a red-and-white striped candy cane, and canes him for 122 damage.
Round 7: possibility giant takes 122 damage.
Round 7: fluxxdog wins the fight!
After Battle: Snuggle Buster surveys the scene from atop the throne and sighs.
You gain 449 Meat
You acquire an item: chaos butterfly
You gain 21 Fortitude
You gain 8 Wizardliness
You gain 8 Sarcasm
Parsed round number: 3
Look! You found 1 chaos butterfly (124μ)!
Building options...
Evaluating '45*loc(chasm)'...
Evaluating 'max(5,floor(0.1*588.0))+25'...
Evaluating '-max(5,floor(0.1*588.0))'...
Evaluating 'floor(50+10*2.0^0.7)'...
Evaluating '192.5*zone(sea)'...
Evaluating '0.25*141.0*(zone(sea)-1)'...
Evaluating 'min(100,ceil(5*sqrt(min(369.0,161.0))))'...
Evaluating 'min(50,ceil(5*sqrt(min(320.0,161.0))))'...
Evaluating 'min(120.0+3,10)+floor(sqrt(max(120.0-7,0)))'...
Options built! (153 actions)
Stasis action chosen: use 3114
Top of the stasis loop.
Executing macro: scrollwhendone; sub batround; if haseffect 264 || haseffect 284; abort "BatBrain abort: poisoned"; endif; endsub; sub batsub1; use 3114; call batround; endsub; call batsub1; repeat hascombatitem 3114 && (!hpbelow 588.0 && !mpbelow 480.0 && !pastround 28); if hpbelow 4; abort "BatBrain abort: Danger, Will Robinson"; endif; 
Parsed round number: 0
Building options...
Evaluating '45*loc(chasm)'...
Evaluating 'max(5,floor(0.1*588.0))+25'...
Evaluating '-max(5,floor(0.1*588.0))'...
Evaluating 'floor(50+10*2.0^0.7)'...
Evaluating '192.5*zone(sea)'...
Evaluating '0.25*141.0*(zone(sea)-1)'...
Evaluating 'min(100,ceil(5*sqrt(min(369.0,161.0))))'...
Evaluating 'min(50,ceil(5*sqrt(min(320.0,161.0))))'...
Options built! (147 actions)
Stasis action chosen: use 8
Stasis loop complete.

> ashtest current_gear()

Returned: aggregate item [slot]
acc1 => Uncle Hobo's belt
acc2 => stinky cheese eye
acc3 => stinky cheese eye
hat => Crown of Thrones
off-hand => Bag o' Tricks
pants => poodle skirt
shirt => origami pasties
weapon => bottle-rocket crossbow
First question: why does it always use the Miniborg Destroy-O-Bot? I thought it would use something that wouldn't kill, like a facsimile dictionary?

Second: why does it try recalculating not just once but twice after the end of combat?

(Not dissing, love the script, but curiosity demands I ask ^^)
 
Last edited:
This issue came up in the spamattack thread and Theraze pointed out that it's a BatBrain issue so here's a link to the original thread

In short: BatBrain thinks that Shieldbutt is a viable option when a ranged weapon is equipped.
 
I guess changing the fvars-line for shieldpower to the following would make it not consider that:
Code:
fvars["shieldpower"] = (item_type(equipped_item($slot[offhand])) == "shield" && current_hit_stat() != $stat[moxie]) ? get_power(equipped_item($slot[offhand])) : 0;
 
Not necessarily, since if it's still a valid option otherwise (even if it's additional damage dealt is 0) it has a 100% hitchance, which might make it the cheapest/best option...
 
zarqon, I'm having a bit of trouble with Stringozzi Serpent. Unfortunately BatBrain thinks that it will do spelldmgpercent*(28+min(0.25*buffedmys,30)+min(spelldmg,60)+elembonus) damage on the turn you cast it. It actually does an extra 50% damage on that very turn since the poison is FAST. Obviously you haven't yet figured out how to implement "ongoing poison"

Until you figure out the correct way of fixing this, I'm changing BatFactors to do *1.5 damage, unless you object. That would be correct for the round in which it is cast although it still ignores damage on subsequent rounds. That makes things closer to correct. Obviously I can fix my personal copy, but that would get overwritten every time there's a change to BatFactors.
 
Heh. BatBrain is smarter than I am!

I decided to keep my consult script from using up combat items since some are necessary to beat the Tower and I didn't want to get finicky. At first I was going to check if contains_text(opts.id, "use ") to exclude items, but then I realized that checking opts.meat is a simpler method, so I put in while(i < count(opts) && opts.meat < 0) i += 1;

Today in the Orc Chasm my consult script started using my dictionary! BatBrain knows which combat items are reusable and I wasn't even thinking about that!
 
Last edited:
For the last few days I've been getting:

Bad monster value: "demon spirit of new wave" (BatBrain.ash, line255)
Consult script 'smartstasis.ash' not found.
You're on your own, partner.


on the 1st adv crashing me out of my scripts completely. I have commented the line out for now and everything seems to work fine, is that actually a monster? I can't find anything about it on the wiki and I don't recall having seen it before.
 
Back
Top