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.
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
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
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.
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.
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]
moveout_weight[i]
is negative, total_weight
(which is unsigned) will underflow, and the game will start rolling out of values just under 2^32 (4294967296); this will cause move-out probabilities for all villagers to drop to near (but not) zerorandom -= (unsigned) moveout_weight[i];
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.
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.
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.
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% |