Badel2 / slime_seed_finder

https://badel2.github.io/slime_seed_finder
GNU General Public License v3.0
49 stars 4 forks source link

Analyze hash functions used to seed the PRNG #12

Open Badel2 opened 4 years ago

Badel2 commented 4 years ago

In PR #9 I started documenting the different hash functions used by the minecraft code. But that's incomplete so I'm opening this issue.

A hash function in this context is how the world seed is combined with the x and z coordinates in order to seed to PRNG. For example, the old nether fortress algorithm:

protected boolean canSpawnStructureAtCoords(int chunkX, int chunkZ)
{
    int x = chunkX >> 4;
    int z = chunkZ >> 4;
    this.rand.setSeed((long)(x ^ z << 4) ^ this.worldObj.getSeed());
    this.rand.nextInt();

    if (this.rand.nextInt(3) != 0) {
        return false;
    } else if (chunkX != (x << 4) + 4 + this.rand.nextInt(8)) {
        return false;
    } else if (chunkZ != (z << 4) + 4 + this.rand.nextInt(8)) {
        return false;
    } else {
        return true;
    }
}

source: minecraftforum

In this case, the hash function would be (long)(x ^ (z << 4)) ^ worldSeed, where x and z are the chunk coordinate divided by 16, or the block coordinate divided by 256.

Conventional wisdom says that nether fortresses align along the z-axis, and that can be easily verified using tools like AMIDST. Is that just because the x coordinate is checked before the z coordinate? Or is it just because of the weak hash function?

The goal is to write a document that answers the questions:

If someone finds an answer to any of this questions, feel free to reply with a link or whatever.

The following is just a copy of the work-in-progress docs/hash_functions.md:

slime_hash

The hash used to calculate slime chunks results in some interesting patterns, which can be seen in this visualization:

https://badel2.github.io/slime_seed_finder/slime_map.html (press generate and then run, red = no slime chunk, green = yes slime chunk)

A = 0x4c1906
B = 0x5ac0db
C = 0x4307a7
D = 0x5f24f
E = 0x3ad8025f
int slimeHashA = (x * x * A);
int slimeHashB = (x * B);
long slimeHashC = ((long)(z * z)) * (long)C;
int slimeHashD = (z * D);
long slimeHash = (slimeHashA + slimeHashB + slimeHashC + slimeHashD);
long slimeSeed = (worldSeed + slimeHash) ^ E;

And then the algorithm is just:

Random r = new Random(slimeSeed);
return r.nextInt(10) == 0;

So what happens if x and z are 0? Then slimeSeed = worldSeed ^ E. But that's not useful at all.

Only x 0? Only z 0?

If x and z are equal?

If x and z are small?

If x and z are large?

Note that x only affects the lower 32 bits.

Some collisions?

Some tricks to find big areas full of slime chunks?

Visualization of nextInt(10)%2?

structure_hash

long seed = x * SA + z * SB + worldSeed + SK;

For burried treasures, the constants are:

SA = 341873128712
SB = 132897987541
SK = 10387320