OpenBaseBack to the huntSeedScript reference

Writing a hunt

Your code runs once per seed inside a sandboxed Web Worker with the map's own engine behind it. The worker hands it one argument, the seed object, and takes back one verdict: a truthy return is a hit and lands on the results list; anything else moves the walk on.

the two shapes your code can take

A plain body: return truthy on a hit.

return seed.biomeAt(0, 0) === 1;
// plains at the origin

Or declare a check function; it is detected and called.

function check(seed) {
  return seed.heightAt(0, 0) > 100;
}

The seed object

The table below is the whole API. The values it answers are the SAME values the map, the finder and /api/check answer: same resolution, same heights, same climate decode, one engine.

The seed object

Your code runs once per seed with one argument: the seed object. It is the SAME mutable object on every call (no per-seed allocation), so read it inside the call. value is a Number when the seed fits safely and a BigInt otherwise; big and u64 always carry the exact 64-bit value.

seed.value

returns number | bigint

The world seed, signed 64-bit reading. Number when it fits safely (|v| <= 2^53 - 1), BigInt otherwise. Cheap arithmetic pre-filters belong here: an early return on seed.value costs zero engine time.
if (seed.value % 7 !== 0) return false; // free pre-filter
seed.big

returns bigint

The exact signed 64-bit seed, always a BigInt, never a float.
seed.u64

returns bigint

The unsigned 64-bit view (0 .. 2^64 - 1).
seed.hex

returns string

The unsigned hex reading, "0x" prefixed.
seed.dim()

returns 0 | 1 | 2

The run's active dimension: 0 overworld, 1 nether, 2 end. Set on the hunt panel before the run; biome and terrain answers follow it, and structures() answers an empty list for types alien to it. Free - no engine call, so it costs nothing even inside a pre-filter.
if (seed.dim() !== 0) return false; // overworld-only hunt

Biomes

Block coordinates, the same resolution the map paints. Passing a y samples the REAL 3D biome at that height (the only place the cave biomes answer); without it you get the depth-0 surface voice.

seed.biomeAt(x, z)

returns number

The surface biome id at block (x, z). The same number the map paints and /api/check reports.
return seed.biomeAt(0, 0) === 132; // Flower Forest at spawn-ish
seed.biomeAt(x, z, y)

returns number

The 3D biome at block height y. This is where Dripstone Caves (174), Lush Caves (175), Deep Dark (183) and Sulfur Caves (187) live; the surface voice never answers for them.
return seed.biomeAt(0, 0, 0) === 175; // Lush Caves at y 0
seed.biomeName(id?)

returns string

The biome's display name for an id, or for the biome at (0, 0) when omitted. Ids never collide across dimensions, so one table serves all three.
const name = seed.biomeName(seed.biomeAt(sp.x, sp.z));
seed.biomeId(name)

returns number | null

The reverse lookup: 'lush_caves' -> 175, 'Cherry Grove' -> 185. Spaces, hyphens or underscores, case-insensitive - the same normalization the structure slugs use. null when the name is not in the table; it never guesses. Free - no engine call.
const LUSH = seed.biomeId('lush caves'); // 175, no magic number in your gate
seed.biomeList()

returns array

The engine's known-biome table, [{ id, name }] sorted by id - the same 66-entry catalog the biome table further down this page prints. Frozen (the array and every entry) and cached, so it costs nothing per seed and is safe to stash.
const OCEAN = seed.biomeList()
  .filter((b) => b.name.includes('Ocean'))
  .map((b) => b.id);
seed.biomeFind(target, x, z, radius)

returns object | null

The NEAREST cell of any target biome within radius blocks of (x, z), or null. The fast way to ask 'cherry grove near spawn?': the map's own batched fill walks the biome lattice in RINGS around your point, chord-clipped to the circle, and stops the moment an exact rule proves nothing closer exists - a near hit answers in a handful of rings, an absent one pays the full disc. Measured: ~1.9us per lattice cell, ~1.8x cheaper per cell than a biomeAt loop, and on real hunts (spawn + search, 40 random seeds) 1.5x a hand-written smart ring walk, 2.7x a naive full-grid walk at radius 2048, 5.6x at radius 512. target is one id, one name, or an array mixing both (any-of); the answer is { biome, name, x, z, dist, cells } - x and z the hit cell's center block (the map's own sample point, so biomeAt(x, z) confirms the hit), dist whole blocks to your query point, cells the honest count of lattice cells the engine computed (early exit means it is far below the full disc on near hits). Ties break to the lowest x then z, so the answer is deterministic. Radius is 0..4096 blocks (a full 4096 disc costs ~1.5s overworld; the End's per-block lattice costs ~16x that). Runs in the active dimension, surface voice only - the cave biomes answer through caveAt.
// nearest Cherry Grove within 2 km of spawn, with its distance
const sp = seed.spawn();
const hit = sp && seed.biomeFind('cherry grove', sp.x, sp.z, 2048);
return hit && { biome: hit.name, dist: hit.dist, cells: hit.cells };

Terrain

The engine's own ground answers, per active dimension: fastHeight overworld, netherSurfaceY nether, endGroundY end (the End answers 0 for open void, the engine's own convention). The column profile goes one step deeper: the solid runs under a block, the floating-terrain detector the floating-soil hunt is built on. Overworld only; null elsewhere.

seed.heightAt(x, z)

returns number

Terrain height under block (x, z) in the run's dimension. The same number the map popup shows. Overworld surfaces answer per QUART (4-block cell), so the worker memoizes them per seed: a repeat probe inside an already-answered quart is a Map read instead of a fresh ~41us binary search, and seed.spawn() pre-answers the spawn's own quart - which means sp.y and heightAt(sp.x, sp.z) are the same number; read sp.y and it costs nothing. The nether and End answer per block column (no memo there).
return seed.heightAt(0, 0) > 120; // a high spawn point
seed.columnAt(x, z, floor?)

returns object | null

The column's solid-interval profile: { surface, intervals, grounded }. intervals is the list of solid runs under (x, z), TOP run first, each [bottom, top] inclusive; interval 0's top is the surface. An empty list is the classic solid column - anything else means AIR below the top run: a floating slab (one run, nothing under it) or a slab over a gap. grounded tells whether the lowest run reaches the scan floor. floor sets that scan floor (default 0, range -64..255). The same record the 3D flight view meshes, on the engine's raw terrain density - caves do not answer here, only real hollow ground. Overworld only; null elsewhere. Costs more than heightAt, so gate it behind the cheap checks.
const col = seed.columnAt(sp.x, sp.z);
return col && !col.grounded; // soil flying: no run reaches the floor

Area scans

One seed, many probes: the questions a single block cannot answer (how much of this kilometer is ocean, is the spawn plateau flat, how many slime chunks sit nearby). A biome census is one batched engine call - the map's own tile fill - so its probes are the cheapest in the API (measured ~1.9-2.6us per lattice cell). Height probes run the per-point surface search (~41us per cold call, ~10x a biomeAt call), so their grids cap tighter - though the engine batches the overworld ones into a single fill and memoizes repeats, and the pool of workers runs up to 6 shards. All three scans take probe counts, not block sizes: w x h samples spaced step blocks apart from (x, z). The nearest-biome question does not need a census at all - biomeFind walks the rings for you and stops early. ORDER YOUR GATES CHEAP-FIRST: a ~30us biome census that rejects 95% of seeds before a ~0.9ms height grid is the difference between 347 and 468 seeds per second per thread on the same hunt (measured - same hits, same walk).

seed.biomeArea(x, z, w, h, step?)

returns object

A w x h biome census, probes `step` blocks apart (default 16), from (x, z): { x, z, w, h, step, ids, counts, unique, dominant }. ids is the flat grid, row-major (z outer, x inner); counts maps each biome id to its sample count; unique is the distinct count; dominant is the most-probed id. Probes sit on the dimension's native biome lattice - 4-block quarts in the overworld and nether, single blocks in the End - so a sub-lattice step rounds up to whole cells and the returned step is the EFFECTIVE spacing, never a lie. Sides cap at 512 (262,144 probes, the map's own tile budget); measured ~2us per probe - a 33 x 33 census costs ~2.4ms per seed, the island hunt's sparser 17 x 17 one ~0.65ms.
// is the kilometer around spawn at least 90% ocean?
const ring = seed.biomeArea(sp.x - 512, sp.z - 512, 17, 17, 64);
const OCEAN = new Set([0, 10, 24, 44, 45, 46, 48, 49, 50]);
let sea = 0;
for (const id in ring.counts)
  if (OCEAN.has(+id)) sea += ring.counts[id];
return sea / ring.ids.length >= 0.9;
seed.heightArea(x, z, w, h, step?)

returns object

A w x h terrain summary, heightAt probes `step` blocks apart (default 16), from (x, z): { min, max, avg, minAt, maxAt } in the active dimension. avg carries one decimal; minAt and maxAt name the block coordinates of the extremes. Sides cap at 64 x 64 - measured ~34-43us per probe (a 13 x 13 grid costs ~5-6ms per seed, a 32 x 32 one ~35ms), so keep grids small and gate them behind cheap biome checks. Overworld grids whose step is a multiple of 4 run as ONE batched fill (the map's own height tile, bit-exact with the per-probe loop) and seed the height memo - a follow-up heightAt inside the grid answers for free. Odd steps and the nether/end keep the per-probe loop. The flatness and prominence questions live here.
// a flat spawn plateau: nothing sticks out more than 8 blocks
const h = seed.heightArea(-96, -96, 13, 13, 16);
return h.max - h.min <= 8 && { flat: h.max - h.min, avg: h.avg };
seed.slimeArea(x, z, w, h, step?)

returns object

A w x h slime census, probes `step` blocks apart (default 16), deduped to 16-block chunks - probes inside one chunk are the same slime answer, so the census counts chunks, not probes: { x, z, w, h, step, slime, spots }, with spots listing each slime chunk's block origin. Sides cap at 64 x 64; measured ~4us per probe. An overworld organ: other dimensions answer 0 (the structures() rule, not a throw).
// slime farm country around spawn: 44+ chunks in the box
const g = seed.slimeArea(sp.x - 128, sp.z - 128, 17, 17, 16);
return g.slime >= 44 && { slime: g.slime };

Caves & surfaces

Below the surface voice. caveAt walks the column per block and reports the CAVE-BIOME bands (the F3 readout's own scan - the only place those bands answer as runs); surfacesAt answers the checked surface trio the /api/check page prints. Both are overworld organs; null elsewhere.

seed.caveAt(x, z, top?)

returns array | null

The column's cave-biome bands: up to 64 { biome, yLow, yHigh } runs, HIGHEST first, scanned per block from top (default 60, the F3 readout's own top) down to -64. Only the four 3D cave biomes ever answer - Dripstone Caves 174, Lush Caves 175, Deep Dark 183, Sulfur Caves 187 - so an empty array means the column has no cave-biome band at all. For a single point's 3D biome use biomeAt(x, z, y) instead. Measured ~0.1ms per column (the whole scan). Overworld only; null elsewhere.
// a lush band close enough to dig into: within 20 blocks
// of the grass
const surf = seed.heightAt(sp.x, sp.z);
const runs = seed.caveAt(sp.x, sp.z, 64);
return runs.some((r) => r.biome === 175 && surf - r.yHigh <= 20);
seed.surfacesAt(x, z)

returns object | null

The checked surface trio at one block: { world, oceanFloor, preliminary }. world is the worldSurface heightAt answers; oceanFloor is the game's own CHECKED topmost solid (the real seabed under the water - the difference between the two is the water depth); preliminary is the k_l preliminary surface level. The checked scan is the most expensive single-block call in the API - measured ~0.8ms, ~20x a cold heightAt - so gate it behind the cheap checks. Overworld only; null elsewhere.
// a shallow reef: water no deeper than 12 blocks
const s = seed.surfacesAt(x, z);
return s && s.world - s.oceanFloor <= 12;

Structures

The full structure catalog, by slug or engine id. Every type the map draws is here, including the two computed highlights: enchanted_apple (the desert temples whose hidden chest rolled the apple) and zombie_village (the 2% zombie roll). Types are per-dimension, set the run's dimension accordingly; a type alien to the dimension just answers an empty list.

seed.structures(type, x, z, radius)

returns array

All structures of the type within radius BLOCKS of block (x, z), each { x, z, chunkX, chunkZ, y }. y is the engine's own placement height where it computed one (trial chamber, ruined portal, camp, geode, well, gateway), else null. Radius caps at 8192.
const v = seed.structures('village', 0, 0, 512);
return v.length >= 2 && Math.hypot(v[0].x - v[1].x, v[0].z - v[1].z) <= 180;
seed.nearest(type, x, z, radius)

returns object | null

Sugar over structures(): the CLOSEST structure of the type within radius blocks of (x, z), or null. The same hit shape { x, z, chunkX, chunkZ, y }, reduced by true squared distance - the loop every 'nearest one' hunt used to hand-roll.
const v = seed.nearest('village', 0, 0, 2048);
return v && { gap: Math.round(Math.hypot(v.x, v.z)) };

Spawn

The engine's own spawn point hunt, overworld only (the engine's rule, not ours).

seed.spawn()

returns object | null

The spawn point { x, y, z } the map's S marker draws, or null outside the overworld. y is the surface heightAt(sp.x, sp.z) would answer (the same quart's binary search), so gate on sp.y directly - the redundant probe answers for free, but the read is cleaner anyway. THE cost anchor of every spawn-centered hunt: the engine's own spiral (coarse rings to 2048, then a fine pass to 512, ~917 climate samples) runs ~1.9ms per seed inside the pinned wasm - measured, irreducible, and paid before any other organ. Stage everything else cheap-first after it.
const sp = seed.spawn();
return sp && seed.biomeAt(sp.x, sp.z) === 132; // Flower Forest spawn

Slime chunks

One chunk-level lookup, the same answer the map's SC badge gives.

seed.slimeChunk(x, z)

returns boolean

Whether the chunk containing block (x, z) is a slime chunk.
let n = 0;
for (let i = 0; i < 8; i++)
  if (seed.slimeChunk(i * 16, 0)) n++;
return n >= 3;

Climate & noise

The climate channels the map's climate layer paints, decoded through the SAME table ranges (ob_para_extremes), so the numbers match what the map legend prints. 2D channels (temp, humid, cont, ero, weird, pv, shiftx, shiftz, jagged) answer at the surface; the 3D cave-router noises need a y. Overworld only, the engine's climate tables are an overworld organ. Values carry the fill's 255-step quantization (span/255 raw units), the honest precision, stated here like the finder states it.

seed.climate(x, z)

returns object

All six climate channels at block (x, z): { temp, humid, cont, ero, weird, pv }.
return seed.climate(0, 0).cont < -0.3; // the deep-ocean side of the continent
seed.climate(x, z, channel)

returns number

One 2D channel's value. channel is a name or id 0..8.
seed.noiseAt(x, y, z, channel)

returns number

The 3D cave-router noises (ids 9..24: cavelayer, cavecheese, caveent, the spaghetti and pillar noises), sampled at block height y.
seed.climateRange(channel)

returns [number, number]

The channel's measured [min, max], for writing honest bands. The six climate ranges are static; the cave-router ranges are measured per y.
const [lo, hi] = seed.climateRange('cont');
return seed.climate(0, 0).cont < lo + (hi - lo) * 0.25;

Sister seeds

The Bedrock structure seed is the world seed's low 48 bits: every structure seed corresponds to 65,536 sister world seeds (seed + k * 2^48) with IDENTICAL structure placement and completely reshuffled biomes. The finder's Sister seeds toggle walks these classes; in code you can both walk them (run mode: Sisters) and ask about them from a seed.

seed.structureSeed()

returns bigint

The seed & 0xFFFFFFFFFFFF - the part that decides structure placement.
seed.sister(k)

returns bigint

The k-th sister world seed of this seed's class (k 0..65535). Same structures, different biomes.
seed.sisters(count)

returns bigint[]

The first count sisters as an array. Caps at 65,536.
seed.kIndex()

returns number

This seed's in-class index (the top 16 bits), 0..65535.
seed.isSisterOf(other)

returns boolean

Whether the other seed (bigint or parseable) shares this seed's low 48 bits.

The region grid

Every region structure (villages, outposts, mansions, temples...) places on a grid of spacing x spacing cells: each cell rolls ONE candidate chunk, and a gate chain decides whether the structure exists there. regionOf hands your code the map popup's own detective answer for any cell - the placement verdict, and the exact reason it failed when it did.

seed.regionOf(type, x, z)

returns object

The region cell containing block (x, z), for the type: { spacing, separation, region, candidate, gate, gateName, py, centerBiome, killX, killY, killZ, killBiome, roll, extra, placed }. region is the cell's grid id; candidate is the chunk the cell rolled; gateName reads the engine's own verdict - 'pass' (placed), 'surface', 'biome' (the kill fields name the killing block and biome), 'monument', 'roll' (the roll field is the dice), 'ground' (extra is the failing y), 'jigsaw'. placed is the placed hit ({ x, z, chunkX, chunkZ, y }) when the gate passed, else null. The type's own dimension must be the run's active dimension - an alien type throws, naming where to switch. Measured ~10us per call.
// does the region cell AT the origin hold a village?
const r = seed.regionOf('village', 0, 0);
return r.gateName === 'pass' && r.placed && { at: r.placed.x + ', ' + r.placed.z };

Structure slugs

The type argument of seed.structures: a slug (spaces or underscores, case-insensitive) or the engine id. Aliases like woodland_mansion or desert_pyramid land on the same types.

slugstructuredimension
'village'VillageOverworld
'outpost'Pillager OutpostOverworld
'monument'Ocean MonumentOverworld
'mansion'Woodland MansionOverworld
'treasure'Buried TreasureOverworld
'mineshaft'MineshaftOverworld
'stronghold'StrongholdOverworld
'desert_temple'Desert TempleOverworld
'jungle_temple'Jungle TempleOverworld
'witch_hut'Witch HutOverworld
'igloo'IglooOverworld
'shipwreck'ShipwreckOverworld
'ocean_ruin'Ocean RuinOverworld
'ancient_city'Ancient CityOverworld
'trial_chamber'Trial ChamberOverworld
'trail_ruins'Trail RuinsOverworld
'slime_chunk'Slime ChunkOverworld
'geode'Amethyst GeodeOverworld
'fossil'FossilOverworld
'desert_well'Desert WellOverworld
'ravine'RavineOverworld
'ruined_portal'Ruined Portal (Overworld)Overworld
'camp'Abandoned CampOverworld
'dungeon'DungeonOverworld
'fortress'Nether FortressNether
'bastion'Bastion RemnantNether
'ruined_portal_nether'Ruined Portal (Nether)Nether
'nether_fossil'Nether FossilNether
'end_city'End CityEnd
'end_gateway'End GatewayEnd
'enchanted_apple'Enchanted Apple (desert temples whose chest rolled the apple)Overworld
'zombie_village'Zombie Village (the 2% zombie roll)Overworld

Biome ids

The ids biomeAt answers and biomeId looks up: the engine's own catalog, 66 known biomes, ids never colliding across dimensions. The four cave biomes (174 Dripstone Caves, 175 Lush Caves, 183 Deep Dark, 187 Sulfur Caves) only answer with a y - through biomeAt(x, z, y) or caveAt's bands - never from the depth-0 surface voice.

idbiomewhere it answers
0OceanOverworld surface + 3D
1PlainsOverworld surface + 3D
2DesertOverworld surface + 3D
3Windswept HillsOverworld surface + 3D
4ForestOverworld surface + 3D
5TaigaOverworld surface + 3D
6SwampOverworld surface + 3D
7RiverOverworld surface + 3D
8Nether WastesNether (run dim 1)
9The Endthe End (run dim 2)
10Frozen OceanOverworld surface + 3D
11Frozen RiverOverworld surface + 3D
12Snowy PlainsOverworld surface + 3D
14Mushroom FieldsOverworld surface + 3D
16BeachOverworld surface + 3D
18Windswept ForestOverworld surface + 3D
21JungleOverworld surface + 3D
23Sparse JungleOverworld surface + 3D
24Deep OceanOverworld surface + 3D
25Stony ShoreOverworld surface + 3D
26Snowy BeachOverworld surface + 3D
27Birch ForestOverworld surface + 3D
29Dark ForestOverworld surface + 3D
30Snowy TaigaOverworld surface + 3D
32Old Growth Pine TaigaOverworld surface + 3D
35SavannaOverworld surface + 3D
36Savanna PlateauOverworld surface + 3D
37BadlandsOverworld surface + 3D
38Wooded BadlandsOverworld surface + 3D
40Small End Islandsthe End (run dim 2)
41End Midlandsthe End (run dim 2)
42End Highlandsthe End (run dim 2)
43End Barrensthe End (run dim 2)
44Warm OceanOverworld surface + 3D
45Lukewarm OceanOverworld surface + 3D
46Cold OceanOverworld surface + 3D
48Deep Lukewarm OceanOverworld surface + 3D
49Deep Cold OceanOverworld surface + 3D
50Deep Frozen OceanOverworld surface + 3D
129Sunflower PlainsOverworld surface + 3D
131Windswept Gravelly HillsOverworld surface + 3D
132Flower ForestOverworld surface + 3D
140Ice SpikesOverworld surface + 3D
155Old Growth Birch ForestOverworld surface + 3D
160Old Growth Spruce TaigaOverworld surface + 3D
163Windswept SavannaOverworld surface + 3D
165Eroded BadlandsOverworld surface + 3D
168Bamboo JungleOverworld surface + 3D
170Soul Sand ValleyNether (run dim 1)
171Crimson ForestNether (run dim 1)
172Warped ForestNether (run dim 1)
173Basalt DeltasNether (run dim 1)
174Dripstone Caves3D only (with a y)
175Lush Caves3D only (with a y)
177MeadowOverworld surface + 3D
178GroveOverworld surface + 3D
179Snowy SlopesOverworld surface + 3D
180Jagged PeaksOverworld surface + 3D
181Frozen PeaksOverworld surface + 3D
182Stony PeaksOverworld surface + 3D
183Deep Dark3D only (with a y)
184Mangrove SwampOverworld surface + 3D
185Cherry GroveOverworld surface + 3D
186Pale GardenOverworld surface + 3D
187Sulfur Caves3D only (with a y)
188Dappled ForestOverworld surface + 3D

The walk modes

Where the seeds come from, chosen on the hunt panel before the run. The walk itself is sharded across a pool of worker threads sized to your machine (cores minus one, RAM and device class trimmed, capped at 8) - each thread tests its own slice of the same walk in parallel, so the hunt runs as fast as your machine allows.

Random walk
a random odd 64-bit stride over the whole seed space, full period: every one of the 2^64 world seeds is visited exactly once, in a shuffled order. The default hunt.
Sequential
from the From seed upward, one by one. For watching a known neighborhood.
Range
From through To, inclusive. For exhaustive small-space sweeps, like every seed from 0 to 100000.
Sister seeds
structure-seed classes instead of world seeds: each class holds From + classIndex, and the worker hands you its sisters k = 0..Sisters/class. Structures are IDENTICAL inside a class; biomes differ. The stronghold sits at the same coordinates for every sister, the biome under it does not.

The sandbox's honest rules

what your code can and cannot do

  • No network, no storage. The worker deletes its own fetch, XMLHttpRequest, WebSocket, importScripts, Worker and indexedDB globals before your code runs, so the code cannot reach anywhere. The engine module and its memory stay in the worker's closure, out of the code's reach.
  • Sync code, one seed at a time. There is no await in a check; keep it synchronous and quick.
  • A 4-second silence kills the run. A healthy worker posts progress at least every 200ms; the page terminates the whole pool when every worker goes silent for 4 seconds (an infinite loop) and names the cause. That silence guard is a hang detector, not a time limit: runs have no budget, so a walk ends only at its seed count, the hit cap, or your Stop.
  • A throw ends the hunt with the error and the failing seed on the error panel.
  • Cheap first, engine second. A check that returns on seed.value arithmetic never touches the engine: order the code so free pre-filters run before biome and structure calls.

That is the whole language. Thirteen ready hunts ship on the editor's template row, each one a working example of a different part of this table, and a blank canvas starts your own.

Write a hunt