# Deliverance in Malta — world simulation (vertical slice)

The game has one authoritative world simulation underneath the 3D Malta (internal name: *The Republic*):
people, households, an aggregate population, money, property, a market, jobs, a restaurant, weather,
policies and authority. It lives in `republic/` (plain JS, no dependencies), runs in the browser and in
Node, and is documented in `docs/` (start with `docs/ARCHITECTURE.md`; design addenda status in
`docs/ADDENDA_STATUS.md`; integrated roadmap progress in `docs/ROADMAP_PROGRESS.md`).

**Play it.** Run `START-WINDOWS.cmd`, open the game, and choose a start: **Delivery Rider**, **Property Heir**,
**Prime Minister** or **Sandbox**. The Rabat world already exists and keeps running before you choose; every
start is a different person in it, and you can add the others later.

| Key / control | What it does |
|---|---|
| **J** | World panel: Me & money · Delivery · Property · Government · World & save |
| **E** | Collect at the restaurant / hand over at the customer's door (the kernel checks you are really there) |
| **I** or double-click | Inspect the building you are at: owner, who lives there and on what terms, condition, sale; manage it if it is yours |
| HUD card (bottom left) | Cash, world time, current job, *Guide me* (cyan route on real roads) |
| World & save tab | Add characters, skip time (+1 h … +30 days), ledger audit, state hash, module switch, save/export/import, new world |

**Time and the timeline** (details in `docs/AGENDA.md`). Your character can live on its own, as in The Sims:
the bar along the bottom is its timeline. **+** plans actions (ride or bus somewhere, deliver orders for a few hours,
go to work, eat, sleep, refuel, service the bike, see the doctor), **▶** hands over and any steering key takes back
control. A **weekly routine** (phone → Routine) fills every day. **=** / **−** set the speed of time from 1× (real
time) to 2880× (a day in 30 s); riding by hand caps it at 3×, and anything that needs you (an order, a wreck, rent
missed, the timeline done) brings it back to 1×. The phone's **Planner** (📒) shows today, yesterday or the last 7
days. On the delivery board, **Timeline** beside Accept hands an order to the timeline. **B** cycles the riding
camera (Display → Camera).

* **Delivery.** Rabat households (5 named, 420 more in aggregate in 93 real buildings) order lunch and dinner
  from *Kċina tal-Bastjun* (fictional business, real building) when they can afford it. Accept an order, ride
  there, press E, ride to the door, press E. The household pays the fee; you pay fuel (duty included) and rent
  €300 for a room from an NPC landlord. A customer you meet at the door becomes a persistent person. Orders you
  leave for 15 minutes go to background couriers. Rain brings more orders.
* **Landlord.** You inherit a townhouse with a tenant who cooks at the restaurant. Rent arrives monthly with
  rental-income tax withheld. Buildings decay: repair them, or the tenant leaves. Rent rises are capped by policy.
* **Prime Minister.** Set fuel duty, rental-income tax and the rent cap, and watch the treasury and the district
  economy. Other characters can open the same dashboard; the kernel refuses their changes.
* **Saves** are automatic (every 30 s, compressed, per browser) and exportable as JSON. World time follows the
  in-game clock; moving the calendar forward runs every payment in between, in order.

* **Property.** Three Rabat houses are on the market (Sandbox can buy one and let it). Raise a tenant's rent and
  watch their finances, meal orders and — past 45 % of income — their notice.

**Tests.** `npm test` (Node 22) runs 58 tests: ledger, idempotency, authority, save/load, replay, geography
stability, the three loops, the GDD addendum §14 demonstration, Addendum A's three-module acceptance test, the engine checks (grade separation, junction continuity, clear streets) and the sky astronomy against Skyfield.

# Vehicles and street lighting

* The Unity bike systems as a world module: four real-scale scooters, fuel, wear, crashes, towing, and services at
  real OSM fuel stations, dealers, repair and tyre shops and car washes (Vehicle tab, press E at a place).
* The test driver is now a real-scale scooter and rider with lean, steering and the model's performance.
* Street lights placed from OSM `lit` tags and the street layout — wall lanterns in narrow streets, poles
  elsewhere — lighting streets and buildings through a cheap clustered light field.

# Engine update — bridges and tunnels, clear streets, one road surface, real sky

* Bridges, tunnels, over- and underpasses from the OSM tags, engineered and traversable on their own level.
* Streets claim their corridor: footprints are trimmed, split or given passages; drawn streets fit the real gap;
  trees move off carriageways.
* One road datum surface for every at-grade way: no steps or flicker at junctions, eased tile seams.
* Real sun and moon for Malta, 8,870 Hipparcos stars (Skyfield), sun/moon shadows (`?shadows=0` to turn off).
* After pulling, run `UPDATE-WORLD.cmd` once to re-cook the tiles. Details: `docs/ENGINE_STREETS_AND_SKY.md`.

# Pipeline 07 — whole archipelago

The map now covers Malta, Gozo, Comino and the coastal islets: 420 tiles in total, 129 land tiles plus 291 open-sea tiles, including a 2 km sea margin. The Gozo and Comino land tiles come from the archipelago export and go through the same locality, architecture, road and wall pipeline as Malta.
- **Sea.** Open-sea tiles have no asset folder; `world-tile.js` builds them at runtime with the shared procedural sea in `water.js`. Coastal tiles get the same water surface over the photographed sea, so coast and open sea meet without a seam. The rider can't leave the land except on mapped roads.
- **Gozo character.** `world/districts.json` adds the Cittadella (citadel), Victoria (townhouses), Xlendi and Marsalforn/Qbajjar (seafront resort blocks) and Mġarr harbour (fishing).
- **Localities and events.** `systems/localities.js` adds 18 Gozo and Comino localities. Gozo entries in `Event.csv` (Xagħra, Nadur) now dress their streets.
- **Navigation.** `world/road-graph.json.gz` now includes Gozo's roads (44,106 nodes). Routes work within each island. Between islands the planner explains the Ċirkewwa–Mġarr ferry link; there is no ferry ride yet.
- **Atlas.** `island.html` shows the whole archipelago, with sea tiles in blue.

# Pipeline 06 — local architecture, streaming island, integrated roads, rubble walls, world systems

This section supersedes older notes below where they differ. START-HERE.md has the controls.

## Locality-aware architecture
- `prepare-locality.mjs` extracts per-tile land use, mapped walls, POIs, place nodes and coastline from each tile's OSM extract into `locality.json`, and island-wide place names into `world/places.json`.
- `locality.js` classifies each building into one of 15 styles: baroque, citadel, village, townhouse, highrise, resort, hotel, fishing, modern, terrace, villa, farmhouse, industrial, utility and church. Evidence: OSM tags and POIs, the hand-drawn districts in `world/districts.json`, coast distance, land use, distance to place nodes, density, height and footprint fill. Where the source height is a fallback, the district norm is used (for example 8-storey seafront in Sliema).
- `architecture.js` has a style table (storey height, bay width, finish, openings) and per-style facades: gallariji and persjani shutters, niches, shopfronts, garages, cafés with awnings, hotel canopies, glass balconies and industrial roller doors, plus roofscapes (penthouses, stair rooms, water tanks, solar heaters). Windows light up at night.
- `prepare-architecture.mjs` cooks a version-2 instance cache per tile. The cache records a hash of the rule files, so `node rebuild-world.mjs --stale` only rebuilds what changed.

## Streaming and stitching
`world-tile.js` loads a complete tile. `app.js` keeps a 3×3 ring loaded around the rider, unloads tiles further than two away and averages heights along shared edges. Tiles without aerial imagery fall back to a neutral ground texture.

## Roads in the terrain
Roads are no longer cached. `road-worker.js` grades them at load time. Shoulders are replaced by short skirts. Footways and steps drape over the graded terrain. `ground-mask.js` builds a distance field (roads, buildings, urban, paths) that the terrain shader uses to paint kerbs, pavements, verges and junction asphalt. Roads get patches, cracks, tyre polish, gutters, wet sheen and night lamp pools. The old `road-cache.*` and `terrain-graded.json` files are no longer read; they can be deleted.

## Rubble walls
`rubble-walls.js` generates mapped walls, roadside walls on rural roads with gates, field-parcel strips, and terrace walls following slope contours. The walls are chunked, distance-culled and collide with the rider.

## World systems (ported from the Unity scripts)
- `systems/weather-time.js`: WeatherAndTimeManager. The clock, day phases, the CSV weather schedule by month and day, eased wind and rain, a three-day forecast and a saved clock.
- `systems/atmosphere.js`: the sky, fog, sun path, rain bent by the wind, dust, lightning, wet surfaces and night lighting.
- `systems/events.js`: CalendarManager and EventSpawner. `Event.csv` festas dress the real streets of the locality with bunting, pavaljuni statues and lights. Nationwide holidays put flags on the main roads. Gozo entries are ignored because Gozo is not in the map.
- `systems/navigation.js` with `world/road-graph.json.gz` (built by `build-road-graph.mjs`): MapManager and RouteGeneration on the real road graph. A* respects one-way streets. Locality connections come from road catchments instead of hand-placed connectors.
- `systems/ui.js`: the HUD, forecast, calendar and route planner.
- `systems/localities.js`: the locality list from LocalityPlacer, with name aliases and the CSV reader.

Not ported: orders, tips, balance and saves.

## Validation
`node check-island.mjs` checks every tile's terrain, its v2 architecture cache, its building classification and unique IDs, the road graph and the world data tables. It writes `world/validation.json`. Last run: 93 tiles, 87,218 buildings, 13.1 M facade components, 38,373 road graph nodes, no problems.

---

# Main-island offline build

All 93 main-island tiles are generated locally: the original Mdina tile plus 92 added tile packs. Open island.html to select a generated tile. This is an offline tile atlas with playable individual tiles; seamless streaming and cross-border stitching are not enabled yet.

The build contains 87,218 building instances referencing 79,895 source IDs, 40,194 road sections and 13,066 mapped/inferred tree instances. Source IDs are data references, not a guaranteed count of distinct real buildings. Coastal tiles include sea. About 1.6 GB is retained under tiles/, including public source extracts and generated assets. Only the selected tile is loaded in the current viewer.

Shared modules and the central world/building-overrides.json registry control the procedural library. Shader changes apply centrally; structural rule/override edits require recooking the affected caches. Every building is individually addressable through its source ID and tile-instance ID. Double-click a building to inspect its ID. See world/README.md for the override format, targeted rebuild commands and source preservation.

All 93 packs passed finite geometry/terrain, cache integrity, building count and unique instance-ID checks. The offline report is world/build-summary.json. Source elevation encoding, generated heights, canopy guesses and architecture remain provisional. Tile-edge terrain profiles will need stitching for seamless travel.

## Road edge and riding fixes
The 2048-pixel shoulder exclusion mask has been removed. Shoulders are now cut geometrically against road triangles with interpolated height and UVs, eliminating repeated mask teeth and shoulder intrusion. MSAA is enabled while retaining the retro palette and selectable render scale. The exact clipped shoulder mesh currently has 401,185 triangles; a future offline simplification can reduce this without returning to the pixel mask.

Movement samples the visible shoulder geometry as well as road profiles. Roads bypass the terrain slope rejection; off-road slopes are measured over a 3 m span rather than the tiny per-frame step. Movement uses 30 cm substeps and slides on a clear axis. Building clearance is 35 cm instead of the former 1.2 m margin. WASD remains active after using checkbox controls. Buildings and map limits still block movement. Source building/road overlaps may still require data cleanup.

Validation: exact polygon subtraction area test; full road/subgrade checks; building and route checks; isolated collider margin regression; browser ride moved from 138 m to 88 m from the first gate without a false stop; no browser errors.

## Road grading update — supersedes terrain-draped traffic roads
Roads now establish smoothed longitudinal profiles and level cross-sections. A load-time cut/fill pass grades the surrounding terrain, creates textured shoulder transitions, and clears the terrain below the road bed. Natural paths, tracks and steps retain terrain-following geometry. The rider uses the road profile when on a road. These are inferred engineering profiles, not surveyed road elevations. Junction merging, shoulder overlaps and road/building transitions remain approximate.

727 road ways receive profiles. The road mesh has 64,444 triangles and the batched shoulders 79,508, compared with 216,457 road triangles in the previous draped implementation. The grading calculation took about 350 ms in a local Node check; browser/device performance varies. Geometry checks at four interior points of every road face confirmed at least 6 cm subgrade clearance.

## Close-up surface detail update

The styled terrain now loads aerial-ps2-hi.jpg at the saved original resolution, 2048 by 2048 (approximately 0.98 metres per texel over the 2 km image). The previous 1024 image remains for the minimap. The source service maximum has not been verified. The additional image has four times the base texture memory of the former 1024 terrain image.

Procedural surface detail adds world-aligned limestone courses to building masses and fortifications, colour-guided paving/vegetation grain to terrain, and aggregate grain to highlighted roads. Detail fades with distance and pixel footprint to reduce shimmer. It adds shader work but no geometry, texture atlas or network requests. Surface classifications and masonry are stylistic estimates; they do not recover real material boundaries, remove baked aerial shadows, or reconstruct missing objects. The checkbox supports comparison. Reference aerial mode still uses the untouched saved source image; switch surface detail off for an unmodified material comparison.

# Iteration 05 — missing roof sections recovered

The previous whole-footprint overlap rejection dropped missing wings and adjoining roofs when a Microsoft footprint partially overlapped OSM. Completely-inside-tile filtering also discarded boundary-crossing buildings.

This correction re-examines the full local Microsoft extract, subtracts the already covered ground area, clips to the terrain boundary, protects mapped roads and retains usable remaining polygons. Thin slivers and tiny fragments are excluded. It adds 156 separate volumes covering approximately 68,740 square metres, bringing the scene to 1200 volumes. They are sections, not 156 independently verified whole buildings.

**Inspect recovered roof gaps** moves to the repaired Rabat area and highlights these sections in orange. **Highlight recovered sections** can also be used while exploring elsewhere. The source footprints and inferred heights still need visual review. Buildings missing from both OSM and Microsoft's source can remain flat; this change does not prove 100% image coverage.

The prepared component cache was rebuilt: about 1.16 MB compressed, 91,618 component instances. One inferred tree candidate intersecting a recovered roof was removed. Collision, non-overlap, finite geometry, boundary clipping, cache checks and browser rendering passed; the entire map has not been exhaustively traversed.

To reproduce this correction from the preserved iteration-04 snapshot, run build-roof-recovery.py (NumPy, Pillow, Shapely), then node prepare-architecture.mjs. The script uses data/buildings-v4.json and data/source-roof-gap-candidates.json, and retains the clipped feature provenance. The prior build-enrichment.py stage alone rebuilds iteration 04; run roof recovery afterward to retain this fix. Newer source surveys would require regenerating the candidates and baseline instead of reusing these snapshots.

roof-recovery-audit.jpg shows the pre-fix candidate regions over the unmodified aerial source; orange in the live scene shows the actual accepted recovered volumes after road exclusions and sliver filtering. Source licensing and attribution are unchanged.

---

# Deliverance — satellite + procedural world, iteration 04

The default now uses cleaned 2018 aerial photography with a PS2-inspired palette, geometric buildings and geometric trees. Earlier solid-colour and source-image modes remain available for comparison. No Google key or live map service is required for this world.

## Added coverage

- 657 OSM building outlines retained.
- 387 additional Microsoft ML footprints added where overlap with OSM is at most 12% of the proposed footprint. The sources are different dates; additions are inferred and can contain errors.
- 1044 extruded buildings in total. No claim of complete roof coverage: missing and mismatched buildings may remain.
- 54 explicitly mapped OSM trees, with assumed size.
- 484 optional inferred tree crowns from conservative image-colour and DSM-minus-ground maxima, excluding building buffers and mapped roads. These are unvalidated vegetation candidates, not a surveyed inventory. Mapped and inferred trees have separate visibility controls.

## Image treatment

The original 2048 × 2048 image stays untouched. An offline pass downsamples to 1024 × 1024, applies a small median filter and mild colour/contrast adjustment, then quantises to 96 colours before JPEG encoding. JPEG compression introduces intermediate colours. This removes fine photographic noise; it does not remove every baked shadow, car or rooftop object. Geometry covers mapped roofs; building content is not inpainted into fabricated ground.

The prepared texture uses roughly 75% less base-level GPU texture memory than the original. Startup loads only this prepared image, including the mini-map. The original is loaded lazily if a reference mode is explicitly selected; it remains cached for later comparisons. This texture-size comparison is not a measurement of total game memory.

## Runtime efficiency

Architecture is generated offline by prepare-architecture.mjs, not recomputed at game startup. The resulting gzip binary stores ready-made instance transforms and colours. Current cache: 88,528 components, 243 spatial batches, about 1.12 MB transfer compressed / 6.73 MB instance data uncompressed. JSON metadata accompanies it. These are file/data sizes, not measured whole-game memory or FPS.

At runtime, facade details are distance-filtered in 128-metre cells and frustum-culled by the renderer. Shared box/arch/dome/cylinder geometry uses GPU instancing. Landmark domes remain visible farther away. Tree cells also use instancing and distance filtering. Simplified building masses stay visible beyond facade-detail range. This prototype loads the complete small tile's instance data into memory; true demand-loaded island tiles remain a next step. A generation fallback exists if the prepared cache cannot load.

Large inferred rural footprints use utility-building rules instead of being filled with apartment windows. Location/road/party-wall rules from iteration 03 remain. Height encoding, landmark proportions and inferred classifications remain provisional. Trees are visual objects and do not yet have dedicated trunk collision.

## Rebuild

1. Install Python packages numpy, pillow, pyproj, shapely and scipy in your chosen environment.
2. Run build-enrichment.py. It uses the included OSM baseline, local Microsoft subset, source terrain, surface grid, original aerial image and OSM map extract. It regenerates the footprint merge, texture and vegetation files. The included subset contains accepted gap-fill candidates, so the overlap-rejection tally will differ from the original full-country scan.
3. Run node prepare-architecture.mjs to refresh the compressed geometry cache after changing footprints or procedural rules.
4. Start python -m http.server 8777 --bind 127.0.0.1 in this folder and open http://127.0.0.1:8777/index.html.

world-audit.json records the counts and known completeness limitation. OSM is preferred over inferred footprints; classification and height provenance remain in the feature data. geometry.json-style feature edits should be made in buildings.json, then the architecture cache rebuilt.

## Additional attribution

Microsoft Global ML Building Footprints, Malta, catalogue dated 13 August 2026. Source geometry is licensed under CDLA Permissive 2.0. Local accepted source features are included in data/microsoft-local.geojson. Combined OSM-derived data retains ODbL attribution; original government rasters retain their existing attribution.

- https://github.com/microsoft/GlobalMLBuildingFootprints
- https://cdla.dev/permissive-2-0/
- https://bfppub.blob.core.windows.net/%24web/2026-08-13/dataset-links.csv

Prior iteration notes follow for source acquisition and architectural rules. This iteration 04 section takes precedence for current behaviour and counts.

---

# Deliverance — procedural Malta, iteration 03

## Current implementation

The default world now uses geometric facade components and solid-colour ground. Building facades have no textures or procedural window shader. Aerial imagery is retained only as an optional reference view and mini-map; the default terrain's vertex colours are derived from a simplified six-colour sampling of the imagery.

`architecture.js` holds deterministic building recipes and a reusable component library. Height informs floor count; floor spacing governs opening placement. Recipes distinguish historic Mdina houses, townhouses, apartments, commercial frontages, utilities and churches. Timber/stone colours and balcony style are stable for each building ID. Door placement follows the closest mapped street-facing exposed edge. Garage frontages require a nearby drivable road. Openings are suppressed on party walls using outward footprint probes. This is a heuristic, not a complete parcel/visibility analysis.

Geometry components include framed windows, sills, shutters, doors with optional arches, open rail balconies, enclosed Maltese-style balconies, cornices, parapets, shop glazing/signboards/canopies, segmented garage doors and roof tanks. Openings are shallow inserts over solid masses: they do not yet cut traversable door/window holes. Signboards carry colour rather than text. Corner treatments, setbacks, entrances and landmark proportions need further refinement.

Mapped city-wall polylines become separate retaining-wall and parapet components with no house windows. Churches receive dedicated doors, high windows, domes and optional twin towers rather than residential balconies. These are explicitly stylised interpretations, not surveyed reproductions of the cathedral. Roof and tower placement may need per-landmark parameters. Inferred building heights remain provisional.

`roads.js` creates solid-colour ribbons from 821 mapped road/path ways, conformed to the existing terrain. Widths are class defaults, not surveyed kerb-to-kerb widths. Bridges, level crossings and junction joins are not specially resolved. Terrain collision and footprint collision remain simplified; mapped city-wall collision is also included. Facade details do not individually collide with the rider.

All building bodies are batched. Details use four instanced primitive meshes (boxes, arches, domes and cylinders). The source buildings, source-derived height estimates and licences are unchanged. `build-context.py` regenerates world-context.json from the included OSM extract and aerial image (requires Python, Pillow, NumPy, pyproj). No live external data service or API key is required to run this procedural prototype.

Use **Inspect Mdina buildings** and **Inspect street components** to compare distances. Toggle **Geometric facade elements** to inspect the underlying masses. The initial view now focuses on Mdina. The separate coverage.html explorer remains available and still needs a Google API key.

Validation includes finite geometry/instance transforms, deterministic recipes, no church garages/balconies, no fortification windows/house doors, mapped parapet generation, clear delivery spawn, and basic collision/toggle behaviour. Browser rendering, close-up controls and detail toggling were checked. Full street traversal and every facade are not exhaustively verified.

The notes below describe the underlying geographic data and earlier prototype. Where visual behaviour differs, this iteration 03 section takes precedence.

---

## Underlying data and iteration 02 notes

Browser-native foundation: Three.js, JavaScript modules and local map files. No runtime framework, cloud dependency or Unity requirement. PS-style colour crush is the default. All rendering assets are bundled locally.

## Run

From this folder run `python -m http.server 8777 --bind 127.0.0.1`, then open http://127.0.0.1:8777/. Directly opening index.html will fail due to browser restrictions on local modules/data.

WASD/arrows to drive; Shift to boost; V for riding/overview; R to reset. Drag to orbit, wheel to zoom overview. **Inspect Mdina buildings** gives a close view. The building checkbox hides geometry and disables building collision for comparison. Graphics controls affect terrain; buildings retain their PS-style material.

## Geographic data

2 × 2 km around Mdina and Rabat. EPSG:32633 extent: E445500–447500, N3970500–3972500. Local X is east, Z south, Y up. Terrain retains GeoTIFF pixel-centre alignment. Resampled grid: 257 × 257 vertices, approximately 7.782 m spacing, 131,072 triangles. The 2048 × 2048 aerial image has approximately 0.977 m pixels. Nominal source resolution differs from prototype mesh resolution.

657 real OpenStreetMap building outlines become separate extrusions, merged into one rendering batch (16,608 triangles). Thirteen courtyard holes are preserved. Incomplete relation rings and boundary-crossing outlines are omitted. OSM coverage is partial. Roofs are flat; windows and stone courses are procedural, not surveyed facades. Sources may reflect different dates.

Height provenance is retained per feature in buildings.json:

| Height source | Count |
|---|---:|
| Explicit mapped height | 3 |
| Mapped floor count × assumed 3 metres | 107 |
| 75th percentile of DSM minus terrain samples within footprint | 505 |
| Assumed 9-metre fallback | 42 |

**Vertical accuracy remains unverified.** The terrain and surface services return unsigned 8-bit grids with ambiguous unit metadata. Surface-derived height differences and absolute elevations are provisional. Confirm original float elevation data, vertical datum and control points before calling these measured building heights. Building tops are raised where necessary to clear sloping ground foundations.

## Playability and checks

Ground contact samples the same triangles as the visible mesh. Patch edges and terrain slopes above approximately 35 degrees block movement. Building collision uses a spatial index and footprint checks with 1.2 m wall clearance; courtyard interiors stay open. No building interiors, rooftop traversal, bridge/tree collision or finished motorcycle physics yet. Three manually traced gates give a test route, not an imported road network.

Browser checks: loading, PS shader compilation, close-up building view and building toggle; prior iteration checked movement and first gate. Geometry checks: finite vertices, one building batch, wall collision, clear spawn, outside bounds and visibility-toggle collision behaviour. Full route and every building are not exhaustively traversed.

## Rebuild and development direction

`build-reference.py` rebuilds terrain.json and OBJ from untouched source-terrain.tif (Python, NumPy, Pillow).

`build-buildings.py` rebuilds buildings.json from data/osm-map.xml and data/surface.tif (Python, NumPy, Pillow, pyproj). Install these packages in your preferred local Python environment. All source files needed for rebuilding are included.

Keep game layers separate: geographic locations/roads, vehicle movement, orders/economy, simulation clock/calendar, event scheduling, UI and saves. Recreate the rules and data of the existing C# managers as JavaScript/TypeScript modules; Unity MonoBehaviour code cannot run directly here. Add a physics library when handling needs it. Streamable tiles and distance-based geometry are the next scaling step.

The first iteration's Unity utility is a historical workspace artifact and is excluded from the iteration 02 archive. OBJ export is engine-neutral and optional.

## Attribution and downloads

Contains adapted Malta Planning Authority DTM 2018, DSM 2018 and Orthoimagery 2018, downloaded 17 October 2026. Changes: crop, resampling, coordinate conversion, geometry generation, provisional height estimation and visual styling.

- https://portal.data.gov.mt/dataset/digital-terrain-model-2018
- https://portal.data.gov.mt/dataset/digital-surface-model-2018
- https://portal.data.gov.mt/dataset/orthoimagery-2018
- Catalogue CC BY 4.0 listing: https://portal.data.gov.mt/en_GB/dataset/?_theme_limit=0&high_value_dataset=true&license_id=CC-BY-4.0&openness_score=3&res_format=wcs
- Licence: https://creativecommons.org/licenses/by/4.0/

© OpenStreetMap contributors. OSM-derived footprint data and adapted database are available under ODbL 1.0; source extract and derived data are included. Government rasters retain their separate attribution. No endorsement is implied.

- https://www.openstreetmap.org/copyright
- https://opendatacommons.org/licenses/odbl/1-0/
- Source map extract: https://api.openstreetmap.org/api/0.6/map?bbox=14.390,35.874,14.424,35.900

Terrain request:
https://malta.coverage.wetransform.eu/dtm_1m_2018/ows?service=WCS&version=2.0.1&request=GetCoverage&coverageId=dtm_1m_2018&subset=E(445500,447500)&subset=N(3970500,3972500)&scaleSize=E(257),N(257)&format=image/tiff

Surface request:
https://malta.coverage.wetransform.eu/dsm_1m_2018/ows?service=WCS&version=2.0.1&request=GetCoverage&coverageId=dsm_1m_2018&subset=E(445500,447500)&subset=N(3970500,3972500)&scaleSize=E(1025),N(1025)&format=image/tiff

Aerial request:
https://malta.coverage.wetransform.eu/wms/ortho_2018/ows?service=WMS&version=1.3.0&request=GetMap&layers=OI.OrthoimageCoverage&styles=&crs=EPSG:32633&bbox=445500,3970500,447500,3972500&width=2048&height=2048&format=image/jpeg

Three.js 0.170.0 (MIT; licence bundled): https://github.com/mrdoob/three.js/tree/r170

## Street overlay
Actual names from the existing OpenStreetMap road data: 180 distinct mapped names in this tile. One transparent canvas draws up to 36 decluttered labels at about 11 updates per second, using precomputed anchors, road-class priority, distance filtering and sampled terrain occlusion. No external map tiles, text geometry or font downloads. Names are cartographic labels and may overlay buildings; source omissions remain unnamed. Optional road-surface highlighting uses the existing batched terrain-following mesh. Use Actual street names and Highlight road surfaces in the workbench.

## Satellite detail and road materials update
Procedural satellite detail mode now retains the full saved original aerial colour detail with mild palette treatment and filtered sampling, under the metre-scaled surface shader. It no longer uses the coarse 65 by 65 colour grid. Baked photographic shadows remain.

Road data now preserves OSM surface, width, lanes, oneway, lane_markings, maxspeed and motor_vehicle tags. There are 482 explicitly tagged surfaces and 339 inferred surfaces among the 821 road ways. Tagged paving stones/setts use staggered joints, asphalt uses aggregate grain, and concrete/unsealed surfaces receive separate colours. Unknown surfaces use road-class defaults, not image segmentation. A single batched ribbon mesh follows the terrain with joined corners.

37 eligible two-way asphalt ways receive inferred dashed centre lines. Narrow streets, pedestrian routes, unsealed roads, one-way roads and explicit lane_markings=no receive no centre stripe. Markings fade at way endpoints; junction topology, exact marking layouts, stop lines, crossings, lane arrows and kerbs are not reconstructed yet. Source geometry alignment and default widths can still cause overlaps. Inspect road materials opens a close road view. Markings are shader detail, not additional meshes.

## Road/terrain intersection fix
Road faces are now clipped against the same terrain grid triangles used for rendering. Each clipped vertex receives the exact terrain height plus 0.04 m; interpolated road coordinates preserve surface patterns and markings. This replaces broad faces that bridged terrain ridges. Generation happens once during loading and retains a single batched road mesh. The resulting road mesh has 216,457 triangles. Automated checks at four interior points per face found clearance between 0.039957 and 0.040035 m, with finite texture coordinates.
