Last active
February 28, 2025 22:22
-
-
Save mjtb49/07935eb9bbd61ae785f5365b83044079 to your computer and use it in GitHub Desktop.
Illustrating how to iterate over seeds giving a particular ruined portal without having to think about lattices. To do this in the fastest way takes lattices, but a naive iteration is perfectly possible and not much slower, and this gist is to iterate that point.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# lcg constants. Replace A and B to change indices of targeted call. | |
A = 25214903917 | |
B = 11 | |
M = 1 << 48 | |
mask = M - 1 | |
# Can mess with this | |
argument_to_next_int = 28 # must not be a perfect power of 2 | |
known_lower_bits = 17 # must be >= 17 | |
lower_bits = 0 # must be the lower bits of some valid seed | |
target = (0, 0) | |
# zero out upper bits. | |
lower_bits &= (1 << known_lower_bits) - 1 | |
# extract the "true" argument to this nextInt | |
_n = argument_to_next_int | |
_v2n = 0 | |
while _n % 2 == 0: | |
_v2n += 1 | |
_n //= 2 | |
assert known_lower_bits >= 17 | |
assert _n != 1 | |
# Don't mess with these | |
# How much should we increase the upper bits of the first seed by? Must be a multiple of 2^17 | |
BASE_INCREMENT = argument_to_next_int << (max(known_lower_bits - _v2n, 17)) | |
# Smallest modulus that is a multiple of both the argument to the nextInt and 2^48 | |
BIG_MOD = _n * M | |
# the effect an increment of BASE_INCREMENT at the first seed has on the second seed. | |
BASE_SECOND_SEED_INCREMENT = (A * BASE_INCREMENT) % BIG_MOD | |
def _next(seed): | |
return (A * seed + B) & mask | |
def test_seed(target, seed1, seed2=None): | |
if seed2 is None: | |
seed2 = _next(seed1) | |
return ((seed1 >> 17) % argument_to_next_int, (seed2 >> 17) % argument_to_next_int) == target | |
def print_if_statements(): | |
upper_bound = -1 | |
lower_bound = M | |
i = 1 | |
prefix = "if" | |
while lower_bound > upper_bound: | |
guessed_effect_on_second_seed = ((i * BASE_SECOND_SEED_INCREMENT) // M) % _n | |
if guessed_effect_on_second_seed == 0: | |
bound = M - (i * BASE_SECOND_SEED_INCREMENT) % BIG_MOD | |
if bound > upper_bound: | |
upper_bound = bound | |
# print(i, i * second_seed_increment / M % n) | |
print(f"{prefix} b < {bound}:") | |
print(f"\t a += {i * BASE_INCREMENT}") | |
prefix = "elif" | |
elif guessed_effect_on_second_seed == _n - 1: | |
bound = BIG_MOD - (i * BASE_SECOND_SEED_INCREMENT) % BIG_MOD | |
if bound < lower_bound: | |
lower_bound = bound | |
# print(i, i * second_seed_increment / M % n) | |
print(f"{prefix} b >= {bound}:") | |
print(f"\t a += {i * BASE_INCREMENT}") | |
prefix = "elif" | |
i += 1 | |
smallest_solution = lower_bits | |
print_if_statements() | |
seeds_found_fast = set() | |
print("Starting search for seeds") | |
# find solution with smallest possible upper bits to start our search from | |
while not test_seed(target, smallest_solution): | |
smallest_solution += 1 << known_lower_bits | |
print("Found initial seed") | |
# Start the main search | |
a = smallest_solution | |
while a < M: | |
b = _next(a) | |
seeds_found_fast.add(a) | |
assert test_seed(target, a, b) | |
# paste here the output from print_if_statements() | |
if b >= 66166523953152: | |
a += 3670016 | |
elif b < 31479762518016: | |
a += 95420416 | |
elif b < 97646286471168: | |
a += 99090432 | |
# One of these branches is guaranteed to occur, so this last one could be an "else". | |
else: | |
assert False | |
print("Finished search for seeds and beginning test") | |
for a in range(lower_bits, M, 1 << known_lower_bits): | |
if test_seed(target, a) and a not in seeds_found_fast: | |
print(a) | |
assert False | |
print("Test complete") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment