Update tradeship spawn & gold meta (#3232)

## Description:

Now that pathfinding is much more efficient with hpa*, we can add more
trade ships.

This PR does the following:

1. No gold bonus for additional ports, keeps the meta simple
2. cut the gold per trade ship roughly in half.
3. Use a "pity bonus", the more times a port has failed to spawn a
tradeship, the higher the likelyhood it will spawn one
4. Increase the sigmoid so the mid-point is 200, with a half life of 50.
In tests about ~400 trade ships max.

It's pretty difficult to balance on singleplayer so I'm sure the values
will be adjusted after playtests.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

evan
This commit is contained in:
Evan
2026-02-17 21:40:20 -06:00
committed by GitHub
parent 7bf6bfd4b9
commit 2b830e9fcd
4 changed files with 17 additions and 47 deletions
+10 -33
View File
@@ -297,52 +297,29 @@ export class DefaultConfig implements Config {
return 120;
}
tradeShipGold(dist: number, numPorts: number): Gold {
tradeShipGold(dist: number): Gold {
// Sigmoid: concave start, sharp S-curve middle, linear end - heavily punishes trades under range debuff.
const debuff = this.tradeShipShortRangeDebuff();
const baseGold =
100_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 100 * dist;
const numPortBonus = numPorts - 1;
// Hyperbolic decay, midpoint at 5 ports, 3x bonus max.
const bonus = 1 + 2 * (numPortBonus / (numPortBonus + 5));
50_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 50 * dist;
const multiplier = this.goldMultiplier();
return BigInt(Math.floor(baseGold * bonus * multiplier));
return BigInt(Math.floor(baseGold * multiplier));
}
// Probability of trade ship spawn = 1 / tradeShipSpawnRate
tradeShipSpawnRate(
tradeShipSpawnRejections: number,
numTradeShips: number,
numPlayerPorts: number,
numPlayerTradeShips: number,
): number {
// Geometric mean of base spawn rate and port multiplier
const combined = Math.sqrt(
this.tradeShipBaseSpawn(numTradeShips, numPlayerTradeShips) *
this.tradeShipPortMultiplier(numPlayerPorts),
);
const decayRate = Math.LN2 / 50;
return Math.floor(25 / combined);
}
// Approaches 0 as numTradeShips increase
const baseSpawnRate = 1 - sigmoid(numTradeShips, decayRate, 200);
private tradeShipBaseSpawn(
numTradeShips: number,
numPlayerTradeShips: number,
): number {
if (numPlayerTradeShips < 3) {
// If other players have many ports, then they can starve out smaller players.
// So this prevents smaller players from being completely starved out.
return 1;
}
const decayRate = Math.LN2 / 10;
return 1 - sigmoid(numTradeShips, decayRate, 55);
}
// Pity timer: increases spawn chance after consecutive rejections
const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);
private tradeShipPortMultiplier(numPlayerPorts: number): number {
// Hyperbolic decay function with midpoint at 10 ports
// Expected trade ship spawn rate is proportional to numPlayerPorts * multiplier
// Gradual decay prevents scenario where more ports => fewer ships
const decayRate = 1 / 10;
return 1 / (1 + decayRate * numPlayerPorts);
return Math.floor((100 * rejectionModifier) / baseSpawnRate);
}
unitInfo(type: UnitType): UnitInfo {