From 1535fd2be83c790cbd5ecea8a3f3de400369177f Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 12 Aug 2025 10:34:37 -0700 Subject: [PATCH] update trade freq --- src/core/Util.ts | 8 ++++ src/core/configuration/Config.ts | 2 +- src/core/configuration/DefaultConfig.ts | 49 ++++++++++++++++--------- src/core/execution/PortExecution.ts | 5 ++- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/core/Util.ts b/src/core/Util.ts index 6133b9a187..0ded7f5de3 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -287,3 +287,11 @@ export const flattenedEmojiTable: string[] = emojiTable.flat(); export function replacer(_key: string, value: any): any { return typeof value === "bigint" ? value.toString() : value; } + +export function sigmoid( + value: number, + decayRate: number, + midpoint: number, +): number { + return 1 / (1 + Math.exp(-decayRate * (value - midpoint))); +} diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 263d92f6ed..ff41c4fb73 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -129,7 +129,7 @@ export interface Config { defaultDonationAmount(sender: Player): number; unitInfo(type: UnitType): UnitInfo; tradeShipGold(dist: number, numPorts: number): Gold; - tradeShipSpawnRate(numberOfPorts: number): number; + tradeShipSpawnRate(numTradeShips: number, numPlayerPorts: number): number; trainGold(isFriendly: boolean): Gold; trainSpawnRate(numberOfStations: number): number; trainStationMinRange(): number; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 95af6e07d0..dd04c983c8 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -24,7 +24,7 @@ import { PlayerView } from "../game/GameView"; import { UserSettings } from "../game/UserSettings"; import { GameConfig, GameID, TeamCountConfig } from "../Schemas"; import { NukeType } from "../StatsSchemas"; -import { assertNever, simpleHash, within } from "../Util"; +import { assertNever, sigmoid, simpleHash, within } from "../Util"; import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config"; import { PastelTheme } from "./PastelTheme"; import { PastelThemeDark } from "./PastelThemeDark"; @@ -343,28 +343,41 @@ export class DefaultConfig implements Config { } tradeShipGold(dist: number, numPorts: number): Gold { - const baseGold = Math.floor(50000 + 100 * dist); - const basePortBonus = 0.25; - const diminishingFactor = 0.9; - + const baseGold = Math.floor(50_000 + 100 * dist); + const basePortBonus = 0.5; + const diminishingFactor = 0.8; let totalMultiplier = 1; - for (let i = 0; i < numPorts; i++) { - totalMultiplier += basePortBonus * Math.pow(diminishingFactor, i); + for (let i = 1; i < numPorts; i++) { + totalMultiplier += Math.max( + 0.05, + basePortBonus * Math.pow(diminishingFactor, i - 1), + ); } - return BigInt(Math.floor(baseGold * totalMultiplier)); } - // Chance to spawn a trade ship in one second, - tradeShipSpawnRate(numTradeShips: number): number { - if (numTradeShips < 20) { - return 5; - } - if (numTradeShips <= 150) { - const additional = numTradeShips - 20; - return Math.floor(Math.pow(additional, 0.85) + 5); - } - return 1_000_000; + // Probability of trade ship spawn = 1 / tradeShipSpawnRate + tradeShipSpawnRate(numTradeShips: number, numPlayerPorts: number): number { + // Geometric mean of base spawn rate and port multiplier + const combined = Math.sqrt( + this.tradeShipBaseSpawn(numTradeShips) * + this.tradeShipPortMultiplier(numPlayerPorts), + ); + + return Math.floor(10 / combined); + } + + private tradeShipBaseSpawn(numTradeShips: number): number { + const decayRate = Math.log(2) / 30; + return 1 - sigmoid(numTradeShips, decayRate, 100); + } + + private tradeShipPortMultiplier(numPlayerPorts: number): number { + // Higher number => faster expected number of trade ships levels off + // This decays gradually to prevent the scenario where more ports => fewer trade ships + // Expected number of trade ships is proportional to numPlayerPorts * tradeShipPortMultiplier + const decayRate = 0.1; + return 1 / (1 + decayRate * numPlayerPorts); } unitInfo(type: UnitType): UnitInfo { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 6d3f453514..4e6bb6e7ec 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -78,7 +78,10 @@ export class PortExecution implements Execution { shouldSpawnTradeShip(): boolean { const numTradeShips = this.mg.unitCount(UnitType.TradeShip); - const spawnRate = this.mg.config().tradeShipSpawnRate(numTradeShips); + const numPlayerPorts = this.player.unitCount(UnitType.Port); + const spawnRate = this.mg + .config() + .tradeShipSpawnRate(numTradeShips, numPlayerPorts); for (let i = 0; i < this.port!.level(); i++) { if (this.random.chance(spawnRate)) { return true;