Well, I have done that research. At least some of it.
I did a lot of reverse engineering in Ghidra over the past week.
Along the way I have written code to parse igb data files included in the game (
https://gist.github.com/mateon1/b123a53d824305767989ebc2ba3e5814)
(.afs file parsing not included since that was simple enough I did it in an interactive repl, it's just magic, file count, offset, length, offset, length, ...)
I've mostly looked at RNG behavior so far.
Most (but not all!) of the game's RNG is handled by a single virtual function (the other code does share the same RNG instance though)
vtable[0x24] on the
beNDMWGameRam type, implemented at address 0x803717a4.
This method seems to handle a bunch of Lua script calls. I have not analyzed the scripts themselves yet, they are in .igb files within the .afs files. It's Lua 4 compiled for a 32-bit VM, so I couldn't get a lua decompiler to work on them - I have seen a couple WIP projects that seemed promising but I prioritized other things.
The outer switch in this function switches over a
Message enum (values
GETCOMMON, GETPLAYER, GETPLAYER1, GETPLAYER2, GETPLAYER3, GETPLAYER4, GAMEDATAINIT, GAMESTART, GAMEREMOVE, ADDHP, ADDMP, RECALCPLAYER, BASE2CURRENT, CURRENT2BASE, WEAPONEQUIPCHECK, WEAPONEQUIPSTRONG, DEADRESTART, CTRLITEM, FIGHTCALC, DISPFIGHTDATA, BKCHIPBITEXP, CTRLPLAYER)
The inner switch in
FIGHTCALC switches over a
FightCom enum (values
E2P_IS_HIT_CHECK, P2E_IS_HIT_CHECK, P2E_IS_CRITICAL, E2P_DAMAGE, E2P_DAMAGE_RANK, P2E_DAMAGE, P2E_EXP_UP, E2P_STUN_TIME, E2P_POISON_TIME, E2P_SLEEP_TIME, E2P_BIND_TIME, E2P_ATKDWN_TIME, E2P_PWRDWN_TIME, P2E_SKILL_UP, P2E_STUNT_TIME, P2E_POISON_TIME, P2E_SLEEP_TIME, P2E_BIND_TIME, P2E_ATKDWN_TIME, P2E_PWRDWN_TIME, P2P_KOUKA_MAGIC, GET_PATK_ZOKUSEI)
Most of these FightCom handlers call RNG one or multiple times.
The RNG itself is a mersenne twister RNG that looks fairly standard. Even without reimplementing the RNG you can predict a block of the 600ish future values as they are stored in memory, although you need to apply some further post-processing to get the actual final values:
i ^= i>>11; i ^= (i&0x13a58ad)<<7; i ^= (i&0x1df8c)<<15; return i^(i>>0x12)
Focusing on the P2E_SKILL_UP code:
it first checks skill caps:
1-36: 30 per level
-107: 20 per level (360+20*lv)
-227: 10 per level (1430+10*lv)
228+: 8 per level (1884+8*lv)
If the tech can still grow within the skill cap, it takes a difficulty out of the following table, based on digimon level tier (gets harder), skill tier (gets easier), and whether the rate has been boosted (I don't know what sets it, it uses an argument that comes from message object/lua code, the fourth integer argument == 1 gives the boost):
base tier up difficulty: (normal/boosted)
skill tier \ lv tier
1- 36 37-107 108-227 228+
0: 1 10/ 12 5/ 6 2/ 3 1/ 1
1: 2 13/ 16 6/ 8 3/ 4 1/ 2
2: 3 16/ 21 8/ 10 4/ 5 2/ 2
3: 4 21/ 27 10/ 13 5/ 6 2/ 3
4: 5 28/ 35 14/ 17 7/ 8 3/ 4
5: 6 37/ 46 18/ 23 9/ 11 4/ 5
6: 7 48/ 60 24/ 30 12/ 15 6/ 7
7: 8 62/ 78 31/ 39 15/ 19 7/ 9
8: 9 81/101 40/ 50 20/ 25 10/ 12
9: 10 106/132 53/ 66 26/ 33 13/ 16
10: 20 132/165 66/ 82 33/ 41 16/ 20
11: 30 165/207 82/103 41/ 51 20/ 25
12: 100 207/258 103/129 51/ 64 25/ 32
13: 300 258/323 129/161 64/ 80 32/ 40
14: 500 323/404 161/202 80/101 40/ 50
15: 1k 404/505 202/252 101/126 50/ 63
16: 2k 505/632 252/316 126/158 63/ 79
17: 3k 0/ 0 316/395 158/197 79/ 98
18: 4k 0/ 0 0/ 0 205/256 102/128
19: 6k 0/ 0 0/ 0 0/ 0 128/160
20: 10k 0/ 0 0/ 0 0/ 0 160/200
(There's also a third debuffed entry for each of these table entries, but the code cannot access it in any way, it would be a roughly 50% debuff)
The game takes the difficulty check value from this table, and modifies it. For weapon skills, it looks up your digimon in a database file to see if it's a specialist for that weapon type, in that case it multiplies the check by 65, otherwise by 50. For magic skills, it multiplies the check by a flat 100.
If the random number generated, modulo 100000 is strictly smaller than the calculated check, you tech up.
This means, for a Guilmon stab tech up, you need to hit a .65% chance at 1 skill point, then .845%, then 1.04%, then 1.365%, then 1.82% and so on, until 6.89% at skills 10-20. This is obviously manipulable. The first couple of tech points are the hardest, but I just checked the values in the RNG state in my memory dump, and there are three offsets in the buffered random values that can hit a lv1 tech up. I think the statistical expectation is about 4.
Whether enemies hit is also interesting, some of it probably irrelevant to the TAS (but maybe dropping an armor board and equipping it in the field makes sense?)
Some armor boards (of the first slot ones) have a block rate stat, of the ones available early game, the only relevant one is the Brave Board series. Alpha gives a flat 2% block rate before everything else, which increases by 1% per tier. If
rand % 100 < block rate, blockable attacks simply fail and the second RNG check is skipped.
The second check compares speeds: if
rand % 100 < enemyAtkAccuracy * enemySpeed / playerBuffedSpeed the hit succeeds, otherwise it doesn't. The buffed speed is equal to
playerSpeed * (speedBuff + 100) / 100. Most enemy attacks have a 85-95% base accuracy, but because it actually depends on speed difference that doesn't matter all that much.
Whether the player succesfully hits uses a different formula which I haven't fully explored yet, it does seem to involve skill levels, a player accuracy stat (not sure where it comes from, on my memory dump with a base guilmon it's 88 though), and enemy (debuffed) speed. It has a generous base rate so it's never too likely to miss. The actual check is
rand % 10000 < playerAcc * (playerSpeed * 30 + weaponSkill * 20 + 6000) / enemyDebuffedSpeed
Damage calc is extremely complex, both for player -> enemy and enemy -> player, ignoring that for now.
Anyway, that's my current progress, I'll end here to not spend all night on this again.
By the way, to find the RNG instance itself, do a pointer scan for
0x804bb42c, that's the
igMersenneTwisterRandomNumber vtable address, the first hit is the type metadata object, the second hit is the actual RNG in memory.