Add seed validation for server world creation and override-seed property

The dedicated server previously picked a completely random seed with no
biome diversity checks (the client validates but the server skipped it).
On top of that, the client's findSeed() was hardcoded to only check a
54-chunk (Classic) area, so Large worlds had no diversity guarantee
beyond the center.

New server worlds now use findSeed() scaled to the full target world
size. Added override-seed in server.properties to fix existing worlds
without deleting them.
This commit is contained in:
itsRevela 2026-03-24 13:07:04 -05:00
parent 5dad6c24f7
commit 08f14e32ae
10 changed files with 66 additions and 9 deletions

View file

@ -206,6 +206,8 @@ CMinecraftApp::CMinecraftApp()
m_dwRequiredTexturePackID=0;
m_bResetNether=false;
m_seedOverride = 0;
m_hasSeedOverride = false;
#ifdef _XBOX
// m_bTransferSavesToXboxOne=false;

View file

@ -706,6 +706,8 @@ private:
bool m_bGameNewWorldSizeUseMoat;
unsigned int m_GameNewHellScale;
#endif
int64_t m_seedOverride;
bool m_hasSeedOverride;
unsigned int FromBigEndian(unsigned int uiValue);
public:
@ -723,6 +725,10 @@ public:
void SetGameNewHellScale(unsigned int newScale) { m_GameNewHellScale = newScale; }
unsigned int GetGameNewHellScale() { return m_GameNewHellScale; }
#endif
void SetSeedOverride(int64_t seed) { m_seedOverride = seed; m_hasSeedOverride = true; }
bool HasSeedOverride() { return m_hasSeedOverride; }
int64_t GetSeedOverride() { return m_seedOverride; }
void SetResetNether(bool bResetNether) {m_bResetNether=bResetNether;}
bool GetResetNether() {return m_bResetNether;}
bool CanRecordStatsAndAchievements();

View file

@ -735,10 +735,11 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
if( findSeed )
{
int worldSizeChunks = (initData && initData->xzSize > 0) ? (int)initData->xzSize : 54;
#ifdef __PSVITA__
seed = BiomeSource::findSeed(pLevelType, &running);
seed = BiomeSource::findSeed(pLevelType, &running, worldSizeChunks);
#else
seed = BiomeSource::findSeed(pLevelType);
seed = BiomeSource::findSeed(pLevelType, worldSizeChunks);
#endif
}

View file

@ -824,6 +824,20 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
config.maxPlayers = ReadNormalizedIntProperty(&merged, "max-players", kDefaultMaxPlayers, 1, kMaxDedicatedPlayers, &shouldWrite);
config.seed = 0;
config.hasSeed = ReadNormalizedOptionalInt64Property(&merged, "level-seed", &config.seed, &shouldWrite);
config.overrideSeed = 0;
config.hasOverrideSeed = false;
{
auto it = merged.find("override-seed");
if (it != merged.end() && !TrimAscii(it->second).empty())
{
__int64 parsed = 0;
if (TryParseInt64(TrimAscii(it->second), &parsed))
{
config.overrideSeed = parsed;
config.hasOverrideSeed = true;
}
}
}
config.logLevel = ReadNormalizedLogLevelProperty(&merged, "log-level", eServerLogLevel_Info, &shouldWrite);
config.autosaveIntervalSeconds = ReadNormalizedIntProperty(&merged, "autosave-interval", kDefaultAutosaveIntervalSeconds, 5, 3600, &shouldWrite);

View file

@ -31,6 +31,9 @@ namespace ServerRuntime
bool hasSeed;
/** `level-seed` */
__int64 seed;
/** `override-seed` replaces the seed for biome generation on existing worlds */
bool hasOverrideSeed;
__int64 overrideSeed;
/** `log-level` */
EServerLogLevel logLevel;
/** `autosave-interval` (seconds) */

View file

@ -28,6 +28,8 @@
#include "../../Minecraft.World/TilePos.h"
#include "../../Minecraft.World/compression.h"
#include "../../Minecraft.World/OldChunkStorage.h"
#include "../../Minecraft.World/BiomeSource.h"
#include "../../Minecraft.World/LevelType.h"
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
#include "../../Minecraft.World/Random.h"
@ -545,6 +547,12 @@ int main(int argc, char **argv)
app.SetGameNewHellScale(serverProperties.worldHellScale);
#endif
if (serverProperties.hasOverrideSeed)
{
LogInfof("startup", "Seed override active: %lld", serverProperties.overrideSeed);
app.SetSeedOverride(serverProperties.overrideSeed);
}
StorageManager.SetSaveDisabled(serverProperties.disableSaving);
// Read world name and fixed save-id from server.properties
// Delegate load-vs-create decision to WorldManager
@ -584,9 +592,17 @@ int main(int argc, char **argv)
{
param->seed = config.seed;
}
else if (worldBootstrap.saveData == nullptr)
{
// Only run seed validation when creating a brand-new world.
// Existing worlds already have their seed in level.dat.
LogInfof("startup", "Finding seed with biome diversity for %d-chunk world...", config.worldSizeChunks);
param->seed = BiomeSource::findSeed(LevelType::lvl_normal, config.worldSizeChunks);
LogInfof("startup", "Selected seed: %lld", param->seed);
}
else
{
param->seed = (new Random())->nextLong();
param->seed = (new Random())->nextLong(); // placeholder; level.dat seed takes priority
}
#ifdef _LARGE_WORLDS
param->xzSize = (unsigned int)config.worldSizeChunks;

View file

@ -411,9 +411,9 @@ void BiomeSource::update()
// 4J added - find a seed for this biomesource that matches certain criteria
#ifdef __PSVITA__
int64_t BiomeSource::findSeed(LevelType *generator, bool* pServerRunning) // MGH - added pRunning, so we can early out of this on Vita as it can take up to 60 secs
int64_t BiomeSource::findSeed(LevelType *generator, bool* pServerRunning, int worldSizeChunks)
#else
int64_t BiomeSource::findSeed(LevelType *generator)
int64_t BiomeSource::findSeed(LevelType *generator, int worldSizeChunks)
#endif
{
@ -440,8 +440,8 @@ int64_t BiomeSource::findSeed(LevelType *generator)
// Raw biome data has one result per 4x4 group of tiles.
// Removing a border of 8 from each side since we'll be doing special things at the edge to turn our world into an island, and so don't want to count things
// in the edge region in case they later get removed
static const int biomeWidth = ( 54 * 4 ) - 16; // Should be even so we can offset evenly
static const int biomeOffset = -( biomeWidth / 2 );
const int biomeWidth = ( worldSizeChunks * 4 ) - 16; // Should be even so we can offset evenly
const int biomeOffset = -( biomeWidth / 2 );
// Storage for our biome indices
intArray indices = intArray( biomeWidth * biomeWidth );

View file

@ -36,9 +36,9 @@ private:
static void getFracs(intArray indices, float *fracs); // 4J added
public:
#ifdef __PSVITA__
static int64_t findSeed(LevelType *generator, bool* pServerRunning); // MGH - added pRunning, so we can early out of this on Vita as it can take up to 60 secs // 4J added
static int64_t findSeed(LevelType *generator, bool* pServerRunning, int worldSizeChunks = 54);
#else
static int64_t findSeed(LevelType *generator); // 4J added
static int64_t findSeed(LevelType *generator, int worldSizeChunks = 54);
#endif
~BiomeSource();

View file

@ -13,6 +13,15 @@ LevelData::LevelData()
LevelData::LevelData(CompoundTag *tag)
{
seed = tag->getLong(L"RandomSeed");
// Allow server.properties to override the world seed for biome generation
// on existing worlds (e.g. to fix monotonous biomes after world expansion).
if (app.HasSeedOverride())
{
app.DebugPrintf("Overriding world seed: %lld -> %lld\n", seed, app.GetSeedOverride());
seed = app.GetSeedOverride();
}
m_pGenerator = LevelType::lvl_normal;
if (tag->contains(L"generatorName"))
{

View file

@ -14,6 +14,12 @@ This project is based on source code of Minecraft Legacy Console Edition v1.6.05
## Latest:
Dedicated server biome diversity fix:
- The dedicated server previously used a completely random seed with no biome diversity checks — unlike the client which validates seeds to guarantee varied biomes. This could result in server worlds with large regions dominated by only one or two biome types (e.g. all taiga/snowy)
- On top of that, the client's seed validation was hardcoded to only check a 54-chunk (Classic) area, so even validated seeds had no diversity guarantee beyond that — making the problem especially noticeable on Large worlds or worlds expanded from Classic to Large
- New server worlds now validate seeds for biome diversity, and the validation scales to the full target world size
- Added `override-seed` in server.properties to fix existing worlds without deleting them — set it to any seed number and newly generated chunks will use it instead of the original
Server list and connection improvements:
- Server edits and deletions now apply immediately without needing to restart the game
- Connecting to an offline/unreachable server no longer freezes the game indefinitely