feat(worldgen): irregular ledges and detached-rock filtering in alpha30.2
Build Sanctuary / build (push) Canceled after 0s
Build Sanctuary / build (push) Canceled after 0s
This commit is contained in:
@@ -666,3 +666,11 @@ tasks.register('river30Smoke', JavaExec) {
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.River30Smoke'
|
||||
}
|
||||
tasks.named('check') { dependsOn('river30Smoke') }
|
||||
|
||||
tasks.register('surfaceRelief302Smoke', JavaExec) {
|
||||
group = 'verification'
|
||||
dependsOn tasks.named('testClasses')
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.SurfaceRelief302Smoke'
|
||||
}
|
||||
tasks.named('check') { dependsOn('surfaceRelief302Smoke') }
|
||||
|
||||
+66
-9
@@ -22,34 +22,34 @@ public final class Terraces29WorldGameTests {
|
||||
var field = (PopulationIslandDensity) level.registryAccess().lookupOrThrow(Registries.DENSITY_FUNCTION)
|
||||
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION,
|
||||
SanctuaryMod.id("population_" + generator.capacity().players()))).value();
|
||||
helper.assertTrue(field.relief30() && !field.terraces29(), "Alpha30.1 restores global deformation except a narrow plateau");
|
||||
helper.assertTrue(field.relief30() && !field.terraces29() && field.surface302(), "Alpha30.2 adds non-periodic local relief to the global field");
|
||||
var random = generator.starterContext().random();
|
||||
var current = random.getSampler(field); var previous = random.getSampler(field.legacy28());
|
||||
var current = random.getSampler(field); var previous = random.getSampler(field.legacy301());
|
||||
var context = SamplerContext.builder().enableCaches().build();
|
||||
var report = new LinkedHashMap<String, Object>();
|
||||
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
|
||||
report.put("process_id", ProcessHandle.current().pid());
|
||||
int radius = Math.min(400, generator.initialDiameter() / 2), lowerSamples = 0, unchanged = 0, changed = 0;
|
||||
for (int z = -radius; z <= radius; z += 32) for (int x = -radius; x <= radius; x += 32) {
|
||||
for (int y = 32; y <= 180; y += 13) {
|
||||
for (int y = 32; y <= 140; y += 13) {
|
||||
helper.assertTrue(Float.floatToIntBits(current.sampleValue(context, x, y, z))
|
||||
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Lower density is exactly alpha28");
|
||||
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Deep density is exactly alpha30.1");
|
||||
lowerSamples++;
|
||||
}
|
||||
for (int y = 181; y <= 320; y++) {
|
||||
for (int y = 141; y <= 320; y++) {
|
||||
boolean old = previous.sampleValue(context, x, y, z) > 0;
|
||||
boolean now = current.sampleValue(context, x, y, z) > 0;
|
||||
if (old == now) unchanged++; else changed++;
|
||||
if (Math.hypot(x, z) <= 48) helper.assertTrue(old == now, "Arrival silhouette is unchanged");
|
||||
}
|
||||
}
|
||||
helper.assertTrue(changed > 0 && unchanged > changed * 3L, "A narrow plateau retains most of the accepted global relief");
|
||||
helper.assertTrue(changed > 0 && unchanged > changed * 3L, "Local surface offsets retain most of the accepted rock volume");
|
||||
report.put("unchanged_lower_density_samples", lowerSamples);
|
||||
report.put("unchanged_upper_samples", unchanged); report.put("changed_upper_samples", changed);
|
||||
var sections = new LinkedHashMap<String, Object>();
|
||||
for (int z : new int[]{0, 96}) {
|
||||
var before = new ArrayList<String>(); var after = new ArrayList<String>();
|
||||
for (int y = 305; y >= 205; y--) {
|
||||
for (int y = 305; y >= 145; y--) {
|
||||
var a = new StringBuilder(); var b = new StringBuilder();
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
a.append(previous.sampleValue(context, x, y, z) > 0 ? '#' : '.');
|
||||
@@ -59,10 +59,67 @@ public final class Terraces29WorldGameTests {
|
||||
}
|
||||
sections.put("z" + z, Map.of("before", before, "after", after, "min_x", -radius, "max_y", 305, "step", 1));
|
||||
}
|
||||
report.put("sections", sections); report.put("passed", true);
|
||||
Path path = Path.of("diagnostics/alpha301", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
report.put("sections", sections);
|
||||
// One block-resolution volume, chosen using the old field's surface transitions.
|
||||
// Both versions sample the exact same coordinates, without structures or decoration.
|
||||
int bestX=80,bestZ=80,bestScore=-1;
|
||||
for(int cz=-radius+48;cz<=radius-48;cz+=64)for(int cx=-radius+48;cx<=radius-48;cx+=64){
|
||||
if(Math.hypot(cx,cz)<80||Math.hypot(cx,cz)>generator.capacity().radius()*.78)continue;
|
||||
int score=0;
|
||||
for(int dz=-24;dz<=24;dz+=8)for(int dx=-24;dx<=24;dx+=8){
|
||||
int last=-1;
|
||||
for(int y=290;y>=175;y--){
|
||||
int solid=previous.sampleValue(context,cx+dx,y,cz+dz)>0?1:0;
|
||||
if(last>=0&&last!=solid)score++;last=solid;
|
||||
}
|
||||
}
|
||||
if(score>bestScore){bestScore=score;bestX=cx;bestZ=cz;}
|
||||
}
|
||||
var oldVolume=new ArrayList<String>();var newVolume=new ArrayList<String>();
|
||||
int size=64,minY=164,maxY=291;
|
||||
for(int y=minY;y<=maxY;y++)for(int z=bestZ-size/2;z<bestZ+size/2;z++){
|
||||
var a=new StringBuilder();var b=new StringBuilder();
|
||||
for(int x=bestX-size/2;x<bestX+size/2;x++){
|
||||
a.append(previous.sampleValue(context,x,y,z)>0?'#':'.');
|
||||
b.append(current.sampleValue(context,x,y,z)>0?'#':'.');
|
||||
}oldVolume.add(a.toString());newVolume.add(b.toString());
|
||||
}
|
||||
report.put("volume",Map.of("min_x",bestX-size/2,"min_z",bestZ-size/2,"min_y",minY,
|
||||
"size_x",size,"size_z",size,"size_y",maxY-minY+1,"before",oldVolume,"after",newVolume));
|
||||
int oldFragments=tinyComponents(oldVolume,size,maxY-minY+1);
|
||||
int newFragments=tinyComponents(newVolume,size,maxY-minY+1);
|
||||
report.put("tiny_components_before",oldFragments);report.put("tiny_components_after",newFragments);
|
||||
helper.assertTrue(newFragments<=oldFragments,"The retouched volume must not introduce extra detached fragments");
|
||||
var reverseContext=SamplerContext.builder().enableCaches().build();int reversed=0;
|
||||
for(int y=maxY;y>=minY;y-=8)for(int z=bestZ+size/2-1;z>=bestZ-size/2;z-=8)
|
||||
for(int x=bestX+size/2-1;x>=bestX-size/2;x-=8){
|
||||
boolean expected=newVolume.get((y-minY)*size+z-(bestZ-size/2)).charAt(x-(bestX-size/2))=='#';
|
||||
helper.assertTrue((current.sampleValue(reverseContext,x,y,z)>0)==expected,"Rock membership is independent of cache and reverse sample order");reversed++;
|
||||
}
|
||||
report.put("reverse_order_samples",reversed);
|
||||
report.put("passed", true);
|
||||
Path path = Path.of("diagnostics/alpha302", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
|
||||
helper.succeed();
|
||||
}
|
||||
private static int tinyComponents(List<String> rows,int size,int height){
|
||||
int plane=size*size,total=plane*height,tiny=0;
|
||||
boolean[] visited=new boolean[total];int[] queue=new int[total];
|
||||
for(int start=0;start<total;start++){
|
||||
if(visited[start]||rows.get(start/size).charAt(start%size)!='#')continue;
|
||||
int head=0,count=1;queue[0]=start;visited[start]=true;boolean boundary=false;
|
||||
while(head<count){
|
||||
int p=queue[head++],x=p%size,z=(p/size)%size,y=p/plane;
|
||||
if(x==0||x==size-1||z==0||z==size-1||y==0||y==height-1)boundary=true;
|
||||
int[] neighbours={x>0?p-1:-1,x<size-1?p+1:-1,z>0?p-size:-1,z<size-1?p+size:-1,y>0?p-plane:-1,y<height-1?p+plane:-1};
|
||||
for(int next:neighbours)if(next>=0&&!visited[next]&&rows.get(next/size).charAt(next%size)=='#'){
|
||||
visited[next]=true;queue[count++]=next;
|
||||
}
|
||||
}
|
||||
if(!boundary&&count<=32)tiny++;
|
||||
}
|
||||
return tiny;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
/** Removes detached components of at most 32 blocks in the locally retouched field.
|
||||
* The flood search reads the unfiltered field and stops on its 33rd connected block.
|
||||
* All coordinates are global: a chunk edge is never treated as the edge of a component. */
|
||||
public final class DetachedRock302 implements DensitySampler {
|
||||
private static final int MIN_BODY = 33;
|
||||
private static final ThreadLocal<Cache> CACHE = ThreadLocal.withInitial(Cache::new);
|
||||
private static final int[][] SIDES = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
|
||||
private final DensitySampler input;
|
||||
private final double radius;
|
||||
private final long seed;
|
||||
|
||||
public DetachedRock302(DensitySampler input, double radius, long seed) {
|
||||
this.input = input; this.radius = radius; this.seed = seed;
|
||||
}
|
||||
private boolean affected(int x, int y, int z) {
|
||||
return SurfaceRelief302.weight(x,y,z,radius) * Relief30.influence(seed,x,z,radius) > 0;
|
||||
}
|
||||
@Override public float sampleValue(SamplerContext context,int x,int y,int z) {
|
||||
if (!affected(x,y,z)) return input.sampleValue(context,x,y,z);
|
||||
var cache=CACHE.get();cache.select(this,context);
|
||||
int slot=cache.get(this,context,x,y,z);float value=cache.values[slot];
|
||||
if(value<=0 || cache.status[slot]==2) return value;
|
||||
if(cache.status[slot]==3) return -0.000001F;
|
||||
int[] xs=new int[MIN_BODY],ys=new int[MIN_BODY],zs=new int[MIN_BODY];xs[0]=x;ys[0]=y;zs[0]=z;
|
||||
int count=1;boolean attached=false;
|
||||
search:for(int head=0;head<count;head++)for(var side:SIDES){
|
||||
int nx=xs[head]+side[0],ny=ys[head]+side[1],nz=zs[head]+side[2];
|
||||
boolean seen=false;
|
||||
for(int i=0;i<count;i++)if(xs[i]==nx&&ys[i]==ny&&zs[i]==nz){seen=true;break;}
|
||||
if(seen)continue;
|
||||
int next=cache.get(this,context,nx,ny,nz);
|
||||
if(cache.values[next]<=0 || cache.status[next]==3)continue;
|
||||
if(cache.status[next]==2 || !affected(nx,ny,nz)){attached=true;break search;}
|
||||
xs[count]=nx;ys[count]=ny;zs[count]=nz;
|
||||
if(++count==MIN_BODY){attached=true;break search;}
|
||||
}
|
||||
for(int i=0;i<count;i++)cache.status[cache.get(this,context,xs[i],ys[i],zs[i])]=(byte)(attached?2:3);
|
||||
return attached?value:-0.000001F;
|
||||
}
|
||||
@Override public void sampleVolume(SamplerContext context,DensityBuffer buffer,DensityVolume volume){
|
||||
DensitySampler.sampleVolumeNaive(context,buffer,volume,this);
|
||||
}
|
||||
/** Bounded primitive cache; weak identities cannot retain a closed world or sample context. */
|
||||
private static final class Cache {
|
||||
private static final int SIZE=1<<16;
|
||||
private WeakReference<DetachedRock302> owner=new WeakReference<>(null);
|
||||
private WeakReference<SamplerContext> context=new WeakReference<>(null);
|
||||
private final int[] xs=new int[SIZE],ys=new int[SIZE],zs=new int[SIZE],stamps=new int[SIZE];
|
||||
private final float[] values=new float[SIZE];
|
||||
private final byte[] status=new byte[SIZE];
|
||||
private int generation;
|
||||
void select(DetachedRock302 selected,SamplerContext selectedContext){
|
||||
if(owner.get()==selected&&context.get()==selectedContext)return;
|
||||
owner=new WeakReference<>(selected);context=new WeakReference<>(selectedContext);
|
||||
if(++generation==0){java.util.Arrays.fill(stamps,0);generation=1;}
|
||||
}
|
||||
int get(DetachedRock302 sampler,SamplerContext sample,int x,int y,int z){
|
||||
int h=x*0x9e3779b9^Integer.rotateLeft(y*0x85ebca6b,11)^Integer.rotateLeft(z*0xc2b2ae35,22);
|
||||
int slot=(h^(h>>>16))&(SIZE-1);
|
||||
if(stamps[slot]!=generation||xs[slot]!=x||ys[slot]!=y||zs[slot]!=z){
|
||||
float value=sampler.input.sampleValue(sample,x,y,z);
|
||||
xs[slot]=x;ys[slot]=y;zs[slot]=z;values[slot]=value;status[slot]=1;stamps[slot]=generation;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-10
@@ -13,7 +13,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
|
||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
||||
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28,
|
||||
boolean terraces29, boolean relief30) implements DensityFunction {
|
||||
boolean terraces29, boolean relief30, boolean surface302) implements DensityFunction {
|
||||
public static final MapCodec<PopulationIslandDensity> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
DensityFunction.CODEC.fieldOf("terrain").forGetter(PopulationIslandDensity::terrain),
|
||||
DensityFunction.CODEC.fieldOf("distortion").forGetter(PopulationIslandDensity::distortion),
|
||||
@@ -28,14 +28,15 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensityFunction.CODEC.optionalFieldOf("erosion_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::erosion28),
|
||||
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28),
|
||||
Codec.BOOL.optionalFieldOf("terraces_29", false).forGetter(PopulationIslandDensity::terraces29),
|
||||
Codec.BOOL.optionalFieldOf("relief_30", false).forGetter(PopulationIslandDensity::relief30)
|
||||
Codec.BOOL.optionalFieldOf("relief_30", false).forGetter(PopulationIslandDensity::relief30),
|
||||
Codec.BOOL.optionalFieldOf("surface_302", false).forGetter(PopulationIslandDensity::surface302)
|
||||
).apply(instance, PopulationIslandDensity::new));
|
||||
|
||||
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
|
||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity) {
|
||||
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, capacity,
|
||||
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false, false);
|
||||
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false, false, false);
|
||||
}
|
||||
|
||||
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
@@ -43,7 +44,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
||||
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28) {
|
||||
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, capacity,
|
||||
caves28, ridges28, erosion28, geology28, false, false);
|
||||
caves28, ridges28, erosion28, geology28, false, false, false);
|
||||
}
|
||||
|
||||
@Override public DensitySampler compileSampler(CompileContext context) {
|
||||
@@ -56,7 +57,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
var erosion = geology28 ? erosion28.compileSampler(context) : null;
|
||||
long seed = context.createRandom(SanctuaryMod.id("population_rifts_alpha10")).nextLong();
|
||||
var layouts = new ConcurrentHashMap<IslandCapacity.Region, RiftShape.Layout>();
|
||||
return new DensitySampler() {
|
||||
DensitySampler field = new DensitySampler() {
|
||||
private float terrainAt(SamplerContext sample, double x, double y, double z) {
|
||||
int ix = (int) Math.floor(x), iy = (int) Math.floor(y), iz = (int) Math.floor(z);
|
||||
double fx = x - ix, fy = y - iy, fz = z - iz;
|
||||
@@ -79,12 +80,16 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
float terrainValue;
|
||||
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
|
||||
if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
|
||||
if (weight > 0) {
|
||||
var shift = CavesRelief28.warp(weight,
|
||||
double surfaceWeight = surface302 ? SurfaceRelief302.weight(x, y, z, capacity.radius())
|
||||
* Relief30.influence(seed, x, z, capacity.radius()) : 0;
|
||||
if (weight > 0 || surfaceWeight > 0) {
|
||||
var shift = weight > 0 ? CavesRelief28.warp(weight,
|
||||
erosion.sampleValue(sample, x + 173, y - 59, z - 89),
|
||||
ridges.sampleValue(sample, x, y, z),
|
||||
erosion.sampleValue(sample, x - 211, y + 137, z + 307));
|
||||
erosion.sampleValue(sample, x - 211, y + 137, z + 307)) : new CavesRelief28.Warp(0,0,0);
|
||||
double sourceY = y - 64 - shift.y();
|
||||
if (surfaceWeight > 0) sourceY = SurfaceRelief302.sourceY(sourceY,surfaceWeight,
|
||||
coarse.sampleValue(sample,x-233,y+419,z+157));
|
||||
if (terraces29) sourceY = Terraces29.sourceY(sourceY, coarseValue, fineValue,
|
||||
Terraces29.weight(y, weight));
|
||||
terrainValue = terrainAt(sample, x - shift.x(), sourceY, z - shift.z());
|
||||
@@ -111,6 +116,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensitySampler.sampleVolumeNaive(sample, buffer, volume, this);
|
||||
}
|
||||
};
|
||||
return surface302 ? new DetachedRock302(field,capacity.radius(),seed) : field;
|
||||
}
|
||||
/** Native diagnostics can compile the exact accepted27 field without new adaptations. */
|
||||
public PopulationIslandDensity legacy27() {
|
||||
@@ -120,7 +126,11 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
/** Exact accepted28 deformation and caves, without the fine local ledges. */
|
||||
public PopulationIslandDensity legacy28() {
|
||||
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
|
||||
capacity, caves28, ridges28, erosion28, geology28, false, false);
|
||||
capacity, caves28, ridges28, erosion28, geology28, false, false, false);
|
||||
}
|
||||
public PopulationIslandDensity legacy301() {
|
||||
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
|
||||
capacity, caves28, ridges28, erosion28, geology28, terraces29, relief30, false);
|
||||
}
|
||||
public static long regionSeed(long seed, IslandCapacity.Region region) {
|
||||
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
|
||||
@@ -131,7 +141,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
@Override public DensityFunction rewriteChildren(DfRewriteRule rule) {
|
||||
return new PopulationIslandDensity(rule.rewrite(terrain), rule.rewrite(distortion), rule.rewrite(sculpt),
|
||||
rule.rewrite(detail), rule.rewrite(underside), rule.rewrite(riftWarp), rule.rewrite(riftDetail), capacity,
|
||||
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28, terraces29, relief30);
|
||||
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28, terraces29, relief30, surface302);
|
||||
}
|
||||
@Override public Interval range() { return Interval.of(-1, 1); }
|
||||
@Override public int domainAxes() { return ALL_AXES; }
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
/** Locally phased ledges in the existing rock field, with no shared horizontal level. */
|
||||
public final class SurfaceRelief302 {
|
||||
private SurfaceRelief302() {}
|
||||
|
||||
public static double weight(int x, int y, int z, double radius) {
|
||||
double distance = Math.hypot((double) x, z);
|
||||
return smooth((y - 140) / 48.0) * (1 - smooth((y - 316) / 36.0))
|
||||
* smooth((distance - 48) / 32.0)
|
||||
* (1 - smooth((distance / radius - .86) / .12));
|
||||
}
|
||||
|
||||
/** Coherent patches move together; narrow, continuous transitions form local ledges.
|
||||
* No rounding of Y, shared layer height or per-block random displacement. */
|
||||
public static double sourceY(double originalY, double weight, float noise) {
|
||||
if(weight<=0)return originalY;
|
||||
double period=6.5+1.5*Math.tanh(noise*1.5);
|
||||
double phase=11*Math.tanh(noise);
|
||||
double band=(originalY+phase)/period;
|
||||
double fraction=band-Math.floor(band);
|
||||
double ledge=smooth((fraction-.30)/.40);
|
||||
double offset=Math.clamp((ledge-fraction)*period,-2.25,2.25);
|
||||
return originalY+offset*weight;
|
||||
}
|
||||
|
||||
private static double smooth(double value) {
|
||||
double t = Math.clamp(value, 0, 1);
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -65,5 +65,6 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
}
|
||||
|
||||
+2
-1
@@ -65,5 +65,6 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
}
|
||||
|
||||
+2
-1
@@ -65,5 +65,6 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.util.*;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
public final class SurfaceRelief302Smoke {
|
||||
private record P(int x,int y,int z) {}
|
||||
private static DetachedRock302 filter(Set<P> blocks){
|
||||
return new DetachedRock302(new DensitySampler(){
|
||||
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return blocks.contains(new P(x,y,z))?.5F:-.5F;}
|
||||
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
|
||||
},362,0);
|
||||
}
|
||||
private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
|
||||
public static void main(String[] args){
|
||||
var context=SamplerContext.EMPTY_UNCACHED;var cube=new HashSet<P>();
|
||||
for(int x=95;x<=96;x++)for(int y=230;y<=231;y++)for(int z=100;z<=101;z++)cube.add(new P(x,y,z));
|
||||
var removed=filter(Set.copyOf(cube));
|
||||
for(var p:cube)require(removed.sampleValue(context,p.x,p.y,p.z)<0,"Eight detached blocks crossing a chunk edge must disappear");
|
||||
for(int x=95;x<=97;x++)for(int y=230;y<=232;y++)for(int z=100;z<=102;z++)cube.add(new P(x,y,z));
|
||||
var clump=filter(Set.copyOf(cube));
|
||||
for(var p:cube)require(clump.sampleValue(context,p.x,p.y,p.z)<0,"A detached 3 by 3 by 3 clump is also removed");
|
||||
for(int z=100;z<=102;z++)for(int x=95;x<=97;x++)cube.add(new P(x,233,z));
|
||||
var retained=filter(Set.copyOf(cube));
|
||||
var order=new ArrayList<>(cube);Collections.reverse(order);
|
||||
for(var p:order)require(retained.sampleValue(context,p.x,p.y,p.z)>0,"A connected body of 36 blocks must remain");
|
||||
require(retained.sampleValue(context,94,230,100)<0,"Cleanup never fills surrounding air");
|
||||
var singleton=filter(Set.of(new P(96,230,100)));
|
||||
require(singleton.sampleValue(context,96,230,100)<0,"A different sampler cannot inherit another body's cached support");
|
||||
var outside=new P(0,64,0);require(filter(Set.of(outside)).sampleValue(context,0,64,0)>.0,"The arrival and deep field are untouched");
|
||||
for(double y=-10;y<400;y+=.125)for(float noise:new float[]{-2,-1,-.25F,0,.25F,1,2}){
|
||||
require(Math.abs(SurfaceRelief302.sourceY(y,1,noise)-y)<=2.25001,"Local ledges must remain bounded");
|
||||
require(SurfaceRelief302.sourceY(y,0,noise)==y,"An excluded region retains its exact field");
|
||||
}
|
||||
System.out.println("SurfaceRelief302Smoke: detached fragments across chunks removed, connected body and void retained, cache isolation and bounded local ledges passed");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user