Files
Battle-for-Tismo/shared/game_state/visibility.js
T
adrien 0d1995c0bb Stopped a tile-work rebuild from discarding every cached trade walk
The trade graph's cache stamp mixed in the tile-improvement version, which a mine, mill or plant rebuilt in place bumps many times a day, so every city's sea-lane flood was thrown away and rebuilt. The stamp now keys on the transport network, the territory and the storage-node set alone: over ten days the rebuilds fall from hundreds to the 45 the world needs, and the daily resource tick from about 700 ms to about 150 ms.

The snapshot path is memoised against the counters that actually move it -- the per-civ headline population and GDP, the per-city building and per-nation government/research modifiers, the port and food bonuses, the road and railway tile counts and the ethnic make-up rebuild only when their version changes; the flattened visible set is cached alongside explored; and the HUD upkeep sums without building the labelled budget breakdown. DELTA_COLLECTIONS had drifted from the snapshot's key names (it listed gdpBaseline while the state ships productionBaseline, and tileEthnicity while the version is ethnicity), so the static baseline was re-sent as JSON on every broadcast; the collection/version pairs are now explicit.

Together a broadcast falls from roughly 60 ms to 18 ms and the bytes for fifty of them from 57 MB to 24 MB, and 200 in-game hours simulate in about a third of the time. regionTiles caches the per-city owned lists behind the daily economy walks, and supplier reserves read a region's headcount without deriving its GDP; getCityEconomy itself stays uncached outside a snapshot so a simulation read still reflects a direct population edit at once.
2026-09-23 22:22:50 +02:00

119 lines
4.2 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);
},
// The viewer's currently visible tiles, flattened for the wire. The whole
// visible set is rebuilt only on a visibility refresh, which bumps
// `_visibleVersion`, so an unchanged fog is not re-walked on every broadcast.
_cachedVisible(civ) {
const cached = this._serializedVisible.get(civ);
if (cached && cached.version === this._visibleVersion) return cached.value;
const value = serializeSet(this.visible.get(civ) || new Set());
this._serializedVisible.set(civ, { version: this._visibleVersion, value });
return value;
},
_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;
},
};