diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index b4a6b2b65..aa14c8f65 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -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, diff --git a/src/client/render/types/Renderer.ts b/src/client/render/types/Renderer.ts index be262a75f..0c99d958d 100644 --- a/src/client/render/types/Renderer.ts +++ b/src/client/render/types/Renderer.ts @@ -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[]; diff --git a/src/client/view/PlayerView.ts b/src/client/view/PlayerView.ts index 771fd23a6..24a8abe25 100644 --- a/src/client/view/PlayerView.ts +++ b/src/client/view/PlayerView.ts @@ -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: [], diff --git a/src/core/game/GameUpdateUtils.ts b/src/core/game/GameUpdateUtils.ts index a4e6d7fdd..e52cd89b8 100644 --- a/src/core/game/GameUpdateUtils.ts +++ b/src/core/game/GameUpdateUtils.ts @@ -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; } diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 55922c9c5..28c831ccc 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -198,6 +198,7 @@ export interface PlayerUpdate { outgoingAllianceRequests?: PlayerID[]; alliances?: AllianceView[]; hasSpawned?: boolean; + spawnTile?: TileRef; betrayals?: number; lastDeleteUnitTick?: Tick; isLobbyCreator?: boolean; diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index be6941910..5fbe7d41b 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -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(),