Aqueducts on a Terrace Grid
May 5, 2026 • Provincia Dev Team
Players expect Roman water infrastructure to do something. Our aqueducts had to carry the CA sim across terrace steps without becoming accidental dams, and they had to look like stacked stone runs instead of stretched boxes.
That split the problem in two: placement math and mesh assembly.
Placement: deck height from anchors
aqueduct_placement.gd walks a polyline on the grid. For each cell it:
- Reads anchor
floor_y_maxto set deck height. - Excavates terrain where the run needs a trench.
- Counts piers and merges corners when modules meet at angles.
Footprint cells mark pier positions as permeable in water_simulator.gd so water can move through the arch line instead of pooling behind a stone wall.
Visual runs, not per-cell spam
AqueductRunMesh merges straight modules into one draw call per layer. Bottom double-height runs and top channels bake from GLBs listed in BuildingCatalog (GLB_AQUEDUCT_BOTTOM_DOUBLE, GLB_AQUEDUCT_TOP).

./tools/aqueduct_iterate.sh spawns test modules for material checks. The harness PNG can glitch when the GL pipeline is mid-refactor — we use in-game colony captures for the site instead.
Corners without gaps
Corner cells switch to dedicated meshes so the top channel bends instead of z-fighting two straights.

Nineteen script-only tests in ./tools/run_aqueduct_tests.sh cover placement math without opening a window.
Blender → GLB → Godot
AqueductStoneMaterial can use the PBR material embedded in each GLB (USE_GLB_IMPORTED_MATERIAL = true). We preserve Blender UVs (PRESERVE_BLENDER_UV) — random UV shifts on merge looked awful on large stone surfaces.
Workflow that actually stuck:
- Unwrap in Blender the way you want stone coursing to read.
- Export glTF with materials and textures.
- Drop over the path in
BuildingCatalog.GLB_AQUEDUCT_*. - Reimport in Godot and run the iterate harness.

Failure modes we hit
- Deck too high for pier count — water visually disconnects from the channel; placement now clamps from anchor surveys.
- Impermeable footprint — CA backed up entire ponds; pier roles in
building_footprints.csvfix that. - Merged mesh UV stretch — fixed by baking per-module instead of averaging UVs on merge.
Aqueducts are the clearest example of why Provincia is a simulation game with Roman dressing, not a asset flipper: the mesh exists to serve grid state the player can reason about.