Jump to content

So it turns out Jealousy was a bug


Robert Stewart
 Share

Recommended Posts

Most people probably know about FE4's love system. Many people may also be aware of a "Jealousy" mechanic which is part of it.

The way it works is described on SF. But in short:

  • If a female unit A is standing next to male unit B and female unit C, and
  • C has higher priority with Unit B than unit A does, then
  • Unit C will receive +5 love points with Unit B, while Unit A will get 0.

The mechanic is occasionally, useful, but the priority system seems a bit odd. It changes based on whether you've reset the game, and the "normal" way it works is based on recruitment order rather than character relationships. Edain/Lachesis have a higher priority with Lewyn than Erinys or Sylvia? What were the game makers thinking here?

Well, it turns out they weren't thinking. It was a bug the whole time.

FE4 calculates adjacent growth bonuses in more or less the following way:

  • For every female unit A, check if some other unit B is
    1. Less than 1 space away
    2. Is not female.
    3. Apply the adjacent bonus if both conditions passed

This mostly works, but the developers forgot to reset a variable between steps one and two which is supposed to hold a pointer to Unit A between steps 1 and 2. This means that if Unit A happens to be next to a female unit C, all future distance checks will actually use the position of Unit C for checking distance.

A pseudocode example maybe illustrates this better:

Spoiler

unit* self, other, tmp;
while (self = getNextFemaleUnit()) {
  int offset = MAX_UNIT_POS;
  while (offset > 0 && other = unitList[offset]) {
      if (getDistance(self, other) < 2) {
          tmp = self;
          self = other;
          int id = getUnitId(self);
          if (!isFemale(id)) {
              self = temp;
              applyBonus(self, other);
          }
      }
      offset--;
  }
}

 

So, yeah. It was a bug all along.

Edited by Robert Stewart
Link to comment
Share on other sites

You mean to tell me the ability to pair Seliph and Julia through this "jealousy" mechanic was a  b u g ? ? ?

Just kidding, of course, but I think the fact that you can exploit characters like Seliph and Julia to be paired up through that method was pretty telling that it wasn't an intended "mechanic" since she has a negative love growth him to begin with. Still, it's a very interesting concept; it would be cool to see it actually be a part of an FE4 remake, barring the unintended pairings.

Link to comment
Share on other sites

That is a fairly interesting discovery there

2 hours ago, indigoasis said:

Still, it's a very interesting concept; it would be cool to see it actually be a part of an FE4 remake, barring the unintended pairings.

I kinda hope they do this, adding it in like the Noseratu bug in the Gaiden to Echoes shift, only changing up the priority system a bit (fixing that Celice-Julia bug in the process). Maybe even adding in a male centric version of it as well.

Link to comment
Share on other sites

  • 3 weeks later...

Improving ones unit relationship through jealousy is a game mechanic so intricated and morally dubious that it's too good to be implemented on purpose. Even in the current series in which we get the occasional Yandere and Tsundere for some less orhodox takes on relationships, I'm confident that IS will never implement them on a sufficient complex level and resort to bland, clean outcomes which development everyone can see from a mile away. FE4 was the closest and even that is now implied to be accidental.

Link to comment
Share on other sites

  • 2 months later...

That is pretty bizarrely written, to the extent that I'm not even really sure it's a bug. I have no easy way of looking at or interpreting the actual ASM, let alone the original source code, but I can't think of a reason why they would write the code that way.

 

Here's the original C-like code OP wrote again (to avoid having to jump around on this page).

unit* self, other, tmp;
while (self = getNextFemaleUnit()) {
  int offset = MAX_UNIT_POS;
  while (offset > 0 && other = unitList[offset]) {
      if (getDistance(self, other) < 2) {
          tmp = self;
          self = other;
          int id = getUnitId(self);
          if (!isFemale(id)) {
              self = temp;
              applyBonus(self, other);
          }
      }
      offset--;
  }
}

 

 

If I understand the code correctly, the following edited code will not have any 'jealousy' mechanic at all

unit* self, other;
while (self = getNextFemaleUnit()) {
  int offset = MAX_UNIT_POS;
  while (offset > 0 && other = unitList[offset]) {
      if (getDistance(self, other) < 2) {
          int id = getUnitId(other);
          if (!isFemale(id)) {
              applyBonus(self, other);
          }
      }
      offset--;
  }
}

 

As you can see, this code is a bit more clear too, there's no business with swapping the self and other using tmp. I see no reason why that would be in the code at all, unless it's deliberate. But if it was intentional, designing the jealousy to be based on the character's position in the unit list is very strange.

 

The point is, the way it works is weird, but if they didn't want this to happen, I don't see why anyone would have written the code like this (again based on the pseudocode rather than the original), even unintentionally.

 

I think it might be intentional, but weirdly optimized to save precious memory space.

 

 

Edited by Galap
Link to comment
Share on other sites

For example, if I were to intentionally write the jealousy system myself, to behave in the same way, I would probably have done this:

 

unit* self, other, jealous;
while (self = getNextFemaleUnit()) {
  int offset = MAX_UNIT_POS;
    jealous = NULL;
  while (offset > 0 && other = unitList[offset]) {
      if (getDistance(self, other) < 2) {
          int id = getUnitId(other);
		  if (isFemale(id))
			jealous = other;
		  }else{
			if (jealous == NULL){
              			applyBonus(self, other);
			}else{
			  applyBonus(jealous, other);
            }
      }
      offset--;
  }

Which is in some ways a bit more human-sensible (though it still relies on the weird index priority, though not doing so would require a bunch more code). However, this solution has significantly more operations in it, and thus will be heavier in terms of both ROM program data and RAM requirements ( think it will have to store stuff in order to do these comparisons). so, I think that it's likely that the mechanic is intentional, but looks strange due to optimization for the limited SNES hardware.

Edited by Galap
Link to comment
Share on other sites

On 2/10/2023 at 12:38 PM, Galap said:

I have no easy way of looking at or interpreting the actual ASM, let alone the original source code, but I can't think of a reason why they would write the code that way.

Here is the ASM in question.

addAdjacentBonus:
    phx 
    phy 
    stx $00 
    ldx #$008E 
-:
    lda $7E438B,X
    bit #$0080 
    beq .continue 
    lda $7E477B,X 
    bne .continue 
    stx $02 
    ; subtracts ($7e441B + the current index) from ($7e441B + ($00))
    ; and subtracts ($7e444B + the current index) from ($7e444B + ($00))
    ; adds the absolute value of the two results together
    jsl calcDistance 
    cmp #$0002 ; distance check
    bcs .continue ; "BCS" is equivalent to "branch if greater or equal"
    sta.w PointIncrease 
    lda #$0006
    sec 
    sbc.w PointIncrease
    sta.w PointIncrease
    ldy.w UnitPointer
    lda $7E46EB,X
    sta.w UnitPointer
    jsl getUnitSupportID 
    bcs .continue
    cmp #$0010
    ; prevents two female units from getting points
    ; also causes jealousy because UnitPointer isn't reset
    bcs .continue
    sty.w UnitPointer
    jsl increasePoints 
.continue:
    dex 
    dex 
    bpl - 
    ply
    plx 
    rts 

It's pretty apparent to me that not resetting the UnitPointer address was an oversight and not intentional.

Edit: I guess in the end it comes down to individual interpretation. But to me, given how important gameplay/story integration was in FE4, jealousy's odd and inconsistent priority makes it hard to believe it was intentional.

Edited by Robert Stewart
Link to comment
Share on other sites

Thanks for posting the ASM. It'll take a bit of effort to decode but I'm interested in it.

 

The priority thing is strange though I agree. Though I think the only way to avoid it would be to make two lists of all the adjacent male and female units and then apply the normal bonus if the female list is empty, and apply it to the female(s?) on the other list if it is not. 

Link to comment
Share on other sites

Was the jealousy mechanic ever acknowledged by IS or any of the official manuals or strategy guides? Obviously, the part about Seliph and Julia seems to indicate it was a bug, but it could be that they just failed to catch that corner case. 

Link to comment
Share on other sites

  • 3 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...