From 58e8a5fabdf859450f3925e77ca8f6d7337ef814 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 18 Jun 2026 13:43:09 -0700 Subject: [PATCH] Fix ocean color change reverting nuke-created water to land (#4343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Changing the ocean/water color in **Graphics settings** repaints the terrain — and any water tiles created by water nukes (land → water) snap back to their original land appearance. ## Root cause `TerrainPass` captures the `terrainBytes` buffer at construction and reuses it in two places: - `setOceanColor()` does a **full** terrain texture re-upload from `terrainBytes` when the ocean color changes. - `applyTerrainDelta()` applies live land→water nuke conversions, but only wrote to the **GPU texture** — never back into `terrainBytes`. So the CPU buffer stayed frozen at the map's original terrain. Changing the ocean color rebuilt the whole texture from that stale buffer, reverting every nuke crater to land. ## Fix Write each delta byte back into `terrainBytes` inside `applyTerrainDelta()`, so the buffer stays the live source of truth and full re-uploads reflect conversions. ```ts this.terrainBytes[ref] = bytes[i]; ``` The indexing already lines up — `terrainBytes` is indexed by linear ref (`y * mapW + x`), the same `ref` the delta loop iterates. The buffer is only otherwise read once at construction by `RailroadPass`/`TerrainPass` to seed GPU textures (which copy), so mutating it has no side effects elsewhere. ## Testing The WebGL passes have no unit-test harness (they need a live GL context), so this isn't covered by an automated test. Verified by reasoning through the data flow; can confirm in-game by nuking land into water and then changing the ocean color. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) --- src/client/render/gl/passes/TerrainPass.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/render/gl/passes/TerrainPass.ts b/src/client/render/gl/passes/TerrainPass.ts index 1df627b8c..9e1050c3d 100644 --- a/src/client/render/gl/passes/TerrainPass.ts +++ b/src/client/render/gl/passes/TerrainPass.ts @@ -91,6 +91,9 @@ export class TerrainPass { * nuke). `bytes[i]` is the new terrain byte for `refs[i]` (parallel arrays). * One 1×1 texSubImage2D per ref — fine for the small bursts a single nuke * produces. + * + * Also writes back into `terrainBytes` so a later full re-upload (e.g. + * setOceanColor) reflects these conversions instead of reverting them. */ applyTerrainDelta(refs: readonly number[], bytes: Uint8Array): void { if (refs.length === 0) return; @@ -101,6 +104,7 @@ export class TerrainPass { const ref = refs[i]; const x = ref % this.mapW; const y = (ref - x) / this.mapW; + this.terrainBytes[ref] = bytes[i]; encodeTerrainTile(bytes[i], this.pixelScratch, 0, this.oceanColor); gl.texSubImage2D( gl.TEXTURE_2D,