Releases: barrulus/settlemaker
Releases · barrulus/settlemaker
Release list
v0.2.0 — Questables-consumable gate output (P0)
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.roadBearingscan now benumber[]orArray<{bearing_deg, route_id?, kind?}>. The numeric form still works, but object form is required to have the matchedroute_idechoed back on the gate output.GenerationParams.roadEntryPointschanged fromPoint[]toRoadEntry[]({point, bearingDeg, routeId?, kind?}). Callers buildingGenerationParamsdirectly 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_versionis 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_systemdocuments 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'skind.prev_gate_id/next_gate_idwalk 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_entrancesTests
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
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/ytile grid for slippy-map rendering
Building-shape pipeline (this release)
rectangularize()now returnsnullon LP failure instead of silently passing the unchanged wedge throughisDegenerate()rejectslength < 4, tightens edge-ratio to 0.15, and adds a compactness < 0.3 guard- A single
tryEmitBuildinghelper runs rectangularize then validates; bothcreateAlleysandcreateOrthoBuildingflow through it - Recursive callers of
poly.cutdetect the "uncut" return (halves.length === 1) and stop recursing instead of looping on the same shape
Rendering fix
- Background
<rect>now useswidth="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