Skip to content

Releases: barrulus/settlemaker

v0.2.0 — Questables-consumable gate output (P0)

Choose a tag to compare

@barrulus barrulus released this 19 Apr 08:02

This release lands P0 of the questables integration contract: a structured gate export so downstream systems can use settlemaker gates as route terminators instead of the burg centroid.

Breaking

  • AzgaarBurgInput.roadBearings can now be number[] or Array<{bearing_deg, route_id?, kind?}>. The numeric form still works, but object form is required to have the matched route_id echoed back on the gate output.
  • GenerationParams.roadEntryPoints changed from Point[] to RoadEntry[] ({point, bearingDeg, routeId?, kind?}). Callers building GenerationParams directly must update.

New GeoJSON output contract

Top-level metadata block:

{
  "schema_version": 1,
  "settlemaker_version": "0.2.0",
  "settlement_generation_version": "qefjq4",
  "coordinate_system": "local_origin_y_down",
  "coordinate_units": "settlement_units",
  "generated_at": "2026-04-19T12:00:00Z"
}
  • settlement_generation_version is a stable content hash of every gate-affecting input (seed, population, walls, port, oceanBearing, roadBearings, harbourSize). Unchanged inputs → unchanged hash, so it can drive upsert-vs-skip decisions in downstream tables.
  • coordinate_system documents the axis convention explicitly: origin near burg centroid, Y+ points down (SVG convention).

Per-gate Point Feature:

{
  \"layer\": \"gate\",
  \"gate_id\": \"g14\",
  \"kind\": \"harbour\",
  \"sub_kind\": \"harbour\",
  \"wall_vertex_index\": 14,
  \"bearing_deg\": 274.1,
  \"matched_route_id\": \"route-west-trail\",
  \"bearing_match_delta_deg\": 3.0,
  \"prev_gate_id\": \"g13\",
  \"next_gate_id\": \"g18\"
}
  • gate_id = g{wall_vertex_index} — stable within a generation, identical across re-runs with identical inputs.
  • kind ∈ {land, harbour} — topology. sub_kind ∈ {road, foot, harbour} — narrative category inherited from the matched route's kind.
  • prev_gate_id / next_gate_id walk the wall polygon (useful for 'patrol from A to B' narratives).

Invariants:

  • Walled burgs always emit gate features.
  • Unwalled burgs emit zero gate features (approach-point synthesis is P1).
  • Wall polygon is emitted as a layer: \"wall\" Feature.

Usage

import { generateFromBurg } from 'settlemaker';

const result = generateFromBurg(
  {
    name: 'Thornwall',
    population: 15000,
    port: true,
    citadel: true,
    walls: true,
    plaza: true,
    temple: true,
    shanty: false,
    capital: false,
    oceanBearing: 180,
    harbourSize: 'large',
    roadBearings: [
      { bearing_deg: 0, route_id: 'route-north', kind: 'road' },
      { bearing_deg: 90, route_id: 'route-east', kind: 'road' },
      { bearing_deg: 270, route_id: 'route-west-trail', kind: 'foot' },
    ],
  },
  { seed: 42 },
);

// result.geojson.metadata.settlement_generation_version  → upsert key
// result.geojson.features.filter(f => f.properties.layer === 'gate')
//   → per-gate records for maps_burg_entrances

Tests

138/138 passing. New file tests/gate-output.test.ts has 18 tests covering the metadata block, version stability under same/different seed / population / roadBearings, gate property shape, route round-trip, back-compat with numeric bearings, foot sub_kind, neighbour links, harbour gates, unwalled burgs, wall polygon emission, and the direct generateGeoJson entry point.

What's next (P1)

  • Unwalled 'approach point' synthesis (item 14 from the questables spec)
  • Optional gate name generation
  • Fallback picker metadata when the bearing match is loose (>15°)

v0.1.0 — Initial release

Choose a tag to compare

@barrulus barrulus released this 19 Apr 07:21

First tagged release of settlemaker — a TypeScript port of watabou's Medieval Fantasy City Generator.

Highlights

  • Six-phase generation pipeline — patches → junctions → walls → water → harbour → streets → wards → geometry
  • Zero runtime dependencies — Voronoi, A*, polygon ops, and PRNG are all ported in-tree
  • Deterministic — identical SVG output from the same seed
  • Azgaar-compatible burg input — drops in as a settlement renderer for Azgaar's Fantasy Map Generator
  • Port cities — harbour/dock wards with warehouses, piers, and a harbour gate for street connectivity
  • Farmlands — strip fields with furrows and sinusoidal plot boundaries
  • Tile-pyramid output — crop an SVG into a z/x/y tile grid for slippy-map rendering

Building-shape pipeline (this release)

  • rectangularize() now returns null on LP failure instead of silently passing the unchanged wedge through
  • isDegenerate() rejects length < 4, tightens edge-ratio to 0.15, and adds a compactness < 0.3 guard
  • A single tryEmitBuilding helper runs rectangularize then validates; both createAlleys and createOrthoBuilding flow through it
  • Recursive callers of poly.cut detect the "uncut" return (halves.length === 1) and stop recursing instead of looping on the same shape

Rendering fix

  • Background <rect> now uses width="100%" height="100%" so it tracks the viewBox through tile cropping — fixes the sepia-square artifact in cropped tiles of large cities

Testing

  • 120 tests across 10 files (npx vitest run)
  • End-to-end smoke test covers hamlet → metropolis and a port city