Use when changing ghost board generation, path / road / river continuity, deterministic regen, edge connections, or anything that runs inside makeGhostWorld in tiny-world-builder.html.
Scanned 5/27/2026
Install via CLI
openskills install jasonkneen/tiny-world-builder---
name: tinyworld-ghost-world-gen
description: Use when changing ghost board generation, path / road / river continuity, deterministic regen, edge connections, or anything that runs inside makeGhostWorld in tiny-world-builder.html.
---
# Tiny World Ghost World Generation
`makeGhostWorld(boardX, boardZ)` produces the contents of a single
non-editable ghost board. It must be:
- **Deterministic.** Same `(boardX, boardZ)` always yields the same
cells. Cached in `ghostBoardCells` keyed by `'bx,bz'`. Panning away
and back must regenerate identically (we rely on this for the
sticky-reveal jigsaw — if content shifted between regens the
reveal cache would lie).
- **Connection-aware.** Paths and rivers must line up across board
edges. The user should be able to walk a road from one ghost board
into the next.
## The seeded RNG
`ghostHash(a, b, salt)` is a tiny mulberry-style 32-bit mix used for
all board-level decisions. Per-cell randomness goes through the older
`cellRand(x, z, salt)` but always with **global** coords
`(boardX * GRID + x, boardZ * GRID + z, salt)`, never local coords.
That guarantees a given world cell renders identically regardless of
which board it was sourced from.
## Connection rubric
- **Horizontal path Z is a function of `boardZ` only**
(`pathZForRow(boardZ)`). Every board on that world row either has
the path at the same Z or has no path on that row.
- **Vertical path X is a function of `boardX` only**
(`pathXForCol(boardX)`). Every board in the column shares the same
vertical-path X.
- Where horizontal and vertical paths coincide inside a board you
get a crossroads "for free".
- **Rivers** are column-shared via `riverXForCol(boardX)` so they
flow continuously down a column. A river that would collide with
a vertical path is nudged one column over.
- **Bridges**: where a river crosses a horizontal path, drop a
`kind: 'bridge'` tile so the path stays walkable.
Rough density knobs (tweak in the helpers themselves):
- ~30 % of world rows have no horizontal path
(`(h % 100) < 30 → -1`).
- ~35 % of world cols have no vertical path.
- ~88 % of world cols have no river (so ~12 % do).
## Cross-board neighbours
The visual tile renderer needs to know what's on the *other side* of a
board edge — otherwise a path that exits east terminates with a stub
end-piece. The neighbour helpers handle this:
- `ghostCellAt(boardX, boardZ, x, z)` resolves any local coord. If
`x` / `z` are out of `[0, GRID)` it walks into the adjacent board and
pulls from its `makeGhostWorld(...)` result. If the wrap lands on
board `(0, 0)` it reads from the home `world[][]` instead so user
edits on the home board's edges feed the ghost adjacency too.
- `getGhostNeighbors(cells, x, z, prop, value, boardX, boardZ)` and
`getGhostTerrainNeighbors(cells, x, z, boardX, boardZ)` use
`ghostCellAt` so an edge tile sees the real neighbour, not `null`.
Always pass `boardX, boardZ` when calling these from inside
`buildGhostBoard`.
## Cells layout
The cell schema must match the home board so `setCell` /
`renderCellObject` work on ghost cells too. Always include the full
shape:
```
{ terrain, kind, floors, buildingType, fenceSide, extras }
```
Omitting fields (especially `extras: []`) caused subtle bugs in the
old generator when ghost data flowed through helpers that assumed the
full shape.
## Blank ghost boards
The Generate dialog can disable outside auto-fill. That path sets
`ghostBoardsBlank = true`, clears existing ghost boards, and lets
`makeGhostWorld(...)` return deterministic blank grass cells for every
off-home board. Keep this as an early return inside `makeGhostWorld` so
panning remains cheap and no generated scenery appears outside the
current generated board.
## Don't
- Don't make paths or rivers depend on both `boardX` and `boardZ` —
that breaks edge continuity.
- Don't seed decoration with local coords. A tree at local (3,4) of
board (1,2) must be identical to that same world cell reached from
any other angle.
- Don't mutate `ghostBoardCells` from anywhere except
`makeGhostWorld`. The reveal system relies on stable references.
## User overrides (exceptions)
Anything the user builds / erases on a ghost board is an *override* and
must survive map regeneration:
- The override lives in `world[gx][gz]` at **global** coords. There is
no separate override map — `world[][]` is the single source of truth
for user-built cells whether they sit on the home board or far out
in ghost territory.
- `applyToolToCell` copies the generated ghost cell into
`world[gx][gz]`, calls `removeGhostCellMesh(boardX, boardZ, lx, lz)`
to strip the ghost board's mesh for that cell, then runs `applyTool`
which calls `setCell` to render the home cellMesh at the global
coord. The home cellMesh and the ghost board never both render at
the same world position.
- `ghostCellAt(boardX, boardZ, x, z)` prefers `world[gx][gz]` over
`makeGhostWorld(boardX, boardZ)[x][z]`. That keeps cross-board
adjacency (paths joining, rivers continuing) correct even when the
user has edited the joining tile.
- `buildGhostBoard` skips any local cell whose global coord exists in
`world[][]` — those are owned by `setCell` / `cellMeshes`.
## Persistence
- `saveState` walks `Object.keys(world)` so every populated cell — home
*and* far-flung overrides — is serialised, regardless of how far the
user has panned.
- `applyState` restores both home cells (via the staggered drop-in
loop) and out-of-home overrides (via a second `setCell` pass with
`animate: false, forceTile: true`). The ghost boards regenerate
deterministically around them and `ghostCellAt` + `buildGhostBoard`
ensure overrides paint on top.
The contract: if a user can place / erase it, the world reloads with
that exact change re-applied, anywhere on the map, and the rest of the
ghost world regenerates around it.
## Validation
- Pan east across several boards — horizontal paths should run as a
continuous strip; vertical paths and rivers should cross perfectly
perpendicular.
- Pan a known board out of the preload radius then back — the same
trees / houses / crops / rivers reappear in the same cells.
- A river crossing a horizontal path renders a bridge, not water.
- The home board (0, 0) is not affected — its content is the user's,
not the generator's, and paths that line up with the generated row
/col are coincidental.
No comments yet. Be the first to comment!