slime/include/slime/solver_core.hpp

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