chore: archive early implementations and chunk checker tools
This commit is contained in:
parent
7a06f94577
commit
69f7f7a34b
9 changed files with 1408 additions and 0 deletions
403
codex/slime_cpu.cpp
Normal file
403
codex/slime_cpu.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL;
|
||||
constexpr uint64_t kJavaAddend = 0xBULL;
|
||||
constexpr uint64_t kJavaMask = (1ULL << 48) - 1;
|
||||
constexpr uint64_t kSlimeXor = 987234911ULL;
|
||||
|
||||
struct Args {
|
||||
int64_t startSeed = 0;
|
||||
int64_t endSeed = 1'000'000;
|
||||
int32_t n = 3;
|
||||
int32_t required = -1;
|
||||
int32_t radius = 100;
|
||||
int32_t threads = std::max(1u, std::thread::hardware_concurrency());
|
||||
int64_t seedBatch = 256;
|
||||
int32_t maxResults = 32;
|
||||
bool allLocations = false;
|
||||
};
|
||||
|
||||
struct Hit {
|
||||
int64_t seed;
|
||||
int32_t x;
|
||||
int32_t z;
|
||||
int32_t count;
|
||||
};
|
||||
|
||||
[[noreturn]] void usage(int exitCode = 1) {
|
||||
std::cerr
|
||||
<< "Usage: slime_cpu [args...]\n"
|
||||
<< " -s <startSeed> First world seed, inclusive (default: 0)\n"
|
||||
<< " -e <endSeed> Last world seed, exclusive (default: 1000000)\n"
|
||||
<< " -n <chunkSize> N for the NxN window (default: 3)\n"
|
||||
<< " -d <numChunks> Required slime chunks in the window (default: N*N)\n"
|
||||
<< " -r <radius> Search start positions from -r..r on both axes (default: 100)\n"
|
||||
<< " -p <threads> CPU worker threads (default: hardware concurrency)\n"
|
||||
<< " --batch <seeds> Seeds fetched per worker batch (default: 256)\n"
|
||||
<< " --max-results <n> Print at most n hits, while still counting all hits (default: 32)\n"
|
||||
<< " --all-locations Count every matching location, not just the first per seed\n"
|
||||
<< " --help Show this help\n";
|
||||
std::exit(exitCode);
|
||||
}
|
||||
|
||||
const char* requireValue(int argc, char** argv, int& i) {
|
||||
if (i + 1 >= argc) {
|
||||
usage();
|
||||
}
|
||||
return argv[++i];
|
||||
}
|
||||
|
||||
Args parseArgs(int argc, char** argv) {
|
||||
Args args;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--help") {
|
||||
usage(0);
|
||||
} else if (arg == "-s") {
|
||||
args.startSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-e") {
|
||||
args.endSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-n") {
|
||||
args.n = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-d") {
|
||||
args.required = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-r") {
|
||||
args.radius = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-p") {
|
||||
args.threads = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--batch") {
|
||||
args.seedBatch = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "--max-results") {
|
||||
args.maxResults = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--all-locations") {
|
||||
args.allLocations = true;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t cells = int64_t(args.n) * args.n;
|
||||
if (args.required < 0) {
|
||||
args.required = static_cast<int32_t>(cells);
|
||||
}
|
||||
if (args.startSeed >= args.endSeed || args.n <= 0 || args.radius < 0 ||
|
||||
args.threads <= 0 || args.seedBatch <= 0 || args.maxResults < 0 ||
|
||||
cells > INT32_MAX || args.required <= 0 || args.required > cells) {
|
||||
usage();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
uint64_t javaIntAsLongBits(uint32_t value) {
|
||||
return (value & 0x80000000u) ? (0xFFFFFFFF00000000ULL | value) : uint64_t(value);
|
||||
}
|
||||
|
||||
uint64_t coordTerm(int32_t chunkX, int32_t chunkZ) {
|
||||
const uint32_t x = static_cast<uint32_t>(chunkX);
|
||||
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
||||
|
||||
uint64_t term = 0;
|
||||
term += javaIntAsLongBits(x * x * 4987142u);
|
||||
term += javaIntAsLongBits(x * 5947611u);
|
||||
term += javaIntAsLongBits(z * z) * 4392871ULL;
|
||||
term += javaIntAsLongBits(z * 389711u);
|
||||
return term;
|
||||
}
|
||||
|
||||
bool isSlimeFromTerm(uint64_t worldSeedBits, uint64_t term) {
|
||||
uint64_t randomSeed = ((worldSeedBits + term) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask;
|
||||
|
||||
for (;;) {
|
||||
randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask;
|
||||
const uint32_t bits = static_cast<uint32_t>(randomSeed >> 17);
|
||||
const uint32_t value = bits % 10u;
|
||||
if (bits - value + 9u <= 0x7FFFFFFFu) {
|
||||
return value == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int trailingZeroes(uint64_t value) {
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long index = 0;
|
||||
_BitScanForward64(&index, value);
|
||||
return static_cast<int>(index);
|
||||
#else
|
||||
return __builtin_ctzll(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t lowBitsMask(int32_t bits) {
|
||||
const int32_t rem = bits & 63;
|
||||
return rem == 0 ? ~0ULL : ((1ULL << rem) - 1ULL);
|
||||
}
|
||||
|
||||
struct SearchSpace {
|
||||
int32_t n;
|
||||
int32_t radius;
|
||||
int32_t window;
|
||||
int32_t chunkWidth;
|
||||
int32_t chunkRows;
|
||||
int32_t chunkWords;
|
||||
int32_t windowWords;
|
||||
uint64_t lastWindowMask;
|
||||
std::vector<uint64_t> terms;
|
||||
|
||||
SearchSpace(int32_t radiusIn, int32_t nIn)
|
||||
: n(nIn),
|
||||
radius(radiusIn),
|
||||
window(radiusIn * 2 + 1),
|
||||
chunkWidth(window + nIn - 1),
|
||||
chunkRows(window + nIn - 1),
|
||||
chunkWords((chunkWidth + 63) / 64),
|
||||
windowWords((window + 63) / 64),
|
||||
lastWindowMask(lowBitsMask(window)),
|
||||
terms(size_t(chunkWidth) * chunkRows) {
|
||||
for (int32_t z = 0; z < chunkRows; ++z) {
|
||||
for (int32_t x = 0; x < chunkWidth; ++x) {
|
||||
terms[size_t(z) * chunkWidth + x] = coordTerm(x - radius, z - radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Workspace {
|
||||
std::vector<uint64_t> rows;
|
||||
std::vector<uint64_t> horizontal;
|
||||
std::vector<uint8_t> rowValues;
|
||||
std::vector<int32_t> rowRing;
|
||||
std::vector<int32_t> vertical;
|
||||
|
||||
explicit Workspace(const SearchSpace& space)
|
||||
: rows(size_t(space.chunkRows) * space.chunkWords),
|
||||
horizontal(size_t(space.chunkRows) * space.chunkWords),
|
||||
rowValues(space.chunkWidth),
|
||||
rowRing(size_t(space.n) * space.window),
|
||||
vertical(space.window) {}
|
||||
};
|
||||
|
||||
struct ResultSink {
|
||||
std::mutex outputMutex;
|
||||
std::atomic<uint64_t> hits{0};
|
||||
int32_t maxResults;
|
||||
|
||||
explicit ResultSink(int32_t maxResultsIn) : maxResults(maxResultsIn) {}
|
||||
|
||||
void record(const Hit& hit) {
|
||||
const uint64_t index = hits.fetch_add(1, std::memory_order_relaxed);
|
||||
if (index >= static_cast<uint64_t>(maxResults)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
std::cout << "hit seed=" << hit.seed
|
||||
<< " x=" << hit.x
|
||||
<< " z=" << hit.z
|
||||
<< " count=" << hit.count << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
uint64_t shiftedWord(const uint64_t* row, int32_t words, int32_t word, int32_t shift) {
|
||||
const int32_t whole = shift >> 6;
|
||||
const int32_t bits = shift & 63;
|
||||
const int32_t src = word + whole;
|
||||
const uint64_t lo = src < words ? row[src] : 0ULL;
|
||||
if (bits == 0) {
|
||||
return lo;
|
||||
}
|
||||
const uint64_t hi = src + 1 < words ? row[src + 1] : 0ULL;
|
||||
return (lo >> bits) | (hi << (64 - bits));
|
||||
}
|
||||
|
||||
uint64_t findFullSquare(
|
||||
int64_t seed,
|
||||
const SearchSpace& space,
|
||||
Workspace& workspace,
|
||||
ResultSink& sink,
|
||||
bool allLocations
|
||||
) {
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
std::fill(workspace.rows.begin(), workspace.rows.end(), 0ULL);
|
||||
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords;
|
||||
const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth;
|
||||
for (int32_t x = 0; x < space.chunkWidth; ++x) {
|
||||
if (isSlimeFromTerm(seedBits, termRow[x])) {
|
||||
row[x >> 6] |= 1ULL << (x & 63);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
const uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords;
|
||||
uint64_t* out = workspace.horizontal.data() + size_t(z) * space.chunkWords;
|
||||
for (int32_t word = 0; word < space.chunkWords; ++word) {
|
||||
uint64_t mask = row[word];
|
||||
for (int32_t shift = 1; shift < space.n; ++shift) {
|
||||
mask &= shiftedWord(row, space.chunkWords, word, shift);
|
||||
}
|
||||
out[word] = mask;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t found = 0;
|
||||
for (int32_t z = 0; z < space.window; ++z) {
|
||||
for (int32_t word = 0; word < space.windowWords; ++word) {
|
||||
uint64_t mask = ~0ULL;
|
||||
for (int32_t dz = 0; dz < space.n; ++dz) {
|
||||
mask &= workspace.horizontal[(size_t(z + dz) * space.chunkWords) + word];
|
||||
}
|
||||
if (word == space.windowWords - 1) {
|
||||
mask &= space.lastWindowMask;
|
||||
}
|
||||
|
||||
while (mask != 0) {
|
||||
const int32_t bit = trailingZeroes(mask);
|
||||
const int32_t x = word * 64 + bit;
|
||||
sink.record({seed, x - space.radius, z - space.radius, space.n * space.n});
|
||||
++found;
|
||||
if (!allLocations) {
|
||||
return found;
|
||||
}
|
||||
mask &= mask - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint64_t findAtLeastCount(
|
||||
int64_t seed,
|
||||
int32_t required,
|
||||
const SearchSpace& space,
|
||||
Workspace& workspace,
|
||||
ResultSink& sink,
|
||||
bool allLocations
|
||||
) {
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
std::fill(workspace.vertical.begin(), workspace.vertical.end(), 0);
|
||||
|
||||
uint64_t found = 0;
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth;
|
||||
for (int32_t x = 0; x < space.chunkWidth; ++x) {
|
||||
workspace.rowValues[x] = isSlimeFromTerm(seedBits, termRow[x]) ? 1 : 0;
|
||||
}
|
||||
|
||||
int32_t* ringRow = workspace.rowRing.data() + size_t(z % space.n) * space.window;
|
||||
if (z >= space.n) {
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
workspace.vertical[x] -= ringRow[x];
|
||||
}
|
||||
}
|
||||
|
||||
int32_t rowSum = 0;
|
||||
for (int32_t x = 0; x < space.n; ++x) {
|
||||
rowSum += workspace.rowValues[x];
|
||||
}
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
if (x != 0) {
|
||||
rowSum += workspace.rowValues[x + space.n - 1];
|
||||
rowSum -= workspace.rowValues[x - 1];
|
||||
}
|
||||
ringRow[x] = rowSum;
|
||||
workspace.vertical[x] += rowSum;
|
||||
}
|
||||
|
||||
if (z + 1 < space.n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32_t startZ = z - space.n + 1;
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
const int32_t count = workspace.vertical[x];
|
||||
if (count >= required) {
|
||||
sink.record({seed, x - space.radius, startZ - space.radius, count});
|
||||
++found;
|
||||
if (!allLocations) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void worker(
|
||||
const Args& args,
|
||||
const SearchSpace& space,
|
||||
std::atomic<int64_t>& nextSeed,
|
||||
ResultSink& sink
|
||||
) {
|
||||
Workspace workspace(space);
|
||||
for (;;) {
|
||||
const int64_t begin = nextSeed.fetch_add(args.seedBatch, std::memory_order_relaxed);
|
||||
if (begin >= args.endSeed) {
|
||||
return;
|
||||
}
|
||||
const int64_t end = std::min(args.endSeed, begin + args.seedBatch);
|
||||
for (int64_t seed = begin; seed < end; ++seed) {
|
||||
if (args.required == args.n * args.n) {
|
||||
findFullSquare(seed, space, workspace, sink, args.allLocations);
|
||||
} else {
|
||||
findAtLeastCount(seed, args.required, space, workspace, sink, args.allLocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const Args args = parseArgs(argc, argv);
|
||||
const SearchSpace space(args.radius, args.n);
|
||||
ResultSink sink(args.maxResults);
|
||||
std::atomic<int64_t> nextSeed(args.startSeed);
|
||||
|
||||
std::cout << "CPU search: seeds [" << args.startSeed << ", " << args.endSeed
|
||||
<< "), N=" << args.n << ", required=" << args.required
|
||||
<< ", radius=" << args.radius << ", threads=" << args.threads
|
||||
<< (args.allLocations ? ", all locations\n" : ", first location per seed\n");
|
||||
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(args.threads);
|
||||
for (int32_t i = 0; i < args.threads; ++i) {
|
||||
workers.emplace_back(worker, std::cref(args), std::cref(space), std::ref(nextSeed), std::ref(sink));
|
||||
}
|
||||
for (std::thread& thread : workers) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
const auto finished = std::chrono::steady_clock::now();
|
||||
const std::chrono::duration<double> elapsed = finished - started;
|
||||
std::cout << "checked " << (args.endSeed - args.startSeed)
|
||||
<< " seeds, hits=" << sink.hits.load(std::memory_order_relaxed)
|
||||
<< ", elapsed=" << elapsed.count() << "s\n";
|
||||
return 0;
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "error: " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue