Turn Order Calculations

If you are stuck in the Dunan Unification Wars; or wish for more details on the gameplay systems, this is the place.
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

IDA Pro has FLIRT signatures (Fast Library Identification and Recognition...something) that come with it for PSX development libraries, which will identify and name functions like rand, the padread, and so on with a fairly good success rate. It won't ID anything specific to the game, including any wrappers they might build around those library functions.

If you're looking at my disassemblies, the names and comments are mostly mine. Since Konami wasn't terribly fussed about refining their code, my names are often bookmarks based on what I was doing at the time, e.g., they have a function that checks, retrieves, sets, and increments or decrements potch based on a function parameter. When I initially found it, I was only worried about the incrementing/decrementing, so I named it "UpdatePotch?" or something like that. It was obviously doing a lot more, but I didn't care about anything other than being able to find it again easily.

Edit: Omnigamer, are you looking at the Japanese version or what? Your addresses don't seem to coincide with the NTSC version from what I can see.
Metsu
Posts: 66
Joined: Wed Apr 15, 2015 9:27 am

Re: Turn Order Calculations

Post by Metsu »

Pyriel wrote:IDA Pro has FLIRT signatures (Fast Library Identification and Recognition...something) that come with it for PSX development libraries, which will identify and name functions like rand, the padread, and so on with a fairly good success rate. It won't ID anything specific to the game, including any wrappers they might build around those library functions.
That still seems reason enough for me to download it and try it! Thanks for the info.

Yeah, I've never been able to get psxfin's code breakpoints to work on anything that I needed haha. In battle and mini-games especially. But oh well.

You should see my notes on assembly when I jot them down to figure out what is going on. Looks like a madman's ramblings. It's only pertinent for me for about a week and then I forget how it works and reading through it only helps if I'm stepping through it too haha.
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

Is this what you were looking at Omnigamer?

Code: Select all

RAM:8003F6EC                 lbu     $v0, 0x340($a0)
RAM:8003F6F0 nop
RAM:8003F6F4 slt $v0, $a1, $v0
RAM:8003F6F8 beqz $v0, loc_8003F754
RAM:8003F6FC addiu $a0, 0x354
RAM:8003F700 sll $v0, $a1, 5
RAM:8003F704 subu $v0, $a1
RAM:8003F708 sll $v0, 2
RAM:8003F70C addu $v0, $a0
RAM:8003F710 lhu $s1, 0x3A($v0) # Applies a 30% bonus to speed for later calcs (not saved)
RAM:8003F714 lw $v0, 0x58($v0)
RAM:8003F718 lui $v1, 0x1000
RAM:8003F71C and $v0, $v1
RAM:8003F720 beqz $v0, loc_8003F864
RAM:8003F724 li $v1, 0x51EB851F
RAM:8003F72C sll $v0, $s1, 4
RAM:8003F730 subu $v0, $s1
RAM:8003F734 sll $v0, 1
RAM:8003F738 mult $0, $v0, $v1
RAM:8003F73C sra $v0, 31
RAM:8003F740 mfhi $a2
RAM:8003F744 sra $v1, $a2, 5
RAM:8003F748 subu $v1, $v0
RAM:8003F74C j loc_8003F864
RAM:8003F750 addu $s1, $v1
RAM:8003F754 # ---------------------------------------------------------------------------
RAM:8003F754
RAM:8003F754 loc_8003F754: # CODE XREF: sub_8003F6D4+24j
RAM:8003F754 lui $v1, 0x6666
RAM:8003F758 sll $v0, $a1, 5 # Get battle slot offset (X * 0x7C)
RAM:8003F75C subu $v0, $a1
RAM:8003F760 sll $v0, 2
RAM:8003F764 addu $s3, $v0, $a0
RAM:8003F768 lhu $s1, 0x3A($s3) # Speed
RAM:8003F76C li $v1, 0x66666667 # Divide by 2.5 or get ~40%?
RAM:8003F770 mult $0, $s1, $v1
RAM:8003F774 li $v0, 0x800D4C2C # rand()
RAM:8003F77C lui $s2, 0x8005
RAM:8003F780 sw $v0, dword_800536C0
RAM:8003F784 sra $v1, $s1, 31 # sign bit
RAM:8003F788 mfhi $a2 # 40%
RAM:8003F78C sra $s0, $a2, 2 # Speed * .4 / 4
RAM:8003F790 jalr $v0
RAM:8003F794 subu $s0, $v1 # sign
RAM:8003F798 sll $v1, $s0, 1 # Speed * .4 / 4 * 2
RAM:8003F79C addiu $v1, 1 # (Speed * .4 / 4 * 2) + 1
RAM:8003F7A0 mult $0, $v0, $v1 # Result * random value
RAM:8003F7A4 mflo $v0 # tmp
RAM:8003F7A8 bgez $v0, loc_8003F7B4
RAM:8003F7AC addu $v1, $s1, $s0 # speed + (speed * .4 / 4) ... speed + 10%
RAM:8003F7B0 addiu $v0, 0x7FFF # Keep tmp +
RAM:8003F7B4
RAM:8003F7B4 loc_8003F7B4: # CODE XREF: sub_8003F6D4+D4j
RAM:8003F7B4 sra $v0, 15 # only keep upper 17-bits. Reduction could be as much as 3.
RAM:8003F7B8 subu $s1, $v1, $v0
This is from BP0_SEC.BIN because it happened to be more convenient for me, but the code is replicated in BP0_FST.BIN. It does your check for 0x10000000 being set, and operates extensively on speed. If that bit is set, it applies a flat 30% bonus to speed, but without the bit it's either the actual speed value or the 10% with random fudge factor. However, I think the latter might apply only to enemies. If it doesn't, and it's applied randomly to every character that could explain the oddities you're seeing. Flik gets a flat 30% with some truncation involved, Riou and Luc get 10% with maybe a little lopped off, but not enough to make them slower than Flik.

Edit: OK, after debugging a few battles, I'm pretty sure 0x340($a0) (0x80145860 + 0x340: 0x80145BA0) contains the ordinal of the first battle position occupied by an enemy. We already knew $a1 was the slot number, so that first slt operation is just checking to see if it's dealing with the player's party or an enemy.

It redoes that calculation all the time, since it doesn't save the results off anywhere. It seems to do it for turn order, and then again when it needs to compare speeds for dodges and so forth, which could be a little interesting if the random penalty lopped off more.

Edit 2: Almost forgot. This could be a little helpful.

Code: Select all

80145BA4 - In-battle character stats 
0x7C per character

Address Size Offset Desc
80145BA4 1 0x0 Character ID
80145BA5 9 0x1 Character Name (including null)
80145BAE 6 Unknown bytes
80145BB4 2 0x10 Maximum HP
80145BB6 2 0x12 Current HP
80145BB8 2 0x14 Attack
80145BBA 2 0x16 Magic
80145BBC 2 0x18 Defense
80145BBE 2 0x1A Magic Defense
80145BC0 2 0x1C Tech
80145BC2 2 0x1E Speed
80145BC4 2 0x20 Luck
80145BC6 14 Unknown bytes or padding
80145BD4 2 0x30 Attack (this and the below are probably "current" factoring in items' and spells' effects)
80145BD6 2 0x32 Magic
80145BD8 2 0x34 Defense
80145BDA 2 0x36 Magic Defense
80145BDC 2 0x38 Tech
80145BDE 2 0x3A Speed
80145BE0 2 0x3C Luck
Edit 3: OK, here's a suggestion: Throw a breakpoint in on 0x80040270 (FST.BIN) or 8003FA30 (SEC.BIN after the first round) or trace the surrounding function and see if the order seems wanky there based on your earlier test cases. This just looks like a pure comparison of speed, so once the different boosts and other factors are applied in the function I posted before, this shouldn't be anything but a top down stack of characters by speed. About 0x100 down from the last two addresses I mentioned (in each file) it looks like the character can be passed up, but I'm not sure what the criteria are. It's not speed.

If you can upload your save files that would help.
Omnigamer
Posts: 324
Joined: Wed Feb 13, 2013 11:48 am

Re: Turn Order Calculations

Post by Omnigamer »

I can see about grabbing the save files; I'm not sure how portable they are between emulators and the digital PSN versions, though. They currently exist on Ps3 and PSTV/Vita.

The 10% fudge factor makes sense for a variety of other things, mainly just noting that occasionally enemies will go faster than their listed speed would indicate. I believe there's also variation with attack strength on both sides, but that seems to have other variance beyond just 10%. So far everything makes sense, but the confusion comes in for Rune Unite attacks. Some value that is persistent in saves ends up determining whether different unite attacks get a speed boost. I tested this across the different files by just setting up strict orders; Luc with the highest SPD would cast something without a boost, and then Riou and Flik would perform a Rune Unite off of Rina. In some files, this consistently results in them going before Luc, indicating a boost occurs; in other files, Luc always goes first.

As best as I can tell, none of the Rune Unites are supposed to have the bonus innately, but due to whatever reason it can be applied to them. That's what I'm trying to figure out, so I'm hoping to find the code that actually makes the determination about whether to set the 0x10000000 bit. I've narrowed down where it happens at least, but it seems to be based on some constant that comes from ???. This is what I meant about the 59 separate blocks; each one sets this constant, but none are too much alike. I also can't tell where execution is before it goes into any of them.
Metsu
Posts: 66
Joined: Wed Apr 15, 2015 9:27 am

Re: Turn Order Calculations

Post by Metsu »

You can convert a PSV save into any standard save file pretty easily, just not the other way. PSV only has a header with some encrypted number that needs to be removed to make it a standard game save.
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

Code: Select all

RAM:8004D5A4 ApplyStatusEffects?:                     # CODE XREF: sub_80039E40+294p
RAM:8004D5A4 # RAM:8003A37Cp ...
RAM:8004D5A4
RAM:8004D5A4 var_20 = -0x20
RAM:8004D5A4 var_1C = -0x1C
RAM:8004D5A4 var_18 = -0x18
RAM:8004D5A4 var_14 = -0x14
RAM:8004D5A4 var_10 = -0x10
RAM:8004D5A4 var_C = -0xC
RAM:8004D5A4 var_8 = -8
RAM:8004D5A4
RAM:8004D5A4 addiu $sp, -0x30
RAM:8004D5A8 sw $s3, 0x30+var_14($sp)
RAM:8004D5AC move $s3, $a0
RAM:8004D5B0 sw $s1, 0x30+var_1C($sp)
RAM:8004D5B4 move $s1, $a1
RAM:8004D5B8 sw $s5, 0x30+var_C($sp)
RAM:8004D5BC move $s5, $a2
RAM:8004D5C0 sw $s2, 0x30+var_18($sp)
RAM:8004D5C4 addiu $s2, $s3, 0x354
RAM:8004D5C8 sw $s0, 0x30+var_20($sp)
RAM:8004D5CC la $v0, dword_8004FE8C
RAM:8004D5D4 sll $v1, $s5, 2
RAM:8004D5D8 addu $v1, $v0
RAM:8004D5DC sw $ra, 0x30+var_8($sp)
RAM:8004D5E0 sw $s4, 0x30+var_10($sp)
RAM:8004D5E4 lbu $a0, 0x340($s3)
RAM:8004D5E8 lw $s4, 0($v1) # Get the status effect mask (idx doesn't necessarily correspond to bit number)
RAM:8004D5EC slt $a0, $s1, $a0
RAM:8004D5F0 beqz $a0, caseApply
RAM:8004D5F4 addiu $s0, $s3, 0x96C
RAM:8004D5F8 move $a0, $s3
RAM:8004D5FC li $a1, 0x1B # Master Robe
RAM:8004D600 jal SearchArmorAcc # Master Robe
RAM:8004D604 move $a2, $s1
RAM:8004D608 # ---------------------------------------------------------------------------
RAM:8004D608 bnez $v0, swApplyOrPrevent
RAM:8004D60C addiu $v1, $s5, 0xFFFF
RAM:8004D610 move $a0, $s3
RAM:8004D614 li $a1, 0x2C # Earth Shield
RAM:8004D618 jal SearchArmorAcc # Earth Shield
RAM:8004D61C move $a2, $s1
RAM:8004D620 # ---------------------------------------------------------------------------
RAM:8004D620 beqz $v0, caseApply
RAM:8004D624 addiu $v1, $s5, 0xFFFF
RAM:8004D628
RAM:8004D628 swApplyOrPrevent: # CODE XREF: ApplyStatusEffects?+64j
RAM:8004D628 sltiu $v0, $v1, 0x17
RAM:8004D62C beqz $v0, caseApply
RAM:8004D630 la $v0, off_8002BF7C # Only two possibilities in table. Jump to the bypass, or to apply.
RAM:8004D638 sll $v1, 2
RAM:8004D63C addu $v1, $v0
RAM:8004D640 lw $v0, 0($v1)
RAM:8004D644 nop
RAM:8004D648 jr $v0
RAM:8004D64C nop
RAM:8004D650 # ---------------------------------------------------------------------------
RAM:8004D650
RAM:8004D650 caseBypass: # DATA XREF: RAM:off_8002BF7Co
RAM:8004D650 # RAM:8002BF88o ...
RAM:8004D650 li $v0, 0x800D4CBC
RAM:8004D658 lui $v1, 0x8005
RAM:8004D65C sw $v0, dword_80054988
RAM:8004D660 lui $a0, 0x8003
RAM:8004D664 jalr $v0
RAM:8004D668 la $a0, aDaitiNoYoroi # "daiti no yoroi \n"
RAM:8004D66C j loc_8004DB68
RAM:8004D670 nop
RAM:8004D674 # ---------------------------------------------------------------------------
RAM:8004D674
RAM:8004D674 caseApply: # CODE XREF: ApplyStatusEffects?+4Cj
RAM:8004D674 # ApplyStatusEffects?+7Cj ...
RAM:8004D674 addiu $v1, $s5, 0xFFFF # Switch for 33 possible effects
RAM:8004D678 sltiu $v0, $v1, 0x21
RAM:8004D67C beqz $v0, loc_8004DB48
RAM:8004D680 la $v0, off_8002BFDC
RAM:8004D688 sll $v1, 2
RAM:8004D68C addu $v1, $v0
RAM:8004D690 lw $v0, 0($v1)
RAM:8004D694 nop
RAM:8004D698 jr $v0
RAM:8004D69C nop
I think this function is primarily responsible for applying status effects, including limited speed boost you're interested in. There are one or two other places it might be done, and a few where the bit can be unset as well. The status effect you're interested in is index 0x1E. There's one place where that value is passed explicitly, and perhaps 8 calls where the status value is apparently dynamic.

Here's the table of effect masks:

Code: Select all

RAM:8004FE8C dword_8004FE8C: .word 0                  # DATA XREF: ApplyStatusEffects?+28o
RAM:8004FE8C # sub_8004DB90+8o
RAM:8004FE90 .word 1
RAM:8004FE94 .word 2
RAM:8004FE98 .word 3
RAM:8004FE9C .word 4
RAM:8004FEA0 .word 8
RAM:8004FEA4 .word 0x100
RAM:8004FEA8 .word 0x200
RAM:8004FEAC .word 0x800
RAM:8004FEB0 .word 0x1000
RAM:8004FEB4 .word 0x2000
RAM:8004FEB8 .word 0x20000
RAM:8004FEBC .word 0x40000
RAM:8004FEC0 .word 0x80000
RAM:8004FEC4 .word 0x80
RAM:8004FEC8 .word 0x4000
RAM:8004FECC .word 0x10000
RAM:8004FED0 .word 0x20
RAM:8004FED4 .word 0x40
RAM:8004FED8 .word 0x200000
RAM:8004FEDC .word 0x400000
RAM:8004FEE0 .word 0x10
RAM:8004FEE4 .word 0x400
RAM:8004FEE8 .word 0x8000
RAM:8004FEEC .word 0x100000
RAM:8004FEF0 .word 0x800000
RAM:8004FEF4 .word 0x1000000
RAM:8004FEF8 .word 0x2000000
RAM:8004FEFC .word 0x4000000
RAM:8004FF00 .word 0x8000000
RAM:8004FF04 .word 0x10000000
RAM:8004FF08 .word 0x20000000
RAM:8004FF0C .word 4
RAM:8004FF10 .word 4
All this is from BP0_FST.BIN.

I'll do more testing this afternoon.

So far, the only way I've seen this triggered is:

Code: Select all

RAM:8003F560 loc_8003F560:                            # CODE XREF: RAM:loc_8003F53Cj
RAM:8003F560 jal sub_8003EC10
RAM:8003F564 move $a1, $s4
RAM:8003F568 # ---------------------------------------------------------------------------
RAM:8003F568 beqz $v0, loc_8003F57C
RAM:8003F56C move $a0, $s5
RAM:8003F570 move $a1, $s4
RAM:8003F574 jal ApplyStatusEffects? # Maybe?
RAM:8003F578 li $a2, 0x1E
It checks the result of that first called routine there and then applies status 0x1E if it returns a non-zero value. This appears to be the relevant bit of that routine:

Code: Select all

RAM:8003EC10  # Attributes: noreturn
RAM:8003EC10
RAM:8003EC10 sub_8003EC10: # CODE XREF: RAM:loc_8003F560p
RAM:8003EC10
RAM:8003EC10 var_10 = -0x10
RAM:8003EC10 var_C = -0xC
RAM:8003EC10 var_8 = -8
RAM:8003EC10
RAM:8003EC10 addiu $sp, -0x20
RAM:8003EC14 move $a2, $a0 # Save ptr battle data
RAM:8003EC18 addiu $v0, $a2, 0x354 # add 0x354 (going after charas)
RAM:8003EC1C sll $v1, $a1, 5 # Get slot offset ($a1 * 0x7C)
RAM:8003EC20 subu $v1, $a1
RAM:8003EC24 sll $v1, 2
RAM:8003EC28 sw $s1, 0x20+var_C($sp)
RAM:8003EC2C addu $s1, $v1, $v0 # save ptr to chara
RAM:8003EC30 sw $ra, 0x20+var_8($sp)
RAM:8003EC34 sw $s0, 0x20+var_10($sp)
RAM:8003EC38 lb $v1, 0x50($s1)
RAM:8003EC3C li $v0, 2 # Is action magic?
RAM:8003EC40 bne $v1, $v0, loc_8003ECB4
RAM:8003EC44 li $v0, 3
RAM:8003EC48 li $v0, 0x8006F4D0 # Yes
RAM:8003EC50 move $a0, $0
RAM:8003EC54 sll $v1, $a1, 2
RAM:8003EC58 addu $v1, $a2
RAM:8003EC5C lw $v1, 0x954($v1)
RAM:8003EC60 lb $a2, 0x53($s1) # Get rune slot used?
RAM:8003EC64 li $a1, 2
RAM:8003EC68 addu $v1, $a2 # Add it to the offset into main chara array
RAM:8003EC6C lbu $a2, 0x15($v1) # Get actual rune id
RAM:8003EC70 lui $s0, 0x8005
RAM:8003EC74 jalr $v0
RAM:8003EC78 sw $v0, dword_80054988
RAM:8003EC7C lb $v1, 0x51($s1) # Get spell # for rune
RAM:8003EC80 lui $a1, 0x8007
RAM:8003EC84 addu $v0, $v1 # Add it to the offset into Rune data
RAM:8003EC88 lbu $a0, 0x38($v0) # Get spell ID?
RAM:8003EC8C li $a1, 0x80074784
RAM:8003EC90 jalr $a1 # Get spell data
RAM:8003EC94 sw $a1, dword_80054988
RAM:8003EC98 lbu $v0, 0x36($v0) # Load fx flags
RAM:8003EC9C nop
RAM:8003ECA0 andi $v0, 0x10 # Does spell get a boost?
RAM:8003ECA4 beqz $v0, loc_8003ED30 # No, branch to set zero and exit
RAM:8003ECA8 li $v0, 1 # Yes, set 1 and jump to exit
RAM:8003ECAC j loc_8003ED34
RAM:8003ECB0 nop
So, if the character is doing magic/using a rune and the spell indicates a boost (which you can see as the second unknown byte with value 0x10 below), then the character will get one.

Code: Select all

000C	Healing Wind
Fully heals 1 ally
Damage: 7D00
Element:Wind
Unknowns: 23 10 00 00 00 00
I have the hero, Luc and Mazus in my party. Mazus is slowest by a fair margin, but he somehow went before Luc when casting Blazing Camp. So far, I've only managed to get the turns to go awry once with my setup. I wasn't prepared for it at that moment, so I've no idea what the state of anything was.

Edit 2: Something just occurred to me. When you use a Rune Unite, rather than storing the character's Rune slot and the spell slot chosen, it stores the Rune slot and the actual spell index of the unite. So rather than left hand, Flaming Arrows ordinal (2, 0) it stores left hand, Blazing Camp index (2, 3A). I didn't see any special handling in the routine, and just assumed it must happen elsewhere. I just remembered I'm dealing with Konami and this game, so maybe it seems intermittent because it's actually trying to find spell #58 (from 0) on the Fire Rune. I know Runes and item data are loaded on an as-needed basis, and assuming it even stays within the bounds of those arrays it could still pick up random garbage.

I watched an execution, and that's exactly what happens. I have Mazus cast Blazing Camp off a Thunder Rune and it tries to get spell 0x3A on the rune, which pushes it out into garbage padding or data used for another purpose. If it happens to pick up a byte that indicates a spell where 0x10 is set, he'd get a boost.

Feel free to double-check me. As far as I'm concerned, this mystery is solved and it's another bug.
Omnigamer
Posts: 324
Joined: Wed Feb 13, 2013 11:48 am

Re: Turn Order Calculations

Post by Omnigamer »

That makes sense then. Really unfortunate that there's no consistency to it; I'll just have to keep planning strategies around boost-agnostic ordering. I'll poke around and see if there's anything in particular that gets loaded to those addresses, but you're right in that it's just a silly bug and most people will never even know it happened. Thanks for digging in!
Last edited by Omnigamer on Thu Apr 16, 2015 4:57 pm, edited 1 time in total.
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

I tested with people who had both runes equipped because I assumed that was your setup. I didn't watch what happens when it's a two-person unite, but based on previous analyses that should be true. The passive participant has their action cancelled.
KFCrispy
Global Admin
Posts: 6176
Joined: Wed Jun 30, 2004 3:29 pm

Re: Turn Order Calculations

Post by KFCrispy »

wow that's some crazy bug! you guys are doing some great investigations.
User avatar
ninjaluc79
Posts: 867
Joined: Tue Sep 13, 2005 10:45 pm
Location: The Island Nations
Contact:

Re: Turn Order Calculations

Post by ninjaluc79 »

So from the data above, what is the turn order calculation formula, then?
It's not all about knowledge, but it helps.
KFCrispy
Global Admin
Posts: 6176
Joined: Wed Jun 30, 2004 3:29 pm

Re: Turn Order Calculations

Post by KFCrispy »

something about the fact that the code tries to look up a rune unite spell as if it's the 5th spell of the rune, and since it can't find it, it retrieves garbage and may or may not add a speed boost.....
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

I didn't look at the calculation, aside from seeing that 1.3x is indeed the multiplier for a restorative spell or buff. I imagine whatever's already been reported is correct, with the quirk that Rune Unites can receive the 1.3x multiplier at random times.

It's not that it looks for the 5th spell. The Rune unites are 0x38 or 0x37 to 0x3C in the array of spells. It replaces (rune index, spell number) with (rune index, spell index). At minimum, it would be looking for the 56th spell on the Rune, which is why what it retrieves is a crapshoot. After the game gets that value, it's used to retrieve data from the spell array, and it could realistically run way beyond that as well. It's sort of like if you had a warehouse system where shelves were numbered sequentially, but also by aisle and row because it's easier for people to find their way around. Then somebody gave you aisle and shelf number, and is amazed when the wrong item comes back. For good measure, Konami makes this mistake two different times while performing the same search, so instead of retrieving a Roomba from the second shelf in the thirtieth aisle, our poor warehouse worker is two states over, stealing kitty litter from Wal-mart because that's where the order sheet sent him.
Omnigamer
Posts: 324
Joined: Wed Feb 13, 2013 11:48 am

Re: Turn Order Calculations

Post by Omnigamer »

The actual formula is simply a comparison of speeds. What is better understood now though is how enemy speed behaves, as well as what spells give a modification. If you want a formula close to how other formulas are presented on the site, here you go:

Character SPD = SPD * speed_mod
Enemy SPD = SPD * random(0.9:1.1)

speed_mod = 1.3 if has some party effect (ie, healing), otherwise 1

After doing this calculation, it just orders them from fastest to slowest.

CAVEAT 1: Rune Unite attacks may or may not get the speed bonus depending on garbage data in memory. This is the bug described above.
CAVEAT 2: Any final SPD must be a full integer, so any fractional parts are essentially floored away. I don't remember the exact data range of the random value used for enemies, but I think it's 16-bit, so any full integer is possible between .9 and 1.1 of the starting speed.
CAVEAT 3: I don't know how ties are broken between characters and enemies. I believe characters get priority because they have a lower search position. In the case of ties while having something like double beat, the character will attack first, then the enemy, and then the character again.
Omnigamer
Posts: 324
Joined: Wed Feb 13, 2013 11:48 am

Re: Turn Order Calculations

Post by Omnigamer »

I revisited this thread today to pull some things from the assembly, but looking through the code again there are a couple things I want to doublecheck.

Every other place I've seen the game call RNG, I believe it delivers an unsigned 16-bit value; it could also be 15-bit, it's been a while. In this case the multiply that occurs expects signed values, but it shouldn't be possible for either operand to be negative in this case. This also means that the result will always be positive within 32 bits, and that it will always branch over adding 0x7FFF. Thus when the final subtraction is applied, you will always wind up with an amount between 0 and .2*SPD+1 being subtracted (for 15-bit RNG) or 0 and .4*SPD+2 (for 16-bit RNG). I'm pretty sure this calculation is only applied to enemies, but I will need to double-check whether it applies to characters as well.

In this case, it means that every stat within the range of .9 and 1.1 of the SPD is possible with equal likelihood. This is good to know, since I was earlier assuming that the numbers on the fringe would have less likelihood than others. A very minor difference, but one that's good to plan against.
User avatar
Pyriel
Webmaster
Posts: 1229
Joined: Wed Aug 18, 2004 1:20 pm

Re: Turn Order Calculations

Post by Pyriel »

The last operation in the version of rand() they use ANDs the initial result with 0x7FFF. It's impossible for it to return a value beyond the max for an unsigned short, and it can only conceivably return a negative value if the calling function expects a signed byte, and goes through the trouble of getting it sign-extended. So rand() in this game is limited to 0-32,767.
Post Reply