Saturday, September 3, 2011

Second Verse, Same as the First

Results of trial run: TPK.

Details: Lost to the monster that came from a trap door in the Sealed Cave. We went into the fight with all members nearly dead. Starting to sound familiar? I added more Recovery statements in for the Sealed Cave, and I'm just about ready to give up and add a Recovery interrupt.

Almost.

Take One for the Team

Results of trial run: TPK.

Details: Lost while fighting two Red Dragons for the Crystal Gauntlets.


We did come into the fight with one party member dead and one nearly dead, which put us at a serious disadvantage. I don't think that was what killed us, but I went back and put a couple more Recovery commands in our command queue for looting the Lunar Subterrain.

The problem here was that we managed to get 3 party members dead with 1 Red Dragon left, and spent all of our time bringing them back to life to get lasered down again. Occasionally we'd pull back up a bit, but it just wasn't quite enough.

The borg has a single battle strategy for this whole section of the game, and so far it's worked pretty well. Up until now, Rosa's part of the strategy has followed something similar to what we've done for most of the game:
  • If someone's dead, bring them back with Life or a Phoenix Down.
  • If someone's near dead, heal just them.
  • If someone's just kind of hurt, heal everybody.
For most of the end game boss fights, I wrote a custom strategy to manage them instead of using the default settings.

This strategy doesn't work for end-game stuff, and here's why: the real threats to the survival of the party are attacks that hit everybody, not big hits on single party members. We can handle one party member dying at a time pretty well. We get in trouble when a big group of them go down at once because they were near dead to begin with and the party got hit with an AOE.

Solution: Changed the strategy for this section. Now, if Rosa's going to do any healing, it's Cure 3 on all allies if a live party member is below 70% HP, or use a Phoenix Down otherwise. No more Cure 3 on individuals.

Two battle bug fixes

While I was testing out the new Zeromus strategy, I noticed that TimesPerBattle bug happening again, and finally figured out what was happening. It has nothing to do std::map or the used_commands cache. The class I'm using to store battle command information has two constructors, and one of them doesn't initialize the boolean that determines whether the command has been chosen for execution. Fixed!

I also had a couple of issues with party members missing the item/spell they were supposed to use and choosing something else instead. I was having this issue earlier in development and cranked up the cooldown between button presses, but apparently it still happens every once in a while. Instead of increasing the cooldown even more, I chose to rewrite the code that chooses button inputs to only queue one button press at a time while choosing a spell or item to use, and then re-evaluate what to do after each press. That prevents the occasional miss, and it also lets me turn the cooldown way down. The borg's battle commands are flying by again.

Back to the Training, Yeah!

Results of trial run: TPK.

Details: Lost to Zeromus.


This is a great example of a fight where a lot of little things could have gone better, but the screenshot above illustrates the final mistake we made.

Currently, the DeadAlly target (as well as most of the other options that target allies) will choose the first character in the party that meets the conditions, starting from the top of the list. In our current party arrangement, that means that if 4 characters are dead and one of them is Rosa, Rosa will be the last one brought back. This is very, very bad planning.

I could write some code that lets me prioritize which party members get raised first, but I'd like to avoid adding more code if it's not necessary to get what we need, and in this case, it's not; we can just arrange the party so the most important characters to raise are at the top.

Solution: Added commands to rearrange the party before the final battle to this order, from top to bottom: Rosa, Cecil, Kain, Edge, Rydia. I also made a few more adjustments to the battle strategy.
  • Rosa no longer fires off a one-time Shell on all allies. I think it might actually not help at all against Big Bang. This has been replaced with a one-time Haste on herself.
  • Kain now has a one-time command to use Spiderweb (Slow effect item) on first target. Ideally, we'd reapply Slow every time Zeromus wipes statuses, but I haven't written any code to determine enemy statuses, and I don't want to. That flirts with the boundary of what a human player would be able to tell about an enemy, and I'm trying to build this borg to only use information available to a human player. Still, getting Slow out once gives us a bit of an edge for the first part of the fight.
  • If all live party members are above 70% HP and someone is dead, Rosa will cast Life 2 on them instead of using a Phoenix Down (unless she doesn't have the MP; then she'll use the Phoenix Down). It takes a bit longer to cast than to use an item, but this is a fight where having someone go from dead to full HP is very useful.
  • If everyone is alive and at least one party member is below 30% HP, Rydia will summon Ashura instead of Bahamut. A bit of a long shot move since Ashura is so unpredictable, but in those crevices where the fight isn't faring well, it can help quite a bit. Might choose to bump this up above using Phoenix Down, but probably not: If Rosa is dead, I don't want to cast Ashura and hope that maybe she'll bring everybody back to life. I want Rosa back on her feet.

Valor of the Royal

Results of trial run: Battle state bug resulting in TPK.

Details: Lost during the level grind at Damycam, of all places.


I probably should have anticipated this the last time we had a problem with Gilbert. His default command here is to Fight, but he ran and hid because he's low on HP and now Show is the only option. So the borg is just pressing up repeatedly here until that one Mini Mage whittles the other two party members down. Then Gilbert comes out, gets off one attack, and succumbs to Hold and a couple more shots.

Solution: Added a top-priority conditional for Gilbert to come out of hiding whenever he has the Hide status for all parts of the game where we have Gilbert. It's a bit silly watching him run back and forth, but it means the other party members can take actions while Gilbert is running laps.

Friday, September 2, 2011

A Curious State

Results of trial run: Battle state bug.

Details: In the Ashura fight, Rosa kept rapidly switching between deciding to cast Reflect a second time and deciding that our once-per-battle command had already been used and she needed to choose another.

I'm not entirely sure why this was happening. As soon as I put logging statements around our used_commands counter, it stops happening and everything behaves as it should. I have a shred of a guess about using += or -= on a std::map<std::string, int>, but it still doesn't make any sense to me.

"Solution": Changed to using value = value + 1 instead of value += 1, and it stopped happening. Not at all convinced that this is the end of this particular problem, but I'm not sure what else to do with it at the moment.

Thursday, September 1, 2011

Not a Grenade, Horseshoe, or the Government

Results of trial run: TPK.

Details: Lost to Zeromus fight. The core of the problem was that Rosa was using a Phoenix Down when she should have been casting Cure 4. This wasn't a problem before because we had Cure 4 as a higher-priority conditional.

Solution: Added a conditional Cure 4 on top of the default Cure 4; now if any live party member is less than 70% on HP, Rosa will cast Cure 4, even if someone is dead. I ran this strategy several times to see how it works out, and I'm feeling pretty good about it.



While I was at it, I thought I would make Edge pull his weight, so I added support for the Dart command and added conditionals to have him throw all of our expensive weapons and Hellwinds during the fight.

Meaningless

Results of trial run: TPK.

Details: Died somewhere in Upper Babil running from fights. Probably was too weakened after an unfortunate SteelGolems fight. Yes, I need more Recovery calls during that section, and no, I still don't want to write a Recovery interrupt.

Solution: Using a tent soon after the SteelGolems fight, and sprinkled some Recovery calls from there until we butcher Edge's parents.

Wednesday, August 31, 2011

Bringing a Knife to a Pudding Feed

Results of trial run: TPK.

Details: The borg got into a fight it thought it was supposed to win in the Tower of Babil. This might have been fine, but Rydia was set to defend instead of summon, and we got into a fight with three of those white marshmallow things that are essentially immune to physical attacks. Lost at the end of a long, embarrassing fight.



The real problem here was that the borg thought it was supposed to fight in the first place. It had just entered a room with a monster-in-a-box treasure chest. The convention up to this point has been: Walk into a room with a monster-in-a-box, switch from running to fighting, open the chest and fight, switch from fighting to running. Most of the time this works, even though it picks up the occasional extra fight on the way to the chest. But if we get into one of these fiddly fights that require a specific strategy, that could put us in trouble.

Solution: Our GetLoot:[Item Name] and GetAccessibleLoot commands now switches to fighting mode when opening a treasure chest, and back to whatever our strife specibus was before after the treasure chest is open. Not only does this avoid trying to win unnecessary fights, but it also cleans up our command queue in a few places.

Tuesday, August 30, 2011

Couldn't You Just ... You Know ...

Results of Trial Run: Battle state bug.

Details: We were fighting one of the Behemoths on the way to Bahamut, and Rosa ran out of arrows. Her default command was still Aim, and we don't know how to cancel out of trying to use disabled menu options, so we just sat there while Rosa fiddled with her bow trying to figure out how to Aim with no arrows.

Solution: I don't feel like solving the disabled menu option problem; we're already avoiding it when a caster is unable to cast spells. Just changed Rosa's default command to Defend in this section.

Sleeping Under the Stars

Results of trial run: Inventory management problem.

Details: We had no tents left when we went to use one after the first Scarmiglione fight. I have no idea how this happened, but it's easy to fix; we're buying two tents earlier on now.

Really More of a Taupe Elf

Results of trial run: TPK.

Details: Lost to the Dark Elf. Tellah's first Tornado in the second stage failed. The second Tornado connected, but again, we were too busy using Phoenix Downs to deliver the final blow. We really need someone dedicated to striking during the second stage.

Solution: Cecil no longer casts Cure on Tellah during the second stage of the fight. Since we're still Covering him, this should work okay.

Der Dunkelelfen

Results of trial run: TPK.

Details: Lost to the Dark Elf.


Everything goes great during the first stage. Then he turns into a dragon and hits Tellah for more than his maximum HP. We do eventually get him alive long enough to deliver a Tornado, but then everyone is too busy using Phoenix Downs to poke the Dark Elf once in the ribs, and we get Dark Breathed down.

Tellah going down in one hit made me really sit back and reconsider what needs to happen to make this a reliable fight. I still really, really don't want to have to add another level grind before the the Golbeze fight.

Solution: So what else can we do? Well, maybe we can increase his armor rating. I added some commands back in Baron to buy him a Kenpou and Bandanna; that raises his armor a little bit, but not by a whole lot. However, there's another solution that I don't normally consider; Cecil's Cover command. Firing that off once at the beginning of the fight makes Tellah very, very hard to kill in stage two, assuming he isn't near dead from a Tornado in stage one.


A Journey of a Thousand Miles Often Ends Very, Very Badly

Result of trial run: TPK.

Details: Made it all the way to Zeromus and lost.


We simply got Big Banged more than the party can handle. No, stop it, this is serious. The real problem was that I thought I could cut down on MP usage by having Rosa default to Cure 3 and use Cure 4 if anyone is even moderately injured. This is insufficient; without a human behind the wheel or writing some code to estimate when the next Big Bang is coming, we need it to be Cure 4 all the time.
Solution: Rosa's default command is to use Cure 4 on everyone, and the conditional Cure 4 command is gone.

Slow and Steady Wins a Brace

Results of Trial Run: TPK.

Details: Lost against the Giant CPU.


Our strategy up to this point has been for Rydia to spam Bahamut, Cecil and Edge to attack, and Rosa and Fusoya to provide healing. Everyone is on Phoenix Down duty, and Cecil is in charge of using Ethers when casters get low on MP. Most of the time, this works just fine for this fight. The problem comes if the CPU chooses Rosa and Fusoya for its insta-kill attack before bringing it's bits back. If that happens, then we get into a situation where we bring Rosa and Fusoya back just in time to get lasered down by the Attack System, and the rest of the party withers away waiting for a healing spell that will never come.

Solution: After some deliberation, I decided to rewrite the strategy for this fight to the much more boring but much more reliable track: kill just the Defense System and then peck away at the CPU with melee, periodically healing from laser damage. It's boring, but it means the CPU never uses its insta-kill attacks because we never kill the Attack System until the end of the fight.

Packrat

Results of trial run: Inventory management failure.

Details: We tried to buy a Kotetsu for Edge at the Summonville weapon shop, but our inventory was full. Looks like we just got a little bit junk on this run than we normally do.

Solution: Added a SellGarbage command and a SortItems command before buying anything in Summonville. Also, it looks like we weren't loading the mid-game garbage list during the Summonville milestone, so I added that as well.

Monday, August 29, 2011

Traffic Jams

Results of trial run: Pathing/routing failure.

Details: This is where the borg got stuck:


The obvious problem is that our party is trying to walk North, and we've managed to pin an NPC between us and the stairway to the North. However, to really understand everything that's going on here, you're going to need to know more about how FF4 maps and mobile NPCs work.

Every map in FF4 that isn't an overworld map has a byte of layer information for each tile of the map. This layer info helps the game know which tiles you can walk on and when. To help visualize this, here's a picture of the save room in the Antlion's Den:


And here's an ASCII representation of the movement layers of that map:
                                 
   ###                           
  #,,,T#                         
 T,,__,_#                        
 #,,##_#                         
 #,##._.#                        
 #,.....T                        
  #.#.#.#                        
  #..S..#                        
  #.#.#.#                        
  #.....#                        
   ##.##                         
     +                           
                                 
  • Pound signs (#) are layer 0 - obstacles. These are never passable under any circumstances. The capital Ts are actually layer 0 as well, but I've marked them differently because they contain treasures.
  • Periods (.) and commas (,) are layers 1 and 2 respectively. Both can be walked on, but you can't step directly from layer 1 to layer 2 and vise versa. To get between them, you need to step onto layer 3, which is represented here by underscores (_). Layer 3 acts as a connecting layer - you can always step on and off of it.
  • Save points (S) are layer 11, but they are essentially always accessible.
  • Layers 17 and 19 represent transition squares - if you step onto one of these, you'll end up on a different map. I've chosen to mark these with plus signs (+) and greater than signs (>) respectively. Layer 17 is only accessible from layer 1. Layer 19 can be reached from anything.
  • There's one other layer, layer 5, that acts as a bridge layer. You can always step on a bridge layer from layer 1 or 2, but when you leave the bridge you have to step off onto the same layer you entered on. This allows bridges that can be walked over or under, with different rules for where you can walk for each.
So that's how the movement rules work.  Except that that's only how they work for the player, which is where our movement bug comes in.

When the borg chooses a path to a destination and starts moving down it, at every step it checks for NPCs in the way. If someone's in the way, it checks to see if the NPC would get pinned in the players path if we took the next step. If so, it politely waits until the NPC moves out of the way before moving forward. Without this politeness, all too often the borg would get itself into situations like this:


Our destination is the square the NPC is standing on, they have no way to get out of the way, and the borg doesn't know how to solve the situation. The borg eventually gives up and panics.

The problem the borg ran into in this last run is that NPCs don't have the same movement rules as the player does. NPCs are restricted to either layer 1 or layer 2; they can't transition to any layer other than the one they started on, including layer 3. So when you have a map like the top floor of Summonville, whose movement layers look like this:

             ########                  
        #####........#                 
       #.......#####.##                
      T........#   #...#               
    ##..######>#   #...##              
   #...#     #.#######...#             
  #....#     #.......#...#             
  #...#       ######.###..##           
   #.#              T  #....#    
   #_#                 #....#    
   #.T####              ##.#           
   #......#####          #_#           
    #..........#     ####T.#           
    #..........#    #......#           
     ###....>..#   ##......#           
        #......####>#......#           
         ##.####....##.####            
          #_####....##_#               
          #.............#              
           ###.........#               
             #........#                
              ####T###              


...there's a few opportunities scattered about for the borg to pin an NPC in a place that it believes the NPC can escape.

Solution: The borg method that looks for a way for an NPC to escape the player's path, pathToAvoidPlayer(), now starts with all layer 3 tiles on the current map blacklisted. This gets us 99% of the truth for NPC mobility, and should help us avoid any more pinned NPCs.

Mute Shenanigans

Results of trial run: TPK.

Details: Lost the Golbeze fight. Rosa had Silence carried over from the Calcabrena fight, and even though we had purchased 10 Echo Herbs right before this, there were none in our inventory.

Problems with the Calcabrena/Golbeze fights are a bit harder to debug. With most fights, I record a save state right as the fight begins, so if there's a problem and we lose, I can replay with the current strategy and see exactly what happened. This one, however, is a two-part fight, and the real cause of the problems is almost always in the Calcabrena fight, which gets trampled over when the Golbeze fight starts. This situation is also unusual because the Golbeze fight is one of those exceptions where the borg generally fares better than a normal player. It's actually possible to have all 4 party members back on their feet, and have Yang get in a strike before Rydia steps into the battle, all because the borg is able to input commands so quickly. So if the borg loses here, it's because we ended the Calcabrena fight in a rotten situation.

The root of the problem lies in how status flags are handled in FF4. Some status ailments are wiped when a character dies. Others, it turns out, are not, and Silence is one of those. As far as I can tell, two things were happening:
  • When Rosa would get killed in the fight and then brought back with a Phoenix Down, she still had the Silence status and couldn't heal party members.
  • In the strategy for these fights, our conditional battle command to use an Echo Herb on anyone with the Silence status comes before the command to use a Phoenix Down on dead party members. I can imagine Cecil, Kain, and Yang all huddled around Rosa's lifeless body, urgently mashing Echo Herbs into her mouth and manually moving her jaw to make chewing motions, all the while whispering: "Please. Please heal us." All with a horrifying, 12-foot tall animated doll standing behind them. Unfortunately, using an Echo Herb on a dead character doesn't remove Silence from them, but it does consume the item.
Solution: The AllyWithStatus and AllyWithoutStatus targets now only look at live characters.

No One to Blame But the Toroians

Results of trial run: TPK.

Details: Lost the Magus Sisters fight. Don't have any single dominant cause for the loss. We ran out of Hi-Potions. Tellah's reflect casting killed a lot of allies. Things just didn't go very well.

There's a few different ways we could solve this:
  • Teach Tellah/Cecil to cast healing spells on injured party members that don't have Reflect at the moment.
  • Level grind before the fight.
  • Come up with a source of more Hi-Potions.
I really, really want to avoid level grinding for the mid-game boss streak. They're all beatable without it. I'd also like to avoid commands in battle that take more than one condition; I'm sure I'll want them eventually, but I'd like to see how far I can get just allowing one condition. And finally, Hi-Potions can't be purchased at this stage in the game, so I'm stuck with the supply I find in treasure chests or from enemy drops. Farming them would essentially amount to level grinding.

Solution: After reviewing the atlas for sources of Hi-Potions, I made a discovery; we were never actually looting the room in Toroia with like 16 treasure chests, and it has 2 more Hi-Potions. Added that to the command queue after beating the Dark Elf. That should give us a bit more survivability for that fight.

Sunday, August 28, 2011

L'elfe Noir

Results of trial run: TPK.

Details: Lost to the Dark Elf. Random seeds lined up to create a beautiful test case for the current strategy; the elf almost exclusively hammers Tellah throughout the fight, and Tellah's first casting of Tornado failed in the second stage. Couldn't pull it together after that.

Solution: Cecil now has commands to cast Cure on Tellah during the second stage. It works for this iteration, but I'm not convinced this will be enough for this fight.