286 lines
9 KiB
C++
286 lines
9 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#if defined(__CUDACC__)
|
|
#define SLIME_HD __host__ __device__
|
|
#define SLIME_INLINE __forceinline__
|
|
#else
|
|
#define SLIME_HD
|
|
#define SLIME_INLINE inline
|
|
#endif
|
|
|
|
namespace slime_research {
|
|
|
|
constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL;
|
|
constexpr uint64_t kJavaAddend = 0xBULL;
|
|
constexpr uint64_t kMask48 = (1ULL << 48) - 1ULL;
|
|
constexpr uint64_t kSlimeXor = 987234911ULL;
|
|
constexpr uint64_t kCombinedXor = kSlimeXor ^ kJavaMultiplier;
|
|
constexpr int kLowerBits = 35;
|
|
constexpr int kUpperBits = 13;
|
|
constexpr uint32_t kUpperSize = 1U << kUpperBits;
|
|
constexpr uint32_t kUpperMask = kUpperSize - 1U;
|
|
constexpr uint32_t kHalfDeltaInverse = 4965U;
|
|
constexpr int kMaxPatternCells = 64;
|
|
constexpr uint32_t kX2Coeff = 4987142U;
|
|
constexpr uint32_t kXCoeff = 5947611U;
|
|
constexpr uint32_t kZ2Coeff = 4392871U;
|
|
constexpr uint32_t kZCoeff = 389711U;
|
|
|
|
enum Requirement : uint8_t {
|
|
kNonSlime = 0,
|
|
kSlime = 1,
|
|
};
|
|
|
|
struct CellTerm {
|
|
uint64_t term;
|
|
uint8_t requirement;
|
|
};
|
|
|
|
struct UpperEvent {
|
|
uint16_t threshold;
|
|
uint8_t before;
|
|
};
|
|
|
|
SLIME_HD SLIME_INLINE uint64_t javaIntAsLongBits(uint32_t value) {
|
|
return static_cast<uint64_t>(static_cast<int64_t>(static_cast<int32_t>(value)));
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE uint64_t coordinateTerm(int32_t chunkX, int32_t chunkZ) {
|
|
const uint32_t x = static_cast<uint32_t>(chunkX);
|
|
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
|
return javaIntAsLongBits(x * x * kX2Coeff) +
|
|
javaIntAsLongBits(x * kXCoeff) +
|
|
javaIntAsLongBits(z * z) * kZ2Coeff +
|
|
javaIntAsLongBits(z * kZCoeff);
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE uint64_t coordinateXTerm(int32_t chunkX) {
|
|
const uint32_t x = static_cast<uint32_t>(chunkX);
|
|
return javaIntAsLongBits(x * x * kX2Coeff) +
|
|
javaIntAsLongBits(x * kXCoeff);
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE uint64_t coordinateZTerm(int32_t chunkZ) {
|
|
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
|
return javaIntAsLongBits(z * z) * kZ2Coeff +
|
|
javaIntAsLongBits(z * kZCoeff);
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE uint64_t nextStateFromTerm(uint64_t seed48, uint64_t term) {
|
|
uint64_t state = ((seed48 + term) ^ kCombinedXor) & kMask48;
|
|
return (state * kJavaMultiplier + kJavaAddend) & kMask48;
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE bool exactIsSlimeFromTerm(uint64_t seed48, uint64_t term) {
|
|
uint64_t state = ((seed48 + term) ^ kCombinedXor) & kMask48;
|
|
for (;;) {
|
|
state = (state * kJavaMultiplier + kJavaAddend) & kMask48;
|
|
const uint32_t bits = static_cast<uint32_t>(state >> 17);
|
|
const uint32_t value = bits % 10U;
|
|
if (bits - value + 9U <= 0x7FFFFFFFU) {
|
|
return value == 0U;
|
|
}
|
|
}
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE bool verifyPattern(
|
|
uint64_t seed48,
|
|
const CellTerm* cells,
|
|
int cellCount
|
|
) {
|
|
for (int i = 0; i < cellCount; ++i) {
|
|
const bool slime = exactIsSlimeFromTerm(seed48, cells[i].term);
|
|
if (slime != (cells[i].requirement == kSlime)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE bool partialPatternPossible(
|
|
uint64_t seedPrefix,
|
|
int knownBits,
|
|
const CellTerm* cells,
|
|
int cellCount
|
|
) {
|
|
const uint64_t mask = (1ULL << knownBits) - 1ULL;
|
|
seedPrefix &= mask;
|
|
for (int i = 0; i < cellCount; ++i) {
|
|
uint64_t state = (((seedPrefix + cells[i].term) ^ kCombinedXor) *
|
|
kJavaMultiplier + kJavaAddend) & mask;
|
|
const uint64_t knownOutput = state >> 17;
|
|
if (cells[i].requirement == kSlime && (knownOutput & 1ULL) != 0ULL) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE uint32_t firstWithResidue(
|
|
uint32_t start,
|
|
uint32_t residue
|
|
) {
|
|
return start + ((residue + 5U - (start % 5U)) % 5U);
|
|
}
|
|
|
|
SLIME_HD SLIME_INLINE bool upper13HasCandidate(
|
|
uint64_t lower35,
|
|
const CellTerm* cells,
|
|
int cellCount
|
|
) {
|
|
UpperEvent events[kMaxPatternCells];
|
|
uint16_t residueCounts[5] = {0, 0, 0, 0, 0};
|
|
int eventCount = 0;
|
|
lower35 &= (1ULL << kLowerBits) - 1ULL;
|
|
for (int i = 0; i < cellCount; ++i) {
|
|
if (cells[i].requirement != kSlime) continue;
|
|
const uint32_t output = static_cast<uint32_t>(
|
|
nextStateFromTerm(lower35, cells[i].term) >> 17);
|
|
const uint32_t low17 = output & ((1U << 17) - 1U);
|
|
if ((low17 & 1U) != 0U) return false;
|
|
const uint32_t upper14 = output >> 17;
|
|
const uint32_t parity = upper14 & 1U;
|
|
const uint32_t v0 = (upper14 - parity) >> 1;
|
|
const uint32_t wanted = (3U * ((5U - ((low17 >> 1) % 5U) +
|
|
5U - parity) % 5U)) % 5U;
|
|
const uint32_t before = (wanted + 5U - (v0 % 5U)) % 5U;
|
|
const UpperEvent event{static_cast<uint16_t>(kUpperSize - v0),
|
|
static_cast<uint8_t>(before)};
|
|
int insert = eventCount;
|
|
while (insert > 0 && events[insert - 1].threshold > event.threshold) {
|
|
events[insert] = events[insert - 1];
|
|
--insert;
|
|
}
|
|
events[insert] = event;
|
|
++eventCount;
|
|
++residueCounts[before];
|
|
}
|
|
if (eventCount == 0) return true;
|
|
uint32_t start = 0;
|
|
int index = 0;
|
|
for (;;) {
|
|
const uint32_t stop = index < eventCount ? events[index].threshold : kUpperSize;
|
|
for (uint32_t residue = 0; residue < 5; ++residue) {
|
|
if (residueCounts[residue] == eventCount &&
|
|
firstWithResidue(start, residue) < stop) {
|
|
return true;
|
|
}
|
|
}
|
|
if (index == eventCount) return false;
|
|
const uint32_t threshold = events[index].threshold;
|
|
while (index < eventCount && events[index].threshold == threshold) {
|
|
const uint32_t before = events[index].before;
|
|
--residueCounts[before];
|
|
++residueCounts[(before + 2U) % 5U];
|
|
++index;
|
|
}
|
|
start = threshold;
|
|
}
|
|
}
|
|
|
|
template <typename Emit>
|
|
SLIME_HD SLIME_INLINE uint32_t solveUpper13(
|
|
uint64_t lower35,
|
|
const CellTerm* cells,
|
|
int cellCount,
|
|
Emit& emit
|
|
) {
|
|
UpperEvent events[kMaxPatternCells];
|
|
uint16_t residueCounts[5] = {0, 0, 0, 0, 0};
|
|
int eventCount = 0;
|
|
lower35 &= (1ULL << kLowerBits) - 1ULL;
|
|
|
|
for (int i = 0; i < cellCount; ++i) {
|
|
if (cells[i].requirement != kSlime) {
|
|
continue;
|
|
}
|
|
|
|
const uint32_t output = static_cast<uint32_t>(
|
|
nextStateFromTerm(lower35, cells[i].term) >> 17
|
|
);
|
|
const uint32_t low17 = output & ((1U << 17) - 1U);
|
|
if ((low17 & 1U) != 0U) {
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t upper14 = output >> 17;
|
|
const uint32_t parity = upper14 & 1U;
|
|
const uint32_t v0 = (upper14 - parity) >> 1;
|
|
const uint32_t wanted = (3U * ((5U - ((low17 >> 1) % 5U) +
|
|
5U - parity) % 5U)) % 5U;
|
|
const uint32_t before = (wanted + 5U - (v0 % 5U)) % 5U;
|
|
UpperEvent event{
|
|
static_cast<uint16_t>(kUpperSize - v0),
|
|
static_cast<uint8_t>(before),
|
|
};
|
|
|
|
int insert = eventCount;
|
|
while (insert > 0 && events[insert - 1].threshold > event.threshold) {
|
|
events[insert] = events[insert - 1];
|
|
--insert;
|
|
}
|
|
events[insert] = event;
|
|
++eventCount;
|
|
++residueCounts[before];
|
|
}
|
|
|
|
uint32_t emitted = 0;
|
|
if (eventCount == 0) {
|
|
for (uint32_t n = 0; n < kUpperSize; ++n) {
|
|
const uint32_t m = (kHalfDeltaInverse * n) & kUpperMask;
|
|
const uint64_t seed = lower35 + (uint64_t(m) << kLowerBits);
|
|
if (verifyPattern(seed, cells, cellCount)) {
|
|
emit(seed);
|
|
++emitted;
|
|
}
|
|
}
|
|
return emitted;
|
|
}
|
|
|
|
uint32_t start = 0;
|
|
int index = 0;
|
|
for (;;) {
|
|
const uint32_t stop = index < eventCount ? events[index].threshold : kUpperSize;
|
|
int commonResidue = -1;
|
|
for (int residue = 0; residue < 5; ++residue) {
|
|
if (residueCounts[residue] == eventCount) {
|
|
commonResidue = residue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (commonResidue >= 0 && start < stop) {
|
|
for (uint32_t n = firstWithResidue(start, uint32_t(commonResidue));
|
|
n < stop;
|
|
n += 5U) {
|
|
const uint32_t m = (kHalfDeltaInverse * n) & kUpperMask;
|
|
const uint64_t seed = lower35 + (uint64_t(m) << kLowerBits);
|
|
if (verifyPattern(seed, cells, cellCount)) {
|
|
emit(seed);
|
|
++emitted;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index == eventCount) {
|
|
break;
|
|
}
|
|
const uint32_t threshold = events[index].threshold;
|
|
while (index < eventCount && events[index].threshold == threshold) {
|
|
const uint32_t before = events[index].before;
|
|
--residueCounts[before];
|
|
++residueCounts[(before + 2U) % 5U];
|
|
++index;
|
|
}
|
|
start = threshold;
|
|
}
|
|
return emitted;
|
|
}
|
|
|
|
} // namespace slime_research
|
|
|
|
#undef SLIME_HD
|
|
#undef SLIME_INLINE
|