How do I improve my procedural generation script?

I am making a top-down 2D game, and am generating “flat” worlds with many different biomes. I’ve got the script finished, it works, but the results look really unnatural.

Some examples of what I mean

This is my first time working with procedural generation, so I’m not sure if I’m applying the most efficient methods for everything.

What can I do to improve the overall look of how it generates?
Here’s my code:

local biomeInfo = require(script.BiomeInfo)

local heightSeed = math.random(-2147483640, 2147483640)
local moistureSeed = math.random(-2147483640, 2147483640)
local heatSeed = math.random(-2147483640, 2147483640)

local mapSize = 250
local resolution = 20
local frequency = 15
local amplitude = 95

-- Generate noise maps
local grid = {}
for x = 1, mapSize do
	grid[x] = {}

	for z = 1, mapSize do
		local heightMap = math.noise(x / resolution * frequency / amplitude, z / resolution * frequency / amplitude, heightSeed)
		local moistureMap = math.noise(x / resolution * frequency / amplitude, z / resolution * frequency / amplitude, moistureSeed)
		local heatMap = math.noise(x / resolution * frequency / amplitude, z / resolution * frequency / amplitude, heatSeed)

		--[[local heightMap = math.noise(x / resolution * 1.2 / 3.2, z / resolution * 1.2 / 3.2, heightSeed)
		local moistureMap = math.noise(x / resolution * 4 / 5.6, z / resolution * 4 / 5.6, moistureSeed)
		local heatMap = math.noise(x / resolution * 2.7 / 7, z / resolution * 2.7 / 7, heatSeed)]]

		grid[x][z] = {
			["Height"] = math.clamp(heightMap, -0.5, 0.5) + 0.5,
			["Moisture"] = math.clamp(moistureMap, -0.5, 0.5) + 0.5,
			["Heat"] = math.clamp(heatMap, -0.5, 0.5) + 0.5,
		}
	end
end

-- Generate blocks
for x = 1, mapSize do
	for z = 1, mapSize do
		
		-- Get all available biomes for this block
		local availableBiomes = {}
		for name, value in pairs(biomeInfo) do
			local condition = grid[x][z]["Height"] >= biomeInfo[name]["Height"] and grid[x][z]["Moisture"] >= biomeInfo[name]["Moisture"] and grid[x][z]["Heat"] >= biomeInfo[name]["Heat"]
			if condition and not availableBiomes[name] then
				table.insert(availableBiomes, name)
			end
		end
		
		-- Calculate value differences for all available biomes for this block
		local valueDiffs = {}
		for _, biome in pairs(availableBiomes) do
			valueDiffs[biome] = (grid[x][z]["Height"] - biomeInfo[biome]["Height"]) + (grid[x][z]["Moisture"] - biomeInfo[biome]["Moisture"]) + (grid[x][z]["Heat"] - biomeInfo[biome]["Heat"])
		end
		
		-- Get the lowest value difference, assign the corresponding biome
		local lowestValue = 1
		local selectedBiome
		for biome, value in pairs(valueDiffs) do
			if value < lowestValue then
				lowestValue = value
				selectedBiome = biome
			end
		end
		
		-- Generate the block
		local block = Instance.new("Part")
		block.Anchored = true
		block.Size = Vector3.new(8, 8, 8)
		block.Position = Vector3.new(x * 8, 0, z * 8)
		block.Parent = game.Workspace.World
		if grid[x][z]["Height"] <= 0.2 then -- Water level
			block.BrickColor = BrickColor.new("Electric blue")
		elseif selectedBiome == "Grasslands" then
			block.BrickColor = BrickColor.new("Bright green")
		elseif selectedBiome == "Taiga" then
			block.BrickColor = BrickColor.new("Baby blue")
		elseif selectedBiome == "Desert" then
			block.BrickColor = BrickColor.new("Cool yellow")
		elseif selectedBiome == "Swamp" then
			block.BrickColor = BrickColor.new("Slime green")
		elseif selectedBiome == "Mountains" then
			block.BrickColor = BrickColor.new("Dark stone grey")
		elseif selectedBiome == "Jungle" then
			block.BrickColor = BrickColor.new("Earth green")
		elseif selectedBiome == "Snowy tundra" then
			block.BrickColor = BrickColor.new("White")
		else
			block.BrickColor = BrickColor.new("Bright green")
		end

		block.Position = Vector3.new(x * 8, 4.5, z * 8)
		block.Parent = game.Workspace.World
	end
	game:GetService("RunService").Heartbeat:Wait()
end

Note: I’m aware that I can replace the many elseif statements, checking for the selected biome, to make it more efficient, please ignore that.

The “BiomeInfo” module script:

return {
	["Grasslands"] = {
		["Height"] = 0.27,
		["Moisture"] = 0.21,
		["Heat"] = 0.25
	},
	["Desert"] = {
		["Height"] = 0.15,
		["Moisture"] = 0.05,
		["Heat"] = 0.49
	},
	["Taiga"] = {
		["Height"] = 0.28,
		["Moisture"] = 0.16,
		["Heat"] = 0.12
	},
	["Swamp"] = {
		["Height"] = 0.16,
		["Moisture"] = 0.44,
		["Heat"] = 0.14
	},
	["Mountains"] = {
		["Height"] = 0.43,
		["Moisture"] = 0.08,
		["Heat"] = 0.11
	},
	["Jungle"] = {
		["Height"] = 0.22,
		["Moisture"] = 0.51,
		["Heat"] = 0.31
	},
	["Snowy tundra"] = {
		["Height"] = 0.17,
		["Moisture"] = 0.12,
		["Heat"] = 0.03
	},
}

Ah shoot, I realised this might’ve been better off in #help-and-feedback:code-review, excuse me.