#include "../include/slime/upper_interval.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sr=slime_research; namespace fs=full_search_experiment; using Clock=std::chrono::steady_clock; #define CUDA(call) do{cudaError_t status_=(call);if(status_!=cudaSuccess)throw std::runtime_error(std::string(#call)+": "+cudaGetErrorString(status_));}while(0) struct Coord{int32_t x,z;}; struct Job{uint32_t position,root;}; struct Match{uint64_t seed;int32_t x,z;}; struct PatternCell{int x,z;uint8_t requirement;}; struct Pattern{int width=0,height=0,positive=0;std::vector rows;std::vector cells;}; Pattern parsePattern(std::string text){ std::replace(text.begin(),text.end(),'/','\n'); Pattern p;std::stringstream input(text);std::string row; while(std::getline(input,row)){if(!row.empty()&&row.back()=='\r')row.pop_back();if(row.empty())throw std::runtime_error("empty pattern row");p.rows.push_back(row);} if(p.rows.empty())throw std::runtime_error("empty pattern"); p.width=p.rows[0].size();p.height=p.rows.size(); if(p.width>64||p.height>64)throw std::runtime_error("pattern dimensions must be 1..64"); for(auto& r:p.rows)if(r.size()!=size_t(p.width))throw std::runtime_error("pattern rows must have equal width"); for(char wanted:{'#','.'})for(int z=0;zINT32_MAX)throw std::runtime_error("invalid int32 coordinate");return int32_t(n);} uint64_t anchors(uint64_t r){return 1+2*r*(r+1);} uint64_t isqrt(uint64_t v){uint64_t r=std::sqrt(double(v));while((r+1)*(r+1)<=v)++r;while(r*r>v)--r;return r;} Coord coordinate(const Config& c,uint64_t index){ uint64_t top=(c.radius+1)*(c.radius+1);int64_t dx,dz; if(index=(uint64_t(positions)<<18))return; uint32_t pos=t>>18,root=t&262143;const auto* p=cells+uint64_t(pos)*count; for(int i=0;i=n)return;const auto job=jobs[k]; for(int i=0;i>18; prepared[(uint64_t(k)*count+i)*2+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17); } } __host__ __device__ __forceinline__ uint32_t q(const uint32_t*p,int i,uint32_t m){return (p[2*i+1]+2U*uint32_t(sr::kJavaMultiplier)*((m+p[2*i])^uint32_t(sr::kCombinedXor>>18)))&0x7fffffffU;} __host__ __device__ uint32_t groupMask(const uint32_t*p,uint32_t base,int count){ uint32_t mask=65535,q0=q(p,0,base),qe=q(p,0,base+240); for(int i=1;i>4,lo=(1U<=(uint64_t(n)<<13))return; uint32_t g=index&8191,k=index>>13,base=((g>>4)<<8)+(g&15);const uint32_t* p=prepared+uint64_t(k)*count*2; uint32_t mask=groupMask(p,base,positive); Job job=jobs[k];Coord origin=coords[job.position];Emit emit{origin,sr::coordinateTerm(origin.x+originX,origin.z+originZ),cells+uint64_t(job.position)*count+positive,count-positive,out,capacity,total}; while(mask){uint32_t j=__ffs(mask)-1;mask&=mask-1;fs::solveIntervals(job.root+(uint64_t(base+16*j)<<18),cells+uint64_t(job.position)*count,positive,emit);} } templatestruct Buffer{T* p=nullptr;explicit Buffer(size_t n){if(n)CUDA(cudaMalloc(&p,n*sizeof(T)));}~Buffer(){if(p)cudaFree(p);}Buffer(const Buffer&)=delete;}; struct Metrics{int device;std::string name;uint64_t positions=0,classes=0,cacheHits=0,jobs=0,matches=0;double setupMs=0,scanMs=0,rootMs=0,searchMs=0;Clock::time_point start,end;std::vector sample;}; struct Shared{std::atomic next{0},done{0};std::atomic finished{0};int ready=0,workerCount=0;std::condition_variable cv;std::atomic cancel{false};std::mutex mutex;std::string error;}; void worker(const Config& c,int device,Shared& shared,Metrics& result){try{ auto setup=Clock::now();CUDA(cudaSetDevice(device));cudaDeviceProp prop{};CUDA(cudaGetDeviceProperties(&prop,device));result.device=device;result.name=prop.name; int count=c.pattern.cells.size(),positive=c.pattern.positive;size_t rootsCapacity=size_t(c.positionBatch)*(1U<<17); Buffer dcells(size_t(c.positionBatch)*count);Bufferdcoords(c.positionBatch);Bufferdroots(rootsCapacity),djobs(c.jobBatch); Bufferprep(size_t(c.jobBatch)*count*2);Buffertotal(1);Buffermatches(c.maxResults); std::map> cache;std::deque fifo;size_t cachedRoots=0; result.setupMs=std::chrono::duration(Clock::now()-setup).count();{ std::unique_lock lock(shared.mutex);++shared.ready;shared.cv.notify_all(); shared.cv.wait(lock,[&](){return shared.ready==shared.workerCount||shared.cancel.load();}); if(shared.cancel)return; } result.start=Clock::now(); const uint64_t end=anchors(c.radius); while(!shared.cancel){uint64_t first=shared.next.fetch_add(c.positionBatch);if(first>=end)break; uint32_t num=std::min(c.positionBatch,end-first);std::vector coords;std::vector keys; std::map> lists;std::vector missing;std::vector missingKeys; for(uint32_t i=0;isecond;++result.cacheHits;}else{lists[key]={};missing.push_back(p);missingKeys.push_back(key);} } if(!missing.empty()){ auto start=Clock::now();std::vector cells(missing.size()*count);for(size_t i=0;i>>(dcells.p,missing.size(),count,positive,droots.p,total.p);CUDA(cudaGetLastError()); unsigned long long found;CUDA(cudaMemcpy(&found,total.p,sizeof(found),cudaMemcpyDeviceToHost));if(found>rootsCapacity)throw std::runtime_error("root capacity invariant failed"); std::vector roots(found);if(found)CUDA(cudaMemcpy(roots.data(),droots.p,found*sizeof(Job),cudaMemcpyDeviceToHost)); for(auto job:roots)lists[missingKeys[job.position]].push_back(job.root); result.classes+=missing.size(); for(auto key:missingKeys){auto& list=lists[key];std::sort(list.begin(),list.end());if(c.cacheClasses){ while(!fifo.empty()&&(cache.size()>=c.cacheClasses||cachedRoots+list.size()>(1U<<20))){auto old=fifo.front();fifo.pop_front();cachedRoots-=cache.at(old).size();cache.erase(old);} cache[key]=list;cachedRoots+=list.size();fifo.push_back(key); }} result.rootMs+=std::chrono::duration(Clock::now()-start).count(); } // Construct concrete positions only for classes with roots. std::vector active;std::vector cells;std::vector jobs;jobs.reserve(c.jobBatch); for(uint32_t i=0;i>>(dcells.p,djobs.p,jobs.size(),count,positive,prep.p); searchKernel<<>>(dcells.p,dcoords.p,djobs.p,jobs.size(),count,positive,c.pattern.cells.front().x,c.pattern.cells.front().z,prep.p,matches.p,c.maxResults,total.p);CUDA(cudaGetLastError()); unsigned long long found;CUDA(cudaMemcpy(&found,total.p,sizeof(found),cudaMemcpyDeviceToHost));if(found>UINT64_MAX-result.matches)throw std::runtime_error("match count exceeds uint64");result.matches+=found;result.jobs+=jobs.size(); size_t take=std::min(found,c.maxResults-result.sample.size());if(take){size_t old=result.sample.size();result.sample.resize(old+take);CUDA(cudaMemcpy(result.sample.data()+old,matches.p,take*sizeof(Match),cudaMemcpyDeviceToHost));} result.searchMs+=std::chrono::duration(Clock::now()-start).count();jobs.clear();}; for(uint32_t i=0;i(result.end-result.start).count(); }catch(const std::exception& e){shared.cancel=true;std::lock_guard lock(shared.mutex);shared.error="GPU "+std::to_string(device)+": "+e.what();shared.cv.notify_all();}} std::string escape(const std::string&s){std::ostringstream o;for(unsigned char ch:s){if(ch=='"'||ch=='\\')o<<'\\'<=0) --size N Solid square side, 1..6 (default 5) --pattern TEXT Rows separated by /; # slime, . non-slime, ? ignored --pattern-file FILE Same pattern, one row per line (dimensions 1..64) Requires at least one #; exclusive with --size --devices 0,1,... CUDA device indices, or all (default 0) --repeat N Complete passes; fresh root cache each pass (default 1) --position-batch N Anchors per scheduled batch, 1..256 (default 32) --job-batch N Low18-root jobs per GPU launch, 1..65536 (default 2048) --cache-classes N Per-GPU bounded FIFO root cache entries (default 4096) --max-results N Save up to N example matches; count ALL (default 0) --output FILE Also save the complete JSON report (file must not exist) --dry-run Print scope JSON without using CUDA --list-devices List visible CUDA devices --help Show this help Scope: abs(anchor_x-X)+abs(anchor_z-Z)<=R, including boundary. Anchor is the minimum-x/minimum-z corner; the square may extend outside this diamond. Coordinates are not clipped to Minecraft's world border. Small squares can have enormous numbers of matching seeds and take very long. )";} int main(int argc,char**argv){try{ Config c;bool radiusSet=false,sizeSet=false;std::string patternText;bool patternSet=false;for(int i=1;i=argc)throw std::runtime_error("missing value for "+a);std::string v=argv[++i]; auto bounded=[&](uint64_t lo,uint64_t hi){auto n=number(v);if(nhi)throw std::runtime_error("out of range: "+a);return n;}; if(a=="--x")c.x=signed32(v);else if(a=="--z")c.z=signed32(v);else if(a=="--radius"){c.radius=bounded(0,INT32_MAX);radiusSet=true;} else if(a=="--size"){c.size=bounded(1,6);sizeSet=true;} else if(a=="--pattern"||a=="--pattern-file"){if(patternSet)throw std::runtime_error("specify only one pattern");patternSet=true; if(a=="--pattern")patternText=v;else{std::ifstream file(v);if(!file)throw std::runtime_error("cannot read pattern: "+v);std::ostringstream text;text<INT32_MAX)throw std::runtime_error("coordinates including pattern must fit int32"); if(!c.output.empty()&&std::ifstream(c.output).good())throw std::runtime_error("output already exists: "+c.output); std::ostringstream report;report< devices;if(c.devices=="all"){for(int i=0;i=uint64_t(available))throw std::runtime_error("CUDA device unavailable: "+v);devices.push_back(id);} } if(devices.empty()||std::set(devices.begin(),devices.end()).size()!=devices.size())throw std::runtime_error("device list must be nonempty and unique"); report<<",\"position_batch\":"< timings;uint64_t expected=0; for(int round=0;round metrics(devices.size());std::vector workers;auto start=Clock::now(); for(size_t i=0;i lock(shared.mutex);while(shared.finished(Clock::now()-start).count();auto first=metrics[0].start,last=metrics[0].end; uint64_t visited=0,jobs=0,matches=0;std::vector sample; for(auto&m:metrics){first=std::min(first,m.start);last=std::max(last,m.end);visited+=m.positions;jobs+=m.jobs;if(m.matches>UINT64_MAX-matches)throw std::runtime_error("total match count exceeds uint64");matches+=m.matches;for(auto hit:m.sample)if(sample.size()(last-first).count();timings.push_back(scan); if(round)report<<",";report<<"{\"round\":"<sample.size()?"true":"false")<<",\"sample\":["; for(size_t i=0;i