[FE8] The Official AI Documentation Thread

Worth mentioning here that the Label opcode in FE8 is 0x1C instead of 0x1B like it is in FE7. Maybe one additional built in AI??

Added a ton of stuff. These have ? by them because they have the same structure as the corresponding entries in FE7, but I haven’t actually confirmed their effects. They look the same though, so I think that’s accurate.

HOW THE AI DETERMINES WHAT TO STEAL

3B7C8:
Initialization: Push r4-r7, r14. Move r0 (RAM char struct pointer of character being looked at) into r7. Move 0xFF to r4 and r5, and move 0x0 to r4. Load the item id of the first item into r0. BL to 3B794.

3B794:
At 5A83A4, there’s an array of item halfwords, arranged in order of descending importance, and terminated with 0xFFFF. This function iterates through the array and compares each value to the item id in question. r0 has a counter of how far into the array the item is, and this number is what is returned. If the item isn’t in the array by the time it reads 0xFFFF (read: not stealable), it moves 0xFFFFFFFF into r0.

Once returned, compare r0 to r6. If (signed) less than, move r0 to r6 (current most valulable item according to that array), and move the inventory slot into r5. Load the next item and repeat the previous steps.

Once all the items have been iterated through, move r5 (slot with most valuable item) to r0 and return (3DC0C is a BL 3B7C8). Get the item id of that slot and bl to 3B794 again to get the “how valuable is this item” number.
Once obtained, load r3 with [sp,#0x14], which has the previous contender of “most valuable item”. Compare r0 and r3; if r0 is (signed) less than, store r0 in the space r3 used to be
Not entirely sure what happens next. Loads 202E4D8, dereferences that, then does a bunch of other stuff. I think one of the pointers has the allegiance byte of the current character being looked at?
After a while, it stores r6 (current most valuable item) into sp,[0x18].

Gist:
Value is determined by location in an array located at 5A83A4. Only item ID is checked, not weapon type. Therefore, you can repoint and expand this to make enemy thieves steal whatever according to a priority table of your making.
If a character has 2 of the same most valuable items, the one in the lowest (lowest meaning closest to bottom, not numerically) slot will be stolen, regardless of uses.
If two characters have the same most valuable item, the one with a lower deployment number will be stolen from, regardless of uses.

1 Like

Unit position will be the tiebreaker in this scenario.

In FE7, many AI routines start at the bottom-right corner then scan rows right-to-left until it reaches the top-left corner. When an item of equal priority is encountered, it becomes the new steal target. So, when 2 units have the same most valuable item, stealing priority goes to the upmost then leftmost unit. I did a quick test in FE8 and this still seems to be the case.

Here’s a general outline of the FE7 steal AI (08038C44):
Examine each map tile. Start at bottom-right corner, stop at top-left corner, read rows right-to-left.
Check conditions for each tile:

  1. Tile is within steal range
  2. Tile contains a unit
  3. Unit cannot be in the same alliance as thief
  4. There is a open, reachable position around unit (where to execute the steal command)
  5. Thief speed >= target speed
  6. Unit has an item from the stealable item table (lower index = higher priority)
  7. Item index in stealable item table <= index of best item seen so far
    If all conditions met, unit/item becomes the new best target
    Repeat until all tiles have been examined
    Write steal command if a valid target was found

Could you repost this into the FE7 thread as well?

Yeah, sure.

In FE8, the chapter array that controls the AI’s ability to use door keys, lockpicks, and antitoxins is located at 0D8538 (each chapter entry is 4 bytes)

Bit 01 = enable door key. Bit 02 = enable lockpick. Bit 04 = enable antitoxin.

Turn off door keys if you find your enemies being stupid with doors.
Don’t worry about lockpicks since AI2 0x04 and AI2 0x05 will use them anyways.
I have no idea why banning antitoxins is even a thing.

2 Likes

Some things about the move-with-leader AI (Ai1 0x0D) I figured out and discussed with @Pikmin1211

First, how it works:

Ai1 #13
  asmc [0803F51D]([00000000])
  goto 1 if [0203AA8B] == 1
  asmc [0803F72D]([00000000])
  goto start
  label 1
  chai ai1 = 0, ai2 = 0
  goto start

FE8U:0803F51C(+1) checks whether the active unit’s leader exists and sets FE8U:0203AA8B to 1 if not; Otherwise it will “simulate” the leader’s Ai to determine which unit will be their next target (if any) (the target unit id is stored at FE8U:0203AA8A (by unit id here I mean the “allegiance id” or whatever of the unit, not character id)).

FE8U:0803F72C(+1) checks whether FE8U:0203AA8A is set and refers to a valid unit, and try to make an offensive action targetting that unit. If it fails, then it will uhh try to target a unit that is in the same general direction? (FE8U:0803F6B8, it compares the diffence of positions between active and target + active and other and only returns true if both components of the differences are of the same sign?), and if that fails then it will try to move towards that unit.

So a unit with “move with leader” ai1 will only start doing anything once their leader can target an enemy unit (not necessarily after doing so), at which point they will target that specific unit themselves (or move towards them while attacking other things too perhaps if they can’t reach). They will switch to ai 0, 0 (Action in range/pursue) if their leader dies.

(The script part comes from DOC/AiDump.txt at master · StanHash/DOC · GitHub).
(I’m pretty sure most of that was already known but I find it’s never bad to check things yourself).

The one interesting thing that we found out by digging a bit more (as pik was having unexpected ai behaviours) is that FE8U:0203AA8B is only ever set by that function (that is, when the unit’s leader is dead), and never cleared.

This means that if you have two groups of move-with-leader units, assuming group 1 units move before group 2 units, if the leader of group 1 is dead, then group 1 units will have their ai script set FE8U:0203AA8B as expected and will therefore change their ai to 0, 0 accordingly, but that value will carry over to group 2 ai and those units will also have their ai changed to 0, 0! Which is not good (as group 2 leader is still fine).

In addition to that, that value at FE8U:0203AA8B is also (probably) set by various other parts of the ai, including some thieving logic, which means that just bringing a thief as reinforcement may also break your move-with-leader AIs (that also happened to pik).

And since the issue is of a value within the Ai data not being cleared properly, this means that the behaviour of those units will be normal after a RAM clear (reset into load suspend), further confirming this as being a good old bug.

I made a quick fix for that if other people are interested (it makes it clear the problematic byte when entering the function at FE8U:0803F51C:

It hasn’t been tested thoroughly but according to pik it works.

5 Likes

aera is making LeaderAIFix Patch for FE8J.

The contents of the process are similar, but the details are different.
I thought it was probably because the hooked point was a little different.

//aera //FE8J

@thumb
;	@org $0803f4f0	00 48 87 46 XX XX XX XX[アドレス]
	ldr	r4, =$0203AA00
	add	r4, #135
	mov	r0, #0
	strb	r0, [r4]
	ldrb	r0, [r1, #11]
	strb	r0, [r7, #0]
	mov	r0, r1
	add	r0, #66
	ldr	r4, =$0803f4f8
	mov	pc, r4

//stan //FE8U

;	ORG $03F51C
;		WORD $47184B00; POIN (LeaderAiFix|1)

	push {r4-r7, lr}
	mov r7, r8
	push {r7}
	mov r5, #0

	ldr  r3, =0x0203AA04+0x87
	strb r5, [r3]

	ldr r3, =0x0803F524+1
	bx r3

In order to simplify the story,
I compared with the FE8U ASM
the two are hooking at different points.

0803F51C B5F0   push {r4,r5,r6,r7,lr} // << stan's hook
0803F51E 4647   mov r7, r8
0803F520 B480   push {r7}
0803F522 2500   mov r5, #0x0
0803F524 4F0B   ldr r7, [pc, #0x2c] # pointer:0803F554 -> 0202BE44 (gActiveUnitIndex )
0803F526 7838   ldrb r0, [r7, #0x0] # pointer:0202BE44 (gActiveUnitIndex )
0803F528 4680   mov r8, r0

//skip

        0803F552 E038   b 0x803f5c6
0803F554 BE44 0202   //LDRDATA
0803F558 4E50 0300   //LDRDATA
0803F55C AA04 0203   //LDRDATA
    0803F560 7AC8   ldrb r0, [r1, #0xb] // <<< aera's hook
    0803F562 7038   strb r0, [r7, #0x0]   //gActiveUnitIndex
1 Like

Since it’d be nice to have this stuff here, a complete listing of FE8 AI2 values:

0x01 = Move towards opponents, but not character(s) 0x0 (5A817A)
0x02 = Move towards opponents, but not character(s) 0x0 (5A817C)
0x03 = Do nothing
0x04 = Loot villages/open chests, then attack
0x05 = Loot villages/open chests, then escape
0x06 = If could reach opponents in two turns, change AI2 to 0x0
0x07 = If could reach opponents in two turns, change AI2 to 0x1
0x08 = Do nothing
0x09 = Random movement
0x0A = Move to character 0x1 Eirika if not in range, or move to opponents if so
0x0B = Move to character 0xF Ephraim if not in range, or move to opponents if so
0x0C = Escape; if cannot escape, do nothing
0x0D = Move on to nearest terrain 0x1B/0x1F? (0x5A8182)
0x0E = Attack walls until no more remain(?), then CHAI [0x0,0x0]
0x0F = Move towards opponents. Move as close as possible when blocked.
0x10 = If not in area [13,15]-[18,19], move to [15,17]; if in area, CHAI [0x0,0x0]
0x11 = Wait one turn, then change AI2 to 0x4 (raid then attack)
0x12 = Wait one turn, then change AI2 to 0x0 (Pursue)```
9 Likes