Files
Battle-for-Tismo/shared/game_state/visibility.js
T
adrien b2ab4d76eb Sped up large battles and trimmed snapshot traffic
- The bombardment overlay was rebuilt every animation frame, and each barrage
  scanned the whole army to find its target (O(strikes x units)). It now
  redraws at most at the renderer's 24 fps, reuses its SVG/DOM nodes, and
  resolves garrisons from one per-snapshot tile index, so a high-refresh
  display no longer makes a big battle worse.
- Route truncation had the same shape: every step of every route rescanned
  every unit. The snapshot now buckets the army by tile once.
- Idle units no longer have identical left/top rewritten every frame, and the
  whole-map fog buffer is rebuilt less often.
- Snapshot traffic: cityStats and visible are delta-encoded like territory and
  population, city stats carry a content-hash version, tile ethnicity is
  memoised, and a city's economy is derived once per broadcast instead of
  twice. A per-peer civ change now forces a full snapshot, since the visible
  set is per-viewer.
2026-09-21 02:23:04 +02:00

108 lines
3.7 KiB
JavaScript

// Fog of war and the grid walkers. Explored cells stay known; visible cells
// are the ones currently watched by territory and units.
import { key, parseKey } from "../hex.js";
import { UNIT_VISION, TERRITORY_VISION } from "./constants.js";
import { tileImprovementById } from "../data/improvements.js";
import { serializeSet } from "./helpers.js";
export const visibilityMethods = {
isExplored(civ, coords) {
const set = this.explored.get(civ);
return set ? set.has(key(coords.x, coords.y)) : false;
},
isVisible(civ, coords) {
const set = this.visible.get(civ);
return set ? set.has(key(coords.x, coords.y)) : false;
},
canEnter(civ, proto, coords) {
if (!proto || !this.isExplored(civ, coords)) return false;
const tile = this.tiles[key(coords.x, coords.y)];
if (!tile) return false;
return proto.traversableTerrains.includes(tile.terrainClass);
},
_refreshVisibility() {
this.visible = new Map();
for (let i = 0; i < this.civilisations.length; i++) this.visible.set(i, new Set());
for (const [civ, cells] of this._territoryByCiv) {
for (const coords of cells) this._reveal(civ, coords, TERRITORY_VISION);
}
for (const unit of this.units) {
const proto = this.unitProto(unit);
this._reveal(unit.civ, unit.coords, (proto && proto.vision) || UNIT_VISION);
}
// Fortifications and radar towers watch their own neighbourhood too.
for (const [k, id] of this.tileImprovements) {
const proto = tileImprovementById(id);
if (!proto || !proto.vision) continue;
const owner = this.tileImprovementOwner.get(k);
if (owner === undefined || owner < 0) continue;
this._reveal(owner, parseKey(k), Math.max(UNIT_VISION, proto.vision));
}
// The visible sets only move here, so this counter lets a delta snapshot
// omit the (large) per-viewer visible list until the next refresh.
this._visibleVersion += 1;
},
_revealAll() {
for (let i = 0; i < this.civilisations.length; i++) {
this.explored.set(i, new Set(Object.keys(this.tiles)));
}
},
_reveal(civ, center, radius) {
if (!this.explored.has(civ)) this.explored.set(civ, new Set());
if (!this.visible.has(civ)) this.visible.set(civ, new Set());
const exploredCells = this.explored.get(civ);
const visibleCells = this.visible.get(civ);
let discovered = false;
for (const coords of this._cellsInRadius(center, radius)) {
const k = key(coords.x, coords.y);
if (!exploredCells.has(k)) {
exploredCells.add(k);
discovered = true;
}
visibleCells.add(k);
}
if (discovered) this._serializedExplored.delete(civ);
},
_cachedExplored(civ) {
if (!this._serializedExplored.has(civ)) {
this._serializedExplored.set(civ, serializeSet(this.explored.get(civ) || new Set()));
}
return this._serializedExplored.get(civ);
},
_cellsInRadius(center, radius) {
const result = [center];
const visited = new Set([key(center.x, center.y)]);
let frontier = [center];
for (let step = 0; step < radius; step++) {
const next = [];
for (const coords of frontier) {
for (const neighbour of this._neighbours(coords)) {
const k = key(neighbour.x, neighbour.y);
if (visited.has(k)) continue;
visited.add(k);
next.push(neighbour);
result.push(neighbour);
}
}
frontier = next;
}
return result;
},
_neighbours(coords) {
const k = key(coords.x, coords.y);
if (this._neighbourCache.has(k)) return this._neighbourCache.get(k);
const result = this.topology.neighbours(coords.x, coords.y);
this._neighbourCache.set(k, result);
return result;
},
};