- Reduced the train gold refill time from 600 to 300 ticks for faster resource replenishment.
- Updated fare calculation to use a base length bonus and adjusted congestion fare logic, ensuring that only the net congestion premium is added to the fare.
- Adjusted recency penalties to be softer and decay faster, with a maximum penalty of 40% for immediate revisits.
- Introduced a new method to estimate expected profit for a station based on passenger demand and relationship with the train owner.
- Updated edge score calculation to incorporate expected profit and travel time cost, enhancing routing decisions.
- Replaced direct deletion of railroads with dedicated methods in TrainStation for better encapsulation.
- Added a mapping for quick lookup of railroads by neighboring stations.
- Updated clearRailroads and addRailroad methods to maintain consistency in railroad management.
Implement fare-only updates for railroads and enhance client-side coloring
- Introduce fare tracking in RailroadLayer and update rendering logic to handle fare-only updates.
- Modify FxLayer to skip effects for fare-only updates.
- Add fare calculation and update logic in Railroad and TrainExecution classes.
- Update RailTile interface to include optional fare property for client-side use.
- Ensure significant fare changes trigger updates to clients for visual feedback.
- Update increment and decrement methods in Railroad to accept current tick for congestion calculations.
- Implement exponential moving average for congestion to adjust fare based on train density.
- Modify TrainExecution to pass tick information when managing train counts.
- Install terser-webpack-plugin dependency
- Configure keep_classnames with regex pattern for layer classes
- Preserve names for FrameProfiler layer identification in production
- Covers most layer classes with regex, handles exceptions explicitly
Expanded tests/core/game/TrainStation.test.ts mocks: added game.x/y, ensured trainExecution.shareJourneyInfo() returns the expected structure, and gave the otherUnit mock a tile() for parity with real units.
The new station-aware routing introduced a bug where train arrivals were being
double-counted and termination conditions were incorrectly classified as arrivals.
Problems fixed:
- targetReached() was called twice for successful arrivals: once in getNextTile()
when destination was reached, and again in tick() when no tile was returned
- Trains removed due to hop limit were counted as successful arrivals
- Trains stuck with no routing options were counted as successful arrivals
Solution implemented:
- Introduced MoveResult union type with explicit cases: "move", "arrived",
"hopLimit", "stuck" to clearly distinguish termination conditions
- Renamed getNextTile() to getNextStep() and changed return type to MoveResult
- Removed targetReached() call from navigation logic to prevent double counting
- Updated tick() method to use switch statement on MoveResult for proper handling
- Ensured recordTrainArrival() only called for actual destination arrivals
- Ensured recordTrainRemovedDueToHopLimit() only called for hop limit terminations
- Stuck trains are deleted without recording any arrival statistics
This ensures accurate train statistics tracking with the new routing system.
- Added search radius
- Updated several properties in TrainStation class to be readonly for better immutability and clarity.
- Introduced heat decay interval and factor for more flexible heat management.
- pre-computed decay factors avoiding Math.pow in critical paths.
- Enhance logging
- Refined routing logic
- removed journeyPreviousStation property
- removed RecentArrivals
- unbounded StationTraffic.heat -> score can now be negative
Replace fixed pathfinding with dynamic routing system featuring:
- Local greedy routing: Trains evaluate neighbors based on profit potential, traffic congestion, distance, and recent history
- Exploration capability: 10% randomness prevents suboptimal but discovers new routes
- Congestion avoidance: Trains naturally spread to less busy stations
- Loop prevention: Memory of recent visits prevents getting stuck
- Adaptive behavior: System responds to changing network conditions
- Enhanced journey tracking: Share complete route information instead of just start position
Includes BATMAN-style routing protocol (currently disabled) for future network-wide knowledge distribution.
- Interpret trainGold as per-level max gold and add trainGoldRefillTime with a 60-tick full refill baseline
- Add per-station passenger pool with lazy tick-based refill and proportional depletion on train arrival
- Make city and port train payouts depend on station level, owner relation, and current passenger demand instead of flat values
- Expose getPassengerDemandScore for future logic
- Update TrainStation tests for the new config and payout behavior
## Description:
* Update copyright notice to "OpenFront and Contributors"
* remove the "how to play" on the footer, since that will be moved to
the death screen.
* Removed css and used tailwind instead for better mobile support
Describe the PR.
## 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:
This PR optimizes how the rail network looks up railroads connecting two
stations by introducing an O(1) neighbor→railroad map on `TrainStation`.
It also updates `getOrientedRailroad` and railroad deletion to use this
new API, avoiding repeated linear scans over all railroads attached to a
station.
### What changed
- **TrainStation neighbor→railroad index**
- Added `railroadByNeighbor: Map<TrainStation, Railroad>` to
`TrainStation` for quick edge lookup.
- Kept `railroads: Set<Railroad>` for iteration and existing APIs.
- Updated lifecycle methods to keep both data structures in sync:
- `addRailroad(railRoad: Railroad)` now:
- Adds to `railroads`.
- Computes the neighbor station (`railRoad.from === this ? railRoad.to :
railRoad.from`).
- Stores the mapping in `railroadByNeighbor`.
- `removeRailroad(railRoad: Railroad)` now:
- Removes from `railroads`.
- Removes the corresponding entry from `railroadByNeighbor`.
- `clearRailroads()` now clears both `railroads` and
`railroadByNeighbor`.
- Added `getRailroadTo(station: TrainStation): Railroad | null` to
retrieve the connecting railroad in O(1).
- **Use the new API in `TrainStation` and `Railroad`**
- `TrainStation.removeNeighboringRails(station)` now calls
`removeRailroad(toRemove)` instead of manually deleting from the set,
ensuring the map stays in sync.
- `Railroad.delete(game)` now calls `from.removeRailroad(this)` and
`to.removeRailroad(this)` instead of mutating the sets directly.
- **Refactor `getOrientedRailroad` to use O(1) lookup**
- Replaced a linear scan over `from.getRailroads()` with a direct
lookup:
```ts
export function getOrientedRailroad(
from: TrainStation,
to: TrainStation,
): OrientedRailroad | null {
const railroad = from.getRailroadTo(to);
if (!railroad) return null;
// If tiles are stored from -> to, we go forward when railroad.to === to
const forward = railroad.to === to;
return new OrientedRailroad(railroad, forward);
}
```
- Behavior is preserved:
- `getRailroadTo` returns the same `Railroad` instance that was
previously found by scanning `getRailroads()`.
- Direction (`forward` vs reversed) is still derived from the
`Railroad.from` / `.to` fields in the same way as before.
### Motivation
- `getOrientedRailroad` and upcoming logic both need to resolve “the
railroad between station A and station B” frequently.
- The old pattern (`for (const railroad of from.getRailroads()) { ...
}`) was:
- O(degree) per lookup,
- Repeated in multiple places,
- Harder to maintain as more features (like fare-based costs) touch this
code.
- Centralizing edge lookup in a dedicated `railroadByNeighbor` map makes
this:
- **O(1)** per lookup,
- Less error-prone (one source of truth),
- Easier to reuse from new systems (e.g. train pathfinding, fare-aware
logic).
### Impact / Risk
- **Public behavior:** No functional change in how railroads are
created, deleted, or oriented; only the lookup mechanism changed.
- **Internal invariants:** Correctness relies on:
- All railroad creations using `addRailroad` on both endpoints (already
true via `RailNetworkImpl.connect`).
- All removals (`Railroad.delete`,
`TrainStation.removeNeighboringRails`, `disconnectFromNetwork`) using
`removeRailroad` / `clearRailroads`, which this PR updates.
- **Tests:** Existing `TrainStation` tests still pass; they exercise
`addRailroad`, `removeNeighboringRails`, and `getRailroads()`, which
continue to behave the same from the outside.
## 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:
DISCORD_USERNAME
Resolves#2448
Hi team,
I've implemented and locally tested the alliance-related changes
(including unit tests and some manual simulation with multiple browser
profiles).
Unfortunately I wasn't able to perform full end-to-end testing on the
live game server with two separate machines/accounts.
If someone on the team (or another contributor) can verify the alliance
flow with two real players, that would be greatly appreciated before
merging. Happy to hop on a call or provide any clarification needed.
Thanks!
## Description:
Fixed a race condition bug where donations (troops/gold) between human
players failed after forming an alliance. The issue was caused by a
one-tick delay in `AllianceRequestReplyExecution`: the alliance
acceptance logic ran in `tick()` instead of `init()`, meaning the
alliance wasn't created until the tick after the execution was added. If
a donation execution was added in the same turn as the alliance
acceptance, it would fail the `isFriendly()` check because the alliance
didn't exist yet.
**Root cause:** When human players formed alliances via reply, the
execution model delayed alliance creation by one tick, while bots called
`accept()` directly without this delay.
**Solution:** Moved alliance acceptance logic from `tick()` to `init()`
in `AllianceRequestReplyExecution.ts`, ensuring immediate alliance
creation and eliminating race conditions with donations.
**Changes:**
- Modified
`src/core/execution/alliance/AllianceRequestReplyExecution.ts` to
process alliance replies in `init()` instead of `tick()`
- Added comprehensive test suite `tests/AllianceDonation.test.ts` with 5
test cases covering donation scenarios after alliance formation (reply
and mutual request flows)
- All existing tests pass (323 total)
## Please complete the following:
- [x] I have added screenshots for all UI updates (N/A - backend logic
fix only)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (N/A - no user-facing text
changes)
- [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
Discord: loacky
GitHub: @LoackyBit
---------
Co-authored-by: Evan <evanpelle@gmail.com>
Nginx was stripping query params when routing requests to workers, so the creatorClientID param was stripped when creating a private game. This caused the game server to not know who the lobby owner was, so it rejected the kick requests.
## Description:
mls for v27
Version identifier within MLS: v4.9
## 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:
aotumuri
## Description:
Change the wiki site from the inactive openfront.miraheze.org to the new
openfront.wiki link.
Ideally for v27?
## 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:
Lavodan
## Description:
Change the wiki site from the inactive openfront.miraheze.org to the new
openfront.wiki link.
Ideally for v27?
## 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:
Lavodan
Bumps the npm_and_yarn group with 1 update in the / directory:
[glob](https://github.com/isaacs/node-glob).
Updates `glob` from 10.4.5 to 10.5.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/isaacs/node-glob/commit/56774ef73b495eb0b17cdd0f42921f5ef62297c1"><code>56774ef</code></a>
10.5.0</li>
<li><a
href="https://github.com/isaacs/node-glob/commit/1e4e297342a09f2aa0ced87fcd4a70ddc325d75f"><code>1e4e297</code></a>
bin: Do not expose filenames to shell expansion</li>
<li>See full diff in <a
href="https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/openfrontio/OpenFrontIO/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Description:
Made a type of map we don't already have: Small square map, one Island
in each corner. Could be great for boat gameplay or maybe even nuke
wars.
The special thing is: **All islands are exactly 25% of the territory!**
Was a lot of work drawing that in GIMP...
<img width="1164" height="1168" alt="Screenshot 2025-11-20 000839"
src="https://github.com/user-attachments/assets/ad8345c7-562d-49e8-b367-12be9274f3e4"
/>
<img width="1227" height="1222" alt="Screenshot 2025-11-20 000633"
src="https://github.com/user-attachments/assets/f7e2c58a-fcb3-4e07-91f2-aead5f497fad"
/>
<img width="361" height="288" alt="Screenshot 2025-11-20 000655"
src="https://github.com/user-attachments/assets/120f82ef-2d19-497b-8a31-819e30013c89"
/>
<img width="756" height="282" alt="Screenshot 2025-11-20 005949"
src="https://github.com/user-attachments/assets/8ee04da3-d5fa-4ec9-9e99-9e30ebda2b78"
/>
## 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: Evan <evanpelle@gmail.com>
Resolves#1092
## Description:
Added a team preview to the Host Lobby: players listed on the left,
teams on the right in two scrollable columns with color dots matching
in-game colors. Implemented accurate server-parity team assignment
(including clan grouping).
Screenshots:
<img width="817" height="519" alt="Screenshot 2025-11-13 173721"
src="https://github.com/user-attachments/assets/ec646238-7efa-4c8f-9c0a-171b61fd3f20"
/>
<img width="762" height="425" alt="Screenshot 2025-11-13 175400"
src="https://github.com/user-attachments/assets/ebdccb80-4c07-41d5-8f69-3ea983d4b243"
/>
## 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: Evan <evanpelle@gmail.com>