# A Walkable World: how it is built

The seventh demo is the guide "How Wander is built" in one page: the ground from Wander's laws, the triangles from the kit's mesher, the view from two hundred lines of WebGL, all from `sitoolz` alone.

## What it imports

```html
<script src="../../../sitoolz/js/rangeblends.js"></script>   <!-- the ruler, the hash, the noise, the ridge -->
<script src="../../../sitoolz/js/bank.js"></script>          <!-- the river bank scene -->
<script src="../../../sitoolz/js/wanderlaw.js"></script>     <!-- the basin, the almanac, the census -->
<script src="../../../sitoolz/js/sudoku-towns.js"></script>  <!-- eight solved grids as towns -->
<script src="../../../sitoolz/js/citylaw.js"></script><script src="../../../sitoolz/js/rulerland.js"></script><script src="../../../sitoolz/js/terrain.js"></script>
<script src="../../../sitoolz/js/primerails.js"></script><script src="../../../sitoolz/js/folds.js"></script><script src="../../../sitoolz/js/cascade.js"></script>   <!-- the levelled-up laws register their worlds with terrain -->
<script src="../../../sitoolz/js/wander.src.js"></script>    <!-- Wander's worlds.js, byte for byte: WanderWorlds -->
<script src="../../../sitoolz/js/mesh.js"></script>          <!-- the builders lifted out of gl.js: WanderMesh -->
```

From `WanderWorlds`: `open(id, opts)`, `inst.chunk(ci, cj)`, `inst.ground(x, z)`, `inst.start`, `lanterns(inst)`, `DEFS`. From `WanderMesh`: `buildChunk(chunk)`, `boundsOf(chunk)`.

## The twenty lines that make it

```js
const inst = WanderWorlds.open("rulerhills", {}), meshes = new Map();
const cam = {x: inst.start.x, z: inst.start.z, y: inst.ground(inst.start.x, inst.start.z) + 1.7, yaw: inst.start.yaw, pitch: -0.1};
const worker = new Worker("worker.js"); worker.postMessage({type: "open", world: "rulerhills", token: 1});   // the same kit, another thread, the same world
worker.onmessage = e => { const m = e.data; if (m.type === "chunk") meshes.set(m.id, {parts: m.parts.map(upload), bounds: m.bounds});      // ten floats a vertex: position, normal, colour, flag
  if (m.type === "tile") tiles.set(m.id, {mesh: {parts: m.parts.map(upload), bounds: m.bounds}, boxKey: m.boxKey}); };
function ensure(){ const ci = Math.floor(cam.x/64), cj = Math.floor(cam.z/inst.CZ);                 // the chunk under the walker now, the ring from the worker
  if (!meshes.has(ci + "," + cj)) meshes.set(ci + "," + cj, {parts: WanderMesh.buildChunk(inst.chunk(ci, cj)).map(upload), bounds: WanderMesh.boundsOf(inst.chunk(ci, cj))});
  for (let i = ci - 2; i <= ci + 2; i++) for (let j = cj - 1; j <= cj + 1; j++) if (!meshes.has(i + "," + j)) worker.postMessage({type: "chunk", id: i + "," + j, ci: i, cj: j, token: 1});
  for (const {s, T, R} of [{s: 4, T: 256, R: 640}, {s: 16, T: 1024, R: 1600}])                     // the horizon: far tiles at a stride, cut where the chunks cover the ground
    for (const [ti, tj] of tilesWithin(R, T)) if (!tiles.has(s + ":" + ti + "," + tj)) worker.postMessage({type: "tile", id: s + ":" + ti + "," + tj, s, T, ti, tj, skip: [chunkBox()], token: 1}); }
function step(dt){ const g = inst.ground(cam.x, cam.z); vy -= 24*dt; cam.y = Math.max(cam.y + vy*dt, g + 1.7); if (cam.y === g + 1.7) vy = 0; ensure(); }
function setHour(h){ night = h < 6 || h >= 19 ? 1 : h < 7 ? 1 - (h - 6) : h >= 18 ? h - 18 : 0; const a = (h - 6)/12*Math.PI; sun = [Math.cos(a)*0.6, Math.max(0.08, Math.sin(a)), 0.35]; }   // the world's hour, then the slider
function draw(){ perspective(proj, 70*Math.PI/180, aspect, 0.1, 2000); view(vw, cam.x, cam.y, cam.z, cam.yaw, cam.pitch); const P = planes(mul(pv, proj, vw));
  for (const m of [...meshes.values(), ...[...tiles.values()].map(t => t.mesh)]) if (m && visible(P, m.bounds)) for (const p of m.parts) drawPart(p); }
```

The terrain shader is twelve lines: a sun term on the normal, a wave on water faces, emissive faces left alone, fog by distance. The sky is Lars Rocha's "Celestial Zones v2" shader (`celestial_shader_v2.glsl`): a domain-warped nebula, a breathing hex lattice masked by the nebula's density, hashed oracle pulses and three parallax star layers, drawn behind everything and turned by the camera; by day it fades to Wander's blue. `Demo.act("night", 1)` shows it at any hour.

## How to change the law

- Any of the 82 worlds: `open("ziggurats", {})`, `open("cities", {})`, `open("lightcone2", {walls: "or,xor", coneDepth: 30, coneUnit: 3})`. The knobs are the same the Wander links carry.
- A world of your own: add an entry to `WanderWorlds.DEFS` in the shape of `rulerhills` (kind `epic`, a `law` with `land` and its knobs, base, water, top) before opening it; or change one knob on a copy.
- Ground with nothing on it: `SITerrain.window(world, x0, z0, w, h)` gives heights and colour classes for any renderer; `view3d.html` stacks it without WebGL.

## How to verify it

`fixtures/wander.json` holds 29 worlds, three or four chunks each, every cell, written by Node from Wander's own worlds.js; `tests/test_wander.py` checks the Python port against it, and `tests/twins-mesh.test.cjs` and `tests/test_mesh.py` check that the mesher makes the same triangles on both sides (`fixtures/mesh.json`). `smoke.cjs` here opens the page, checks the walker stands 1.7 above `inst.ground`, walks, teleports onto the gate hill, waits for far tiles from the worker, turns the hour to night, noon and dawn, and opens a second world.

## What was missing

Nothing from the kit. The page now keeps a worker (`worker.js`, the same kit loaded with `importScripts`, the same world opened) that answers `chunk` and `tile` messages with mesh parts, the buffers transferred, the same protocol Wander's own worker speaks; the chunk under the walker is still built on this thread so a teleport lands on ground. The horizon is two rings of far tiles from `WanderWorlds.tile` and `WanderMesh.buildBlocks`, a stride of 4 cells in tiles of 256 out to 640 cells and a stride of 16 in tiles of 1024 out to 1,600, cut where the near chunks cover the ground (the `skip` boxes), and drawn with the same shader. The hour slider moves the sun and blends the sky: the world's own `hours` sets it on opening.

![](shot.png)
