Skip to content

Settlements

The settlement generator shares one architectural layout between meshes, collision, prop placement and resident navigation. Buildings are playable spaces: the entrance, stair flights, room openings, bed approaches and work positions are explicit in the layout, not guessed from a building radius.

BodyArchitecture (StructureKind)
Merys (Luna)Grey observatory courts (grey_habitat); mini and mega cities add a tower district (luna_tower, 8 to 24 storeys)
Meroros (home)Medieval homes, markets, workshops and inns (castle_habitat)
VovaneoSkell basalt forge wards with buttressed roofs (skell_habitat, plus the oversized skell_greathall in villages and larger towns)
NathNacre harbour terraces: broad houses under shell roofs (nacre_habitat)
VorythosVesper listening cloisters: narrow, vaulted buildings (vesper_habitat)

The alien styles are defined in SETTLEMENT_STYLES (src/structures/settlement-layout.ts); the home planet’s towns use castle_habitat and Luna’s tower district uses luna_tower. Streets are drawn from the shared road mask, not from per-style paving. Each habitat kind has sixteen plans. Variants 0 to 7 are single-block buildings whose use cycles home, market, workshop, inn, with one to three floors; inns and workshops always have an annex, homes on variants 4 to 7. Variants 8 to 15 are modular (src/structures/modular-layout.ts, modular-buildings.ts): independent room sections, each with its own elevation, walls, roof, floor, terrain pad and interior bounds, joined by explicit stair or gallery links between their wall openings. Culture sets the roof geometry, proportions and facade rhythm; use adds rooms and equipment (market counters, an inn’s bar and guest rooms, a workshop’s tools and yard).

World-tree settlements use the modular bough_habitat cabins. Skell elders, whose bodies are larger, are housed in the skell_greathall, a double-scale Skell plan.

Nine architecture albedos (limestone ashlar, cut slate, seasoned oak, dark oak, reed thatch, terracotta shingles, lime plaster, basalt courses and a separate roof slate) are listed in assets/architecture-source/manifest.json. npm run import:architecture packs them into seamless 1024x1024 WebP albedo, normal and height maps under public/textures/buildings/. The relief is a conservative estimate derived from the albedo, not scanned displacement. npm run bake:architecture is the older deterministic procedural generator, kept for new content and fallbacks.

Generation runs in the town worker (src/world/town-build-core.ts):

  • Lots and pads. Ordinary settlements do not flatten a town-wide platform. Main buildings and annexes each get a bounded pad; candidate sites are seeded per town, and lots on too much relief, with an unreachable entrance, or without a certified street are rejected. Natural land stays between pads. Lots slide off highway cores, and a lot whose pad would meet a highway at a different height is rejected.
  • Streets. Local streets follow the committed terrain and road mask and are certified for support, headroom, grade and building clearance. Minor lanes share the road cubemap. Where lanes meet highways, the road mask blends the road altitude across overlapping stamps (road-mask-rasteriser.ts) instead of letting one stamp win, to avoid steps at junctions. A final repair pass (smooth-street-lips.ts) removes a redundant terrace strip or adds a ramp where a street edge has a lip the character controller cannot climb. Isolated lots are rejected before population is allocated; their earthworks stay as open terraces so removing a house cannot break the ground next to it.
  • Density order. Footprints composite in a fixed order: disc, capsule, polyline and pillar footprints first, then the road mask, then building pads and wall pieces, then terrace strips (so a building foundation wins over a road that clips its corner). The JavaScript, WGSL and Rust density implementations must keep this order bit-exact; npm run test:densityparity is the gate.
  • Intertown roads keep their routed corners, cross cubemap seams continuously, and stand on bridge pillars where the deck spans natural ground. Signposts name the settlement and can carry an arrow plank naming a destination town.
  • Farms are separate lots at settlement edges, on the real terrain with two modest terraces and planted furrows. They carry crop rows, irrigation, sheds, tools, hay, troughs with water, a cart and a fenced pasture. Two persistent cows graze each active farm, steered and contained in the farm’s rotated frame; stable pasture keys make herd registration idempotent across streaming.
  • Fortifications (build-fortifications.ts, town-wall-placement.ts, wall-structures.ts) ring the home planet’s medieval towns of village size and up (a town needs at least two building lots to be fortified). Hamlets, mega anchors, treehouse villages and open alien settlements get no perimeter wall. See the next section.
  • Guards. Every town keeps guards (guardsWantedFor in src/npc/settlement-population.ts: one for a hamlet, village or treehouse village, two for a mini city, three for a mega city). They come from the resident allocation; when the town has nobody to spare, extra guard slots are added.

Walls are 9.6 m high (WALL_HEIGHT) and form a faceted ring around the town’s building lots (town-wall-placement.ts), set in one town frame so adjacent pieces meet exactly. Each piece’s ground is probed along the town’s up axis, and its plateau is kept within 3 m of that ground (buried at most 3 m, built up at most 3 m), so a wall neither buries itself nor becomes a dam. A piece whose ground climbs faster than 40 degrees (WALL_SLOPE_MAX) steps at its ends. Foundation depth is checked at every corner column, so a thin rock shelf cannot leave a pier ending over a cave.

Gates exist only where a paved road or lane actually crosses the ring (not where it skims along it); crossings closer than one gate and pillar apart merge into one gate. A gate is an arched span at the road’s deck height, with curved spandrels and matching collision. Towers are 6 m square with doorways where the wall walk passes. Wall stairs are solid down to the ground.

Each gate has a working portcullis: a winch in the gate passage, a gear, a pulley and an animated chain. Hover the winch and press E to raise or lower it (“Raise portcullis” / “Lower portcullis”); nobody opens it automatically. The grille is solid only once it has settled.

Walls and buildings carry colliders for their decorative parts (decor-colliders.ts): roof trim, corbels, quoins and window surrounds collide as boxes or as convex hulls (a concave part keeps its authored collision instead). Decor standing in a door passage, or wholly inside a room, gets no collider.

Each plan divides into zones with roles (ZoneRole: hall, common, kitchen, bedroom, guest, parlour, lounge, shop, store, workshop, yard). The furnishing planner (src/props/prop-furnishing.ts) furnishes each zone from its role’s recipe (a kitchen corner by the chimney, seats at every table, an inn’s tables and kegs, a workshop’s forge, anvil and tool shelves), then fills toward a coverage target from the role’s pool. Interior walls stand on the facade’s pier lines, so no partition runs into a window. A geometry QA check (building-geometry-qa.ts) keeps every exterior mass (roofs, buttresses, stacks, fins) out of every room.

Buildings are furnished from the prepared prop packages in public/props. A layout slot (bed, chair, table, shelf, counter, workbench, barrel, planter) takes a package of the settlement’s family; decoration (chests, crates, tavern sets, a hearth with a cauldron, a smithy’s forge and anvil, planters, a signboard outside) looks for free floor that is clear of walls, doors, stairs and walking corridors. Furniture is only ever a package: a slot the planner cannot fit (a chair that would take the bed’s last approach, a shelf in front of a window) stays empty. Package colliders join the blueprint, and each package draws as one instanced stream. npm run test:furnishing covers the planner.

  • Beds. The package’s sleep socket is aligned onto the layout’s bed node, so sleepers walk to the bed’s floor approach, snap onto the mattress and leave by the foot-end approach.
  • Loose items. Tableware only takes a spot its resting box fits (shelf headroom). Items hydrate as fixed bodies and turn dynamic on the first touch, so an interior whose colliders are not built yet cannot drop them. The character’s kick is contact-based. Package cylinders and spheres reach Rapier as native primitives, so round table tops do not jitter.
  • Oil lamps are dynamic mesh props that you can kick or carry; their light follows the body.
  • Planters each grow one of the planet’s harvestable flora species, chosen deterministically by the planter (src/vegetation/planter-plants.ts). They are picked like wild flowers (E) and regrow on the world clock.
  • Crafting stations. craftingStations(bp) (src/structures/package-stations.ts) lists every cook, smelt, smith, craft and prep station in a blueprint.

Homes, inns and workshops get a chimney on the plan’s chimney wall where a rear bay allows it. The hearth or forge stands against it inside, a flue runs to the roof, and the pot smokes while the fire burns. The fire stays in the firebox and lights its room with a clipped, flickering light.

With buildingSchedules on (the default), fires and room lights follow the household’s day in local solar hours (StructureManager.householdSchedule): a home’s hearth burns 06:00 to 09:00 and 16:30 to 23:00, a workshop’s forge 07:30 to 18:30, an inn’s hearth from 06:00 to midnight, and a market has no fire. Homes and businesses sleep (lights out) from 23:00 to 06:00; an inn never does. Pressing E on a hearth or forge lights or douses it until the schedule next changes. window.__buildingSchedules(false) turns scheduling off; __hearths(lit?, radius = 60) lists the hearths within the radius of the player and, given lit, lights or douses them.

With manualDoors on (the default), gates and medieval house doors open and shut on E while hovered; residents open them for themselves and they close behind them. Alien habitats keep automatic pocket doors that animate per instance, move their colliders with the leaves, and play door sounds on state changes.

Entry doors of homes and businesses, and some chests, carry locks. They are enforced when doorLocks (and, for chests, chestLocks) is on, which is the default: a locked door refuses the player, residents pass, and the cursor says “Locked”. Households lock up on a schedule, and locks open to a key or the lockpicking minigame (lockpicking, also on by default).

Per room (the plan’s zones):

  • Inns, markets and alternating home rooms hang a package lantern on a chain over each of up to three tables, with a shadowed light. Other rooms are lit by one to three wall sconces (about one per 35 m²).
  • A grid of unshadowed “flood” lights (about one per 6 m) at the ceiling gives ambient fill. Each is limited by its clip box to the room, so it cannot leak outside and needs no shadow slot.
  • Wall sconces flank the doors. Inside they are shadowed; the outside pair is the door lantern, unshadowed and clipped to the porch.
  • A workshop yard gets a lantern hung from the yard roof.
  • Luna’s towers have no joists or wall boxes to mount fixtures on, so each tower room gets one wide, clipped ambient light.

Fixtures keep off window apertures, and furniture taller than 1.2 m keeps clear of windows. StructurePart.castsShadow = false keeps bulbs, candles and lamp chains out of both sun and punctual shadow passes; batching and mesh-cache keys preserve the flag.

At LOD0, trim adds ceiling joists, wall plates, skirting, sills, architraves and corner posts inside, and ridge caps, rafter tails, bargeboards, finials, quoins, corbels and knee braces outside. City walls get merlon caps, machicolation, arrow slits and tower courses. The ?notrim URL parameter removes trim at boot (buildingTrimDetail).

  • Residents get individual bed and lounge slots. Their schedule chooses homes, counters, workbenches or crop positions, and they physically follow the route, stairs included, before sleeping, sitting, drinking or working.
  • Arrival checks include vertical distance, so a resident below a room cannot use its furniture through the ceiling. Interior collision stays active across simulation tiers and while a resident waits for the shared route budget.
  • Cancelled routes hold position during retry backoff; assigning a walking route clears any pending takeoff or flight mode. Occupied furniture tolerates small contact changes without restarting its action; leaving the arrival envelope releases it.
  • A seated resident is a solid anchor: walking into one stops the player instead of shoving the sitter.
  • Every agent obeys the same building walls whatever its distance from the player (src/world/agent-walls.ts): each structure’s shell and interior colliders, taken from its blueprint for active and dormant buildings alike, as convex prisms judged against the agent’s own height, so a plinth or a ramp’s low end is a step and a wall is a wall. A move is stopped at first contact and slides along the wall; an agent that was already inside is walked out slowly rather than popped. People open doors (every awake NPC counts, not only the ones near the player); animals are kept out by the shut leaf.
  • A resident who has to reach somebody behind a wall (a guard answering a crime, a fight, a follower) walks straight while the line is clear and follows a planned route through doors when it is not (src/npc/pursuit.ts, src/world/nav-query.ts), with re-plans behind cooldowns and a timed give-up so a crowd cannot re-plan every frame.
  • The long working and evening windows are cut into blocks (src/npc/day-life.ts): residents stroll, browse the market, meet a partner to chat, walk a guard’s beat or go to the inn. The choice is a pure function of the town, the resident, the day and the block, so a resident met mid-afternoon is doing what it would have been doing. Merchants and questgivers stay at their posts.
  • Drinking shows an animated vessel. Drinking, work, harvesting, doors, chairs and beds have synthesised spatial foley, footsteps distinguish stone and wood floors, and settlement audio has a bounded voice count with distance culling.

Hover a chair or bed and press E. Furniture occupied by a resident is unavailable, and residents respect the player’s reservation. The player walks the approach route before entering the pose; moving cancels an approach and releases its reservation, and E or movement leaves the pose (including the wake-up transition from a bed). Raised chairs use a seated-chair clip that moves the pelvis back onto the seat while the collision capsule stays on the clear floor in front. Campfire sitting keeps its own ground pose.

Start with src/structures/settlement-layout.ts (getBuildingLayout). Coordinates are metres above the ground anchor, and -Z faces out of the entrance. Add furniture, navigation nodes, edges and an affordance together. A node is the supported foot or contact position; its facing is the interaction orientation. A capacity-one affordance must receive at most one persistent resident assignment.

src/structures/settlement-buildings.ts turns the plan into geometry and collision. Blueprint geometry is recentred vertically before spawning, and semantic anchors must receive the same recentring. Do not infer layout independently in the renderer or AI. Floors need real openings above every stair flight, and the top storey must not generate another flight. Add reachable ramps wherever raised thresholds or furniture supports meet the walking path: the character controller does not step over them automatically.

Static details are batched by shared material, colour and floor. Doors and glazing keep stable, unbatched anchors. Keep per-instance state out of cached blueprints. Large residents need a correspondingly scaled room, doorway and furniture.

Modular variants take width (6 to 24 m), depth (8 to 30 m), level rise (0 to 4 m) and per-section dimension and elevation overrides through getModularLayout and generateModularBuilding. The default dimensions keep the committed world and NPC anchors. This is a parameterised compiler, not a solver for arbitrary terrain or floor-plan polygons.

src/structures/fence-layout.ts (appendFence) takes open or closed boundary polylines, including concave yards, sampled curves and slopes. A gate interval removes both the rails and the collision on its span and adds a hinged panel. Farms and the general fence prop use the same builder, and each gate owns its motion and proximity state.

TOWN_GEN_VERSION (src/world/precompute-version.ts, currently 16) versions the settlement bake. The five inhabited bodies ship bundles for world seed 1337 as public/baked/towns/<body>-v16.bin (planet, luna, cinder, pearl, ember). A missing file, a seed mismatch or a decode error falls back to live generation in the worker, so other seeds work but pay the full generation cost at runtime. Bump the version whenever generation output changes; regeneration changes settlement locations and structure IDs.

npm run bake:towns regenerates the bundles through the real worker pipeline, one isolated Node process per body, in sequence. npx tsx scripts/bake-towns.ts --body=<id> bakes one body, and bodies can be baked in parallel from separate terminals. Each body’s result is first written as a candidate file, audited with audit-town-bundle.ts against the final terrain, colliders and navigation, and only then replaces the shipped bundle. Cold generation with full street certification is slow, so fix a marginal audit failure offline rather than with a full rebake:

  • npm run rebake:fortifications -- <body> rebuilds only the fortification stage against the committed terrain and roads.
  • npx tsx scripts/polish-town-bundle.ts <body> [--file=...] [--check-only] reapplies the final street refinement to a finished bake.

The terrain octree bakes are independent of the town bake.

npm run review:settlements opens the architecture workshop (settlement-workshop.html, port 5187). Choose an architecture and plan, cut away the front and roof, open the doors, show the usable routes, resize modular sections, and audition the interaction sounds. It renders the real generated meshes with their material atlases.

In game, the BUILDINGS panel has visit viewpoints (Forecourt, Doorway, Ground floor, Top floor, Bedroom, Lounge), and the NPCS panel lists live settlement routines: each resident’s activity, distance to it and route state.

Terminal window
npm run build
npm run test:settlements
npm run test:furnishing
npm run test:character-animation
npm run audit:settlement-residents
npm run test:towns
npm run test:townroutes
npm run test:roadlanding
npm run test:densityparity
npm run audit:town-bundle -- planet
npm run audit:town-bundle -- luna
npm run audit:town-bundle -- cinder
npm run audit:town-bundle -- pearl
npm run audit:town-bundle -- ember
npx tsx scripts/check-town-bake.ts
  • test:settlements runs the structure render budget, metropolitan, urban passage, geometry refinement, light fixture, settlement refinement, settlement, settlement runtime, fence and pasture, terrain upload, street merge, finalisation, walk-surface, lot rejection, player furniture, light budget, door fast-path, instance bucket and structure constants cache suites (see package.json). Between them they walk capsules and AI/Rapier journeys through every variant (stairs in both directions, unique bed assignments, route-budget holds, rotated pasture containment) and check roof manifolds, arch bounds and light allocation at saturation.
  • test:character-animation checks the shared animation bridge (pelvis displacement, foot contact and seat height across body presets). It reads a character authoring export (a directory with a manifest.json) passed as --root=<dir>; its default directory is a local working folder that is not part of the repository, so on a fresh checkout it needs that argument.
  • audit:settlement-residents measures the shipped character bodies, including rare elders, against their assigned architectural envelopes.
  • audit:town-bundle <body> [--file=bundle.bin] replays a shipped or candidate bundle with its real wall and gate colliders and checks buried, unsupported and wall-blocked street samples and disconnected resident routines.
  • check-town-bake.ts [bundle] checks the world-structure contract: no ordinary town under the world tree, road clearance around its anchor, and the home spawn still on a road.
  • npx tsx scripts/audit-walls.ts [body] reports how high each rampart stands above the ground outside and inside it; npx tsx scripts/audit-roads.ts [body] walks every ground navigation edge on the physics density surface and reports bumps and sustained grades.

These checks establish specific invariants, not universal navigation correctness or a frame-rate guarantee on target hardware. Some intertown edges are deliberately classified as jumps rather than walking routes, and a few steep streets can still carry small lips.

  • Cold generation for a non-default seed is expensive; only the default seed ships baked bundles.
  • There is no room-portal renderer, no underground cellars or dungeons, and no spaceship interiors. The section graph is a useful starting contract for these, not a finished portal system.
  • Modular habitat variants (8 to 15) have no billboard impostor (the impostor atlas holds variants 0 to 7), so they keep their LOD1 shell at distance, which favours correct silhouettes over the lowest triangle count.
  • The optional local asset pipeline for generated props is documented in trellis-assets.md.

Written by release:site-sync from docs/settlements.md at v2026.09.24. Edit the engine document, not this page.