## Summary
Round 3 of GC-churn reduction (follow-up to #4494 and #4496). All
changes are **behavior-preserving** — final game-state hash unchanged on
three seeded runs.
### Changes
| Site | Change | Churn target |
|---|---|---|
| `MiniMapTransformer` | Path upscaling works in pure numeric
coordinates and emits main-map `TileRef`s directly, replacing three
intermediate `Cell`-object arrays per path (cell path → scaled path →
smoothed path → final map). Identical arithmetic, so identical rounding
and identical tiles. | ~7.1 GB |
| `ShoreCoercingTransformer` | Reused neighbor buffers instead of
`neighbors()` arrays; no per-call `{water, original}` objects.
Tie-breaking preserved (helpers share the unified N,S,W,E order since
#4495). | ~1.5 GB |
| `diffPlayerUpdate` | Allocation-free all-equal fast path. Runs per
player per tick and usually returns `null` (gold/troops/tiles travel via
packed arrays), but previously allocated the diff object + a closure
first. Field list matches the diff exactly. | ~2.6 GB |
| Large-`Set` iteration | `for..of` over a `Set` allocates an
iterator-result object per element — significant on 100k-tile border
sets. `calculateClusters`, `calculateBoundingBox` (indexed fast path for
arrays too) and `getAttackFrontTiles` (also dropped its `neighbors()`
arrays) now use `Set.forEach`. | ~3.6 GB |
### Results (Giant World Map, 400 bots, 12,000 ticks, seed
`perf-default`)
| Metric | Before | After | vs. original (pre-#4494) |
|---|---|---|---|
| Sampled allocations (incl. collected) | 37.8 GB | **24.1 GB (−36%)** |
97.7 GB (**−75%**) |
| Ticks/sec | 82 | **88** | 66 (+33%) |
| Mean / p99 tick | 12.2 / 36.0 ms | 11.3 / 34.8 ms | 15.2 / 49.9 ms |
| Peak heap | 762 MB | 529 MB | 758 MB |
## Determinism
Final hash unchanged on all three reference runs:
- Giant World Map 12,000 ticks: `57830793797434300` ✓
- Giant World Map 2,000 ticks: `55125379638382860` ✓
- World 1,800 ticks: `32337437717390864` ✓
## Test plan
- [x] Full suite green (1,906 tests; the `getAttackFrontTiles` test stub
gained a `neighbors4` implementation to match the real interface)
- [x] Hash equality on 3 seeded headless runs (2 maps)
- [x] Before/after 20-min GC benchmarks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Description:
Would be very good to get these fixes last minute into v31.
- **Farming nations for cities is fixed**: Here you can see city farming
in action, vari farms Finland:
https://www.youtube.com/watch?v=J4J0ajlnSHs&t=137s - Lots of 125k gold
cities... Now nations will build defense posts instead of cities:
- **Nations now build defense posts reactively** instead of through the
normal structure build order. When a nation is under significant land
attack (incoming/own troops >= 35%), it places a defense post near the
attack front -- skipped on Easy, capped at 1 on Medium, and scaled by
the incoming troop ratio on Hard/Impossible. Posts spread along the
front. The old `defensePostValue()` placement path is removed.
- **Nations now capture nuked territory.**
`hasLandBorderWithTerraNullius()` was incorrectly filtering out tiles
with fallout, causing the `nuked` attack strategy to never dispatch a
attack. Big bug
.
- [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:
FloPinguin
## Description:
In this example, the two nations DONT see each other as neighbors, but
as ISLANDERS. Because they dont have a direct border connection, there
is water in between.
<img width="526" height="329" alt="image"
src="https://github.com/user-attachments/assets/cf2c15b5-7793-4445-afd2-920d6cd50a2a"
/>
This is a big problem, because most of the logic in AiAttackBehavior
gets ignored. Only the "islander" strategy runs (late, because its a
not-important strat).
### Summary
- `PlayerImpl.neighbors()` now includes cross-water neighbors: a new
`shoreReachableNeighbors()` helper samples every 10th shore border tile
and looks up to 5 tiles in each cardinal direction across water, finding
land owners on the other side (covers rivers up to 4 tiles wide).
- `AiAttackBehavior.maybeAttack()` extends the `hasNonNukedTerraNullius`
check to also trigger on TN detected via `player.neighbors()`, so
nations notice and pursue TN that is only reachable across a river.
- `sendAttack()` uses a new `hasLandBorderWithTerraNullius()` land-only
adjacency check to decide between a land attack and a boat attack for
TN, rather than `sharesBorderWith()` which includes water tiles.
- Added `sendBoatAttackToNearbyTerraNullius()`: when no TN land is
directly adjacent, the AI scans its shore border tiles for unowned land
across water and dispatches a transport ship.
### Also works for Tribes!
Tribes can boat rivers now, really cool.
https://github.com/user-attachments/assets/382e85aa-c437-4e0c-afc2-0c381432da3d
## 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:
FloPinguin
## Description:
Now that cities snap to existing rails, it's possible to line up dozens
of cities in a row, producing way too much gold. This PR reduces the
gold after each stop to prevent that. Gold only starts decreasing after
the 3rd city.
## 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
## Description:
Introduces a dedicated `factoryValue()` scoring function for AI factory
placement, replacing the generic `interiorStructureValue()` previously
shared with cities and missile silos.
Scoring criteria:
- High elevation and spacing from other factories (unchanged from
city/silo logic)
- Rail connectivity: bonus per distinct rail cluster reachable within
`trainStationMaxRange`, weighted by trade gold potential — allied
clusters score highest (1.0), team/neutral clusters score ~0.71, own
clusters ~0.29 (based on `config.tradeGold()` values). Based on
difficulty
- Cluster deduplication: connecting to the same cluster multiple times
does not inflate the score
- Embargoed and bot neighbors are excluded; all other non-embargoed
neighbors are included
The result is that the AI tends to place factories where they can bridge
separate rail networks or connect to high-value trade partners, rather
than deep in its own interior.
### EDIT
Added a dedicated `cityValue()` scoring function that takes into account
the connectivity score. This allows placement of cities in a
"factory-aware" way, while also enforcing spreading structures (we want
the network to grow, not a cluster of cities and factories all
together).
## 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:
deshack_82603
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>