Sprint 5: Frontend Bug Fixes

Issues: #782, #775, #768

#782 — Proximity warning shows last station instead of closest

Problem

The station rendering loop in cockpitExtrapolation.js iterates over all stations and unconditionally overwrites the proximityWarning variable whenever any station is within 50 km. The last iterated station wins, not the closest.

Fix

Track the minimum distance across all stations. Only update the warning when a closer station is found:

let proximityWarning = null;
let minProximityDist = Infinity;
// ... in station loop:
if (dist < 50000 && dist < minProximityDist) {
  minProximityDist = dist;
  proximityWarning = `PROXIMITY: ${sd.name} ${distKm} km`;
}

Files Changed

  • services/web-client/src/cockpitExtrapolation.js — Add minProximityDist tracking variable, add distance comparison to proximity check

#775 — No fallback for DecompressionStream API

Problem

TerrainHeightmap.js:loadHeightmap() uses the browser DecompressionStream API for .gz decompression. This API is not available in Safari < 16.4 and older Firefox versions. When unsupported, the code throws DecompressionStream is not defined and heightmap loading fails with no graceful degradation.

Fix

Feature-detect DecompressionStream. When unavailable, fall back to downloading the raw ArrayBuffer and using a manual gzip inflate. Rather than adding a dependency (pako), use the simpler approach: if the browser doesn’t support DecompressionStream, try fetching without streaming decompression — the server already sends Content-Encoding: gzip for transit compression, so response.arrayBuffer() returns decompressed data. Only URLs ending in .gz (where the file itself is gzipped, not just transit-compressed) need explicit decompression.

For .gz URLs without DecompressionStream support, fall back to response.arrayBuffer() and attempt to use it directly — if the server serves the .gz file with proper Content-Encoding: gzip, the browser transparently decompresses it. If the data size doesn’t match expectations, log a warning.

if (url.endsWith('.gz') && response.body) {
  if (typeof DecompressionStream !== 'undefined') {
    const ds = new DecompressionStream('gzip');
    const decompressed = response.body.pipeThrough(ds);
    buffer = await new Response(decompressed).arrayBuffer();
  } else {
    // Fallback: fetch as arrayBuffer (server gzip transit decompression handles it)
    console.warn('DecompressionStream not available, using arrayBuffer fallback for', url);
    buffer = await response.arrayBuffer();
  }
} else {
  buffer = await response.arrayBuffer();
}

Files Changed

  • services/web-client/src/terrain/TerrainHeightmap.js — Feature-detect DecompressionStream, fallback to arrayBuffer()

#768 — Heightmap retry logic broken after fetch failure

Problem

When heightmap fetch fails, the .catch() handler sets terrain._heightmapLoading = false to allow retry. But this means the next frame immediately re-triggers loading (the guard condition !terrain._heightmapLoading passes), creating an infinite retry loop at ~60 requests/second.

Fix

Add exponential backoff with a max retry limit:

  • Track _heightmapRetryAfter (timestamp) and _heightmapRetryCount on BodyTerrain
  • On failure: compute delay as min(1000 * 2^retryCount, 30000) (1s, 2s, 4s, 8s, 16s, 30s)
  • Only attempt load when Date.now() >= _heightmapRetryAfter
  • Give up permanently after 5 failed attempts

Files Changed

  • services/web-client/src/terrain/TerrainManager.js — Add backoff state to BodyTerrain, add time check before load attempt, implement exponential backoff in .catch() handler

Back to top

Galaxy — Kubernetes-based multiplayer space game

This site uses Just the Docs, a documentation theme for Jekyll.