# Material-only environmental upgrade

Ready to integrate into the existing `dist` folder:

1. Copy `environment-materials.js` beside `environment.js`.
2. Copy the proposed `environment.js` and `presentation.js`, or apply `environment-integration.patch` to those two files. The patch is against the existing v3.5 copies. No `main.js` change is required.
3. Copy `terrain-surface-mask.png` and `terrain-surface-mask.json` into `dist/assets/environment/`.
4. Keep existing photograph textures, HDR, Water vendor module, and terrain files. The new module uses their existing paths.
5. Preserve the OpenStreetMap attribution already in the game's credits. The material-mask source is ODbL, as documented below.

## Ocean

Replaces the Water fragment material while retaining the existing plane at sea level, reflection target, `time`, `sunDirection`, `sunColor`, and `waterColor` API. It adds three moving normal-texture scales (roughly 27 m, 5.8 m, and 1.6 m repeats), with different rotations and drift velocities. Fine detail fades with distance to avoid shimmer. All coherent analytic sine/cosine wave slopes and color bands have been removed following root-browser visual feedback; only irregular texture-based variation remains, and distant slope strength fades between 250 m and 2200 m. Texture mipmaps filter the surface into a calmer horizon. Fresnel starts at 0.0204 and blends a blue/teal water-medium color with the actual reflected scene. Small, bounded sun highlights replace the old multiplication of an entire bright reflection by a broad specular lobe. No guessed bathymetry, artificial lagoons, coastline edits, or added water geometry are used.

The reflection wrapper checks `scene.overrideMaterial` before any capture or cadence update. Thus SSAO normal/depth passes cannot contaminate the reflection. It updates the eye uniform every beauty pass and captures one in three beauty passes. Existing raw Water callback must remain unwrapped until `improveCaribbeanWater` is called: the proposed environment file removes the old wrapper. `water.userData.caribbeanMaterial.getStats()` reports captures and skipped override passes for browser QA.

The three old water adjustments are removed from `createAtmosphere` so it cannot overwrite the installed ocean material settings. Atmosphere/cloud behavior is otherwise untouched.

## Terrain

The new 2048 × 1280 RGB mask is 340,818 bytes, aligned to the existing orthophoto world bounds. At runtime it is sampled in world coordinates, so it uses exactly the same mask across the core, Morro, and Cristóbal meshes. It changes material classification only. No vertex, index, UV, height, measured position, coastline, physics, or collision data is modified.

The old color threshold is removed. Explicit mapped grass/meadow/scrub/wood polygons provide vegetation coverage. Hard-surface polygons and existing road corridors clear grass; mapped bare rock selects rock. Buildings, water features, and cemetery areas are excluded. Broad park or national-park boundaries are never assumed to be all grass. Unknown cover retains the aerial image, with restrained fine surface modulation instead of becoming beige concrete nearby.

The cathedral-front follow-up adds a conservative **0.115 linear-luminance floor** to dark aerial albedo near the camera. This removes black building/tree shadows baked into the photograph before the game's current lighting is applied. The recovery fades out from 45 to 260 m, leaves already brighter photograph values unchanged, and is disabled on mapped vegetation and rock. Existing photo chromatic variation, material grain, and normal detail remain. No new mask polygon or geometry is added.

Two large actual OSM lawn polygons, ways **410617402** and **410617405**, classify the El Morro esplanade. Additional El Morro lawn ways **410617400** and **410617401** are included. Vegetation classification persists at long distance; only fine photographic grain and normals fade. The existing Poly Haven turf, concrete, and stone textures are sampled at their documented 2.51 m, 2.08 m, and 1.36 m tile scales. Normal maps add fine lighting relief without displacement. Turf albedo varies subtly at broad scales using original procedural noise, so repeated tiles do not dominate. Roughness is 1 and IBL response is reduced for a matte ground surface.

Performance: no new meshes or draw calls; one extra mask texture. Near material texture fetches are selected by cover and distance. Distant terrain skips the fine normal and ground-grain samples. Ocean uses three normal samples and a single reflection sample, compared with four normal samples in the vendor material.

## Sources and provenance

- [OpenStreetMap copyright / ODbL](https://www.openstreetmap.org/copyright).
- Geometry retrieved from `https://overpass-api.de/api/interpreter`; exact query is `osm-surface-query.txt`, original response `osm-surface-source.json`, timestamp and way IDs are in `terrain-surface-mask.json`. The atlas also uses the already supplied OSM roads/buildings. The generator is `build-surface-mask.py`.
- [OSM grass tag definition](https://wiki.openstreetmap.org/wiki/Tag:landuse%3Dgrass).
- [NPS El Morro sod replacement notice](https://www.nps.gov/saju/learn/news/updadate-san-juan-national-historic-site-will-begin-sod-replacement-on-grounds-of-castillo-san-felipe-del-morro.htm) independently identifies grass acreage on the esplanade. This is supporting context, not the geometric source of the mask.
- Existing texture and water sources are unchanged; see the existing Poly Haven CC0 provenance and Three.js MIT license.
- Original material/shader code and procedural noise were written for this project.

## Verification

`node validate-environment.mjs` passes tests against the actual bundled Three.js version: shader replacement hooks, resolution of every shader include, mask world coordinates, matte material settings, preservation of per-frame eye updates, exactly three reflection captures across seven beauty passes, and zero reflection captures/cadence increments across seven override passes. It also tests idempotent setup and callback restoration. `environment-validation.json` records the results.

`mask-validation.json` verifies both large El Morro lawn interior points classify as grass and Plaza de Armas does not. It also records the SHA-256 values of every existing terrain mesh; the generator only reads those files. All proposed modules pass `node --check`.

**GPU follow-up:** the root browser exposed `patch` as a reserved GLSL identifier. The source and test fixture now use `lawnVariation`; the test explicitly prevents reintroducing a `patch` variable. Root reported successful GPU rendering after that rename. The follow-up ocean revision removes the long coherent sine-wave contours observed in the high city view and tests that no sine/cosine function remains in the ocean shader.

**Incremental integration:** root now has v4.2 imports and other integration edits. For the wave-band/GLSL follow-up, copy **only `environment-materials.js`**; do not overwrite root's current `environment.js` or `presentation.js` with the initial proposals above.

The same module-only integration applies to the local aerial-shadow recovery revision (material cache key `osm-ground-cover-v4-4`). Regression tests verify recovery of a near-black sample, no change to a bright sample, and no recovery at long distance or on mapped grass/rock.

**Remaining visual QA:** verify this ocean revision in the high city view, then inspect an ocean view in all three lighting modes. The root browser was intentionally left untouched by this asset task. Static shader-hook tests do not substitute for GPU compilation or visual inspection.
