Support custom slime chunk patterns in CUDA finder

This commit is contained in:
Yaossg 2026-10-16 23:51:12 +08:00
parent dd5fab9540
commit 7952207735
10 changed files with 2302 additions and 34 deletions

View file

@ -1,6 +1,6 @@
# 史莱姆区块搜索与 CUDA 基准 # 史莱姆区块搜索与 CUDA 基准
当前可直接使用的入口是 **[bench/slime-bench](bench/README.md)**:指定中心区块、曼哈顿半径和实心 `n×n` 图案,测量全部有效种子的普通候选分支搜索时间,支持单张或多张 NVIDIA GPU。 当前可直接使用的入口是 **[bench/slime-bench](bench/README.md)**:指定中心区块、曼哈顿半径和自定义图案(或实心 `n×n`),测量全部有效种子的普通候选分支搜索时间,支持单张或多张 NVIDIA GPU。
```sh ```sh
make bench make bench
@ -8,6 +8,8 @@ make bench
--position-batch 128 --devices all --repeat 3 --position-batch 128 --devices all --repeat 3
``` ```
自定义图案用 `--pattern` 或 `--pattern-file`,支持史莱姆、非史莱姆和忽略格;见 [6×6 苦力怕示例](bench/README.md#自定义图案6x6-苦力怕)。
H100 编译:`make -C bench -B CUDA_ARCH=90`。需要 CUDA Toolkit 和 C++17 编译器。普通分支不包含拒绝重抽补扫,不能将它的结果声明为严格完整目录;参数和计时口径见 [CLI 使用说明](bench/README.md)。 H100 编译:`make -C bench -B CUDA_ARCH=90`。需要 CUDA Toolkit 和 C++17 编译器。普通分支不包含拒绝重抽补扫,不能将它的结果声明为严格完整目录;参数和计时口径见 [CLI 使用说明](bench/README.md)。
## 目录导航 ## 目录导航

View file

@ -1,6 +1,6 @@
# slime-bench:普通分支全种子搜索基准 # slime-bench:普通分支全种子搜索基准
指定中心区块、曼哈顿半径和实心 `n×n` 图案,搜索范围内每个锚点的全部有效种子,输出 JSON 结果及计时。支持 NVIDIA CUDA 单卡、多卡。 指定中心区块、曼哈顿半径和自定义图案(或实心 `n×n`),搜索范围内每个锚点的全部有效种子,输出 JSON 结果及计时。支持 NVIDIA CUDA 单卡、多卡。
## 本地编译与运行 ## 本地编译与运行
@ -16,6 +16,37 @@ mkdir -p artifacts
默认编译 `sm_86` 并附带 PTX,适合本机 RTX 3060 Laptop。换架构时用 `-B` 强制重建;仅改变 Make 变量不会使已有文件自动过期。 默认编译 `sm_86` 并附带 PTX,适合本机 RTX 3060 Laptop。换架构时用 `-B` 强制重建;仅改变 Make 变量不会使已有文件自动过期。
## 自定义图案:6×6 苦力怕
`--pattern` 接受 `/` 分隔的行,或用 `--pattern-file` 读取每行一排的文本文件。`#` 必须是史莱姆区块,`.` 必须不是,`?` 不限制。支持非正方形,宽、高分别为 1~64,至少含一个 `#`;不能与 `--size` 同时指定。不自动旋转或镜像。
内置示例 [patterns/creeper-6x6.txt](patterns/creeper-6x6.txt) 将脸部像素设为史莱姆:
```text
##..##
##..##
..##..
.####.
.####.
.#..#.
```
```sh
./bench/bin/slime-bench --radius 512 \
--pattern-file bench/patterns/creeper-6x6.txt \
--position-batch 128 --max-results 100 --output artifacts/creeper.json
# 也可直接输入;必须使用引号保护 #、? 等字符
./bench/bin/slime-bench --radius 512 \
--pattern '##..##/##..##/..##../.####./.####./.#..#.' --max-results 100
```
目标是上面的正色模板:眼睛、鼻子和嘴巴为史莱姆区块,背景必须不是。已检查原点半径 2048、中心 `(51069,419124)` 半径 1024、32 个分散区域各半径 256、另外 128 个分散区域各半径 512;还筛选了 65,536 个坐标,选出低位候选最多的 64 个坐标类,检查这些类在世界边界内的全部 104,538 个实际位置。累计 82,047,228 次锚点检查仍未命中(不同路线可能重叠,见[搜索记录](results/creeper-6x6-search.json))。这不证明正色不存在;不能用反色图案作为该目标的搜索结果。
每行从左到右沿 `+X`,从上到下沿 `+Z`。返回坐标仍是矩形左上角,即使该格是 `.` 或 `?`。将 `.` 改为 `?` 可只要求轮廓、允许背景出现额外史莱姆区块。JSON 包含原始 `pattern` 行、宽高及两类约束数;自定义输入的 `square_size` 为 `null`。
筛选和区间求解只使用 `#` 格,随后精确核对所有 `.` 格;`?` 不参与计算。稀疏图案的候选可能很多,搜索时间不能直接套用实心方阵的基准。搜索口径仍为普通分支,不包含拒绝重抽补扫。
## H100 服务器 ## H100 服务器
将仓库内的 `bench/` 和 `include/` 一起复制到服务器,保持相对路径;CLI 依赖 `include/slime/` 中的共用头文件。 将仓库内的 `bench/` 和 `include/` 一起复制到服务器,保持相对路径;CLI 依赖 `include/slime/` 中的共用头文件。
@ -53,7 +84,7 @@ abs(anchor_x - x) + abs(anchor_z - z) <= radius
``` ```
- `x,z,radius` 的单位都是区块,不是方块。 - `x,z,radius` 的单位都是区块,不是方块。
- 锚点是图案最小 X、最小 Z 的角;图案沿 `+X,+Z` 延伸。 - 锚点是图案最小 X、最小 Z 的角;图案沿 `+X,+Z` 延伸。自定义图案的锚点是外接矩形的角。
- 半径限制锚点,图案允许超出菱形范围。 - 半径限制锚点,图案允许超出菱形范围。
- 不自动按世界边界裁剪;锚点和整块图案须在 int32 坐标范围内,否则报错。 - 不自动按世界边界裁剪;锚点和整块图案须在 int32 坐标范围内,否则报错。
- `--size` 支持 1~6,默认 5。小尺寸匹配种子极多,全种子计数也可能耗时很久,不能用 1×1 推断 5×5 性能。 - `--size` 支持 1~6,默认 5。小尺寸匹配种子极多,全种子计数也可能耗时很久,不能用 1×1 推断 5×5 性能。
@ -108,7 +139,7 @@ make -C bench test # 无需 GPU:范围、输入、可变尺寸数学对
make -C bench test-gpu # 需要 GPU:实际搜索与文件输出验证 make -C bench test-gpu # 需要 GPU:实际搜索与文件输出验证
``` ```
验证内容包括:半径 0~64 的完整菱形枚举,较大半径抽样,1~6 中多个尺寸的组 mask 对照,已知 24 个命中,小根批分拆一致性,4×4 的全部 422 个示例经独立 Java 算法验证,6×6 的已知种子子集对照,以及半径 2 批量结果与全部 13 个位置分别搜索的完整列表一致。 验证内容包括:自定义图案解析和边界、稀疏图案高位枚举对照、带忽略格及非史莱姆格的 GPU 结果与独立 Java 算法筛选对照,以及半径 0~64 的完整菱形枚举,较大半径抽样,1~6 中多个尺寸的组 mask 对照,已知 24 个命中,小根批分拆一致性,4×4 的全部 422 个示例经独立 Java 算法验证,6×6 的已知种子子集对照,以及半径 2 批量结果与全部 13 个位置分别搜索的完整列表一致。
本机实测:RTX 3060 Laptop,中心 `(0,0)`,半径 512,5×5,`--position-batch 128 --repeat 3`。525,313 个锚点,三轮均有 368,935 个低位根任务、0 个命中,扫描墙钟中位数约 **3.990 秒**。原始 JSON 在 [results/local-r512-n5.json](results/local-r512-n5.json)。另有半径 64 的快速环境验证记录;其批大小为默认 32,与上述正式样例不同。 本机实测:RTX 3060 Laptop,中心 `(0,0)`,半径 512,5×5,`--position-batch 128 --repeat 3`。525,313 个锚点,三轮均有 368,935 个低位根任务、0 个命中,扫描墙钟中位数约 **3.990 秒**。原始 JSON 在 [results/local-r512-n5.json](results/local-r512-n5.json)。另有半径 64 的快速环境验证记录;其批大小为默认 32,与上述正式样例不同。

View file

@ -0,0 +1,6 @@
##..##
##..##
..##..
.####.
.####.
.#..#.

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,25 @@ using Clock=std::chrono::steady_clock;
struct Coord{int32_t x,z;}; struct Coord{int32_t x,z;};
struct Job{uint32_t position,root;}; struct Job{uint32_t position,root;};
struct Match{uint64_t seed;int32_t x,z;}; struct Match{uint64_t seed;int32_t x,z;};
struct Config{int32_t x=0,z=0;uint64_t radius=0;int size=5,positionBatch=32,jobBatch=2048,repeats=1;uint64_t maxResults=0;size_t cacheClasses=4096;std::string devices="0",output;bool dry=false,list=false;}; struct PatternCell{int x,z;uint8_t requirement;};
struct Pattern{int width=0,height=0,positive=0;std::vector<std::string> rows;std::vector<PatternCell> 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;z<p.height;++z)for(int x=0;x<p.width;++x){char ch=p.rows[z][x];
if(ch!='#'&&ch!='.'&&ch!='?')throw std::runtime_error("pattern characters must be #, . or ?");
if(ch==wanted)p.cells.push_back({x,z,uint8_t(ch=='#'?sr::kSlime:sr::kNonSlime)});
if(wanted=='#'&&ch=='#')++p.positive;
}
if(!p.positive)throw std::runtime_error("pattern requires at least one # cell");
return p;
}
struct Config{Pattern pattern;int32_t x=0,z=0;uint64_t radius=0;int size=5,positionBatch=32,jobBatch=2048,repeats=1;uint64_t maxResults=0;size_t cacheClasses=4096;std::string devices="0",output;bool dry=false,list=false;};
uint64_t number(const std::string& s){size_t used=0;if(s.empty()||s[0]=='-')throw std::runtime_error("expected nonnegative integer: "+s);auto n=std::stoull(s,&used);if(used!=s.size())throw std::runtime_error("invalid integer: "+s);return n;} uint64_t number(const std::string& s){size_t used=0;if(s.empty()||s[0]=='-')throw std::runtime_error("expected nonnegative integer: "+s);auto n=std::stoull(s,&used);if(used!=s.size())throw std::runtime_error("invalid integer: "+s);return n;}
int32_t signed32(const std::string&s){size_t used=0;auto n=std::stoll(s,&used);if(used!=s.size()||n<INT32_MIN||n>INT32_MAX)throw std::runtime_error("invalid int32 coordinate");return int32_t(n);} int32_t signed32(const std::string&s){size_t used=0;auto n=std::stoll(s,&used);if(used!=s.size()||n<INT32_MIN||n>INT32_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 anchors(uint64_t r){return 1+2*r*(r+1);}
@ -38,15 +56,20 @@ Coord coordinate(const Config& c,uint64_t index){
} }
uint64_t classKey(Coord p){return (uint64_t(uint32_t(p.x)&65535)<<17)|(uint32_t(p.z)&131071);} uint64_t classKey(Coord p){return (uint64_t(uint32_t(p.x)&65535)<<17)|(uint32_t(p.z)&131071);}
void terms(Coord p,int n,sr::CellTerm* out){uint64_t anchor=sr::coordinateTerm(p.x,p.z);for(int z=0;z<n;++z)for(int x=0;x<n;++x)out[z*n+x]={(sr::coordinateTerm(p.x+x,p.z+z)-anchor)&sr::kMask48,sr::kSlime};} void terms(Coord p,int n,sr::CellTerm* out){uint64_t anchor=sr::coordinateTerm(p.x,p.z);for(int z=0;z<n;++z)for(int x=0;x<n;++x)out[z*n+x]={(sr::coordinateTerm(p.x+x,p.z+z)-anchor)&sr::kMask48,sr::kSlime};}
__global__ void rootsKernel(const sr::CellTerm* cells,uint32_t positions,int count,Job* out,unsigned long long* total){ void terms(Coord p,const Pattern& pattern,sr::CellTerm* out){
// The grouped filter needs its first positive term to be zero.
auto first=pattern.cells.front();uint64_t anchor=sr::coordinateTerm(p.x+first.x,p.z+first.z);size_t i=0;
for(auto cell:pattern.cells)out[i++]={(sr::coordinateTerm(p.x+cell.x,p.z+cell.z)-anchor)&sr::kMask48,cell.requirement};
}
__global__ void rootsKernel(const sr::CellTerm* cells,uint32_t positions,int count,int positive,Job* out,unsigned long long* total){
uint64_t t=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(t>=(uint64_t(positions)<<18))return; uint64_t t=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(t>=(uint64_t(positions)<<18))return;
uint32_t pos=t>>18,root=t&262143;const auto* p=cells+uint64_t(pos)*count; uint32_t pos=t>>18,root=t&262143;const auto* p=cells+uint64_t(pos)*count;
for(int i=0;i<count;++i)if((((root+uint32_t(p[i].term))^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)&(1U<<17))return; for(int i=0;i<positive;++i)if((((root+uint32_t(p[i].term))^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)&(1U<<17))return;
out[atomicAdd(total,1ULL)]={pos,root}; out[atomicAdd(total,1ULL)]={pos,root};
} }
__global__ void prepareKernel(const sr::CellTerm* cells,const Job* jobs,uint32_t n,int count,uint32_t* prepared){ __global__ void prepareKernel(const sr::CellTerm* cells,const Job* jobs,uint32_t n,int count,int positive,uint32_t* prepared){
uint32_t k=blockIdx.x*blockDim.x+threadIdx.x;if(k>=n)return;const auto job=jobs[k]; uint32_t k=blockIdx.x*blockDim.x+threadIdx.x;if(k>=n)return;const auto job=jobs[k];
for(int i=0;i<count;++i){uint64_t a=job.root+cells[uint64_t(job.position)*count+i].term; for(int i=0;i<positive;++i){uint64_t a=job.root+cells[uint64_t(job.position)*count+i].term;
prepared[(uint64_t(k)*count+i)*2]=a>>18; prepared[(uint64_t(k)*count+i)*2]=a>>18;
prepared[(uint64_t(k)*count+i)*2+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17); prepared[(uint64_t(k)*count+i)*2+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17);
} }
@ -61,22 +84,22 @@ __host__ __device__ uint32_t groupMask(const uint32_t*p,uint32_t base,int count)
return mask; return mask;
} }
struct Emit{ struct Emit{
Coord p;Match* out;uint64_t capacity;unsigned long long* total; Coord p;uint64_t originTerm;const sr::CellTerm* negative;int negativeCount;Match* out;uint64_t capacity;unsigned long long* total;
__device__ void operator()(uint64_t seed){uint64_t slot=atomicAdd(total,1ULL);if(slot<capacity)out[slot]={(seed-sr::coordinateTerm(p.x,p.z))&sr::kMask48,p.x,p.z};} __device__ void operator()(uint64_t seed){if(!sr::verifyPattern(seed,negative,negativeCount))return;uint64_t slot=atomicAdd(total,1ULL);if(slot<capacity)out[slot]={(seed-originTerm)&sr::kMask48,p.x,p.z};}
}; };
__global__ void searchKernel(const sr::CellTerm*cells,const Coord*coords,const Job*jobs,uint32_t n,int count,const uint32_t*prepared,Match*out,uint64_t capacity,unsigned long long*total){ __global__ void searchKernel(const sr::CellTerm*cells,const Coord*coords,const Job*jobs,uint32_t n,int count,int positive,int originX,int originZ,const uint32_t*prepared,Match*out,uint64_t capacity,unsigned long long*total){
uint64_t index=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(index>=(uint64_t(n)<<13))return; uint64_t index=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(index>=(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 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,count); uint32_t mask=groupMask(p,base,positive);
Job job=jobs[k];Emit emit{coords[job.position],out,capacity,total}; 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,count,emit);} 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);}
} }
template<class T>struct 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;}; template<class T>struct 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<Match> sample;}; 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<Match> sample;};
struct Shared{std::atomic<uint64_t> next{0},done{0};std::atomic<int> finished{0};int ready=0,workerCount=0;std::condition_variable cv;std::atomic<bool> cancel{false};std::mutex mutex;std::string error;}; struct Shared{std::atomic<uint64_t> next{0},done{0};std::atomic<int> finished{0};int ready=0,workerCount=0;std::condition_variable cv;std::atomic<bool> cancel{false};std::mutex mutex;std::string error;};
void worker(const Config& c,int device,Shared& shared,Metrics& result){try{ 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; auto setup=Clock::now();CUDA(cudaSetDevice(device));cudaDeviceProp prop{};CUDA(cudaGetDeviceProperties(&prop,device));result.device=device;result.name=prop.name;
int count=c.size*c.size;size_t rootsCapacity=size_t(c.positionBatch)*(1U<<17); int count=c.pattern.cells.size(),positive=c.pattern.positive;size_t rootsCapacity=size_t(c.positionBatch)*(1U<<17);
Buffer<sr::CellTerm> dcells(size_t(c.positionBatch)*count);Buffer<Coord>dcoords(c.positionBatch);Buffer<Job>droots(rootsCapacity),djobs(c.jobBatch); Buffer<sr::CellTerm> dcells(size_t(c.positionBatch)*count);Buffer<Coord>dcoords(c.positionBatch);Buffer<Job>droots(rootsCapacity),djobs(c.jobBatch);
Buffer<uint32_t>prep(size_t(c.jobBatch)*count*2);Buffer<unsigned long long>total(1);Buffer<Match>matches(c.maxResults); Buffer<uint32_t>prep(size_t(c.jobBatch)*count*2);Buffer<unsigned long long>total(1);Buffer<Match>matches(c.maxResults);
std::map<uint64_t,std::vector<uint32_t>> cache;std::deque<uint64_t> fifo;size_t cachedRoots=0; std::map<uint64_t,std::vector<uint32_t>> cache;std::deque<uint64_t> fifo;size_t cachedRoots=0;
@ -95,9 +118,9 @@ void worker(const Config& c,int device,Shared& shared,Metrics& result){try{
if(it!=cache.end()){lists[key]=it->second;++result.cacheHits;}else{lists[key]={};missing.push_back(p);missingKeys.push_back(key);} if(it!=cache.end()){lists[key]=it->second;++result.cacheHits;}else{lists[key]={};missing.push_back(p);missingKeys.push_back(key);}
} }
if(!missing.empty()){ if(!missing.empty()){
auto start=Clock::now();std::vector<sr::CellTerm> cells(missing.size()*count);for(size_t i=0;i<missing.size();++i)terms(missing[i],c.size,cells.data()+i*count); auto start=Clock::now();std::vector<sr::CellTerm> cells(missing.size()*count);for(size_t i=0;i<missing.size();++i)terms(missing[i],c.pattern,cells.data()+i*count);
CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p))); CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p)));
rootsKernel<<<unsigned((uint64_t(missing.size())<<18)/256),256>>>(dcells.p,missing.size(),count,droots.p,total.p);CUDA(cudaGetLastError()); rootsKernel<<<unsigned((uint64_t(missing.size())<<18)/256),256>>>(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"); 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<Job> roots(found);if(found)CUDA(cudaMemcpy(roots.data(),droots.p,found*sizeof(Job),cudaMemcpyDeviceToHost)); std::vector<Job> 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); for(auto job:roots)lists[missingKeys[job.position]].push_back(job.root);
@ -112,12 +135,12 @@ void worker(const Config& c,int device,Shared& shared,Metrics& result){try{
std::vector<Coord> active;std::vector<sr::CellTerm> cells;std::vector<Job> jobs;jobs.reserve(c.jobBatch); std::vector<Coord> active;std::vector<sr::CellTerm> cells;std::vector<Job> jobs;jobs.reserve(c.jobBatch);
for(uint32_t i=0;i<num;++i)if(!lists.at(keys[i]).empty())active.push_back(coords[i]); for(uint32_t i=0;i<num;++i)if(!lists.at(keys[i]).empty())active.push_back(coords[i]);
if(!active.empty()){ if(!active.empty()){
cells.resize(active.size()*count);for(size_t i=0;i<active.size();++i)terms(active[i],c.size,cells.data()+i*count); cells.resize(active.size()*count);for(size_t i=0;i<active.size();++i)terms(active[i],c.pattern,cells.data()+i*count);
CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemcpy(dcoords.p,active.data(),active.size()*sizeof(Coord),cudaMemcpyHostToDevice)); CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemcpy(dcoords.p,active.data(),active.size()*sizeof(Coord),cudaMemcpyHostToDevice));
auto search=[&](){if(jobs.empty()||shared.cancel)return;auto start=Clock::now(); auto search=[&](){if(jobs.empty()||shared.cancel)return;auto start=Clock::now();
CUDA(cudaMemcpy(djobs.p,jobs.data(),jobs.size()*sizeof(Job),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p))); CUDA(cudaMemcpy(djobs.p,jobs.data(),jobs.size()*sizeof(Job),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p)));
prepareKernel<<<unsigned((jobs.size()+127)/128),128>>>(dcells.p,djobs.p,jobs.size(),count,prep.p); prepareKernel<<<unsigned((jobs.size()+127)/128),128>>>(dcells.p,djobs.p,jobs.size(),count,positive,prep.p);
searchKernel<<<unsigned((uint64_t(jobs.size())<<13)/256),256>>>(dcells.p,dcoords.p,djobs.p,jobs.size(),count,prep.p,matches.p,c.maxResults,total.p);CUDA(cudaGetLastError()); searchKernel<<<unsigned((uint64_t(jobs.size())<<13)/256),256>>>(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(); 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<uint64_t>(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));} size_t take=std::min<uint64_t>(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<double,std::milli>(Clock::now()-start).count();jobs.clear();}; result.searchMs+=std::chrono::duration<double,std::milli>(Clock::now()-start).count();jobs.clear();};
@ -130,12 +153,15 @@ void worker(const Config& c,int device,Shared& shared,Metrics& result){try{
}catch(const std::exception& e){shared.cancel=true;std::lock_guard<std::mutex> lock(shared.mutex);shared.error="GPU "+std::to_string(device)+": "+e.what();shared.cv.notify_all();}} }catch(const std::exception& e){shared.cancel=true;std::lock_guard<std::mutex> 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<<'\\'<<char(ch);else if(ch<32)o<<"\\u"<<std::hex<<std::setw(4)<<std::setfill('0')<<int(ch)<<std::dec;else o<<char(ch);}return o.str();} std::string escape(const std::string&s){std::ostringstream o;for(unsigned char ch:s){if(ch=='"'||ch=='\\')o<<'\\'<<char(ch);else if(ch<32)o<<"\\u"<<std::hex<<std::setw(4)<<std::setfill('0')<<int(ch)<<std::dec;else o<<char(ch);}return o.str();}
void help(){std::cout<<R"(slime-bench --radius R [options] void help(){std::cout<<R"(slime-bench --radius R [options]
Search all effective 48-bit seeds for solid n x n slime chunk squares. Search effective 48-bit seeds for slime chunk patterns.
Ordinary candidate branch only; rejection-tail supplement is NOT included. Ordinary candidate branch only; rejection-tail supplement is NOT included.
--x X --z Z Center chunk coordinates (default 0,0) --x X --z Z Center chunk coordinates (default 0,0)
--radius R Manhattan radius in chunks (required, >=0) --radius R Manhattan radius in chunks (required, >=0)
--size N Square side, 1..6 (default 5) --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) --devices 0,1,... CUDA device indices, or all (default 0)
--repeat N Complete passes; fresh root cache each pass (default 1) --repeat N Complete passes; fresh root cache each pass (default 1)
--position-batch N Anchors per scheduled batch, 1..256 (default 32) --position-batch N Anchors per scheduled batch, 1..256 (default 32)
@ -153,20 +179,28 @@ this diamond. Coordinates are not clipped to Minecraft's world border.
Small squares can have enormous numbers of matching seeds and take very long. Small squares can have enormous numbers of matching seeds and take very long.
)";} )";}
int main(int argc,char**argv){try{ int main(int argc,char**argv){try{
Config c;bool radiusSet=false;for(int i=1;i<argc;++i){std::string a=argv[i];if(a=="--help"||a=="-h"){help();return 0;}if(a=="--dry-run"){c.dry=true;continue;}if(a=="--list-devices"){c.list=true;continue;} Config c;bool radiusSet=false,sizeSet=false;std::string patternText;bool patternSet=false;for(int i=1;i<argc;++i){std::string a=argv[i];if(a=="--help"||a=="-h"){help();return 0;}if(a=="--dry-run"){c.dry=true;continue;}if(a=="--list-devices"){c.list=true;continue;}
if(i+1>=argc)throw std::runtime_error("missing value for "+a);std::string v=argv[++i]; if(i+1>=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(n<lo||n>hi)throw std::runtime_error("out of range: "+a);return n;}; auto bounded=[&](uint64_t lo,uint64_t hi){auto n=number(v);if(n<lo||n>hi)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;} 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);else if(a=="--devices")c.devices=v;else if(a=="--repeat")c.repeats=bounded(1,1000); 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<<file.rdbuf();patternText=text.str();}}else if(a=="--devices")c.devices=v;else if(a=="--repeat")c.repeats=bounded(1,1000);
else if(a=="--position-batch")c.positionBatch=bounded(1,256);else if(a=="--job-batch")c.jobBatch=bounded(1,65536); else if(a=="--position-batch")c.positionBatch=bounded(1,256);else if(a=="--job-batch")c.jobBatch=bounded(1,65536);
else if(a=="--cache-classes")c.cacheClasses=bounded(0,1000000);else if(a=="--max-results")c.maxResults=bounded(0,1000000); else if(a=="--cache-classes")c.cacheClasses=bounded(0,1000000);else if(a=="--max-results")c.maxResults=bounded(0,1000000);
else if(a=="--output")c.output=v;else throw std::runtime_error("unknown option: "+a); else if(a=="--output")c.output=v;else throw std::runtime_error("unknown option: "+a);
} }
if(c.list){int n;CUDA(cudaGetDeviceCount(&n));for(int i=0;i<n;++i){cudaDeviceProp p{};CUDA(cudaGetDeviceProperties(&p,i));std::cout<<i<<"\t"<<p.name<<"\tsm_"<<p.major<<p.minor<<"\t"<<p.totalGlobalMem/(1ULL<<20)<<" MiB\n";}return 0;} if(c.list){int n;CUDA(cudaGetDeviceCount(&n));for(int i=0;i<n;++i){cudaDeviceProp p{};CUDA(cudaGetDeviceProperties(&p,i));std::cout<<i<<"\t"<<p.name<<"\tsm_"<<p.major<<p.minor<<"\t"<<p.totalGlobalMem/(1ULL<<20)<<" MiB\n";}return 0;}
if(!radiusSet)throw std::runtime_error("--radius is required"); if(!radiusSet)throw std::runtime_error("--radius is required");
for(int64_t center:{int64_t(c.x),int64_t(c.z)})if(center-int64_t(c.radius)<INT32_MIN||center+int64_t(c.radius)+c.size-1>INT32_MAX)throw std::runtime_error("coordinates including square must fit int32"); if(patternSet&&sizeSet)throw std::runtime_error("--size and pattern options are mutually exclusive");
if(!patternSet){for(int z=0;z<c.size;++z){if(z)patternText+='/';patternText+=std::string(c.size,'#');}}
c.pattern=parsePattern(patternText);
for(auto axis:{std::make_pair(c.x,c.pattern.width),std::make_pair(c.z,c.pattern.height)})
if(int64_t(axis.first)-int64_t(c.radius)<INT32_MIN||int64_t(axis.first)+int64_t(c.radius)+axis.second-1>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); if(!c.output.empty()&&std::ifstream(c.output).good())throw std::runtime_error("output already exists: "+c.output);
std::ostringstream report;report<<std::setprecision(12)<<"{\"schema_version\":1,\"mode\":\"ordinary_all_effective_seeds\",\"rejection_tail_included\":false,\"center\":["<<c.x<<","<<c.z<<"],\"radius_chunks\":"<<c.radius<<",\"square_size\":"<<c.size<<",\"anchors\":"<<anchors(c.radius)<<",\"effective_seed_bits\":48,\"dry_run\":"<<(c.dry?"true":"false"); std::ostringstream report;report<<std::setprecision(12)<<"{\"schema_version\":1,\"mode\":\"ordinary_all_effective_seeds\",\"rejection_tail_included\":false,\"center\":["<<c.x<<","<<c.z<<"],\"radius_chunks\":"<<c.radius<<",\"square_size\":"<<(patternSet?"null":std::to_string(c.size))<<",\"anchors\":"<<anchors(c.radius)<<",\"effective_seed_bits\":48,\"dry_run\":"<<(c.dry?"true":"false");
report<<",\"pattern_width\":"<<c.pattern.width<<",\"pattern_height\":"<<c.pattern.height<<",\"slime_cells\":"<<c.pattern.positive<<",\"non_slime_cells\":"<<c.pattern.cells.size()-c.pattern.positive<<",\"pattern\":[";
for(size_t i=0;i<c.pattern.rows.size();++i){if(i)report<<",";report<<"\""<<c.pattern.rows[i]<<"\"";}report<<"]";
if(c.dry){auto first=coordinate(c,0),last=coordinate(c,anchors(c.radius)-1);report<<",\"first_anchor\":["<<first.x<<","<<first.z<<"],\"last_anchor\":["<<last.x<<","<<last.z<<"]}";} if(c.dry){auto first=coordinate(c,0),last=coordinate(c,anchors(c.radius)-1);report<<",\"first_anchor\":["<<first.x<<","<<first.z<<"],\"last_anchor\":["<<last.x<<","<<last.z<<"]}";}
else{ else{
int available;CUDA(cudaGetDeviceCount(&available));std::vector<int> devices;if(c.devices=="all"){for(int i=0;i<available;++i)devices.push_back(i);}else{ int available;CUDA(cudaGetDeviceCount(&available));std::vector<int> devices;if(c.devices=="all"){for(int i=0;i<available;++i)devices.push_back(i);}else{

View file

@ -26,8 +26,31 @@ def main():
assert r['anchors']==1+2*radius*(radius+1) assert r['anchors']==1+2*radius*(radius+1)
assert r['first_anchor']==[-123,456-radius] and r['last_anchor']==[-123,456+radius] assert r['first_anchor']==[-123,456-radius] and r['last_anchor']==[-123,456+radius]
for bad in [[],['--radius','-1'],['--radius','1','--size','0'],['--radius','1','--size','7'],['--radius','abc'],['--radius','0','--x','2147483647'],['--radius','1','--repeat','0'],['--radius','1','--position-batch','0']]:run(*bad,'--dry-run',ok=False) for bad in [[],['--radius','-1'],['--radius','1','--size','0'],['--radius','1','--size','7'],['--radius','abc'],['--radius','0','--x','2147483647'],['--radius','1','--repeat','0'],['--radius','1','--position-batch','0']]:run(*bad,'--dry-run',ok=False)
summary={'cli_scope_and_errors':True} pattern='?##./#..?/..#?'
r=run('--radius',0,'--pattern',pattern,'--dry-run')
assert r['pattern']==pattern.split('/') and r['square_size'] is None
assert (r['pattern_width'],r['pattern_height'],r['slime_cells'],r['non_slime_cells'])==(4,3,4,5)
for bad_pattern in ['', '.', '??', '#/##', '#x', '#//.', '#/'+'#'*65]:
run('--radius',0,'--pattern',bad_pattern,'--dry-run',ok=False)
run('--radius',0,'--size',5,'--pattern','#','--dry-run',ok=False)
run('--radius',0,'--pattern','#','--pattern','#','--dry-run',ok=False)
run('--radius',0,'--pattern-file','/nonexistent-pattern','--dry-run',ok=False)
run('--radius',0,'--x',2147483647,'--pattern','?#','--dry-run',ok=False)
r=run('--radius',0,'--x',2147483647,'--pattern','#/#','--dry-run')
with tempfile.TemporaryDirectory() as tmp:
path=Path(tmp)/'pattern.txt';path.write_bytes(b'?##.\r\n#..?\r\n..#?\r\n')
assert run('--radius',0,'--pattern-file',path,'--dry-run')['pattern']==pattern.split('/')
summary={'cli_scope_and_errors':True,'pattern_inputs':True}
if args.gpu: if args.gpu:
# A mixed-mask regression fixture; this is not the target creeper pattern.
fixture=json.loads((HERE/'testdata/mixed-mask-regression.json').read_text())
face=run('--x',fixture['chunk_x'],'--z',fixture['chunk_z'],'--radius',0,
'--pattern','/'.join(fixture['pattern']),'--max-results',1000)
hits=face['rounds'][0]
assert not hits['sample_truncated'] and any(s==fixture['seed48'] for s,x,z in hits['sample'])
for seed,x,z in hits['sample']:
assert all(slime(seed,x+dx,z+dz)==(ch=='#') for dz,row in enumerate(fixture['pattern']) for dx,ch in enumerate(row))
summary['mixed_mask_verified_matches']=hits['matches']
common=['--x',51069,'--z',419124] common=['--x',51069,'--z',419124]
reference=run(*common,'--radius',0,'--size',5,'--repeat',2,'--max-results',100) reference=run(*common,'--radius',0,'--size',5,'--repeat',2,'--max-results',100)
expected={61692151016558,15959335054446} expected={61692151016558,15959335054446}
@ -45,6 +68,36 @@ def main():
wanted={t for t in samples[0] if all(slime(t[0],t[1]+dx,t[2]+dz) for dx in range(n) for dz in range(n))} wanted={t for t in samples[0] if all(slime(t[0],t[1]+dx,t[2]+dz) for dx in range(n) for dz in range(n))}
assert set(map(tuple,r['rounds'][0]['sample']))==wanted assert set(map(tuple,r['rounds'][0]['sample']))==wanted
summary[f'size_{n}']=r['rounds'][0]['matches'] summary[f'size_{n}']=r['rounds'][0]['matches']
# The same 5x5 positive constraints, shifted inside a non-square mask.
# Empty anchor exercises normalization independently of the first #.
rows=['?'*7]+['?'+'#'*5+'.' for _ in range(5)]+['?.?????']
pattern='/'.join(rows)
masked=run('--x',51068,'--z',419123,'--radius',0,'--pattern',pattern,'--max-results',100)
wanted={(seed,51068,419123) for seed,x,z in samples[0]
if all(ch=='?' or slime(seed,51068+dx,419123+dz)==(ch=='#')
for dz,row in enumerate(rows) for dx,ch in enumerate(row))}
assert wanted and set(map(tuple,masked['rounds'][0]['sample']))==wanted
assert masked['rounds'][0]['matches']==len(wanted)
# Identical positive subset, but ignored rather than forbidden background.
ignored=pattern.replace('.', '?')
loose=run('--x',51068,'--z',419123,'--radius',0,'--pattern',ignored,'--max-results',100)
assert {s for s,x,z in loose['rounds'][0]['sample']}=={s for s,x,z in samples[0]}
# Force a forbidden cell to reject at least one known positive candidate.
seed=next(iter(samples[0]))[0]
dx,dz=next((dx,dz) for dz in range(16) for dx in range(16)
if not (1<=dx<=5 and 1<=dz<=5) and slime(seed,51068+dx,419123+dz))
rejection=[list('?'*16) for _ in range(16)]
for z in range(1,6):
for x in range(1,6):rejection[z][x]='#'
rejection[dz][dx]='.'
rejected=run('--x',51068,'--z',419123,'--radius',0,'--pattern',
'/'.join(''.join(row) for row in rejection),'--max-results',100)
expected_seeds={s for s,x,z in samples[0] if not slime(s,51068+dx,419123+dz)}
assert len(expected_seeds)<24
assert {s for s,x,z in rejected['rounds'][0]['sample']}==expected_seeds
assert rejected['rounds'][0]['matches']==len(expected_seeds)
summary['mixed_pattern_vs_independent_filter']=len(wanted)
summary['forbidden_cell_excludes_candidates']=True
diamond=run(*common,'--radius',2,'--size',5,'--max-results',1000,'--position-batch',3) diamond=run(*common,'--radius',2,'--size',5,'--max-results',1000,'--position-batch',3)
split=[] split=[]
for dz in range(-2,3): for dz in range(-2,3):

View file

@ -2,9 +2,17 @@
#include "slime_bench.cu" #include "slime_bench.cu"
#undef main #undef main
#include <random> #include <random>
struct HostEmit{
const sr::CellTerm* cells;int count;std::set<uint64_t>* result;
__host__ __device__ void operator()(uint64_t seed){
#ifndef __CUDA_ARCH__
if(sr::verifyPattern(seed,cells,count))result->insert(seed);
#endif
}
};
void check(bool ok,const char*msg){if(!ok)throw std::runtime_error(msg);} void check(bool ok,const char*msg){if(!ok)throw std::runtime_error(msg);}
int main(){ int main(){
uint64_t points=0,groups=0;std::mt19937_64 rng(123456); uint64_t points=0,groups=0,mixed=0;std::mt19937_64 rng(123456);
for(int r=0;r<=64;++r){Config c;c.x=-123;c.z=98;c.radius=r;std::set<std::pair<int32_t,int32_t>> actual; for(int r=0;r<=64;++r){Config c;c.x=-123;c.z=98;c.radius=r;std::set<std::pair<int32_t,int32_t>> actual;
for(uint64_t i=0;i<anchors(r);++i){auto p=coordinate(c,i);actual.emplace(p.x,p.z);} for(uint64_t i=0;i<anchors(r);++i){auto p=coordinate(c,i);actual.emplace(p.x,p.z);}
check(actual.size()==anchors(r),"duplicate anchor"); check(actual.size()==anchors(r),"duplicate anchor");
@ -21,5 +29,36 @@ int main(){
uint32_t expected=0;for(int j=0;j<16;++j)if(fs::differenceFilter(root+(uint64_t(base+16*j)<<18),cells.data(),count))expected|=1U<<j; uint32_t expected=0;for(int j=0;j<16;++j)if(fs::differenceFilter(root+(uint64_t(base+16*j)<<18),cells.data(),count))expected|=1U<<j;
check(groupMask(prepared.data(),base,count)==expected,"variable-size group mask mismatch");++groups; check(groupMask(prepared.data(),base,count)==expected,"variable-size group mask mismatch");++groups;
} }
std::cout<<"{\"diamond_points\":"<<points<<",\"variable_size_groups\":"<<groups<<",\"passed\":true}\n"; // Compare arbitrary sparse layouts and forbidden cells against all high bits.
for(int t=0;t<128;++t){
Coord coord{int32_t(rng()),int32_t(rng())};
// Keep offsets away from overflow even for signed boundary samples.
coord.x/=2;coord.z/=2;
uint64_t world=rng()&sr::kMask48;
std::string text;
for(int z=0;z<5;++z){if(z)text+='/';for(int x=0;x<7;++x)
text+=(x==0&&z==0)||rng()%4==0?'?':sr::exactIsSlimeFromTerm(world,sr::coordinateTerm(coord.x+x,coord.z+z))?'#':'.';}
if(text.find('#')==std::string::npos)continue;
auto pattern=parsePattern(text);int count=pattern.cells.size();
std::vector<sr::CellTerm> cells(count);terms(coord,pattern,cells.data());
uint64_t normalized=(world+sr::coordinateTerm(coord.x+pattern.cells.front().x,coord.z+pattern.cells.front().z))&sr::kMask48;
check(sr::verifyPattern(normalized,cells.data(),count),"normalized pattern mismatch");
uint64_t low=normalized&((1ULL<<35)-1);std::set<uint64_t> actual,expected;
HostEmit emit{cells.data()+pattern.positive,count-pattern.positive,&actual};
fs::solveIntervals(low,cells.data(),pattern.positive,emit);
for(uint64_t high=0;high<8192;++high){uint64_t seed=low+(high<<35);
// Ordinary branch requires a successful first draw at positive cells.
bool ordinary=true;for(int i=0;i<pattern.positive;++i)
if((sr::nextStateFromTerm(seed,cells[i].term)>>17)%10!=0)ordinary=false;
if(ordinary&&sr::verifyPattern(seed,cells.data(),count))expected.insert(seed);
}
check(actual==expected,"mixed pattern high-bit solver mismatch");++mixed;
uint32_t root=low&262143,base=uint32_t(low>>18)&~240U;
std::vector<uint32_t> prepared(count*2);
for(int i=0;i<pattern.positive;++i){uint64_t a=root+cells[i].term;
prepared[2*i]=a>>18;prepared[2*i+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17);}
uint32_t wanted=0;for(int j=0;j<16;++j)if(fs::differenceFilter(root+(uint64_t(base+16*j)<<18),cells.data(),pattern.positive))wanted|=1U<<j;
check(groupMask(prepared.data(),base,pattern.positive)==wanted,"sparse pattern group mask mismatch");
}
std::cout<<"{\"diamond_points\":"<<points<<",\"variable_size_groups\":"<<groups<<",\"mixed_pattern_slices\":"<<mixed<<",\"passed\":true}\n";
} }

View file

@ -0,0 +1,26 @@
{
"pattern": [
"..##..",
"..##..",
"##..##",
"#....#",
"#....#",
"#.##.#"
],
"seed48": 100568620033461,
"chunk_x": -137,
"chunk_z": -53,
"independent_verification": true,
"search": {
"center": [
0,
0
],
"radius_chunks": 256,
"anchors": 131585,
"matches": 1248,
"scan_wall_ms": 2968.438239,
"gpu": "NVIDIA GeForce RTX 3060 Laptop GPU",
"rejection_tail_included": false
}
}

View file

@ -1,8 +1,8 @@
# 共用算法头文件 # 共用算法头文件
- `slime/solver_core.hpp`:Java 整数回绕、48 位随机数、精确史莱姆判定、低位约束和原高位求解器。 - `slime/solver_core.hpp`:Java 整数回绕、48 位随机数、精确史莱姆判定、低位约束和原高位求解器。
- `slime/upper_interval.hpp`:输出差必要条件与高位区间求交,用于实心图案的普通候选分支。 - `slime/upper_interval.hpp`:输出差必要条件与高位区间求交,用于正约束(可稀疏图案)的普通候选分支;CLI 在发出结果前核对非史莱姆约束。
CLI 直接依赖这里。`research/slime_solver_core.hpp` 和原实验目录的 `interval_solver.hpp` 仅转发包含,以便已有研究程序、教学代码和历史复现命令继续工作。 CLI 直接依赖这里。`research/slime_solver_core.hpp` 和原实验目录的 `interval_solver.hpp` 仅转发包含,以便已有研究程序、教学代码和历史复现命令继续工作。
本次只迁移实现位置,保留现有命名空间及算法语义。精确验证支持 Java 重试,不代表普通候选生成已经包含拒绝尾部补扫。 保留现有命名空间及算法语义。精确验证支持 Java 重试,不代表普通候选生成已经包含拒绝尾部补扫。

View file

@ -13,8 +13,9 @@
namespace full_search_experiment { namespace full_search_experiment {
namespace sr = slime_research; namespace sr = slime_research;
// Solid patterns only. Each surviving residue has a single interval. // Positive (slime) constraints only; positions may be sparse.
// Keeping just the anchor's two residues makes intersection constant-space. // Each surviving residue has a single interval. Keeping the first cell's
// two residues makes intersection constant-space. Check negative cells on emit.
template<class Emit> template<class Emit>
FS_HD FS_INLINE void solveIntervals(uint64_t low, const sr::CellTerm* cells, FS_HD FS_INLINE void solveIntervals(uint64_t low, const sr::CellTerm* cells,
int count, Emit& emit) { int count, Emit& emit) {