CodeRabbit noted motionPlannedUnitIds() allocates a new array on every call.
Cache the ID list and rebuild only when motion plan membership changes (plan set/delete/clear), keeping per-tick UnitLayer work allocation-free in steady state.
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.
Only apply derived positions when the tile changes and drop finished grid motion
plans once past the last step to avoid per-tick update churn after arrival.
Remove MOTION_PLANS_SCHEMA_VERSION and simplify packed motion plan buffers to
`[recordCount, ...records]`; update GameView to apply unpacked records directly.
Rewrite MotionPlans.packMotionPlans to precompute total word count and fill a
single pre-sized Uint32Array with an index pointer (no intermediate number[]).
- Remove `markUnitPlanDriven` from `Game`/`GameImpl`
- Centralize “maybe emit UnitUpdate” behind `GameImpl.onUnitMoved`
- Record trade ship motion plan before the first move to avoid redundant per-step unit updates
- Delete unit IDs from `planDrivenUnitIds` in `GameImpl.removeUnit`
- Return early on `PathStatus.COMPLETE`/`NOT_FOUND` in `TradeShipExecution.tick` to avoid post-delete motion-plan logic
Add `Game.markUnitPlanDriven()` so executions can suppress per-step Unit updates
before the first motion plan is recorded.
Use it in TradeShipExecution to drop the spawn-time placeholder motion plan, and
align TransportShipExecution re-plan `startTick` with `ticksPerMove`.
## Description:
updates the Performance Overlay to be more usable
(draggable/resizable/scrollable), adds tick-level metrics (TPS +
per-layer tick timings), and reduces overhead when the overlay is
hidden.
### UI/UX
- Overlay layout updated to a fixed, pixel-positioned panel (default
near top-left) with a dedicated drag handle.
- Overlay is touch-draggable (pointer events) and remains usable on
small viewports via internal scrolling.
- Overlay width is resizable with a right-edge handle; width is clamped
to viewport bounds.
- Render/tick layer breakdown sections are collapsible, with headers and
“last tick” summaries.
### New metrics
- Adds TPS reporting:
- Current TPS (ticks in the last 1s).
- Average TPS over the last ~60s, computed using elapsed time so it’s
accurate before a full 60s passes.
- Adds per-layer tick profiling (“Tick Layers”) alongside render
profiling (“Render Layers”).
- Adds “render-per-tick” metrics so render-layer costs can be understood
per simulation tick (frames + per-layer totals).
### Performance / overhead
- Avoids profiling overhead when the overlay is hidden:
- `GameRenderer` only calls `FrameProfiler.clear()/consume()` and
per-layer `start/end` when profiling is enabled.
- Tick-layer duration tracking is only collected when profiling is
enabled.
### Settings plumbing
- `UserSettings` now dispatches a `user-settings-changed` `CustomEvent`
on `set()` / `setFloat()`.
- The overlay listens for `settings.performanceOverlay` changes so
visibility stays in sync even when toggled outside the overlay.
## Implementation notes (by file)
- `src/client/graphics/layers/PerformanceOverlay.ts`
- Adds TPS tracking using a timestamp ring + moving heads (1s / 60s).
- Adds UI state for collapsibles, drag + resize pointer tracking, and
new breakdown models:
- Render layers: EMA avg/max + per-tick render aggregation.
- Tick layers: EMA avg/max + last-tick durations.
- Copy-to-clipboard snapshot now includes TPS, tick layers, and
render-per-tick last-tick details.
- `src/client/graphics/GameRenderer.ts`
- Gates render-layer profiling behind `FrameProfiler.isEnabled()`.
- Accumulates per-render-layer timings across frames and publishes them
once per tick via `updateRenderPerTickMetrics(...)`.
- Measures tick-layer durations (per layer `tick()` call) and publishes
them via `updateTickLayerMetrics(...)`.
- `src/core/game/UserSettings.ts`
- Adds `emitChange(key, value)` to dispatch `user-settings-changed` to
`globalThis` (best-effort).
- `resources/lang/en.json`
- Adds/updates `performance_overlay.*` strings for TPS and the new
render/tick layer sections.
## 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:
DISCORD_USERNAME
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Description:
Array.from was performed on this.player.alliances(), which already
returns an array. Also it was saved in a const which isn't strictly
necessary, same goes for the array in the loop below it.
## 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:
tryout33
## Description:
Please merge for v30 if possible.
Use .find instead of .filter for tradeShipSpawn since we're only looking
for the first (if any) port found at the given tile anyway.
Also just return targetTile instead of getting porr.tile() because
targetTile is tile we found the port on.
Also use no intermediate const, just return right away based on outcome
of units.find.
Found when working on PR #3220. But tradeShipSpawn is out of 3220's
scope since it won't be called by playerImpl buildableUnits() anymore,
it should and will be only ever used by TradeShipExecution via
playerImpl canBuild().
## 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:
tryout33
## Description:
Can we maintain a one-minute game creation rate with three times as many
lobbies?
I don’t think so - we should allow more time for players to join.
It still shouldn’t bore players, since they can also join the special
mix lobby. That lobby includes both FFA and team games and fills more
quickly due to the higher frequency of compact-map matches (the Wonder
PR https://github.com/openfrontio/OpenFrontIO/pull/3224 needs to be
merged for that).
## 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:
Only need find() instead of filter() in orderRetreat and executeRetreat,
since we just need the first hit. Small perf win.
## 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:
tryout33
## Description:
Because spawning is prohibited on tiles that have an owner, this created
a problem when a person tried to spawn near the center of their previous
spawn. This was resolved by relinquishing all the tiles conquered by the
previous spawn.
## 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:
nikolaj_mykola
## Description:
Fixing
https://discord.com/channels/1359946986937258015/1360078040222142564/1463898386854973642
Now, if not all tiles on the spawn circle can be owned, the algorithm
tries to select another random spawn tile.
## 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:
nikolaj_mykola
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## 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>
## Description:
The master set lobby start times on creation, which caused an issue if
the previous lobby filled up and started before its timer ran out, the
next lobby would have its timer set too far back. For example, if lobby
time is 60 seconds, and the first lobby fills up after 10s, the
subsequent lobby would have its timer set for 110 seconds (60+50).
Instead we have the master set the lobby start time only when it is next
up in rotation. So all lobbies behind it don't have a start time,
because we don't actually know what it should be.
## 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:
### SAM Overwhelming (`NationNukeBehavior.ts`)
On Impossible difficulty, nations can now destroy enemy SAMs by
overwhelming them with coordinated atom bomb salvos. When no good nuke
target is found (all trajectories intercepted by SAMs), the nations
will:
- Identify the easiest enemy SAM to destroy (lowest level first)
- Calculate the total interception capacity of all covering SAMs and
send enough bombs to overwhelm them (+1 extra per 5 needed to account
for enemy building more SAMs during flight)
- Plan launches in NukeExecution's Manhattan-distance silo order,
tracking which silos have interceptable trajectories (wasted bombs)
- Use a sliding window over parabolic flight times to find the best
cluster of bombs that can arrive within half the SAM cooldown window
- Compute per-bomb wait ticks to synchronize arrivals from silos at
different distances
- Skip launching if a salvo is already in flight
- Upgrade the best SAM-protected silo when silo capacity is
insufficient; wait and save gold when only gold is lacking
https://github.com/user-attachments/assets/14fa592f-2902-4604-8e37-1eba2b2f0b85
### 2-Player Endgame Handling (`NationNukeBehavior.ts`)
- On Hard/Impossible with only 2 players remaining,
`findBestNukeTarget()` directly targets the other player (bypasses all
priority logic)
- `getPerceivedNukeCost()` returns actual cost (no MIRV saving
inflation) when only 2 players are left
### SAM Build Rate (`NationStructureBehavior.ts`)
- Reduced SAM perceived cost increase per owned from 1.0 to 0.5, so
nations build more SAMs
### Island Attack Variety (`AiAttackBehavior.ts`)
- `findNearestIslandEnemy()` now collects up to 2 reachable candidates
and has a 33% chance to pick the second-nearest, adding variety to boat
attack targeting
## 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
Reduces CPU + GC pressure from tile update serialization.
**What changed**
- Switched `packedTileUpdates` from `BigUint64Array` (BigInt packing) to
`Uint32Array` `[tileRef, state]` pairs, updating `GameView` ingestion.
- Updated tile state to use `GameMap.tileState(tile)` and
`GameMap.updateTile(tile, state)`.
- Removed per-tile `GameUpdateType.Tile` wrapper allocations by
recording raw `(tile, state)` pairs in `GameImpl` and draining them via
`drainPackedTileUpdates()` in `GameRunner`.
**Why it’s faster**
- Avoids BigInt and pack/unpack.
- Avoids per-tile object allocations.
**Compatibility**
- Wire format change: `packedTileUpdates` is now `Uint32Array` pairs
instead of `BigUint64Array`.
## 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:
DISCORD_USERNAME
## Description:
Noticed this in two singleplayer games:
When a nation's nuke destroys its own transport/trade ship in the blast
radius, `NationWarshipBehavior` incorrectly tries to retaliate against
itself, calling `updateRelation(self)` which throws (GameRunner tick
error).
Added a self-check in `maybeRetaliateWithWarship` to skip retaliation
when the destroyer is the player itself.
## 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:
The following PR replaces the (disabled) alliance request button with an
alliance extension/renewal button when the alliance with the target
player is expiring.
Agreeing to renewal via radial menu also hides the message in the
EventsDisplay.
<img width="369" height="364" alt="image"
src="https://github.com/user-attachments/assets/d8040f5c-ad7b-47d0-852f-925ecbf273a8"
/>
https://github.com/user-attachments/assets/aa589edf-6505-46bf-88a3-aa4c2df9137f
Icon size adjusted:
<img width="294" height="252" alt="image"
src="https://github.com/user-attachments/assets/7ca63500-b1fb-427b-965c-cf121a5213da"
/>
## 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: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)
## Description:
## PR Title
perf(core): reduce hot-path allocations & safe optimizations
This PR brings in a set of allocation-focused optimizations in core hot
paths
### Scope
- `src/core/execution/NukeExecution.ts`
- `src/core/execution/WarshipExecution.ts`
- `src/core/game/UnitGrid.ts`
- `src/core/game/PlayerImpl.ts`
- `src/core/configuration/DefaultConfig.ts`
- `src/core/execution/SAMLauncherExecution.ts`
### What Changed
- `NukeExecution.detonate`: reduced call overhead/allocations by caching
`mg`/`config`, avoiding repeated lookups, and using allocation-free
loops (no `forEach` closures) in the diminishing-effect pass.
- `WarshipExecution.findTargetUnit`: replaced allocate+sort flow with
single-pass best-target selection.
- `UnitGrid.nearbyUnits`: reduced call overhead and allocations via
single-type fast path and cached query coordinates.
- `PlayerImpl.units`: added fast paths for common small-arity type
queries (1-3 unit types).
- `DefaultConfig.unitInfo`: cached `UnitInfo` objects per `UnitType` to
avoid repeated object/closure creation.
- `SAMLauncherExecution` targeting: removed sort churn and streamlined
target selection with single-pass hydrogen prioritization.
### Rebase
- One conflict was resolved in `NukeExecution.detonate` by keeping
`main`'s diminishing-effect-per-impacted-tile behavior, while retaining
the allocation-reduction refactors.
## 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:
DISCORD_USERNAME
## Description:
Some literals were present that could/should have been enums. Replaced
them.
For Util.ts > createRandomName, also changed type of parameter
playerType from string to PlayerType. All callers already send it this
type.
## 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:
tryout33
## Description:
- Build packedTileUpdates directly into a BigUint64Array (avoid
intermediate JS array + copy).
- Transfer packedTileUpdates.buffer in worker postMessage to avoid
structured-clone.
- Avoids large short-lived allocations during tile-update (lower peak
heap and GC pressure).
- Prevents duplicating buffers across thread boundaries when the main
thread is behind. (one buffer per queued update, not two)
**Notes**
- after postMessage(..., [packedTileUpdates.buffer]) transfer the
worker’s packedTileUpdates becomes unusable (its .buffer is detached).
- [ ] 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:
DISCORD_USERNAME
## Description:
Update UI
check https://homepageupdate.openfront.dev/
Improved mobile UI (now fills whole screen for all modals) e.g.:
<img width="432" height="852" alt="image"
src="https://github.com/user-attachments/assets/56de40af-4137-4c57-96b7-3910c9a665b8"
/>
Converted PublicLobby to be "GameModeSelector" to get a nicer 4x4 grid
div, where <GameModeSelector> now handles all the username validation
now (removed redundant code from modals such as matchmaking) also fixed
a bug where someone could have "[XX] X" as thier username (when the
minimum should be 3 chars for their name)
Now visually displays the 3 lobbies ffa/team/special (which is a
continuation from the work done in: #3196 )
<img width="818" height="563" alt="image"
src="https://github.com/user-attachments/assets/a15cd31b-6061-4fb8-83ee-ffde6225cfa7"
/>
updated the background:
<img width="1919" height="807" alt="image"
src="https://github.com/user-attachments/assets/358a7434-51b8-4540-baf2-d1be05053c44"
/>
slightly updated the glassy-look to be less glassy:
<img width="825" height="729" alt="image"
src="https://github.com/user-attachments/assets/1801871b-bbf8-43db-ac53-489337ae80a5"
/>
## 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:
w.o.n
## Description:
PR 7/x in effort to break up PR #3220. Follows on already merged #3238.
Please see if these can be merged for v30.
-**RadialMenuElements**:
- _getAllEnabledUnits_: use camelCase instead of PascalCase so change
Units into units.
- _getAllEnabledUnits_: use StructureTypes to loop through instead of
having 6 individual lines of code to check if a structure is enabled.
StructureTypes contains and will keep containing the same structures as
those that are checked here. PR 3220 will later on also replace the
individual lines for the attack type units into a loop with a newly
introduced Types array, in the same way as we do in this PR with
StructureTypes.
- _getAllEnabledUnits_: rename the long named const
addStructureIfEnabled to just addIfEnabled, which is clear enough from
the context it is used in.
-**PlayerImpl**
- _buildableUnits_: removed unnecessary "as BuildableUnit" after the
in-loop return; the function itself already says it returns
BuildableUnit[].
## 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:
tryout33
## 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:
<img width="1617" height="488" alt="image"
src="https://github.com/user-attachments/assets/07009b18-4342-4caf-9e82-9ae5147b63f8"
/>
<img width="1645" height="375" alt="image"
src="https://github.com/user-attachments/assets/fe9ead87-550a-4166-96ab-092d0c08be82"
/>
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:
<img width="989" height="365" alt="image"
src="https://github.com/user-attachments/assets/25c0faf0-cc34-41b7-8091-b14bde6db595"
/>
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
## Description:
PR 6/x in effort to break up PR
https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already
merged https://github.com/openfrontio/OpenFrontIO/pull/3237.
Please see if these can be merged for v30.
- **PlayerImpl**: validStructureSpawnTiles did a filter on unit types to
get isTerroritoryBound units, on every call again. It read this from
unit info in DefaultConfig. While having it centrally in DefaultConfig
unitInfo is good for maintainability, other code uses hardcoded
StructureTypes and isisStructureType from Game.ts. Which has the same
purpose and thus contains the same unit types. StructureTypes and
isisStructureType do need manual maintainance outside of DefaultConfig.
And are more bug prone/less type safe. But, using them gives more speed
compared to getting these unit types out of DefaultConfig unitInfo
centrally with some cached function in GameImpl for example (tested with
buildableUnits and MIRVPerf.ts). So I went with StructureTypes in
validStructureSpawnTiles too.
- **PlayerExecution**: now validStructureSpawnTiles no longer needs
isTerritoryBound (see the point above), PlayerExecution is the last
place where it was used. Replaced it for isStructureType here too (since
it has the same meaning and outcome).
- **Game.ts** and **DefaultConfig** unitInfo: remove the now unused
_territoryBound_. As it was only used in validStructureSpawnTiles and
PlayerExecution and has been replaced in both (see the two points
above).
## 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:
tryout33
## Description:
PR 5/x in effort to break up PR
https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already
merged https://github.com/openfrontio/OpenFrontIO/pull/3236.
Please see if these can be merged for v30.
**NationStructureBehavior**:
- maybeSpawnStructure: cache this.game to be used twice.
- maybeSpawnStructure: instead of hardcoded ruling out Defense Post for
upgrade check, check dynamically if type is upgradable. That way if
defense posts ever do become upgradable, we don't run into a bug right
away.
- maybeUpgradeStructure: removed canUpgradeUnit check. Since it already
checked this right before in findBestStructureToUpgrade, so only
upgradable units are returned. And canUpgradeUnit is also checked right
after in UpgradeStructureExecution. So we're going from 3 times to 2
times canUpgradeUnit, small perf win too.
- findBestStructureToUpgrade: cache this.game to be used thrice.
- shouldBuildStructure: cache this.game.config() to be used twice.
- getTotalStructureDensity: this.player.units can handle an array of
unit types to count. Input StructureTypes like this so we don't need a
loop and count, and only have to get an array length once.
getTotalStructureDensity needs to ignore unit levels so we can't make
use of other pre-defined functions in PlayerImpl (which were created to
avoid array length calls), but at least this saves a few.
## 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:
tryout33
## Description:
PR 4/x in effort to break up PR
https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already
merged https://github.com/openfrontio/OpenFrontIO/pull/3235.
Please see if these can be merged for v30.
- **Game**/**GameImpl**/**GameView**: nearbyUnits required "UnitType |
UnitType[]" for tiles, but calls UnitGrid nearbyUnits which requires
"UnitType | readonly UnitType[]". Made the requirement the same for
Game/GameImpl/GameView nearbyUnits. This way, we don't have make a
shallow copy of the StructureTypes array everytime we want to send it as
an argument. Other callers than listNukeBreakAlliance in Util.ts are
unaffected.
- **Util.ts**: listNukeBreakAlliance needs no shallow copy of
StructureTypes anymore as argument for NearbyUnits
## 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:
tryout33
## Description:
Based on [this suggestion on
Discord](https://discord.com/channels/1284581928254701718/1447110257196138577)
and feedback gathered in [this
thread](https://discord.com/channels/1359946986937258015/1469598906173227184).
Supersedes #3143
This PR introduces "ghost railways": when you are going to place a city
or port, previews railway connections that will be made when actually
building the structure.
Ghost railways are skipped if the structure is going to be snapped to
existing railways (as in railway snapping functionality introduced in
#3156 ).
### Video
https://github.com/user-attachments/assets/ff8cf325-6501-4df8-801d-c8ae3ced3d0e
### Ghost rails color revisited
black with 40% opacity
<img width="695" height="430" alt="image"
src="https://github.com/user-attachments/assets/272efbcc-4185-426a-921c-7fae61f6c462"
/>
## 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
## Description:
PR 1/x in effort to break up PR
https://github.com/openfrontio/OpenFrontIO/pull/3220.
Please see if these can be merged for v30.
- **BuildMenu**: remove one redundant comment about replacing an icon
(which has been done long ago already). And fix typo in one other
comment.
- **Worker.worker**: correct some existing error messages.
## 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:
tryout33
## Description:
PR 2/x in effort to break up PR
https://github.com/openfrontio/OpenFrontIO/pull/3220.
Removes unused code and properties.
- **Game.ts** and **DefaultConfig** unitInfo: removed
_canBuildTrainStation_ and _expirimental_ properties, as they weren't
used anywhere anymore.
- **PlayerActionHandler**: remove unused getPlayerActions, the only
potential caller MainRadialMenu already just calls myPlayer.actions via
GameView directly.
- **StructureIconsLayer**: remove unused PlayerActions
## 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:
tryout33
## 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
## Description:
Bug (before): In multi-nuke scenarios (e.g., 6 incoming nukes), SAMs,
when stacked, could behave like they only engage one nuke at a time,
letting other nukes slip through and wipe the SAM stack. This was most
noticeable when nukes entered from certain angles/entry sides (from what
I’ve noticed, it happens more often in the 2 o’clock to 6 o’clock
direction).
<img width="734" height="743" alt="asadasada"
src="https://github.com/user-attachments/assets/7ca6c9ef-b2b4-47ea-bed2-249a84c8f5ed"
/>
What was fixed:
1- Removed permanent "unreachable" caching: When a nuke was out of SAM
range on first evaluation, it was cached as null (permanently
unreachable) and never re-evaluated. Since nukes move each tick, they
could later enter interception range but would be ignored. The fix
removes this permanent cache, so nukes are re-evaluated every tick.
2 - Moved targetedBySAM filter into the targeting system: The
targetedBySAM() check was at the launch decision, if the
highest-priority nuke was already claimed by another SAM, the launcher
skipped firing entirely instead of falling back to the next best target.
The fix moves the filter inside getSingleTarget() so claimed nukes are
excluded before ranking.
3 - Cleared targetedBySAM flag on SAM missile abort: When a SAM missile
aborted (for example target became allied), the targetedBySAM flag was
never cleared. This permanently prevented all other SAMs from targeting
that nuke. The fix calls setTargetedBySAM(false) on abort so the nuke
becomes available for re-targeting.
**Videos:**
I uploaded a before/after clip showing SAM performance intercepting 6
nukes, before the fix, nukes could break through, but after the fix,
SAMs consistently intercept as expected.
Before:
https://github.com/user-attachments/assets/d5a85354-f35c-4aca-82f8-902f5966312b
after:
https://github.com/user-attachments/assets/54074c09-fbdf-44d5-a88c-b1d54b20fee2
- Deployed for further testing
## 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:
abodcraft1
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
This PR fixes (at least partially) the attack loss imbalance. Would
recommend adjusting the weighting towards 100% in the long run and then
removing redundant code. I haven't tested it at 100% yet, but it might
actually be fine to change it now. I don't notice any substantial
differences in how the game feels at 50%.
## 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:
1brucben
## Description:
Fixes closed PR and adds the new “Alps” map and register it in the
game/map playlist. With 7 geographically accurate nations. Pure large
land map for land warfare only.
<img width="2564" height="1400" alt="Untitled"
src="https://github.com/user-attachments/assets/2c51fad3-7345-46a8-9256-2874d1c4211b"
/>
<img width="2000" height="1837"
alt="551068953-fe4445cd-c0b2-4a5a-b652-1e480e38cdb6"
src="https://github.com/user-attachments/assets/1c466262-e20b-46bc-b782-944cbf6ee62a"
/>

## 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:
TsProphet
## Description:
As far as I can remember, in v28 the spawn immunity applied to both
humans and nations.
With the configurable spawn immunity (added for v29) the spawn immunity
no longer applies to nations... Because its called PVP immunity now.
So right now it's possible to spawnkill nations. This is a big problem
for the 5M gold modifier games... And you can "cheat" in singleplayer.
This PR changes two things:
- Nations always have 5 seconds spawn immunity now, no matter whats
configured for the PVP immunity
- Nations attack TerraNullius earlier (Otherwise the easy nations would
sometimes do their first attack after the 5 seconds are over, spawnkills
would still be possible)
## 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
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
- `AiAttackBehavior`: Because bots delete stolen structures now, nations
prioritize attacking bots with structures
- `NationMIRVBehavior`: Nations no longer MIRV enemies who already got
MIRVed in the last 30 seconds. Some humans complained about getting
double-MIRVed by nations. And in games with very high starting gold, ALL
nations MIRVed the same player (stop steamroll logic).
- `NationAllianceBehavior`: Fixes a comparison logic bug (Thanks to
Deshack)
- `NationNukeBehavior.ts`: Little atom bomb perceived cost balance
change
- `MIRVExecution`: To make sure the MIRVing nations are attacking the
MIRVed nations (even if they don't share a border), the relation gets
updated in both directions now.
- `SinglePlayerModal` & `HostLobbyModal`: Update the default difficulty
to "Medium" (to synchronize the defaults with the public game default)
## 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
Resolves#3154
## Description:
#2716 introduced nuke cancellation logic on alliance acceptance via
`AllianceRequestReplyExecution`. The radial menu action, though, calls
`AllianceRequestExecution` instead, which accepts the alliance if a
request has already been made by the other player.
This PR moves the nuke cancellation logic to `GameImpl`, hooking into
the `acceptAllianceRequest` method, therefore accounting for every
alliance acceptance, regardless of the specific action that brought to
that.
## 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
## Description:
After an internet problem or page reload the game catches up, replaying
the ticks.
But especially new players might be confused what is happening. The game
runs fast???
And you can't easily tell when its finished catching up. You need to
spot when it stops running faster than usual.
So add a HeadsUpMessage to tell people what is happening.
https://github.com/user-attachments/assets/6fcdd85f-c58e-4549-89d0-5ba51df39339
## 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
---------
Co-authored-by: iamlewis <lewismmmm@gmail.com>
## Description:
The ghost structure calls player actions each frame, which is costly
since it's checking for all possible actions.
This add a unit list filter in actions so if there are units it only
checks for buildability of those units.
Before:

Player actions takes 20-30% of the worker
After:
<img width="825" height="342" alt="image"
src="https://github.com/user-attachments/assets/36e47547-5028-4dc9-bc42-e17df4a87200"
/>
Player actions takes 1-3% of the worker
Both performances are relevant only when a ghost structure is selected
## 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:
Mr. Box