Villager Move-out Probability

Thanks to @_Ninji for their scripts and tools which saved me a tremendous amount of time and energy getting everything set up, and for their datamine of villager move-outs.

This page is not a guide on how to move a villager out, but rather an examination of edge cases where the game’s logic breaks down.

Please link back to this page when using it as a source, as the contents may change over time as more details or fixes are added.

If you notice any inaccuracies, please contact me on Discord (asteriation#6884) (preferred), or via email at [email protected].

Please send any gameplay related questions to the ACNH Info Server.

Background

Per Ninji:

each villager’s chance is based on friendship and calculated by floor((300 - a) / 10) - r

a = average friendship they have with all island residents
r = amount of residents with >200 friendship

Implementation

The following is more or less what the game code does to pick a villager for the move-out prompt. (all integers are 32-bit here)

int num_players = ...;
int num_villagers = ...;
// bitmap of whether each villager can moveout (handling birthdays, etc.)
int can_moveout_bitmap = ...; 
// friendship points for each villager:player pair
int friendship[10][8] = ...;

unsigned total_weight = 0;
signed moveout_weight[10] = { 0 };
for (int i = 0; i < num_villagers; ++i) {
	if (can_moveout_bitmap & (1 << i)) {
		int total_friendship = 0;
		int num_lv6_friends = 0;
		for (int j = 0; j < num_players; ++j) {
			total_friendship += friendship[i][j];
			if (GetFriendshipLevel(friendship[i][j]) > 5) {
				++num_lv6_friends;
			}
		}
		int avg_friendship = (int) ((1.0f / num_players) * total_friendship);
		moveout_weight[i] = (int) ((300 - avg_friendship) * 0.1f) - num_lv6_friends;
		total_weight += (unsigned) moveout_weight[i];
	}
}

// random int in [0, total_weight), but 0 if total_weight == 0
unsigned random = (RandomU32() * total_weight) >> 0x20;
for (int i = 0; i < num_villagers; ++i) {
	if ((random < (unsigned) moveout_weight[i]) &&
	        (0 < (signed) moveout_weight[i])) {
		return i; // villager in slot i to ask to move out
	}
	random -= (unsigned) moveout_weight[i];
}
return -1; // no one will ask to move out

Villager Order

The game save contains an array of size 10 for villager data. This is populated in order as you get more and more villagers (starting with your first Sisterly villager in the first slot, first Jock villager in the second slot, and so on) until you reach 10 villagers. If a villager is instead replacing a moving out villager, the new villager takes the slot of the moving out villager (if your initial Jock villager got replaced, your new villager goes in the second slot).

This order is the order that the game goes in in the above code (and much other code in general); this will be relevant in a bit.

Stopping Villager Move-out Prompts

It is possible to make any particular villager never ask to move-out, by ensuring moveout_weight[i] <= 0 for the villager. This can be accomplished by having enough players on your island (at least 4 are required; they do not need to place the initial tent), with sufficiently high friendship:

Number of Players at 200+ Friendship Required Average Friendship
4 251.00 or higher
5 241.00 or higher
6 231.00 or higher
7 221.00 or higher
8 211.00 or higher

Satisfying this for every villager aside from the latest moved-in villager, and the villager who last asked to move out who you told to stay (if applicable) will result in there being no villager move-outs prompts whatsoever.

Integer Underflow Bug

There are notable integer underflow bugs involving moveout_weight values being treated as an unsigned value, even though it may actually be negative if you have 5+ players at 200+ friendship:

Number of Players at 200+ Friendship Required Average Friendship for Integer Underflow
5 251.00 or higher
6 241.00 or higher
7 231.00 or higher
8 221.00 or higher

The 0 < moveout_weight[i] check treats the value as a signed integer, which means that the bug does not affect the method described in the previous section, but it can have observable consequences on other villagers’ move-out probability.

There are two places where moveout_weight[i] < 0 causes unexpected behavior:

total_weight += (unsigned) moveout_weight[i]
random -= (unsigned) moveout_weight[i];

Examples

The following examples use a set of 8 villagers for simplicity; the villager who most recently moved in, and the villager who last asked to move out (and then got told to stay) are omitted (these are villagers 9 and 10).

The examples will also be assuming a total of 5 players on the island, all with the same number of friendship points for all villagers.

Example 1. Villager Can’t Move Out

If the villager order is:

then since total_weight = 0 in this case, the random roll will always return 0, and by the time to Ione, it would have been shifted upwards to 5. Since Ione has weight 5, this fails the check, and no one will ask to move out.

Example 2. Fixed Move-out Villager

Since total_weight = 1 in this case, the random roll will always return 0, even though there are four villagers with moveout_weight[i] > 0.

If the villager order is:

then the game will always make Ione ask to move out. However, if the order changes a bit, for example,

then after Deena, the random roll has changed to 1, and Marshal will be the one to ask move out instead. Likewise, for

the villager that always asks to move out will be Sasha instead.

Example 3. Extremely Low Move-out Probability

In this case, the sum of the moveout_weight[i] values is -1. Since total_weight is unsigned, this is treated as a value of 4294967295, and a number is picked in the range [0, 4294967295). Due to bias (modulo bias, except we don't use modulos, but the same idea), 0 has a probability of 2/4294967296 or around 0.0000000466%, while all other values have a probability of 1/4294967296 or around 0.0000000233%.

In the case of the following villager order:

this results in:

Villager Move-out Chance
Ione 0.0000000466%
Raymond 0.0000000233%
Sasha 0.0000000233%
None 99.9999999069%

As in the previous example, changing the villager order may change the move-out chances here as well:

results in:

Villager Move-out Chance
Ione 0.0000000233%
Raymond 0.0000000000%
Sasha 0.0000000466%
None 99.9999999302%

and:

results in:

Villager Move-out Chance
Ione 0.0000000233%
Raymond 0.0000000233%
Sasha 0.0000000233%
None 99.9999999302%