From 6c749472e8eeebd4dc6f86a845ce171250a44a1a Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Thu, 26 Feb 2026 00:37:05 +0100 Subject: [PATCH] perf(client): stop train motion plans from churning when finished MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guard train car/engine derived updates behind tile changes and stop advancing once the cursor is clamped at the last path tile. Expire settled train plans defensively to avoid stale motion-planned units being treated as “updated” every tick, even though trains currently also clear plans via their isActive=false despawn Unit update. --- src/core/game/GameView.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index fe5c1224e..55e5df631 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -848,6 +848,7 @@ export class GameView implements GameMap { } const path = plan.path; + const lastIndex = path.length - 1; const cap = plan.usedTilesBuf.length; const pushUsed = (tile: TileRef) => { @@ -869,13 +870,17 @@ export class GameView implements GameMap { return plan.usedTilesBuf[idx] as TileRef; }; + let didMove = false; for (let step = 0; step < steps; step++) { const cursor = plan.cursor; + if (cursor >= lastIndex) { + break; + } for (let i = 0; i < plan.speed && cursor + i < path.length; i++) { pushUsed(path[cursor + i] as TileRef); } - plan.cursor = Math.min(path.length - 1, cursor + plan.speed); + plan.cursor = Math.min(lastIndex, cursor + plan.speed); for (let i = plan.carUnitIds.length - 1; i >= 0; --i) { const carId = plan.carUnitIds[i] >>> 0; @@ -888,22 +893,33 @@ export class GameView implements GameMap { const tile = usedGet(carTileIndex); if (tile !== null) { const oldTile = car.tile(); - car.applyDerivedPosition(tile); if (tile !== oldTile) { + car.applyDerivedPosition(tile); this.unitGrid.updateUnitCell(car); + didMove = true; } } } const newEngineTile = path[plan.cursor] as TileRef; const oldEngineTile = engine.tile(); - engine.applyDerivedPosition(newEngineTile); if (newEngineTile !== oldEngineTile) { + engine.applyDerivedPosition(newEngineTile); this.unitGrid.updateUnitCell(engine); + didMove = true; } } plan.lastAdvancedTick = currentTick; + + // Preserve the final-step redraw (plan remains for the tick where motion ends), + // then clear once the train has settled and no longer moves. + // Note: trains are currently deleted at the end of TrainExecution, and the ensuing + // `Unit` update (isActive=false) also clears any associated motion plan records. + // This expiry is defensive to avoid keeping stale plans around if that behavior changes. + if (!didMove && plan.cursor >= lastIndex) { + staleEngineIds.push(engineId); + } } for (const engineId of staleEngineIds) {