From 031a17d8807e2a34cf95ada6c3a95e27a90a4502 Mon Sep 17 00:00:00 2001 From: DevelopingTom Date: Thu, 19 Feb 2026 00:00:56 +0100 Subject: [PATCH] Optimize nuke explosion (#3176) ## Description: When a nuke detonates, the explosion is looping over each tiles. Among other things, it filters the owner units to find the `TransportShips` units. This operation is cpu-intensive and repeated over each tiles can make the tick noticably slower. On this screenshot the detonation function took 100ms: image image I suspect it led to a few frame freeze when I MIRVed too. Suggestion: loop over the impacted players rather than the tiles. With this suggestion, the same nuke takes between 4ms to 20ms: image However this changes the death formula used, as they were repeated over each tiles with ever-smaller values, and with this change the operation is done all-at-once. This will result in a different outcome. In my opinion the performance gain is seductive enough to maybe tweak the formula to make it work with this revised strategy. What do you think? ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --- src/core/execution/NukeExecution.ts | 82 +++++++++++++++-------------- src/core/game/AttackImpl.ts | 2 +- src/core/game/UnitImpl.ts | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index d35be7a39..b72890da9 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -255,55 +255,59 @@ export class NukeExecution implements Execution { const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type()); const toDestroy = this.tilesToDestroy(); - const maxTroops = this.target().isPlayer() - ? this.mg.config().maxTroops(this.target() as Player) - : 1; - + // Retrieve all impacted players and the number of tiles + const tilesPerPlayers = new Map(); for (const tile of toDestroy) { const owner = this.mg.owner(tile); if (owner.isPlayer()) { owner.relinquish(tile); - owner.removeTroops( - this.mg - .config() - .nukeDeathFactor( - this.nukeType, - owner.troops(), - owner.numTilesOwned(), - maxTroops, - ), - ); - owner.outgoingAttacks().forEach((attack) => { - const deaths = - this.mg - ?.config() - .nukeDeathFactor( - this.nukeType, - attack.troops(), - owner.numTilesOwned(), - maxTroops, - ) ?? 0; - attack.setTroops(attack.troops() - deaths); - }); - owner.units(UnitType.TransportShip).forEach((attack) => { - const deaths = - this.mg - ?.config() - .nukeDeathFactor( - this.nukeType, - attack.troops(), - owner.numTilesOwned(), - maxTroops, - ) ?? 0; - attack.setTroops(attack.troops() - deaths); - }); + const numTiles = tilesPerPlayers.get(owner); + tilesPerPlayers.set(owner, numTiles === undefined ? 1 : numTiles + 1); } - if (this.mg.isLand(tile)) { this.mg.setFallout(tile, true); } } + // Then compute the explosion effect on each player + for (const [player, numImpactedTiles] of tilesPerPlayers) { + const config = this.mg.config(); + const tilesBeforeNuke = player.numTilesOwned() + numImpactedTiles; + const transportShips = player.units(UnitType.TransportShip); + const maxTroops = config.maxTroops(player); + // nukeDeathFactor could compute the complete fallout in a single call instead + for (let i = 0; i < numImpactedTiles; i++) { + // Diminishing effect as each affected tile has been nuked + const numTilesLeft = tilesBeforeNuke - i; + player.removeTroops( + config.nukeDeathFactor( + this.nukeType, + player.troops(), + numTilesLeft, + maxTroops, + ), + ); + player.outgoingAttacks().forEach((attack) => { + const deaths = config.nukeDeathFactor( + this.nukeType, + attack.troops(), + numTilesLeft, + maxTroops, + ); + attack.setTroops(attack.troops() - deaths); + }); + transportShips.forEach((unit) => { + const deaths = config.nukeDeathFactor( + this.nukeType, + unit.troops(), + numTilesLeft, + maxTroops, + ); + unit.setTroops(unit.troops() - deaths); + }); + } + } + const outer2 = magnitude.outer * magnitude.outer; for (const unit of this.mg.units()) { if ( diff --git a/src/core/game/AttackImpl.ts b/src/core/game/AttackImpl.ts index 952ed6933..8b5fc811e 100644 --- a/src/core/game/AttackImpl.ts +++ b/src/core/game/AttackImpl.ts @@ -33,7 +33,7 @@ export class AttackImpl implements Attack { return this._troops; } setTroops(troops: number) { - this._troops = troops; + this._troops = Math.max(0, troops); } isActive() { diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 67791b458..79b7c1ed8 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -164,7 +164,7 @@ export class UnitImpl implements Unit { } setTroops(troops: number): void { - this._troops = troops; + this._troops = Math.max(0, troops); } troops(): number { return this._troops;