Showing posts with label bug_report. Show all posts
Showing posts with label bug_report. Show all posts

Tuesday, October 4, 2011

Problems Worth and Not Worth Solving

I've added the ability to mark a cutscene as a repeatable transition, and I've tought the borg to avoid these when pathing within a map, as well as to use them when navigating to a destination map. Marking a cutscene as such still happens manually, and will probably stay that way.



There's a lot of little fiddly things in this game that don't show up often enough to justify writing code to handle them. For example, there's a few places in the Ship Graveyard where you can hop from stone to stone to reach a destination. This just about the only place in the game where they use this mechanic, so I just included a few manual movement commands to get through those parts. Climbing the vines in the Worus Tower counts as a cutscene, but it only triggers if you walk into the square from the proper direction, *or* if you walk in from the side and then bump the right direction. This happens in more places, but it'll be a big pain to teach the borg that distinction, so I'm leaving that as well. If it happens to walk into a cutscene from the wrong direction, I'll tell it to walk next to the cutscene and add a manual movement command to approach properly.

Other stuff I'm willing to support with new code. FF5 has squares that quickly push you in a specific direction (such as the waterfalls in Worus and, I suspect, sand in the Quicksand Desert). These aren't cutscenes or transitions; they just happen. I haven't implemented any notion of what direction they push you, but I am giving these squares their own thumbnail in the atlas and treating them as obstacles until I find a place where I need to use them. Then we'll decide if anything more involved needs to be written.

There's also an odd quirk with NPCs when entering areas that show a title; NPCs don't seem to get marked as stationary until after that title window gets closed, so if the cartographer walks into a mostly explored map and the only unvisited squares are under stationary NPCs, it'll choose a path to that square, assuming at the time that the NPC could be pushed out of the way. I'll probably teach the Cartographer and Navigator to press A or B to close the title window before choosing their next path.
 

Sunday, September 4, 2011

Race Conditions

Results of trial run: Battle state bug.

Details: Got stuck in Edge's Ninjutsu menu during the fight with Ashura.



The locations in memory that store character stats in battle, like current HP and MP, don't change at exactly the same time as they do on-screen. When an enemy attacks you or you heal yourself, you have to wait for the whole animation to be complete before you see the new balance for your hit points. However, in memory, this balance has already been changed to reflect the new value when the animation starts. There's probably some other location in memory that stores the displayed HP/MP values separately, but I haven't tried to track it down.

The consequence of all this is that the borg makes decisions based on what it sees for character HP and MP, which may not be reflected in the game interface yet. Most of the time this is fine, but if you time it just right, you can get into a situation where a character is given orders to cast a spell right before we see another character use an Ether on them, because the borg thinks they have enough MP.

This brings us to the other half of the problem: once you're in the menu to choose a spell, which spells are disabled and the available MP won't update until you back out of the menu and choose it again. This brings us to the screenshot above. The borg thinks Edge has enough MP to use Raijin because Cecil is using an Ether on him now, but he makes it into the Ninjustu menu before the Ether animation is finished. The borg still thinks it can cast Raijin, and technically it's right, but it doesn't know to back out of the menu and try again, so it just sits there mashing the A button.

Solution: There's at least two ways I could approach this problem: I could find where the displayed HP/MP values are stored in memory, or I could figure out how to tell if a menu option is disabled, and if our choice is disabled we bail on the command and choose a different one. I chose the second option; that may solve a few other potential problems as well. I spent some time digging around the game's RAM and where spell statuses are stored for battle. There's some other interesting information there that might be worth deciphering in the future.

Saturday, September 3, 2011

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.

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.

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.

Monday, August 29, 2011

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.

Saturday, August 27, 2011

It Burns

Results of trial run: TPK.

Details: Lost the Flame Dog monster-in-a-box fight. Tellah didn't have enough juice to cast Ice 3 or heal party members, and everything went to hell in a handbasket. The real problem was that he blew his MP casting Cure 4 twice right before the fight.

Solution: This seemed like a good time to teach the Recovery state when to cast Cure X on an individual and when to cast it on the whole party. The rule is now: If more than one party member is below the HP threshold, cast the given healing spell on everybody. If only one is below the threshold, just cast it on them. We could still be smarter about it and not always use the most expensive Cure spell available to the caster, but this will still make a big difference, and in most parts of the game, it won't really matter that much.

Can't Cure Cowards

Results of trial run: Battle state bug.

Details: Young Rydia saw that Gilbert was very, very low on HP during our Damycam level grind, but he managed to go into hiding before she got her command off. The borg had been sitting there furiously trying to move the cursor to Gilbert's position for a few hundred thousand frames when I came to check on it.

It surprised me that this bug hadn't come up before for Gilbert; his cowardice is almost as legendary as his impotence as a fighter. I certainly had run into a similar problem with Kain a while ago and adjusted targeting to compensate. However, after some thought I decided this really would be a rare occurrence with our current game strategy; Gilbert has a low chance of being in critical condition during the level grind, he just defends during the Antlion fight, and I think after his fateful solo fight in Kaipo he doesn't automatically hide anymore.

Solution: I already had taught the borg that anyone with the Jump flag isn't targetable (just Kain in this game). Adding Hide to that logic was pretty trivial.

Birds, Pursuing of

Results of trial run: Pathing/routing bug.

Details: So I come back to check how the borg is doing this time around, and I'm greeting with this:



This bird is just sitting there staring at me, as if to say, "What now, asshole?"

It turns out that we managed to catch a regular Chocobo instead of a White Chocobo right at the end of the Mount Ordeals level grind. Early on in development I expected this to happen a lot, but this is the first time we've actually talked to the wrong bird.

Solution: Refactored the movement state a bit. Commands to bump in a certain direction now have a much shorter cooldown than normal movement commands. In the process of refactoring, I also discovered a long-present bug with cooldowns that was probably responsible for jumping the gun on movement between commands. To test this, I set up commands to walk into a Chocobo forest and run laps between talking to the White Chocobo and touching a point in the corner of the clearing. We're much more successful at catching fast moving birds now; before we would often have to follow it around a few squares before managing to talk while it's still in front of us.

Dreaded whitespace

Results of trial run: Game crashed.

Details: Game consistently crashed right after getting to the cutscene where the Red Wings bomb Damycam. After poking around for a while, I finally discovered the problem; there was a trailing space after the filename for the Damycam command queue. Up to this point, my command queue parser hadn't been stripping white space from the end of commands, and hadn't been verifying file handles before trying to use them. I ran into the problem a few times before, but usually caught it pretty early.

Solution: Finally broke down and wrote code to strip whitespace from the ends of commands. Also, checking to see if we were actually able to open the target file of LoadGoals, and panicing if we can't.

Can't get to Chocobo forest from there

Results of trial run: Pathing/routing failure.

Details: The borg made it to the Underworld level grind, and then tried to find a path to the Mount Ordeals Chocobo Forest. Apparently, I failed to call ClearGrindRecovery after the last level grind. Added that in, and reviewed the other level grinds to make sure they clear their recovery loop when they're done.