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:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0-alpha.30.2
|
||||||
|
|
||||||
|
- Corniches locales dont la phase et l’espacement suivent un bruit 3D, sur le relief global conservé.
|
||||||
|
- Filtrage borné des petits composants rocheux détachés dans la zone retouchée, indépendant des frontières de chunks.
|
||||||
|
- Rivières courbes, tunnels, sanctuaire minéral et lieux aériens conservés ; nouveau monde pour l’essai.
|
||||||
|
- Minecraft 26.3-pre-2 et les dépendances restent inchangés. Voir `docs/testing-alpha30.2.md` pour les validations effectivement réalisées.
|
||||||
|
|
||||||
## 0.1.0-alpha.30.1 — 2026-09-12
|
## 0.1.0-alpha.30.1 — 2026-09-12
|
||||||
|
|
||||||
Retrait des rivières sur grille de la 30 ; brèches de cascade sur les anciennes
|
Retrait des rivières sur grille de la 30 ; brèches de cascade sur les anciennes
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ le relief et les souterrains de la 24, retire la montgolfière et travaille les
|
|||||||
marais souterrains et la restauration de vrais modèles d’épaves vanilla. Voir le
|
marais souterrains et la restauration de vrais modèles d’épaves vanilla. Voir le
|
||||||
[chantier et ses limites](docs/generation-alpha27.md).
|
[chantier et ses limites](docs/generation-alpha27.md).
|
||||||
|
|
||||||
|
## Alpha.30.2 — corniches et fragments
|
||||||
|
|
||||||
|
Les grandes masses et arches sont conservées. Une passe locale de corniches
|
||||||
|
irrégulières casse les pentes répétitives ; les petits composants rocheux
|
||||||
|
entièrement détachés sont filtrés dans la zone retouchée.
|
||||||
|
|
||||||
|
Créer un **nouveau monde**. Voir [la génération](docs/generation-alpha30.2.md)
|
||||||
|
et [les vérifications](docs/testing-alpha30.2.md).
|
||||||
|
|
||||||
## Alpha.30.1 — rivières courbes et relief global
|
## Alpha.30.1 — rivières courbes et relief global
|
||||||
|
|
||||||
Les rivières angulaires de la 30 sont retirées. Les anciens cours d’eau et
|
Les rivières angulaires de la 30 sont retirées. Les anciens cours d’eau et
|
||||||
|
|||||||
@@ -1009,3 +1009,11 @@ retrouver la déformation globale de la 28 avec une zone restreinte de plateau,
|
|||||||
et ouvrir les berges des anciens cours d’eau vers les espaces vides proches.
|
et ouvrir les berges des anciens cours d’eau vers les espaces vides proches.
|
||||||
Conserver les recherches de traversées et de sanctuaire améliorées en 30.
|
Conserver les recherches de traversées et de sanctuaire améliorées en 30.
|
||||||
Voir [le correctif](generation-alpha30.1.md) et [les essais](testing-alpha30.1.md).
|
Voir [le correctif](generation-alpha30.1.md) et [les essais](testing-alpha30.1.md).
|
||||||
|
|
||||||
|
### WG-34 — Corniches irrégulières et fragments — alpha.30.2
|
||||||
|
|
||||||
|
Branche `codex/irregular-relief-alpha30-2`. Conserver les grandes masses et
|
||||||
|
arches, casser la régularité des pentes par des corniches localisées, et retirer
|
||||||
|
les petits fragments détachés issus du champ de terrain retouché. Comparer les
|
||||||
|
mêmes volumes natifs avant/après ; contrôler les raccords aux frontières de chunks.
|
||||||
|
Voir [la génération](generation-alpha30.2.md) et [les essais](testing-alpha30.2.md).
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Alpha.30.2 — corniches irrégulières et petits fragments
|
||||||
|
|
||||||
|
Retour visuel du 12 septembre 2026 : conserver les grandes masses et les arches
|
||||||
|
acceptées, mais réduire leurs contours trop réguliers et les longues pentes
|
||||||
|
en marches répétées. Le rendu des captures sert de contre-exemple ; il ne
|
||||||
|
s’agit pas de remplacer les massifs par de nouvelles formes prédéfinies.
|
||||||
|
|
||||||
|
## Passe locale
|
||||||
|
|
||||||
|
Le relief global de la 28/30.1 reste actif, ainsi que sa zone limitée de plateau.
|
||||||
|
Une adaptation locale du point d’échantillonnage vertical crée de courtes
|
||||||
|
corniches : leur phase et leur espacement suivent un bruit 3D existant, à
|
||||||
|
l’échelle de quelques dizaines de blocs. Les couches ne partagent pas un même
|
||||||
|
niveau horizontal à travers toute l’île. Le déplacement supplémentaire du
|
||||||
|
point d’échantillonnage reste borné à **2,25 blocs**.
|
||||||
|
|
||||||
|
La passe s’atténue sous Y188 et disparaît sous Y140, près de l’arrivée, dans le
|
||||||
|
cœur de la bande de plateau, à l’extrémité du contour et au-dessus du terrain.
|
||||||
|
Les volumes profonds et les paramètres des grottes natives ne sont pas changés.
|
||||||
|
Les rivières courbes, sorties de berge et leurs écoulements gardent leur
|
||||||
|
algorithme. Comme pour toute modification de densité, la recherche d’un lieu
|
||||||
|
compatible peut aboutir à des coordonnées différentes sur une même graine.
|
||||||
|
|
||||||
|
## Petits blocs flottants
|
||||||
|
|
||||||
|
Une recherche de connexion dans le **champ naturel avant décoration** retire
|
||||||
|
les composants détachés de **32 blocs au maximum** dans la zone retouchée.
|
||||||
|
Elle s’arrête dès qu’elle rejoint 33 blocs connectés ou la roche conservée hors
|
||||||
|
retouche. Les six faces servent de voisinage ; les diagonales ne suffisent pas.
|
||||||
|
Les coordonnées sont globales : les bords de chunks ne coupent pas une attache.
|
||||||
|
|
||||||
|
Ce contrôle ne remplit aucun trou, ne rajoute aucun pilier, ne visite aucune
|
||||||
|
sauvegarde personnelle et ne retire pas les îlots aériens. Il précède la pose
|
||||||
|
ultérieure des arbres, structures et fluides. Il ne constitue pas une promesse
|
||||||
|
qu’aucun élément flottant de quelque origine que ce soit ne puisse exister.
|
||||||
|
Le cache est borné, propre au thread et invalidé selon le sampler et son contexte,
|
||||||
|
avec des références faibles aux identités de monde.
|
||||||
|
|
||||||
|
## Livraison
|
||||||
|
|
||||||
|
Version mod et pack **0.1.0-alpha.30.2**, branche
|
||||||
|
`codex/irregular-relief-alpha30-2`. Nouveau champ de codec optionnel
|
||||||
|
`surface_302`, faux lorsqu’il est absent. Activé dans les trois profils actuels
|
||||||
|
512 / 724 / 1 024 ; Moyen reste le choix par défaut. Le prototype garde
|
||||||
|
`sanctuary:island_v24`, Minecraft 26.3-pre-2 et le bouton natif Personnaliser.
|
||||||
|
**Créer un nouveau monde pour essayer le rendu** ; aucune régénération des
|
||||||
|
sauvegardes personnelles. Pas de nouvelle interface nécessitant une traduction.
|
||||||
|
|
||||||
|
Les vérifications et limites sont dans [les essais](testing-alpha30.2.md).
|
||||||
|
La publication et la synchronisation suivent [packwiz](packwiz.md).
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Alpha.30.2 — vérifications et limites
|
||||||
|
|
||||||
|
Essais sur **Mac**, le 12 septembre 2026, dans le serveur de développement.
|
||||||
|
Aucun essai Windows natif de ce correctif n’est revendiqué. Les comparaisons
|
||||||
|
visuelles sont des volumes natifs de roche, sans textures, arbres ou structures,
|
||||||
|
et ne remplacent pas l’essai du rendu complet dans Minecraft.
|
||||||
|
|
||||||
|
## Méthode
|
||||||
|
|
||||||
|
Le témoin principal est **Moyen 724, graine 0**. Le test compare le champ
|
||||||
|
30.1 au champ retouché aux mêmes coordonnées. Il exporte deux coupes et un
|
||||||
|
volume de **64 × 128 × 64 blocs**, choisi uniquement selon les transitions du
|
||||||
|
champ précédent. Le choix du volume ne recherche pas le meilleur résultat de
|
||||||
|
la nouvelle passe. La profondeur sous Y140 et l’arrivée restent inchangées.
|
||||||
|
|
||||||
|
Le volume permet de compter les composants de roche connectés par leurs six
|
||||||
|
faces. Les composants touchant ses plans de coupe sont exclus de l’inventaire
|
||||||
|
des fragments, car leur attache peut continuer hors du volume. Les longues
|
||||||
|
marches sont comptées sur les surfaces supérieures complètes, le long des deux
|
||||||
|
axes : suites d’au moins six différences de hauteur identiques de ±1 bloc.
|
||||||
|
Ces mesures portent sur cet échantillon, pas sur toutes les graines du jeu.
|
||||||
|
|
||||||
|
Deux essais de déplacement plus simple ont été écartés avant publication : le
|
||||||
|
premier introduisait trop de fragments, le second ne réduisait pas les longues
|
||||||
|
marches du témoin. Les aperçus intermédiaires restent dans les dossiers ignorés
|
||||||
|
`build/alpha302-preview-first-pass/` et `build/alpha302-preview-second-pass/`.
|
||||||
|
|
||||||
|
## Contrôle ciblé des fragments
|
||||||
|
|
||||||
|
`surfaceRelief302Smoke` vérifie les petits amas à cheval sur un bord de chunk,
|
||||||
|
la conservation des corps connectés plus grands, l’absence de remplissage du
|
||||||
|
vide, l’isolation des caches entre deux samplers et la limite de déplacement
|
||||||
|
local. Le filtre s’applique au champ de roche retouché, avant les décorations ;
|
||||||
|
il ne modifie pas les constructions ni les sauvegardes personnelles.
|
||||||
|
|
||||||
|
## Témoin Moyen 724, graine 0
|
||||||
|
|
||||||
|
Le processus **67135** passe les **24/24 GameTests** ; `check build` réussit
|
||||||
|
en **5 min 18 s**. Reçus dans `build/alpha302-evidence/medium-0/`.
|
||||||
|
|
||||||
|
Dans le volume natif choisi (**X-154, Y164, Z-218**, 64 × 128 × 64), **96,72 %**
|
||||||
|
des positions restent identiques. La quantité de roche passe de **174 157 à
|
||||||
|
173 881 blocs**, soit une différence de **-0,16 %**. Les pas appartenant aux
|
||||||
|
longues suites de marches passent de **424 à 81**. Le comptage indépendant
|
||||||
|
observe **0 petit composant isolé de huit blocs ou moins**, contre cinq
|
||||||
|
composants réunissant six blocs avant la passe. L’analyse ne compte pas les
|
||||||
|
composants coupés par la frontière du volume. Le filtre lui-même traite les
|
||||||
|
composants de 32 blocs ou moins.
|
||||||
|
|
||||||
|
Deux sorties de l’ancienne rivière sont vérifiées sous ticks réels :
|
||||||
|
**-105 246 -65** et **-24 246 -45**. La traversée possède **19 départs natifs**,
|
||||||
|
**29 chunks FULL** contrôlés, **272 blocs** entre ses accès et **678 pas** dans
|
||||||
|
le parcours testé. Entrées : **-136 234 82** et **136 236 82**. Le sanctuaire
|
||||||
|
possède son départ et son sol central ; les quatre satellites et une mine
|
||||||
|
vanilla sont également observés dans leurs tests respectifs.
|
||||||
|
|
||||||
|
Les mesures de planification sont distinctes du temps de rendu client : la
|
||||||
|
région hydrologique centrale prend **32,1 s**, et la recherche de traversée,
|
||||||
|
qui inclut la préparation de son terrain, **40,8 s**. Ces durées ne s’ajoutent
|
||||||
|
pas simplement. Le filtre a un coût ; cette livraison ne revendique pas un
|
||||||
|
gain de performance. Le test d’export exhaustif du volume bloque ponctuellement
|
||||||
|
le serveur de test ; cet export n’est pas activé dans le mod livré.
|
||||||
|
|
||||||
|
## Témoin Grand 1 024, graine -7228211907433324401
|
||||||
|
|
||||||
|
Le processus **67379** passe les **24/24 GameTests** en **4 min 23 s** pour la
|
||||||
|
commande complète. Le volume natif contient **0 composant détaché de 32 blocs
|
||||||
|
ou moins**, contre **12 avant**. Le test exige de ne pas augmenter ce nombre ;
|
||||||
|
**1 024 points** rééchantillonnés dans l’ordre inverse, avec un autre contexte de
|
||||||
|
cache, conservent le même état roche/air.
|
||||||
|
|
||||||
|
La traversée est réellement posée : **21 départs**, **48 chunks FULL**,
|
||||||
|
**340 blocs entre les accès** et **574 pas** sur le parcours testé. Entrées :
|
||||||
|
**192 238 -195** et **192 224 145**. Le sanctuaire et les quatre satellites
|
||||||
|
passent leurs contrôles. La région centrale admet ici une rivière courbe ;
|
||||||
|
l’écoulement des brèches est contrôlé sous ticks réels dans huit chunks FULL.
|
||||||
|
|
||||||
|
Reçus : `build/alpha302-evidence/large-user/`. Les deux commandes natives
|
||||||
|
activent `sanctuaryAerial24Tests` et `sanctuaryTransit29Tests` ; le témoin Moyen
|
||||||
|
exige aussi `sanctuaryRequireCurvedRiverBank=true`. Les trois tailles conservent
|
||||||
|
leurs profils et le même filtre. Petit n’a pas fait l’objet d’une nouvelle
|
||||||
|
visite native complète dans cette livraison. Les sauvegardes personnelles
|
||||||
|
ne sont pas régénérées ; les scénarios natifs utilisent des mondes jetables.
|
||||||
|
|
||||||
|
Les reçus de distribution sont consignés dans [packwiz](packwiz.md).
|
||||||
|
|
||||||
|
## Distribution
|
||||||
|
|
||||||
|
`assemblePack` passe après la vérification complète ; `:sanctuary:check` est
|
||||||
|
exclu de cette seconde commande pour ne pas répéter la suite inchangée. Le JAR,
|
||||||
|
le MRpack et le ZIP d’amorçage sont vérifiés localement : versions, index,
|
||||||
|
empreintes et contenu concordent. Reçus : `build/alpha302-artifacts.json` et
|
||||||
|
`build/alpha302-delivery-local.json`. Les reçus publics et la mise à jour Prism
|
||||||
|
sont consignés dans [Distribution](packwiz.md).
|
||||||
+2
-2
@@ -8,6 +8,6 @@ loader_version=0.19.5
|
|||||||
loom_version=1.17.20
|
loom_version=1.17.20
|
||||||
fabric_api_version=0.160.0+26.3
|
fabric_api_version=0.160.0+26.3
|
||||||
|
|
||||||
mod_version=0.1.0-alpha.30.1
|
mod_version=0.1.0-alpha.30.2
|
||||||
pack_version=0.1.0-alpha.30.1
|
pack_version=0.1.0-alpha.30.2
|
||||||
maven_group=fr.koka.sanctuary
|
maven_group=fr.koka.sanctuary
|
||||||
|
|||||||
@@ -666,3 +666,11 @@ tasks.register('river30Smoke', JavaExec) {
|
|||||||
mainClass = 'fr.koka.sanctuary.worldgen.River30Smoke'
|
mainClass = 'fr.koka.sanctuary.worldgen.River30Smoke'
|
||||||
}
|
}
|
||||||
tasks.named('check') { dependsOn('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)
|
var field = (PopulationIslandDensity) level.registryAccess().lookupOrThrow(Registries.DENSITY_FUNCTION)
|
||||||
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION,
|
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION,
|
||||||
SanctuaryMod.id("population_" + generator.capacity().players()))).value();
|
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 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 context = SamplerContext.builder().enableCaches().build();
|
||||||
var report = new LinkedHashMap<String, Object>();
|
var report = new LinkedHashMap<String, Object>();
|
||||||
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
|
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
|
||||||
report.put("process_id", ProcessHandle.current().pid());
|
report.put("process_id", ProcessHandle.current().pid());
|
||||||
int radius = Math.min(400, generator.initialDiameter() / 2), lowerSamples = 0, unchanged = 0, changed = 0;
|
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 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))
|
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++;
|
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 old = previous.sampleValue(context, x, y, z) > 0;
|
||||||
boolean now = current.sampleValue(context, x, y, z) > 0;
|
boolean now = current.sampleValue(context, x, y, z) > 0;
|
||||||
if (old == now) unchanged++; else changed++;
|
if (old == now) unchanged++; else changed++;
|
||||||
if (Math.hypot(x, z) <= 48) helper.assertTrue(old == now, "Arrival silhouette is unchanged");
|
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_lower_density_samples", lowerSamples);
|
||||||
report.put("unchanged_upper_samples", unchanged); report.put("changed_upper_samples", changed);
|
report.put("unchanged_upper_samples", unchanged); report.put("changed_upper_samples", changed);
|
||||||
var sections = new LinkedHashMap<String, Object>();
|
var sections = new LinkedHashMap<String, Object>();
|
||||||
for (int z : new int[]{0, 96}) {
|
for (int z : new int[]{0, 96}) {
|
||||||
var before = new ArrayList<String>(); var after = new ArrayList<String>();
|
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();
|
var a = new StringBuilder(); var b = new StringBuilder();
|
||||||
for (int x = -radius; x <= radius; x++) {
|
for (int x = -radius; x <= radius; x++) {
|
||||||
a.append(previous.sampleValue(context, x, y, z) > 0 ? '#' : '.');
|
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));
|
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);
|
report.put("sections", sections);
|
||||||
Path path = Path.of("diagnostics/alpha301", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
// 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.createDirectories(path.getParent());
|
||||||
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
|
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
|
||||||
helper.succeed();
|
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 sculpt, DensityFunction detail, DensityFunction underside,
|
||||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
||||||
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28,
|
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(
|
public static final MapCodec<PopulationIslandDensity> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||||
DensityFunction.CODEC.fieldOf("terrain").forGetter(PopulationIslandDensity::terrain),
|
DensityFunction.CODEC.fieldOf("terrain").forGetter(PopulationIslandDensity::terrain),
|
||||||
DensityFunction.CODEC.fieldOf("distortion").forGetter(PopulationIslandDensity::distortion),
|
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),
|
DensityFunction.CODEC.optionalFieldOf("erosion_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::erosion28),
|
||||||
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28),
|
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28),
|
||||||
Codec.BOOL.optionalFieldOf("terraces_29", false).forGetter(PopulationIslandDensity::terraces29),
|
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));
|
).apply(instance, PopulationIslandDensity::new));
|
||||||
|
|
||||||
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||||
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
|
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
|
||||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity) {
|
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity) {
|
||||||
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, 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,
|
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||||
@@ -43,7 +44,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
|||||||
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
|
||||||
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28) {
|
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28) {
|
||||||
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, capacity,
|
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) {
|
@Override public DensitySampler compileSampler(CompileContext context) {
|
||||||
@@ -56,7 +57,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
|||||||
var erosion = geology28 ? erosion28.compileSampler(context) : null;
|
var erosion = geology28 ? erosion28.compileSampler(context) : null;
|
||||||
long seed = context.createRandom(SanctuaryMod.id("population_rifts_alpha10")).nextLong();
|
long seed = context.createRandom(SanctuaryMod.id("population_rifts_alpha10")).nextLong();
|
||||||
var layouts = new ConcurrentHashMap<IslandCapacity.Region, RiftShape.Layout>();
|
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) {
|
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);
|
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;
|
double fx = x - ix, fy = y - iy, fz = z - iz;
|
||||||
@@ -79,12 +80,16 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
|||||||
float terrainValue;
|
float terrainValue;
|
||||||
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
|
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
|
||||||
if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
|
if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
|
||||||
if (weight > 0) {
|
double surfaceWeight = surface302 ? SurfaceRelief302.weight(x, y, z, capacity.radius())
|
||||||
var shift = CavesRelief28.warp(weight,
|
* 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),
|
erosion.sampleValue(sample, x + 173, y - 59, z - 89),
|
||||||
ridges.sampleValue(sample, x, y, z),
|
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();
|
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,
|
if (terraces29) sourceY = Terraces29.sourceY(sourceY, coarseValue, fineValue,
|
||||||
Terraces29.weight(y, weight));
|
Terraces29.weight(y, weight));
|
||||||
terrainValue = terrainAt(sample, x - shift.x(), sourceY, z - shift.z());
|
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);
|
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. */
|
/** Native diagnostics can compile the exact accepted27 field without new adaptations. */
|
||||||
public PopulationIslandDensity legacy27() {
|
public PopulationIslandDensity legacy27() {
|
||||||
@@ -120,7 +126,11 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
|||||||
/** Exact accepted28 deformation and caves, without the fine local ledges. */
|
/** Exact accepted28 deformation and caves, without the fine local ledges. */
|
||||||
public PopulationIslandDensity legacy28() {
|
public PopulationIslandDensity legacy28() {
|
||||||
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
|
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) {
|
public static long regionSeed(long seed, IslandCapacity.Region region) {
|
||||||
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
|
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) {
|
@Override public DensityFunction rewriteChildren(DfRewriteRule rule) {
|
||||||
return new PopulationIslandDensity(rule.rewrite(terrain), rule.rewrite(distortion), rule.rewrite(sculpt),
|
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(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 Interval range() { return Interval.of(-1, 1); }
|
||||||
@Override public int domainAxes() { return ALL_AXES; }
|
@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
|
"y_scale": 2.0
|
||||||
},
|
},
|
||||||
"terraces_29": false,
|
"terraces_29": false,
|
||||||
"relief_30": true
|
"relief_30": true,
|
||||||
|
"surface_302": true
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -65,5 +65,6 @@
|
|||||||
"y_scale": 2.0
|
"y_scale": 2.0
|
||||||
},
|
},
|
||||||
"terraces_29": false,
|
"terraces_29": false,
|
||||||
"relief_30": true
|
"relief_30": true,
|
||||||
|
"surface_302": true
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -65,5 +65,6 @@
|
|||||||
"y_scale": 2.0
|
"y_scale": 2.0
|
||||||
},
|
},
|
||||||
"terraces_29": false,
|
"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");
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name = "Sanctuary"
|
name = "Sanctuary"
|
||||||
author = "KOKA99CAB"
|
author = "KOKA99CAB"
|
||||||
version = "0.1.0-alpha.30.1"
|
version = "0.1.0-alpha.30.2"
|
||||||
pack-format = "packwiz:1.1.0"
|
pack-format = "packwiz:1.1.0"
|
||||||
|
|
||||||
[index]
|
[index]
|
||||||
|
|||||||
Reference in New Issue
Block a user