Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076c9a6c16 |
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0-alpha.30.3
|
||||
|
||||
- Retire les microcorniches de la 30.2, jugées trop bruitées en jeu.
|
||||
- Répartit de grandes zones de plateaux naturels et de montagnes déformées en 3D,
|
||||
avec une échelle relative commune à Petit, Moyen et Grand.
|
||||
- Étend les hauts reliefs vers Y360 sans changer la hauteur du monde ; conserve
|
||||
les profondeurs, l’arrivée, les cours d’eau courbes et les infrastructures.
|
||||
- Conserve un filtrage des fragments rocheux détachés jusque dans les sommets.
|
||||
- Adapte la recherche aérienne aux plateaux disponibles : rejet grossier des
|
||||
emprises trop creuses, contrôle de hauteur précoce et recherche autour de l’île.
|
||||
- Corrige le veto de biome qui empêchait un sanctuaire minéral pourtant admis
|
||||
de se poser dans les marais souterrains.
|
||||
- Nouveau monde nécessaire pour tester le nouveau terrain ; aucun monde régénéré.
|
||||
|
||||
## 0.1.0-alpha.30.2
|
||||
|
||||
- Corniches locales dont la phase et l’espacement suivent un bruit 3D, sur le relief global conservé.
|
||||
|
||||
@@ -21,14 +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
|
||||
[chantier et ses limites](docs/generation-alpha27.md).
|
||||
|
||||
## Alpha.30.2 — corniches et fragments
|
||||
## Alpha.30.3 — plateaux et grands reliefs
|
||||
|
||||
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.
|
||||
La passe de petites corniches de la 30.2 est retirée après le retour en jeu.
|
||||
De larges provinces de relief font cohabiter les plateaux naturels issus de la
|
||||
24 et les montagnes déformées en 3D, jusqu’à proximité de Y360. Leur répartition
|
||||
est proportionnée aux trois tailles. Les petits fragments détachés restent filtrés.
|
||||
|
||||
Créer un **nouveau monde**. Voir [la génération](docs/generation-alpha30.2.md)
|
||||
et [les vérifications](docs/testing-alpha30.2.md).
|
||||
Créer un **nouveau monde**. Voir [la génération](docs/generation-alpha30.3.md)
|
||||
et [les vérifications](docs/testing-alpha30.3.md).
|
||||
|
||||
## Alpha.30.1 — rivières courbes et relief global
|
||||
|
||||
|
||||
@@ -1017,3 +1017,13 @@ arches, casser la régularité des pentes par des corniches localisées, et reti
|
||||
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).
|
||||
|
||||
### WG-35 — Plateaux naturels et grandes montagnes — alpha.30.3
|
||||
|
||||
Branche `codex/balanced-relief-alpha30-3`. Le rendu de la 30.2 est rejeté en jeu :
|
||||
les métriques locales de marches ne constituaient pas une validation esthétique.
|
||||
Retirer ce microrelief, retrouver des plateaux issus de la 24 et réserver une
|
||||
part importante aux grandes déformations 3D, vers Y360. Équilibrer les trois
|
||||
tailles par une distribution à l’échelle de l’île. Préserver les profondeurs,
|
||||
les cours d’eau et les infrastructures ; limiter les petits fragments flottants.
|
||||
Voir [le changement](generation-alpha30.3.md) et [les preuves](testing-alpha30.3.md).
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Alpha.30.3 — plateaux et grands reliefs
|
||||
|
||||
## Choix retenu
|
||||
|
||||
Le créateur a rejeté la 30.2 après essai : la retouche fine produisait un aspect
|
||||
rongé et trop bruité. Son nombre réduit de longues marches ne garantissait pas
|
||||
un meilleur paysage. Cette version désactive `surface_302` et `terraces_29`.
|
||||
|
||||
La roche reste le champ volumétrique Minecraft de la base alpha.24, accompagné
|
||||
des grottes et fractures adaptées en 28. Un bruit de grande longueur d’onde
|
||||
sélectionne des provinces montagneuses ; il ne dessine ni cônes ni salles.
|
||||
Les provinces sont échantillonnées dans un repère proportionnel au rayon :
|
||||
Petit 512, Moyen 724 et Grand 1 024 conservent une distribution comparable,
|
||||
avec des masses physiquement plus vastes lorsque l’île grandit.
|
||||
|
||||
Le seuil médian du champ intérieur conserve une grande part des plateaux ;
|
||||
la transition vers les provinces hautes est continue. La bande de plateau
|
||||
utile aux infrastructures reste protégée, ainsi que les 48 blocs autour du
|
||||
centre. Aucun pavage périodique ou bruit supplémentaire à l’échelle du bloc.
|
||||
|
||||
Dans les provinces hautes, le domaine vertical s’étend progressivement
|
||||
au-dessus de Y170, avec les déplacements signés sur trois axes de la 28.
|
||||
Cela relève la roche existante et conserve sa structure volumétrique, ses
|
||||
surplombs et ses vides. Une extinction douce sous Y360 laisse de la place
|
||||
sous le plafond de construction Y384. La hauteur atteinte dépend du terrain
|
||||
et de la graine ; aucun sommet n’est forcé à une altitude identique.
|
||||
|
||||
Le filtre de connexité travaille sur le champ rocheux avant décoration :
|
||||
composants de 32 blocs au plus, connexions par faces, coordonnées globales
|
||||
même à cheval sur plusieurs chunks. Il ne crée aucun pilier ni roche de
|
||||
remplissage. Les grandes masses flottantes naturelles et les îlots aériens
|
||||
ne sont pas supprimés.
|
||||
|
||||
Les grottes sous Y170 conservent exactement leur densité précédente. Les
|
||||
anciens cours d’eau courbes et leurs brèches de débordement, la traversée,
|
||||
le sanctuaire minéral, les secrets et les quatre lieux aériens sont conservés.
|
||||
Leurs recherches utilisent le terrain courant ; leurs emplacements peuvent
|
||||
changer. Pas de retour de la montgolfière ou des rivières angulaires.
|
||||
Le biome `sanctuary:swamp27_caves` rejoint les biomes autorisés des réseaux
|
||||
secrets : un sanctuaire minéral déjà certifié ne doit plus être annulé au
|
||||
stade du départ natif simplement parce qu’il se trouve dans ce marais.
|
||||
La recherche des îlots refuse immédiatement les volumes trop hauts, dès le
|
||||
premier obstacle incompatible, et filtre grossièrement les emprises au-dessus
|
||||
de trop grands trous avant leur certification bloc par bloc. Après le secteur initial, les candidats parcourent tout le tour de l’île.
|
||||
La recherche reste bornée à 64 candidats par site et 16 384 colonnes au total ;
|
||||
la certification détaillée des sites acceptés est conservée.
|
||||
|
||||
## Essai
|
||||
|
||||
Minecraft 26.3-pre-2, mêmes dépendances et bouton natif Personnaliser.
|
||||
Créer un **nouveau monde Sanctuary**. Le codec reste `sanctuary:island_v24`,
|
||||
conformément au workflow de mondes de test jetables demandé par le créateur.
|
||||
Les ressources des trois profils sont mises à jour : ne pas prolonger une
|
||||
ancienne sauvegarde pour comparer des raccords. Cette livraison n’ouvre et
|
||||
ne régénère aucune sauvegarde personnelle.
|
||||
|
||||
Les prévisualisations de densité servent à examiner les silhouettes ; elles
|
||||
ne remplacent pas l’essai visuel du paysage décoré dans le client Minecraft.
|
||||
Voir [les validations réalisées](testing-alpha30.3.md).
|
||||
@@ -0,0 +1,97 @@
|
||||
# Vérification alpha.30.3
|
||||
|
||||
## Premier témoin natif — Mac
|
||||
|
||||
Monde Moyen 724, graine `0`, processus `68244` : 24/24 GameTests réussis.
|
||||
La comparaison utilise les densités natives enregistrées, pas un terrain simulé.
|
||||
La correction finale de bornage numérique à -1, sans effet sur les blocs solides,
|
||||
a ensuite été incluse dans le build de livraison.
|
||||
|
||||
| Profil mesuré (même graine) | Hauts terrains proches de la base 24* | Plus haut échantillon |
|
||||
|---|---:|---:|
|
||||
| Petit 512 | 330 / 463 (71 %) | Y344 |
|
||||
| Moyen 724 | 913 / 1 288 (71 %) | Y344 |
|
||||
| Grand 1 024 | 1 930 / 2 853 (68 %) | Y344 |
|
||||
|
||||
*Colonnes échantillonnées tous les 12 blocs, ancienne surface au-dessus de Y200,
|
||||
écart final de deux blocs au plus. La base de comparaison est le relief 24 repris
|
||||
en 27. Cela ne mesure ni la surface constructible exacte ni une qualité esthétique.
|
||||
Les trois profils sont compilés avec le même état aléatoire natif ; seul le
|
||||
monde Moyen de ce premier essai est parcouru en chunks FULL.
|
||||
|
||||
Le volume local contrôlé passe de cinq petits composants détachés en 30.1 à
|
||||
zéro en 30.3. Le parcours inversé des échantillons donne les mêmes appartenances.
|
||||
La densité sous Y170 est identique à la 30.1 aux points vérifiés.
|
||||
|
||||
La traversée a 19 départs de structure, 29 chunks FULL visités, portée 272.
|
||||
Le sanctuaire minéral est admis et son sol natif est inspecté. Les deux navires
|
||||
et deux îlots sont générés ; les tests du Blocodex et des minerais réussissent.
|
||||
La rivière courbe centrale est présente. Ce témoin ne fournit pas de nouvelle
|
||||
brèche de berge admissible : il ne constitue donc pas une preuve supplémentaire
|
||||
de cascade de surface sous ticks, malgré le succès du test des cellules d’eau.
|
||||
|
||||
Preuves ignorées : `build/alpha303-native-medium.log`,
|
||||
`build/alpha303-evidence/medium-0/`, `build/alpha303-preview/`.
|
||||
Les cartes et coupes visualisent la roche seule ; aucune capture client ni
|
||||
validation esthétique du joueur n’est revendiquée.
|
||||
|
||||
## Build de livraison et témoin Petit — Mac
|
||||
|
||||
`./gradlew check build -PsanctuaryTestDiameter=512 -PsanctuaryTestSeed=42
|
||||
-PsanctuaryTransit29Tests=true -PsanctuaryAerial24Tests=true` : réussite,
|
||||
24/24 tests natifs et contrôles JVM, 4 min 53 au total sur cette machine Mac.
|
||||
Ce temps comprend compilation, anciens tests et inspections ; ce n’est pas
|
||||
une mesure du délai de chargement d’un client.
|
||||
|
||||
Processus `69553`. Les 15 modules de traversée possèdent leurs départs natifs,
|
||||
32 chunks FULL sont parcourus, portée 204. Le sanctuaire minéral apparaît
|
||||
réellement dans le marais. Quatre sites aériens, quatre coffres, deux villageois,
|
||||
une source de cascade sur un îlot ; leurs coffres sont ouverts et les habitants
|
||||
déplacés dans ce monde jetable par le test. Aucun petit composant détaché dans
|
||||
le volume contrôlé ; 1 536 échantillons restent identiques en ordre inversé.
|
||||
|
||||
Les trois profils sous la graine 42 conservent 60 %, 59 % et 58 % des hauts
|
||||
terrains à deux blocs au plus de la base 24. Sommets échantillonnés :
|
||||
Y344 / Y346 / Y346. Une brèche de bassin produit de l’eau sous ticks réels ;
|
||||
la région centrale de ce témoin n’admet pas de rivière courbe.
|
||||
Preuves : `build/alpha303-check-build.log`, `build/alpha303-evidence/small-42/`.
|
||||
|
||||
## Témoin Grand signalé par le créateur — Mac
|
||||
|
||||
Grand 1024, graine `-7228211907433324401`, processus `69750` : 24/24 tests
|
||||
natifs réussis. La traversée compte 23 départs, portée 374, avec 52 chunks FULL
|
||||
parcourus et des accès vérifiés. Localisation témoin : `-187,249,-72`.
|
||||
Le sanctuaire minéral a un départ valide et un véritable sol central ;
|
||||
les quatre lieux aériens génèrent leurs quatre coffres et deux marchands.
|
||||
|
||||
La rivière courbe centrale est présente ; huit cellules d’eau sont constatées
|
||||
sous les ouvertures après propagation native. Le volume local passe de 12
|
||||
petits composants détachés à zéro. Les1 536 points contrôlés en ordre inversé
|
||||
restent identiques et les 7 436 échantillons profonds sont inchangés.
|
||||
|
||||
Sur cette graine, les plus hauts échantillons des profils 512/724/1024 atteignent
|
||||
Y322 / Y344 / Y346. L’altitude n’est pas forcée à 360 sur toutes les graines.
|
||||
Preuves : `build/alpha303-native-large.log`, `build/alpha303-evidence/large-user/`.
|
||||
Les fichiers `verified-run.json` désignent les rapports de chaque processus ;
|
||||
les autres anciens diagnostics copiés ne comptent pas comme des essais 30.3.
|
||||
|
||||
## Défauts corrigés pendant la vérification
|
||||
|
||||
Petit 512, graine 42, révélait deux défauts distincts :
|
||||
|
||||
- La recherche aérienne épuisait ses colonnes sur des emprises trop hautes ou
|
||||
trop creuses. Le rejet de hauteur est maintenant immédiat, l’appui est filtré
|
||||
grossièrement avant certification et les candidats de repli couvrent le tour
|
||||
de l’île. Le plan témoin obtenu emploie 8 058 colonnes, avec quatre sites.
|
||||
- La chambre minérale était admise en `sanctuary:swamp27_caves`, mais ce biome
|
||||
manquait au tag `has_structure/forgotten_networks`. Le départ natif était donc
|
||||
refusé. Le tag inclut désormais ce biome, sans contourner la certification.
|
||||
|
||||
Les premiers journaux en échec sont conservés sous `build/alpha303-small42-*failure.log`.
|
||||
|
||||
## Limites
|
||||
|
||||
Essais exécutés sur Mac ; aucun essai natif Windows par l’agent. Les variations
|
||||
de relief exigent un nouveau monde ; aucun monde personnel n’est régénéré.
|
||||
Le filtrage local de connexité n’est pas un audit exhaustif de l’île entière.
|
||||
La présence des mines, huttes et donjons reste conditionnelle au terrain.
|
||||
+2
-2
@@ -8,6 +8,6 @@ loader_version=0.19.5
|
||||
loom_version=1.17.20
|
||||
fabric_api_version=0.160.0+26.3
|
||||
|
||||
mod_version=0.1.0-alpha.30.2
|
||||
pack_version=0.1.0-alpha.30.2
|
||||
mod_version=0.1.0-alpha.30.3
|
||||
pack_version=0.1.0-alpha.30.3
|
||||
maven_group=fr.koka.sanctuary
|
||||
|
||||
@@ -674,3 +674,11 @@ tasks.register('surfaceRelief302Smoke', JavaExec) {
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.SurfaceRelief302Smoke'
|
||||
}
|
||||
tasks.named('check') { dependsOn('surfaceRelief302Smoke') }
|
||||
|
||||
tasks.register('balancedRelief303Smoke', JavaExec) {
|
||||
group = 'verification'
|
||||
dependsOn tasks.named('testClasses')
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.BalancedRelief303Smoke'
|
||||
}
|
||||
tasks.named('check') { dependsOn('balancedRelief303Smoke') }
|
||||
|
||||
+34
-9
@@ -16,13 +16,13 @@ import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
|
||||
/** Compares registered native fields; does not place blocks or load additional chunks. */
|
||||
public final class Terraces29WorldGameTests {
|
||||
@GameTest(maxTicks = 2400)
|
||||
public void fineLedgesPreserveTheAcceptedMass(GameTestHelper helper) throws Exception {
|
||||
public void broadProvincesRestorePlateausAndRaiseNativeMasses(GameTestHelper helper) throws Exception {
|
||||
var level = helper.getLevel();
|
||||
var generator = (SanctuaryChunkGenerator) level.getChunkSource().getGenerator();
|
||||
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() && field.surface302(), "Alpha30.2 adds non-periodic local relief to the global field");
|
||||
helper.assertTrue(field.balanced303() && !field.terraces29() && !field.surface302(), "Alpha30.3 removes fine bands and uses broad geological provinces");
|
||||
var random = generator.starterContext().random();
|
||||
var current = random.getSampler(field); var previous = random.getSampler(field.legacy301());
|
||||
var context = SamplerContext.builder().enableCaches().build();
|
||||
@@ -31,25 +31,25 @@ public final class Terraces29WorldGameTests {
|
||||
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 <= 140; y += 13) {
|
||||
for (int y = 32; y <= 170; y += 13) {
|
||||
helper.assertTrue(Float.floatToIntBits(current.sampleValue(context, x, y, z))
|
||||
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Deep density is exactly alpha30.1");
|
||||
lowerSamples++;
|
||||
}
|
||||
for (int y = 141; y <= 320; y++) {
|
||||
for (int y = 171; y <= 380; 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, "Local surface offsets retain most of the accepted rock volume");
|
||||
helper.assertTrue(changed > 0, "The upper field now combines broad plateaus and raised rock");
|
||||
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 >= 145; y--) {
|
||||
for (int y = 365; 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 ? '#' : '.');
|
||||
@@ -57,7 +57,7 @@ public final class Terraces29WorldGameTests {
|
||||
}
|
||||
before.add(a.toString()); after.add(b.toString());
|
||||
}
|
||||
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", 365, "step", 1));
|
||||
}
|
||||
report.put("sections", sections);
|
||||
// One block-resolution volume, chosen using the old field's surface transitions.
|
||||
@@ -76,7 +76,7 @@ public final class Terraces29WorldGameTests {
|
||||
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;
|
||||
int size=64,minY=171,maxY=362;
|
||||
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++){
|
||||
@@ -97,8 +97,33 @@ public final class Terraces29WorldGameTests {
|
||||
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);
|
||||
// Broad top views compare all three supported sizes under the same native noise seed.
|
||||
var sizes = new ArrayList<Object>();
|
||||
for (int players : new int[]{5,10,20}) {
|
||||
var f = (PopulationIslandDensity) level.registryAccess().lookupOrThrow(Registries.DENSITY_FUNCTION)
|
||||
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("population_"+players))).value();
|
||||
var now = random.getSampler(f); var old = random.getSampler(f.legacy27());
|
||||
var global = random.getSampler(f.legacy301());
|
||||
int r = (int)Math.round(f.capacity().radius());
|
||||
var columns = new ArrayList<Object>(); int retained=0,land=0,highest=0;
|
||||
for(int z=-r;z<=r;z+=12)for(int x=-r;x<=r;x+=12){
|
||||
int a=0,b=0,c=0;
|
||||
for(int y=380;y>=32;y-=2){
|
||||
if(a==0 && old.sampleValue(context,x,y,z)>0)a=y;
|
||||
if(b==0 && now.sampleValue(context,x,y,z)>0)b=y;
|
||||
if(c==0 && global.sampleValue(context,x,y,z)>0)c=y;
|
||||
if(a>0&&b>0&&c>0)break;
|
||||
}
|
||||
if(a>=200){land++;if(Math.abs(a-b)<=2)retained++;}
|
||||
highest=Math.max(highest,b); columns.add(List.of(x,z,a,b,c));
|
||||
}
|
||||
helper.assertTrue(highest<=358,"Natural summits leave room below the build ceiling");
|
||||
helper.assertTrue(retained>land*.25,"Each size retains substantial original uplands, rather than changing every slope");
|
||||
sizes.add(Map.of("diameter",r*2,"columns",columns,"unchanged_uplands",retained,"uplands",land,"highest_sample",highest));
|
||||
}
|
||||
report.put("sizes",sizes);
|
||||
report.put("passed", true);
|
||||
Path path = Path.of("diagnostics/alpha302", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
Path path = Path.of("diagnostics/alpha303", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
|
||||
helper.succeed();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.util.Arrays;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
/** Broad geological provinces, sampled once in island-relative coordinates.
|
||||
* The mask selects where the existing 3D rock field can rise; it never defines a summit shape. */
|
||||
public final class BalancedRelief303 {
|
||||
private static final int SIZE = 65;
|
||||
private final float[] samples = new float[SIZE * SIZE];
|
||||
private final double radius, low, high;
|
||||
private final long seed;
|
||||
|
||||
public BalancedRelief303(DensitySampler noise, double radius, long seed) {
|
||||
this.radius = radius; this.seed = seed;
|
||||
float[] distribution = new float[SIZE * SIZE]; int count = 0;
|
||||
for (int z = 0; z < SIZE; z++) for (int x = 0; x < SIZE; x++) {
|
||||
float value = noise.sampleValue(SamplerContext.EMPTY_UNCACHED, x * 2 - 64, 87, z * 2 - 64);
|
||||
samples[z * SIZE + x] = value;
|
||||
if (Math.hypot(x - 32, z - 32) <= 26) distribution[count++] = value;
|
||||
}
|
||||
Arrays.sort(distribution, 0, count);
|
||||
low = distribution[count / 2]; high = Math.max(low + .0001, distribution[(int)(count * .82)]);
|
||||
}
|
||||
|
||||
public double province(int x, int z) {
|
||||
double gx = Math.clamp((x / radius + 1) * 32, 0, 63.999999);
|
||||
double gz = Math.clamp((z / radius + 1) * 32, 0, 63.999999);
|
||||
int ix = (int)gx, iz = (int)gz; double fx = gx - ix, fz = gz - iz;
|
||||
double n = lerp(fz, lerp(fx, samples[iz*SIZE+ix], samples[iz*SIZE+ix+1]),
|
||||
lerp(fx, samples[(iz+1)*SIZE+ix], samples[(iz+1)*SIZE+ix+1]));
|
||||
double distance = Math.hypot(x, z);
|
||||
return smooth((n - low) / (high - low)) * smooth((distance - 48) / 40)
|
||||
* (1 - smooth((distance / radius - .68) / .22))
|
||||
* Relief30.influence(seed, x, z, radius);
|
||||
}
|
||||
|
||||
/** Continuous, strictly increasing vertical domain: no bands, ledges or added fine noise. */
|
||||
public static double sourceY(int y, double province) {
|
||||
if (y <= 170 || province <= 0) return y;
|
||||
double d = y - 170;
|
||||
// The first 30 blocks blend into untouched lower caves with a matching first derivative.
|
||||
return 170 + d / (1 + .95 * province * smooth(d / 30));
|
||||
}
|
||||
public static double ceilingErosion(int y) { return 2 * smooth((y - 340) / 36.0); }
|
||||
private static double lerp(double t, double a, double b) { return a + t * (b - a); }
|
||||
private static double smooth(double t) { t = Math.clamp(t, 0, 1); return t*t*(3-2*t); }
|
||||
}
|
||||
@@ -13,11 +13,16 @@ public final class DetachedRock302 implements DensitySampler {
|
||||
private final DensitySampler input;
|
||||
private final double radius;
|
||||
private final long seed;
|
||||
private final boolean broad;
|
||||
|
||||
public DetachedRock302(DensitySampler input, double radius, long seed) {
|
||||
this.input = input; this.radius = radius; this.seed = seed;
|
||||
this(input, radius, seed, false);
|
||||
}
|
||||
public DetachedRock302(DensitySampler input, double radius, long seed, boolean broad) {
|
||||
this.input = input; this.radius = radius; this.seed = seed; this.broad = broad;
|
||||
}
|
||||
private boolean affected(int x, int y, int z) {
|
||||
if (broad) return y > 170 && y < 376 && Math.hypot(x, z) > 48 && Math.hypot(x,z) < radius + 32;
|
||||
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) {
|
||||
|
||||
+18
-12
@@ -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, boolean surface302) implements DensityFunction {
|
||||
boolean terraces29, boolean relief30, boolean surface302, boolean balanced303) 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),
|
||||
@@ -29,14 +29,15 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
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("surface_302", false).forGetter(PopulationIslandDensity::surface302)
|
||||
Codec.BOOL.optionalFieldOf("surface_302", false).forGetter(PopulationIslandDensity::surface302),
|
||||
Codec.BOOL.optionalFieldOf("balanced_303", false).forGetter(PopulationIslandDensity::balanced303)
|
||||
).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, false);
|
||||
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false, false, false, false);
|
||||
}
|
||||
|
||||
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
@@ -44,7 +45,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, false);
|
||||
caves28, ridges28, erosion28, geology28, false, false, false, false);
|
||||
}
|
||||
|
||||
@Override public DensitySampler compileSampler(CompileContext context) {
|
||||
@@ -56,6 +57,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
var ridges = geology28 ? ridges28.compileSampler(context) : null;
|
||||
var erosion = geology28 ? erosion28.compileSampler(context) : null;
|
||||
long seed = context.createRandom(SanctuaryMod.id("population_rifts_alpha10")).nextLong();
|
||||
var balanced = balanced303 ? new BalancedRelief303(coarse, capacity.radius(), seed) : null;
|
||||
var layouts = new ConcurrentHashMap<IslandCapacity.Region, RiftShape.Layout>();
|
||||
DensitySampler field = new DensitySampler() {
|
||||
private float terrainAt(SamplerContext sample, double x, double y, double z) {
|
||||
@@ -78,16 +80,19 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
float coarseValue = coarse.sampleValue(sample, x, localY, z);
|
||||
float fineValue = fine.sampleValue(sample, x, localY, z);
|
||||
float terrainValue;
|
||||
double province = balanced != null ? balanced.province(x, z) : 0;
|
||||
double mappedY = balanced != null ? BalancedRelief303.sourceY(y, province) : y;
|
||||
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
|
||||
if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
|
||||
if (balanced != null) weight *= province;
|
||||
else if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
|
||||
double surfaceWeight = surface302 ? SurfaceRelief302.weight(x, y, z, capacity.radius())
|
||||
* Relief30.influence(seed, x, z, capacity.radius()) : 0;
|
||||
if (weight > 0 || surfaceWeight > 0) {
|
||||
if (weight > 0 || surfaceWeight > 0 || mappedY != y) {
|
||||
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)) : new CavesRelief28.Warp(0,0,0);
|
||||
double sourceY = y - 64 - shift.y();
|
||||
double sourceY = mappedY - 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,
|
||||
@@ -97,7 +102,8 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
// Keep the island envelope at actual coordinates, independent from displaced rock.
|
||||
float natural = PopulationIslandShape.raisedDensity(x, y, z, terrainValue,
|
||||
edge.sampleValue(sample, x, 0, z), coarseValue,
|
||||
fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity);
|
||||
fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity, mappedY - 64);
|
||||
if (balanced != null) natural = Math.max(-1, natural - (float) BalancedRelief303.ceilingErosion(y));
|
||||
if (natural <= 0) return natural;
|
||||
var region = capacity.regionAt(x, z);
|
||||
var layout = layouts.computeIfAbsent(region, r -> RiftShape.layout(regionSeed(seed, r)));
|
||||
@@ -116,7 +122,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensitySampler.sampleVolumeNaive(sample, buffer, volume, this);
|
||||
}
|
||||
};
|
||||
return surface302 ? new DetachedRock302(field,capacity.radius(),seed) : field;
|
||||
return surface302 || balanced303 ? new DetachedRock302(field,capacity.radius(),seed, balanced303) : field;
|
||||
}
|
||||
/** Native diagnostics can compile the exact accepted27 field without new adaptations. */
|
||||
public PopulationIslandDensity legacy27() {
|
||||
@@ -126,11 +132,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, false);
|
||||
capacity, caves28, ridges28, erosion28, geology28, false, false, false, false);
|
||||
}
|
||||
public PopulationIslandDensity legacy301() {
|
||||
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
|
||||
capacity, caves28, ridges28, erosion28, geology28, terraces29, relief30, false);
|
||||
capacity, caves28, ridges28, erosion28, geology28, terraces29, relief30, false, false);
|
||||
}
|
||||
public static long regionSeed(long seed, IslandCapacity.Region region) {
|
||||
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
|
||||
@@ -141,7 +147,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, surface302);
|
||||
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28, terraces29, relief30, surface302, balanced303);
|
||||
}
|
||||
@Override public Interval range() { return Interval.of(-1, 1); }
|
||||
@Override public int domainAxes() { return ALL_AXES; }
|
||||
|
||||
@@ -7,6 +7,12 @@ public final class PopulationIslandShape {
|
||||
/** Generation v3. The natural field and lateral sculpture are sampled in their original frame. */
|
||||
public static float raisedDensity(int x, int y, int z, float terrain, float distortion,
|
||||
float sculpt, float detail, float underside, int offset, int height, IslandCapacity capacity) {
|
||||
return raisedDensity(x, y, z, terrain, distortion, sculpt, detail, underside, offset, height, capacity, (double)y - offset);
|
||||
}
|
||||
|
||||
/** A raised province relaxes only the old upper fade; lateral and lower coast cuts stay in place. */
|
||||
public static float raisedDensity(int x, int y, int z, float terrain, float distortion,
|
||||
float sculpt, float detail, float underside, int offset, int height, IslandCapacity capacity, double upperLocalY) {
|
||||
double distance = Math.hypot((double) x, (double) z);
|
||||
if (distance >= capacity.terrainLimit() || y <= 0 || y >= height) return -1.0F;
|
||||
double localY = (double) y - offset;
|
||||
@@ -21,7 +27,7 @@ public final class PopulationIslandShape {
|
||||
erosion += 2.0 * lower * lower * lower * lower;
|
||||
// Upper silhouette is exactly the old one translated by offset. Absolute-world fades are
|
||||
// safety margins, outside the normal terrain, rather than the island's visible underside.
|
||||
erosion += 2.0 * smoothstep((localY - 224.0) / 32.0);
|
||||
erosion += 2.0 * smoothstep((upperLocalY - 224.0) / 32.0);
|
||||
erosion += 2.0 * smoothstep((24.0 - y) / 24.0);
|
||||
erosion += 2.0 * smoothstep((y - (height - 24.0)) / 24.0);
|
||||
return bounded(terrain, erosion);
|
||||
|
||||
+30
-7
@@ -14,7 +14,7 @@ import fr.koka.sanctuary.worldgen.aerial.AerialPlan24.Site;
|
||||
/** Fixed work limits, not wall-clock-dependent draws. No chunk is requested or modified by this planner. */
|
||||
public final class AerialPlanner24 {
|
||||
public static final int MAX_ATTEMPTS_PER_SITE = 64;
|
||||
public static final int MAX_COLUMNS = 8192;
|
||||
public static final int MAX_COLUMNS = 16384;
|
||||
public static final int ARRIVAL_RADIUS = 96;
|
||||
public static final int MIN_ISLET_GAP = 16;
|
||||
private AerialPlanner24() {}
|
||||
@@ -58,11 +58,15 @@ public final class AerialPlanner24 {
|
||||
int bodyHeight = merchant ? 18 : 20 + (int) Math.floorMod(mix(siteSeed), 16);
|
||||
int height = bodyHeight + (merchant ? 0 : AerialPlan24.VEGETATION_HEADROOM);
|
||||
Site accepted = null;
|
||||
for (int attempt = 0; attempt < MAX_ATTEMPTS_PER_SITE; attempt++) {
|
||||
candidates: for (int attempt = 0; attempt < MAX_ATTEMPTS_PER_SITE; attempt++) {
|
||||
probe.candidate = ids[ordinal] + " attempt=" + attempt;
|
||||
long draw = mix(siteSeed + attempt * 0x9e3779b97f4a7c15L);
|
||||
// Keep the four remaining bearings and seeded shapes; the former first sector is simply free.
|
||||
double angle = initialAngle + (ordinal + 1) * (Math.PI * 2 / 5) + (unit(draw) - .5) * .9
|
||||
+ (attempt / 16) * .41;
|
||||
// After the initial sector, cover the whole island instead of repeatedly inspecting
|
||||
// the same mountain. First-choice locations and all geometry rules remain intact.
|
||||
if (!merchant && attempt >= 4) angle = initialAngle + (attempt - 4) * 2.399963229728653;
|
||||
int facing = (int) Math.floorMod(mix(draw), 4);
|
||||
int worldWidth = facing % 2 == 0 ? width : depth, worldDepth = facing % 2 == 0 ? depth : width;
|
||||
Shore shore;
|
||||
@@ -85,13 +89,18 @@ public final class AerialPlanner24 {
|
||||
cx = (int) Math.round(Math.cos(angle) * distance);
|
||||
cz = (int) Math.round(Math.sin(angle) * distance);
|
||||
int top = probe.surface(cx, cz);
|
||||
if (top < 120 || probe.terrain.reserved(cx, cz, top, top + 3)) continue;
|
||||
if (top < 120 || top + MIN_ISLET_GAP + 1 + height > 350
|
||||
|| probe.terrain.reserved(cx, cz, top, top + 3)) continue;
|
||||
shore = dryViewpoint(probe, cx, cz);
|
||||
if (shore == null) continue;
|
||||
int highest = top;
|
||||
for (int ox = -worldWidth / 2; ox <= worldWidth / 2; ox += 4)
|
||||
for (int oz = -worldDepth / 2; oz <= worldDepth / 2; oz += 4)
|
||||
for (int oz = -worldDepth / 2; oz <= worldDepth / 2; oz += 4) {
|
||||
highest = Math.max(highest, probe.highest(cx + ox, cz + oz));
|
||||
// One obstruction is enough to refuse a candidate. Do not consume the
|
||||
// remaining column budget on an island that already cannot fit vertically.
|
||||
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
|
||||
}
|
||||
bottom = highest + MIN_ISLET_GAP + 1;
|
||||
}
|
||||
if (bottom < 32 || bottom + height > 350) continue;
|
||||
@@ -104,19 +113,32 @@ public final class AerialPlanner24 {
|
||||
} else {
|
||||
// Certify every physical rock column, not only the coarse samples used to
|
||||
// choose a height. This may raise the whole unchanged island, never the ground.
|
||||
// A coarse footprint rejects candidates mostly over holes before the full
|
||||
// per-block certification. Matching the final75% rule avoids exhausting the
|
||||
// shared budget on the same recessed coast in a small island.
|
||||
int coarseCount = 0, coarseSupported = 0;
|
||||
for (int x = 0; x < width; x += 4) for (int z = 0; z < depth; z += 4) {
|
||||
if (!isletColumn(site, x, z)) continue;
|
||||
coarseCount++;
|
||||
if (probe.surface(site.worldX(x,z),site.worldZ(x,z)) >= 120) coarseSupported++;
|
||||
}
|
||||
if (coarseSupported * 4 < coarseCount * 3) continue;
|
||||
int highest = 0, supported = 0, count = 0;
|
||||
for (int x = 0; x < width; x++) for (int z = 0; z < depth; z++) {
|
||||
if (!isletColumn(site, x, z)) continue;
|
||||
int wx = site.worldX(x, z), wz = site.worldZ(x, z);
|
||||
highest = Math.max(highest, probe.highest(wx, wz));
|
||||
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
|
||||
if (probe.surface(wx, wz) >= 120) supported++;
|
||||
count++;
|
||||
}
|
||||
if (supported * 4 < count * 3) continue;
|
||||
if (site.waterfall()) {
|
||||
var outlet = site.waterfallClaimBounds();
|
||||
for (int x = outlet.minX(); x <= outlet.maxX(); x++) for (int z = outlet.minZ(); z <= outlet.maxZ(); z++)
|
||||
highest = Math.max(highest, probe.highest(x, z));
|
||||
for (int x = outlet.minX(); x <= outlet.maxX(); x++) for (int z = outlet.minZ(); z <= outlet.maxZ(); z++) {
|
||||
highest = Math.max(highest, probe.highest(x, z));
|
||||
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
|
||||
}
|
||||
}
|
||||
bottom = Math.max(bottom, highest + MIN_ISLET_GAP + 1);
|
||||
if (bottom + height > 350) continue;
|
||||
@@ -217,13 +239,14 @@ public final class AerialPlanner24 {
|
||||
}
|
||||
private static final class Probe {
|
||||
final Terrain terrain; final int radius;
|
||||
String candidate = "unassigned";
|
||||
final Map<Long, NoiseColumn> columns = new HashMap<>();
|
||||
Probe(Terrain terrain, int radius) { this.terrain = terrain; this.radius = radius; }
|
||||
NoiseColumn column(int x, int z) {
|
||||
if (Math.hypot(x, z) >= radius) return null;
|
||||
long key = ((long) x << 32) ^ (z & 0xffffffffL);
|
||||
var present = columns.get(key); if (present != null) return present;
|
||||
if (columns.size() >= MAX_COLUMNS) throw new IllegalStateException("aerial_sites_unavailable: terrain column budget exhausted");
|
||||
if (columns.size() >= MAX_COLUMNS) throw new IllegalStateException("aerial_sites_unavailable: terrain column budget exhausted at " + candidate);
|
||||
var result = terrain.column(x, z);
|
||||
if (result == null) throw new IllegalStateException("Aerial terrain query returned no column");
|
||||
columns.put(key, result); return result;
|
||||
|
||||
+2
-1
@@ -50,6 +50,7 @@
|
||||
"sanctuary:woodland_dry_woodland",
|
||||
"sanctuary:woodland_oak_forest",
|
||||
"sanctuary:woodland_rocky_heath",
|
||||
"sanctuary:woodland_sulfur_depths"
|
||||
"sanctuary:woodland_sulfur_depths",
|
||||
"sanctuary:swamp27_caves"
|
||||
]
|
||||
}
|
||||
|
||||
+2
-1
@@ -66,5 +66,6 @@
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
+2
-1
@@ -66,5 +66,6 @@
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
+2
-1
@@ -66,5 +66,6 @@
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true,
|
||||
"surface_302": true
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
public final class BalancedRelief303Smoke {
|
||||
private static void require(boolean condition,String message){if(!condition)throw new AssertionError(message);}
|
||||
public static void main(String[] args){
|
||||
DensitySampler noise=new DensitySampler(){
|
||||
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return (float)(Math.sin(x*.05)+Math.cos(z*.04));}
|
||||
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
|
||||
};
|
||||
for(int radius:new int[]{256,362,512}){
|
||||
var p=new BalancedRelief303(noise,radius,0);int flat=0,mountain=0,n=0;
|
||||
for(int z=-radius;z<=radius;z+=8)for(int x=-radius;x<=radius;x+=8){
|
||||
if(Math.hypot(x,z)>radius*.8)continue;
|
||||
double w=p.province(x,z);if(w==0)flat++;if(w>.5)mountain++;n++;
|
||||
if(Math.hypot(x,z)<=48)require(w==0,"Natural arrival remains untouched");
|
||||
}
|
||||
require(flat>n*.4 && mountain>n*.05,"Every size combines broad natural plateaus and mountains");
|
||||
}
|
||||
for(double w=0;w<=1;w+=.05){
|
||||
double previous=Double.NEGATIVE_INFINITY;
|
||||
for(int y=0;y<=384;y++){
|
||||
double now=BalancedRelief303.sourceY(y,w);
|
||||
require(now>previous,"Vertical mapping cannot fold rock into repeated layers");previous=now;
|
||||
if(y<=170||w==0)require(now==y,"Zero mountain influence and lower caves preserve original coordinates");
|
||||
}
|
||||
}
|
||||
require(BalancedRelief303.ceilingErosion(358)>=1,"Even fully dense rock fades before Y360");
|
||||
// New fragment cleanup also covers raised summits, outside the former302 height range.
|
||||
DensitySampler singleton=new DensitySampler(){
|
||||
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return x==96&&y==355&&z==100?1:-1;}
|
||||
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
|
||||
};
|
||||
require(new DetachedRock302(singleton,362,0,true).sampleValue(SamplerContext.EMPTY_UNCACHED,96,355,100)<0,"A detached summit block is removed");
|
||||
System.out.println("BalancedRelief303Smoke: scale balance, untouched arrival/depths, monotonic domains, ceiling and detached summit passed");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "0.1.0-alpha.30.2"
|
||||
version = "0.1.0-alpha.30.3"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user