Skip to content

Commit

Permalink
TLOZ: Fix non-deterministic item pool generation
Browse files Browse the repository at this point in the history
The way the item pool was constructed involved iterating unions of sets.
Sets are unordered, so the order of iteration of these combined sets
would be non-deterministic, resulting in the items in the item pool
being generated in a different order with the same seed.

Rather than creating unions of sets at all, the original code has been
replaced with using Counter objects. As a dict subclass, Counter
maintains insertion order, and its update() method makes it simple to
combine the separate item dictionaries into a single dictionary with the
total count of each item across each of the separate item dictionaries.

Fixes #3664 - After investigating more deeply, the only differences I
could find between generations of the same seed was the order of items
created by TLOZ, so this patch appears to fix the non-deterministic
generation issue. I did manage to reproduce the non-deterministic
behaviour with just TLOZ in the end, but it was very rare. I'm not
entirely sure why generating with SMZ3 specifically would cause the
non-deterministic behaviour in TLOZ to be frequently present, whereas
generating with other games or multiple TLOZ yamls would not.
  • Loading branch information
Mysteryem committed Aug 11, 2024
1 parent 68a92b0 commit 9449b05
Showing 1 changed file with 11 additions and 9 deletions.
20 changes: 11 additions & 9 deletions worlds/tloz/ItemPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from .Locations import level_locations, all_level_locations, standard_level_locations, shop_locations
from .Options import TriforceLocations, StartingPosition

from collections import Counter

# Swords are in starting_weapons
overworld_items = {
"Letter": 1,
Expand Down Expand Up @@ -58,11 +60,11 @@
"Small Key": 2,
"Five Rupees": 2
}
basic_pool = {
item: overworld_items.get(item, 0) + shop_items.get(item, 0)
+ major_dungeon_items.get(item, 0) + map_compass_replacements.get(item, 0)
for item in set(overworld_items) | set(shop_items) | set(major_dungeon_items) | set(map_compass_replacements)
}
basic_pool = Counter()
basic_pool.update(overworld_items)
basic_pool.update(shop_items)
basic_pool.update(major_dungeon_items)
basic_pool.update(map_compass_replacements)

starting_weapons = ["Sword", "White Sword", "Magical Sword", "Magical Rod", "Red Candle"]
guaranteed_shop_items = ["Small Key", "Bomb", "Water of Life (Red)", "Arrow"]
Expand Down Expand Up @@ -135,10 +137,10 @@ def get_pool_core(world):
# Finish Pool
final_pool = basic_pool
if world.options.ExpandedPool:
final_pool = {
item: basic_pool.get(item, 0) + minor_items.get(item, 0) + take_any_items.get(item, 0)
for item in set(basic_pool) | set(minor_items) | set(take_any_items)
}
final_pool = Counter()
final_pool.update(basic_pool)
final_pool.update(minor_items)
final_pool.update(take_any_items)
final_pool["Five Rupees"] -= 1
for item in final_pool.keys():
for i in range(0, final_pool[item]):
Expand Down

0 comments on commit 9449b05

Please sign in to comment.