make spawn glow follow the player's currently selected spawn tile

Plumb spawnTile through PlayerUpdate / PlayerState / applyStateUpdate
so the WebGL spawn overlay can read it directly. The glow was reading
nameData.x/y (territory centroid for label placement) which only
recomputes every 2 ticks and only when largestClusterBoundingBox has
been updated by PlayerExecution — both lag the player's actual spawn
click. Using spawnTile updates the same tick setSpawnTile() fires.

Also adds spawnTile to diffPlayerUpdate / applyStateUpdate so changes
after the initial full snapshot actually propagate (the recent
diff-only PlayerUpdate path silently dropped any field not enumerated
in those helpers).
This commit is contained in:
evanpelle
2026-05-18 19:15:01 -07:00
parent 0eb8578996
commit 17e3ac4b05
6 changed files with 14 additions and 3 deletions
+7 -3
View File
@@ -91,7 +91,8 @@ export class WebGLFrameBuilder {
const centers: SpawnCenter[] = [];
for (const p of gameView.players()) {
if (!p.isPlayer() || p.type() !== PlayerType.Human) continue;
if (!p.hasSpawned()) continue;
const spawnTile = p.state.spawnTile;
if (spawnTile === undefined) continue;
const isSelf = me !== null && p.smallID() === me.smallID();
// myPlayer reads as plain white so the local-player ring is visually
// distinct from any team color; everyone else uses their territory tint.
@@ -99,8 +100,11 @@ export class WebGLFrameBuilder {
? { r: 255, g: 255, b: 255 }
: p.territoryColor().toRgb();
centers.push({
x: p.nameData?.x ?? 0,
y: p.nameData?.y ?? 0,
// spawnTile tracks the player's currently-selected spawn directly —
// updates the same tick the player picks a new location (faster than
// the nameData centroid which only refreshes every 2 ticks).
x: gameView.x(spawnTile),
y: gameView.y(spawnTile),
r: c.r / 255,
g: c.g / 255,
b: c.b / 255,
+2
View File
@@ -61,6 +61,8 @@ export interface PlayerState {
traitorRemainingTicks: number;
betrayals: number;
hasSpawned: boolean;
/** TileRef the player picked as their spawn (undefined if not yet spawned). */
spawnTile?: number;
lastDeleteUnitTick: number;
allies: number[];
embargoes: number[];
+1
View File
@@ -82,6 +82,7 @@ function stateFromUpdate(pu: PlayerUpdate): PlayerState {
traitorRemainingTicks: Math.max(0, pu.traitorRemainingTicks ?? 0),
betrayals: pu.betrayals!,
hasSpawned: pu.hasSpawned!,
spawnTile: pu.spawnTile,
lastDeleteUnitTick: pu.lastDeleteUnitTick!,
allies: pu.allies!.slice(),
embargoes: [],
+2
View File
@@ -43,6 +43,7 @@ export function diffPlayerUpdate(
prev.traitorRemainingTicks === next.traitorRemainingTicks,
);
setIfDifferent("hasSpawned", prev.hasSpawned === next.hasSpawned);
setIfDifferent("spawnTile", prev.spawnTile === next.spawnTile);
setIfDifferent("betrayals", prev.betrayals === next.betrayals);
setIfDifferent(
"lastDeleteUnitTick",
@@ -97,6 +98,7 @@ export function applyStateUpdate(target: PlayerState, pu: PlayerUpdate): void {
}
if (pu.betrayals !== undefined) target.betrayals = pu.betrayals;
if (pu.hasSpawned !== undefined) target.hasSpawned = pu.hasSpawned;
if (pu.spawnTile !== undefined) target.spawnTile = pu.spawnTile;
if (pu.lastDeleteUnitTick !== undefined) {
target.lastDeleteUnitTick = pu.lastDeleteUnitTick;
}
+1
View File
@@ -198,6 +198,7 @@ export interface PlayerUpdate {
outgoingAllianceRequests?: PlayerID[];
alliances?: AllianceView[];
hasSpawned?: boolean;
spawnTile?: TileRef;
betrayals?: number;
lastDeleteUnitTick?: Tick;
isLobbyCreator?: boolean;
+1
View File
@@ -202,6 +202,7 @@ export class PlayerImpl implements Player {
}) satisfies AllianceView,
),
hasSpawned: this.hasSpawned(),
spawnTile: this._spawnTile,
betrayals: this._betrayalCount,
lastDeleteUnitTick: this.lastDeleteUnitTick,
isLobbyCreator: this.isLobbyCreator(),