Skip to content
mc-protocol
Esc
↑↓navigate↵open⌘Jpreview
On this page

World generation

Generate flat or seeded terrain with caves, ores, trees, and reusable structures.

ChunkHelper stores blocks; the world-generation helpers fill it. Block names resolve through the bound protocol version, and the implementation has no runtime dependencies.

import { helpers } from "mc-protocol";

const world = helpers.createNoiseWorld({ seed: "mc-protocol", radius: 3 });
const surfaceY = world.generator.heightAt(0, 0);
const spawnY = Math.max(surfaceY, world.generator.seaLevel) + 1;
await client.sendPacket("LevelChunkWithLight", world.getLevelChunkWithLight(0, 0));

The result is a normal ChunkHelper, so its block and packet methods still apply. Use helpers.forVersion(protocol) to bind generation and block resolution to another built-in version.

Flat worlds

createFlatWorld(layers, options?) lists layers from minY upward and fills every chunk within radius:

const world = helpers.createFlatWorld([
  { block: "bedrock", height: 1 },
  { block: "stone", height: 60 },
  { block: "dirt", height: 3 },
  { block: "grass_block", height: 1 },
]);

A block is a name (with or without minecraft:) or a global state ID. radius defaults to 3; minY, height, biomeId, and hasSkyLight are passed to ChunkHelper.

Noise worlds

const boulder = helpers.createBoulderStructure();
const ruin = helpers.createRuinStructure();
const cabin = helpers.createCabinStructure();

const world = helpers.createNoiseWorld({
  seed: "mc-protocol",
  radius: 3,
  seaLevel: 63,
  baseHeight: 70,
  amplitude: 32,
  terrain: { ridgeStrength: 0.25, warpAmplitude: 24 },
  caves: true,
  ores: true,
  trees: { density: 0.02 },
  structures: [
    {
      id: "boulders",
      structure: boulder,
      spacing: 24,
      separation: 8,
      chance: 0.7,
      offsetY: -1,
      maxSlope: 4,
      minDistanceFromOrigin: 24,
    },
    {
      id: "ruins",
      structure: ruin,
      spacing: 48,
      separation: 12,
      chance: 0.5,
      maxSlope: 3,
      foundation: "cobblestone",
      minDistanceFromOrigin: 24,
    },
    {
      id: "cabins",
      structure: cabin,
      spacing: 80,
      separation: 16,
      chance: 0.35,
      maxSlope: 3,
      foundation: "cobblestone",
      minDistanceFromOrigin: 24,
    },
  ],
});

seed accepts a number or string. The same seed and options produce the same result, independently of chunk generation order, including templates that cross chunk boundaries. All newer features are opt-in: omitting terrain, caves, ores, and structures preserves the existing seeded output.

Core options:

Option Default Meaning
radius 3 Chunks on each side of the origin; 3 creates 7 × 7 chunks
seaLevel 63 Highest water-filled Y
baseHeight 64 Average surface Y
amplitude 24 Maximum displacement from baseHeight before customization
scale 0.01 Horizontal terrain frequency
octaves 4 Terrain noise layers
surface, filler, stone grass, dirt, stone Dry top, subsurface, and deep material
fillerDepth 3 Subsurface thickness
bedrock "bedrock" Bottom block; null disables it
water "water" Water material; null disables it
sand "sand" Beach/submerged surface material; null disables it
beachHeight 1 Dry blocks above sea level that still count as beach
trees off { density, log?, leaves? }; omitted leaves are persistent oak. Custom names use vanilla defaults; pass a numeric state ID for custom persistent leaves
minY, height, biomeId, hasSkyLight ChunkHelper defaults World shape, one biome ID for the whole world, and skylight

Terrain shaping

terrain accepts:

  • ridgeStrength (default 0) blends ordinary and ridged noise from 0 to 1.
  • warpAmplitude (default 0) displaces input coordinates horizontally; warpScale defaults to 0.005.
  • terraceStep quantizes heights into steps of that many blocks; 0 disables terraces.
  • lacunarity (default 2) and persistence (default 0.5) control octave frequency and amplitude.
  • heightAt(x, z, height) can replace the computed height. Keep it pure; its result is rounded and clipped to world bounds.

generator.heightAt(x, z) returns the final terrain surface before caves are carved. This is also what surface placement and slope checks use.

Caves and ores

caves: true enables chambers and intersecting tunnel fields. Use an object to tune them:

Cave option Default Meaning
minY, maxY world minimum + 1, world top Inclusive carving bounds; bottom bedrock is always protected
scale 0.035 Horizontal frequency; smaller values make larger cave features
verticalScale 1.5 Vertical frequency multiplier; smaller values make taller chambers
threshold 0.55 Chamber threshold, from -1 to 1; higher values make fewer chambers; 1 disables chambers
tunnelWidth 0.09 Tunnel width in noise space, from 0 to 1; 0 disables tunnels
surfaceDepth 5 Preserve the top five terrain layers, including the surface; 0 allows entrances
lavaLevel world minimum + 10 Fill carved blocks at or below this Y; null leaves all carved blocks as air
lava "lava" Material below lavaLevel

For example, caves: { threshold: 1, tunnelWidth: 0.12, lavaLevel: null } creates tunnels without chambers or lava. Carving is independent of surface water; there is no aquifer simulation. With surfaceDepth: 0, underwater entrances can leave air beneath a water column. Set caves: false or ores: false to disable either feature explicitly.

ores: true enables coal, iron, gold, and diamond deposits. For custom deposits, pass an array:

const generator = helpers.createNoiseGenerator({
  ores: [{
    id: "granite-pockets",
    block: "granite",
    minY: -32,
    maxY: 48,
    scale: 0.1,
    threshold: 0.7,
    replace: ["stone", "deepslate"],
  }],
});

Each ore needs a unique stable id. minY/maxY default to world bounds, scale to 0.12, and threshold to 0.65 (higher means rarer/smaller deposits). replace filters original terrain states and defaults to the configured stone. Bedrock at the world bottom is protected and carved blocks are never refilled. If multiple deposits match one block, lexical-last id wins. The default profile uses thresholds of 0.65/0.7/0.75/0.8 for coal/iron/gold/diamond, with upper limits of world top/64/32/0, clipped to world bounds.

generator.columnAt(x, z) returns terrain after caves and ores, but excludes trees and structures. When caves or structure rules are enabled, calling generateInto with a noise generator replaces the complete target chunk, including clearing old blocks where the new result is air. Without those features, noise generators preserve legacy nonzero-only writes. Plain column functions and flat generators also only write their nonzero entries; they do not clear existing blocks.

Structure templates

Templates are immutable coordinate/block lists. Build one directly with createStructure(blocks) or use every shape method on StructureBuilder:

const tower = new helpers.StructureBuilder()
  .set(0, 0, 0, "stone_bricks")
  .fill(-2, 0, -2, 2, 0, 2, "stone_bricks")
  .hollowBox(-2, 1, -2, 2, 5, 2, "cobblestone")
  .sphere(0, 6, 0, 2, "mossy_cobblestone")
  .cylinder(0, 1, 0, 1, 5, "air")
  .line(-2, 1, -2, 2, 5, 2, "oak_log")
  .add(helpers.createBoulderStructure({ radius: 1 }), 4, 0, 0)
  .build();

The presets produce block templates, not entities or loot containers:

Factory Dimension options (defaults) Palette options (defaults)
createTreeStructure trunkHeight: 5, canopyRadius: 2 log: "oak_log", leaves: "oak_leaves" (persistent when resolved)
createBoulderStructure radius: 2 block: "cobblestone"
createRuinStructure width: 5, height: 4, depth: 5 wall: "mossy_stone_bricks", floor: "stone_bricks"
createWellStructure radius: 2, height: 3 (roof Y, floor at 0, water at 1) stone: "cobblestone", water: "water"
createCabinStructure width: 7, height: 4 (roof Y), depth: 5 wall: "oak_planks", floor: "oak_planks", roof: "cobblestone", air: "air"

All dimensions and shape coordinates are integers. Box bounds are inclusive. hollowBox writes the shell without clearing its interior; write explicit air to carve an existing world. Duplicate positions use the last block. add(template, x, y, z) translates a template before composition. In structure templates, named leaves resolve with persistent: true, including custom tree species; supply a numeric state ID to control persistence yourself. This differs from the legacy trees option described above.

For manual placement, placeStructure(world, structure, x, y, z, options?) clips blocks outside world Y bounds. chunkX and chunkZ together restrict writes to one chunk; replace: "air" only fills targets with state ID 0 (default "all" overwrites). transformStructure and placement options support rotation: 0 | 90 | 180 | 270, mirrorX, and mirrorZ. Mirrors apply before rotation, around local (0,0,0). These transform coordinates only—they do not change directional block-state properties. Resolve stairs, doors, and other directional states yourself when rotating custom templates.

helpers.placeStructure(world, helpers.createWellStructure(), -2, 80, 15, {
  rotation: 90,
  chunkX: -1,
  chunkZ: 0,
});

createStructureHelper(version) binds placement and preset factories to a ProtocolVersion; it also exposes resolveStructure(template) to compile names into numeric states. helpers.forVersion(protocol) includes the same bound methods plus the shared builder, transforms, and noise helpers.

Automatic placement

Each structures entry accepts:

Option Default Meaning
id, structure required Unique stable placement ID and template
spacing 64 Candidate-cell width in blocks, not chunks
separation 8 (capped) Blocks excluded from each cell’s high edge
chance 1 Seeded chance that a cell receives a start
salt 0 Extra number/string seed component
rotate true Pick a seeded quarter-turn coordinate rotation
y surface Fixed origin Y, useful underground
offsetY 0 Offset after fixed/surface anchoring
minY, maxY world bounds Inclusive limits on final origin Y
maxSlope 3 Maximum footprint terrain-height difference
allowUnderwater false Permit submerged surface footprints
foundation off Fill under occupied bottom-layer blocks to terrain
minDistanceFromOrigin 0 Minimum footprint distance from (0,0) in blocks

Without fixed y, the template’s minimum local Y is placed one block above the highest terrain column in its entire footprint. A negative offsetY can embed rocks into the ground. With fixed y, surface slope, underwater, and foundation behavior do not apply. Automatic placements are rejected if any template block exceeds world Y bounds; manual placement clips instead. structuresAt(chunkX, chunkZ) takes chunk coordinates and returns starts whose transformed templates intersect that chunk, including starts anchored elsewhere; the returned y is already fixed.

Placement is deterministic across chunk boundaries. Rules are sorted by id, and when structures overlap, lexical-later IDs write last. separation does not account for template size, so large templates can overlap one another even in neighboring cells.

Per-chunk generation

const generator = helpers.createNoiseGenerator({ seed: "mc-protocol" });
const world = new helpers.ChunkHelper();
helpers.generateInto(world, chunkX, chunkZ, generator);

generator.heightAt(x, z);              // world block coordinates
generator.columnAt(x, z);              // world block coordinates
generator.structuresAt(chunkX, chunkZ); // chunk coordinates

generateInto also accepts (x, z) => number[], with state IDs from minY upward and 0 as air. createFlatGenerator(layers) creates such a generator. Generate a chunk’s eight neighbors before requesting its lit packet so edge skylight is correct.

For on-demand generation without blocking the server thread, createNoiseChunkPool distributes built-in noise generation across workers, creates the lighting margin automatically, deduplicates concurrent requests, and caches packet-ready results.

Noise helpers

createNoise2D and createNoise3D return seeded continuous noise in [-1, 1]. fbm and fbm3D combine octaves with octaves, lacunarity, and persistence. hashSeed, hashPosition, and createRandom provide deterministic seed and placement primitives.

The terrain system was designed with projects such as Terra and Iris as inspiration; no code was copied from them.

Scope and limitations

This is a compact custom-world toolkit, not vanilla world generation. A world has one configured biome ID. It does not provide vanilla terrain parity, biome distribution, aquifers, structure loot, or mob spawning. The application remains responsible for game logic and persistence.

Generation cost grows roughly with the number of chunks: radius 3 is 49 chunks, radius 5 is 121, and radius 8 is 289. Generate reusable worlds once or generate chunks on demand.

Was this page helpful?