Resolves [#3815
](https://github.com/openfrontio/OpenFrontIO/issues/3815)
## Description:
Migrates the five `mg.neighbors(tile)` call sites in
`src/core/execution/AttackExecution.ts` to the existing zero-allocation
`forEachNeighbor(tile, cb)` helper (`src/core/game/GameImpl.ts:1107`).
`neighbors()` allocates
a fresh `TileRef[]` of length up to 4 on every call; `forEachNeighbor`
invokes a callback with no allocation. The helper is already used in
`PlayerExecution`, `GameImpl.updateBorders`, and similar hot paths —
this PR
finishes that rollout in the hottest attack loop.
Affected sites in `src/core/execution/AttackExecution.ts`:
| Original line | Context |
| --- | --- |
| 277 | `tick()` border check around `tileToConquer` |
| 326 | `addNeighbors()` outer neighbor loop |
| 335 | `addNeighbors()` nested `numOwnedByMe` count |
| 370 | `handleDeadDefender()` border test |
| 375 | `handleDeadDefender()` neighbor scan |
Notes:
- `handleDeadDefender` previously narrowed `this.target` from `Player |
TerraNullius` to `Player` via the early-return check. Inside an arrow
callback that narrowing isn't preserved by TypeScript, so the function
now
captures `target: Player` once after the check. Same pattern as
`tick()`'s `targetPlayer` cache.
- For loops that previously used `break` or `.some()`, I used a small
flag variable to short-circuit the callback body. With at most 4
neighbors per tile the extra callback invocations are negligible
compared to the
eliminated array allocation.
Behavioral guarantees:
- Iteration order is identical (`forEachNeighbor` enumerates the same
tiles in the same order as `neighbors()`).
- No RNG, no float math, no wire-format changes.
- Determinism preserved.
Verification:
- `npm test` — all 994 tests pass across 104 files. The 22
attack-related tests in `Attack.test.ts`, `AttackStats.test.ts`, and
`AiAttackBehavior.test.ts` exercise the migrated code paths end-to-end.
- `npx tsc --noEmit` — clean for the changed file.
- `npx prettier --check` — clean.
Briefly flagged in `#development` per the contribution guidelines before
opening.
## 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
- [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:
sxndrexe
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves#3174
## Description:
This PR implements a keybind for retaliation against incoming non-bot
attacks (Players and Nations). It currently will target the most recent
(last in array) incoming attack when the keybind is pressed, choosing
the minimum of attack ratio or incoming attack troop size to retaliate
with. This correlates with the last displayed incoming attack on the
bottom modal. It works when there are multiple attacks (press multiple
times to retaliate against each consecutive attack). If the retaliation
size is smaller than the attack, the keybind can be pressed multiple
times to repeatedly blunt the incoming troop count.
Due to the current keybind logic KeyR cannot be used but Shift+KeyR can
so it is set to that until refactor.
<img width="867" height="121" alt="image"
src="https://github.com/user-attachments/assets/56e80810-4900-4db0-8ce7-1856e13529e5"
/>
No tests have been added as the retaliation feature doesn't seem to have
any to begin with since it is just an attack intent. The keybind is not
configured any differently than others. I did run all current tests and
was all good.
## 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:
n1ghtingal3
## Description:
Set timeout to 30s instead of the default 5s for test "en.json keys stay
in sync with source usage". It reads almost all files and therefor can
take longer to complete its run, with the growing repository.
This sometimes lead to error from vitest `Error: Test timed out in
5000ms. If this is a long-running test, pass a timeout value as the last
argument or configure it globally with "testTimeout"`. A workaround was
to re-run the test, sometimes it then ran fast enough to stay below the
5s timeout. But a more permanent fix is in this PR: set the timout to
30s specifically for this test.
<img width="997" height="251" alt="image"
src="https://github.com/user-attachments/assets/0efb138a-fd64-461a-8109-e9e0b3f57c7a"
/>
## 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
Co-authored-by: Copilot <copilot@github.com>
## Description:
Only mentioning removals/major updates/notable changes below, not all
minor upgrades.
### Removed:
- "@aws-sdk/client-s3": not used anywhere (was used in Archive.ts
previously)
- chai, "@types/chai", sinon-chai: not used anywhere, probably leftover.
Vitest uses a bundled version of Chai for its expect asserations under
the hood too.
- protobufjs, "@types/google-protobuf": not used anywhere, probably left
from evan's experiment with it? Removed from vite.config.ts too.
- "@types/jquery": not used anywhere, probably leftover
- sinon, "@types/sinon": not used anywhere just like chai, probably
leftover. And Vitest provides us with the same functionality.
- "@types/systeminformation": dependency systeminformation was removed
last year, this is an unneeded, deprecated and unmaintained remainder.
- vite-tsconfig-paths: removed, and removed the import and usage in
vite.config.ts and replaced it by adding `tsconfigPaths: true` to the
`resolve` block. Because of this message displayed on running the tests:
"The plugin "vite-tsconfig-paths" is detected. Vite now supports
tsconfig paths resolution natively via the resolve.tsconfigPaths option.
You can remove the plugin and set resolve.tsconfigPaths: true in your
Vite config instead."
- vite-plugin-static-copy: removed, we don't use it anymore (was used in
our vite.config.ts once,, probably before Vite natively supported
copying static assets via its publicDir configuration)
### Updated:
- color.js: v0.5 > v0.6, no breaking change affecting us
- cross-env: v7 > v10. It's a publicly archived repo since Nov 2025. But
before that he got it up-to-date from June 2025, porting to TS, dropping
old Node versions, dependencies etc. Seems still good to use for some
amount of time to come.
- dotenv: v16 > v17, now logs an informational message by default when
it loads an environment file. Can be disabled by using
dotenv.config({quite: true}) if needed.
- ejs: v3 > v5: security patches mostly. Vite still uses v3 btw.
- eslint: v9 > v10. Newly enabled rules by default:
'no-unassigned-vars', 'no-useless-assignment' and
'preserve-caught-error'. Mostly faster and minimum support moved to
higher node versions, which shouldn't be a problem.
- "@eslint/compat": v1 > v2. Minimum supported Node versions, which
should not be a problem.
- intl-messageformat: v10 > v11 no breaking changes that affect us
- jdom: v27 > v29. Faster. Most notably minimum support moved to higher
node v22 version, which should not be a problem. Also, see types/node,
kind of expecting v24 to be installed now.
- nanoid: from v3 to v5, no breaking changes that affect us
- "@opentelemetry/sdk-logs": now that addLogRecordProcessor is removed,
changed Logger.ts to pass an (empty) provider array directly to the
LoggerProvider constructor. Follows the changes in
https://github.com/open-telemetry/opentelemetry-js/pull/5588
- "@tailwindcss/vite": supports vite v8 from 4.2.2, and a fix for it in
4.2.4
- tailwindcss: supports vite v8 from 4.2.2
-- in 4.1.15 (we were already above this version) break-words was
deprecated in favor of wrap-break-word. But break-words, which we use in
15 places, will still work as expected
(https://github.com/tailwindlabs/tailwindcss/pull/19157). Same goes for
also deprecated "order-none".
- "@types/node": from v22 to v24, assuming most now use node 24
- vite v7 > v8:
-- is now on 8.0.10 so first bugs are out of it, while v8 itself also
fixed a big number of bugs.
-- in vite.config.ts, fixed Ts error/compilation issue by changing the
manualChunks option in build.rollupOptions.output to use the function
syntax, which is required by the updated types instead of the object
syntax.
- zod: no changes that affect us
### Prettier:
Updated only because of (new because of update?) Prettier errors for
files untouched in this PR originally:
- PathFinder.Parabola.ts
- WorkerMessages.ts
- ClanModal.handlers.test.ts
- ClanModal.rendering.test.ts
- CONTRIBUTING.md
- README.md
### ESLint:
Fixes needed to silence errors coming from newly enabled recommended
rules 'no-useless-assignment' and 'preserve-caught-error':
For 'no-useless-assignment' (default assignment never used because of
unreachable code or they are guaranteed to get a value, so they can be
undefinedat the start. Exception was AttackExecution, so made the
default value of 0 the default case in the switch statement):
- ClientGameRunner
- GameModeSelector
- NameBoxCalculator
- StructureDrawingUtils
- TerritoryLayer
- Diagnostics
- GameRunner
- ColorAllocator
- DefaultConfig
- AttackExecution
- AiAttackBehavior
- Worker.worker
- GamePreviewBuilder
For 'preserve-caught-error', disabled the rule here because the possible
fix `{cause: error}` was introduced in ES2022 while we're still on
target ES2020 currently:
- GameServer
- Privilege
_Error: The value assigned to 'gameMap' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'timeDisplay' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'scalingFactor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'radius' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'teamColor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'gl' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'power' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'tickExecutionDuration' is not used in
subsequent statements. (no-useless-assignment)
Error: The value assigned to 'selectedIndex' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'mag' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'speed' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'matchesCriteria' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'shouldContinue' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'description' is not used in subsequent
statements. (no-useless-assignment)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)_
All tests pass. TypeScript and ESLint errors resolved.
## 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
---------
Co-authored-by: Copilot <copilot@github.com>
## Description:
Corrected inbalance in Luna map between top and bottom. Now both sides
have about equal land tiles.
Testing and editing was done to help boat-pathing at least look normal
along bottom half.
Additional notch cut in orbit lines to take edge off boat-pathing. Now
obstacle is almost a straight line.
Filled in center orbits to help players push easier. It was reported to
be tedious previously.
'Top Secret Military Base' nation was renamed with only vowels blacked
out for easier reading of joke name.
'Monolith' nation was renamed with a special character that displays
like the actual Monolith.
<img width="829" height="1165" alt="l1"
src="https://github.com/user-attachments/assets/8cf10fbf-99c1-4ec8-ae0c-7066d1deae21"
/>
<img width="829" height="1165" alt="l2"
src="https://github.com/user-attachments/assets/828e2caf-60b3-4ac7-aa70-0f5b64fac643"
/>
## 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
## Discord username
PlaysBadly
## Description:
Currently it is impossible to search for 2 letter clan tags (UN, FR,
EU), this is because of an off by one error present in the API
## 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:
Babyboucher
## Description:
Starting v32 maps right now!
Adds map of the Middle East. Probably one of the most highly requested
maps.
Very large map (3.4M land pixels, similar to Two Lakes, would become the
2nd or 3rd largest map by land area) of the arabian peninsula and
surrounding regions.
This map has both huge areas of land without water access full of desert
terrain, and massive trade chokepoints, which combined will result in
crazy endgames.
The theme of this map are historical, based on the end of WW1.
Historical flags have been added for the nation NPCs to use.
(High map rotation of 8 since this is probably going to become one of
the most popular maps given the relevancy of the region in real life.)
Terrain source from NASA DEM, already credited.
https://github.com/user-attachments/assets/6a1b345f-fd92-42c2-8f92-154fac4c9733
## 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
tri.star1011
## Description:
Prevent number-key build shortcuts from opening the unit build ghost
after the player has died.
Keep build hotkeys available only while the player is alive and not in
spawn phase.
## 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
Re-enables Turnstile verification (was temporarily disabled in v31 to
diagnose intermittent `invalid-input-response` rejections) and moves the
verification call off the game servers.
Game servers no longer hold `TURNSTILE_SECRET_KEY` or hit
`challenges.cloudflare.com` directly. Instead they POST to
`${jwtIssuer}/turnstile` on the api-worker (authenticated with the
existing `apiKey`), which holds the secret and proxies to Cloudflare.
Shrinks blast radius and removes the secret from every game host + GH
Actions workflow.
Response from the api-worker is Zod-validated; null tokens short-circuit
to `rejected` locally.
## Please complete the following:
- [x] I have added screenshots for all UI updates (n/a — server only)
- [x] I process any text displayed to the user through translateText()
(n/a — no user-visible text)
- [ ] 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:
evanpelle
## Description:
Other settings previously included emojis, but those have since been
removed.
This change updates the user settings to keep the UI consistent.
## 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
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
The store item cards were truncating names with an ellipsis. This change
updates the cosmetic card name label to wrap instead of truncating, so
the full name is always shown.
before
<img width="748" height="363" alt="スクリーンショット 2026-05-04 10 26 58"
src="https://github.com/user-attachments/assets/32030be3-6e92-4ca6-8117-451c0ae75582"
/>
after
<img width="756" height="585" alt="スクリーンショット 2026-05-04 10 27 30"
src="https://github.com/user-attachments/assets/20e0fd36-dea4-4236-852b-ca5a2cd7e0f5"
/>
## 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:
`MapLandTiles` was fetching map manifests via HTTP at
`http://localhost:3000/maps/<map>/manifest.json`. This stopped working
after PR #3494 moved all public assets from stable paths (e.g.
`/maps/...`) to content-hashed paths under `/_assets/...`. The master
server no longer serves `/maps/` -- requests fell through to the SPA
handler, returned `index.html`, failed JSON parsing, and silently fell
back to the default `1_000_000` land tile count.
With 1M tiles, `calculateMapPlayerCounts` produces `[50, 40, 25]`
instead of the real values (e.g. `[185, 140, 95]` for Alps), causing all
public lobbies to be capped at 25-50 players regardless of map.
Fixes this by reading map manifests directly from disk instead of via
HTTP:
- In production: resolves the source path through
`getRuntimeAssetManifest()` to the hashed file under `static/_assets/`,
then reads it with `fs.readFile`.
- In dev: falls back to `resources/maps/<name>/manifest.json` directly
(the Dockerfile removes `resources/maps` in production, so this branch
only runs locally).
Also adds an in-process cache so each map manifest is only read once per
server lifetime.
Verified that it works on
https://fix-map-land-tiles-asset-manifest.openfront.dev/ and locally.
## 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:
`MapLandTiles` was fetching map manifests via HTTP at
`http://localhost:3000/maps/<map>/manifest.json`. This stopped working
after PR #3494 moved all public assets from stable paths (e.g.
`/maps/...`) to content-hashed paths under `/_assets/...`. The master
server no longer serves `/maps/` -- requests fell through to the SPA
handler, returned `index.html`, failed JSON parsing, and silently fell
back to the default `1_000_000` land tile count.
With 1M tiles, `calculateMapPlayerCounts` produces `[50, 40, 25]`
instead of the real values (e.g. `[185, 140, 95]` for Alps), causing all
public lobbies to be capped at 25-50 players regardless of map.
Fixes this by reading map manifests directly from disk instead of via
HTTP:
- In production: resolves the source path through
`getRuntimeAssetManifest()` to the hashed file under `static/_assets/`,
then reads it with `fs.readFile`.
- In dev: falls back to `resources/maps/<name>/manifest.json` directly
(the Dockerfile removes `resources/maps` in production, so this branch
only runs locally).
Also adds an in-process cache so each map manifest is only read once per
server lifetime.
Verified that it works on
https://fix-map-land-tiles-asset-manifest.openfront.dev/ and locally.
## 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:
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:
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:
QoL changes for the Bosphorus map.
The base image for the map had a little aliasing which resulted in some
rivers being cut off.
<img width="673" height="386" alt="image"
src="https://github.com/user-attachments/assets/b8ed181f-fbeb-4d6f-b7ab-a8b0ea300a22"
/>
All of the nations except istanbul also had absolutely nothing to do
with the region. For example, Varna is a city in Northeast bulgaria and
the aegean isles are also nowhere near. There was also a nation that did
not spawn because its coordinates were in the sea.
<img width="626" height="413" alt="image"
src="https://github.com/user-attachments/assets/a6537f79-3785-4316-8fd4-a99f55faff71"
/>
All of these poor designs are probably the result of the map being
resized, originally being a larger map. It probably explains why this is
the smallest map by land area (not counting Oceania)
Fixes:
- Fixed landlocked rivers and added More accurate bodies of water
- Complete remake of the nations. Nations are now far more accurately
placed districts of the region, and more evenly placed
<img width="664" height="407" alt="image"
src="https://github.com/user-attachments/assets/ff5a34fc-dea0-4a3d-b798-39d5711b91af"
/>
The data and gamestyle of this map should not change much, as the
landmasses remain in practice the same
## 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:
tri.star1011
## Description:
In PR 3654 i left an unnecessary question mark.
## 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:
QoL changes for the Bosphorus map.
The base image for the map had a little aliasing which resulted in some
rivers being cut off.
<img width="673" height="386" alt="image"
src="https://github.com/user-attachments/assets/b8ed181f-fbeb-4d6f-b7ab-a8b0ea300a22"
/>
All of the nations except istanbul also had absolutely nothing to do
with the region. For example, Varna is a city in Northeast bulgaria and
the aegean isles are also nowhere near. There was also a nation that did
not spawn because its coordinates were in the sea.
<img width="626" height="413" alt="image"
src="https://github.com/user-attachments/assets/a6537f79-3785-4316-8fd4-a99f55faff71"
/>
All of these poor designs are probably the result of the map being
resized, originally being a larger map. It probably explains why this is
the smallest map by land area (not counting Oceania)
Fixes:
- Fixed landlocked rivers and added More accurate bodies of water
- Complete remake of the nations. Nations are now far more accurately
placed districts of the region, and more evenly placed
<img width="664" height="407" alt="image"
src="https://github.com/user-attachments/assets/ff5a34fc-dea0-4a3d-b798-39d5711b91af"
/>
The data and gamestyle of this map should not change much, as the
landmasses remain in practice the same
## 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:
tri.star1011
Previously, winnerVotes was keyed only on winner, so a client could send the correct winner with spoofed allPlayersStats and have their vote count toward the majority. The key is now a SHA-256 hash of both winner and allPlayersStats together, so any stats divergence produces a distinct key that must independently reach majority.
When the winner is confirmed, the log now includes votesByKey — a summary of every distinct key, its vote count, and winner value — making stat manipulation visible in the logs.
CSS mask-image triggers a CORS fetch, which failed for the CDN-hosted medal SVG. Switched to a Vite ?raw import so the SVG is embedded as a data URI at build time — no network request, no CORS.
Also stripped the SVG of Inkscape metadata and replaced filter-based color inversion with a plain fill="white", shrinking it from 3,278 → 955 bytes (387 bytes gzipped).
## Description:
The motivation is to prevent snowballing players from also gaining too
much gold by conquering other players
- Adds `conquerGoldAmount` to `Config`/`DefaultConfig`: returns 100% of
captured gold for bots/nations, 50% for human players
- Updates `GameImpl.conquerPlayer` to use this amount for the
conqueror's gold gain (the conquered player still loses their full gold)
## 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:
reloadMissile() was inside the isInCooldown() block. For level-2+ SAMs,
isInCooldown() returns queue.length === level, so after firing one of
two missiles (queue.length = 1 < level = 2) the SAM is not in cooldown —
meaning expired timers were never cleaned up. Stale queue entries caused
subsequent shots to be treated as still-cooling even after the cooldown
elapsed.
SAM execution now mirrors the missile silo execution
## 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:
Comprehensive accuracy and quality-of-life improvements across 8 maps.
No gameplay mechanics changed: only nation names, spawn positions, and
flag codes. All changes bring maps closer to real-world geographic and
historical accuracy.
**Changes by map:**
1. **North America:** Corrected numerous incorrect state spawn
positions. Added all 13 Canadian provinces and territories with correct
flags, as well as a few additional bots for balancing and completeness.
2. **South America:** Corrected spawn positions of Venezuela, Suriname,
and French Guiana. Added four Brazilian states (Amazonas, Pará, Bahia,
São Paulo) for better regional coverage and balancing.
3. **Gateway to the Atlantic:** Full historical consistency pass
targeting the ~1340-1410 CE period, matching the existing Britannia and
Italia map style. Renamed several nations for historical accuracy (Duchy
of Burgundy, Kingdom of Navarre, Kingdom of Majorca, City of Avignon,
Crown of Aragon, Duchy of Aquitaine, Hafsid Sultanate, Marinid
Sultanate, Zayyanid Sultanate, Holy Roman Empire). Added 7 new
historically accurate flag SVGs for these nations.
4. **Europe:** Shortened long-form nation names to short-form for
consistency with other maps. Added Andorra and Monaco for improved
balance.
5. **Europe Classic:** Fixed "Syrian Arab Republic" to "Syria" for
consistency with all other maps. Added Baltic states, Croatia, Denmark,
Sápmi for balancing.
6. **Oceania:** Removed ghost bots that no longer appear on the map.
Fixed truncated and outdated names: Lao PDR to Laos, Brunei Darussalam
to Brunei, TimorLeste to Timor Leste, Taiwan Province of China to
Taiwan.
7. **World:** Fixed outdated names for accuracy and consistency with the
World map and other maps. Added bots in regions lacking coverage for
more consistent global representation.
8. **Giant World Map:** Updated nation names for accuracy and
consistency. Added Canadian provinces, US states, Bolivia, and Gabon for
improved map balance.
All changes made and verified in the map editor. No en.json or UI
changes required as these are map data files only.
## 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:
@islandspiritozempic
## Description:
Update pixijs to 8.18.1, mostly because we might want to use the ability
to send AutoDetectRenderer an array as "preference". And because we want
to stay up-to-date with fixes to a renderer we use.
## 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:
Commit `4d5b7c0` added `adfree: boolean` as a required field to
`UserMeResponseSchema` in `ApiSchemas.ts`, but did not update the mock
object in `tests/ResolveCosmetics.test.ts`. This caused `tsc --noEmit`
to fail with a type overlap error, breaking the production build.
**Fix:** Add `adfree: false` to the `player` mock inside `makeUserMe()`
in the test file.
## 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
Adds adfree: boolean to player in UserMeResponseSchema and replaces the flares-length heuristic in Main.ts with a direct check of this field to determine whether ads should be shown.
## Description:
One dependency less: remove uuid. It is only used to get the three
random digits after "Anon" if no name is present in localStorage.
Crypto.randomUUID also gives us a UUID v4 and can already be used from
Utils > generateCryptoRandomUUID. Not noticable when it comes to speed
either.
## 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:
Removed single pixel line from the bottom of LA map.
Added L.A.X. nation to LA map.
Removed USSR flags from Dyslexdria (originally imported from giant world
map). Left USSR flag on 'Rusha' only.
## 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
PlaysBadly
## Description:
Continuation from #3276
Adds the complete client-side clan UI as a Lit web component
(`<clan-modal>`), a typed API client with Zod-validated responses,
shared response schemas, and a reusable `<confirm-dialog>` component.
### New: `ClanModal.ts`
| View | What it does |
|------|-------------|
| **My Clans** | Lists joined clans + pending join requests (built from
`/users/@me`, no extra fetches) |
| **Browse** | Search by tag (min 3 chars), paginated results,
configurable per-page (10/25/50) |
| **Clan Detail** | Stats, paginated + searchable member list, role
badges, join/leave/request actions |
| **Manage** | Edit name (max 35 chars) + description, toggle
open/invite-only, disband |
| **Transfer** | Leadership transfer with member selector + confirmation
|
| **Requests** | Approve/deny join requests (leader/officer) |
| **Bans** | View and unban (leader/officer) |
| **My Requests** | View and withdraw outgoing requests |
### New: `ConfirmDialog.ts`
Reusable `<confirm-dialog>` Lit component — replaces native
`confirm()`/`prompt()` which are blocked or broken on mobile and
CrazyGames iframes. Supports danger/warning variants and an optional
textarea (used for ban reasons). Fires `confirm`/`cancel` events.
### New: `ClanApi.ts`
Typed API client covering all clan endpoints. Every response is
Zod-validated. Auth header is always last in the spread (can't be
overridden by callers). Unknown server error messages always fall back
to a generic client-side string — never displayed verbatim.
### New: `ClanApiSchemas.ts` (in `src/core/`)
Shared Zod schemas for clan API responses with max-length constraints on
`name` (35) and `description` (200). Lives in `core/` so it can be
consumed by both client code and the leaderboard table.
### Modified: `ApiSchemas.ts`
- Added `clans` and `clanRequests` arrays to `UserMeResponseSchema`
- Moved clan leaderboard schemas out to `ClanApiSchemas.ts`
- Renamed `LeaderboardClanTagSchema` → `RequiredClanTagSchema`
### Modified: `Api.ts`
- Added `invalidateUserMe()` to bust the cached `/users/me` response
after mutations
- Removed `fetchClanLeaderboard` (moved to `ClanApi.ts`)
### Tests
- `ClanModal.test.ts` — rendering, view navigation, user actions
- `ClanApiQueries.test.ts` — fetch functions, error handling, pagination
- `ClanApiMutations.test.ts` — join, leave, kick, ban, promote,
transfer, etc.
- `ClanApiBans.test.ts` — ban/unban calls and error paths
- `ClanApiSchemas.test.ts` — Zod schema validation edge cases
- `LeaderboardModal.test.ts` — updated imports
## Notable design decisions
- **Not-logged-in state** — shows "Sign in to join clans" instead of
false "no clans" empty state
- **Rate limit feedback** — reads `Retry-After` header and surfaces wait
time to the user
## 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
---------
Co-authored-by: evanpelle <evanpelle@gmail.com>
cancelNukesBetweenAlliedPlayers previously only cancelled a nuke if its exact target tile was owned by the new ally. This meant nukes aimed at neutral or own tiles near allied territory would survive alliance formation and still land (and break the alliance).
Now uses wouldNukeBreakAlliance — the same blast-radius logic used by maybeBreakAlliances on impact — so a nuke is cancelled if its blast would have meaningfully hit the ally's tiles or structures. Also switches from the exhaustive listNukeBreakAlliance (scans all players) to wouldNukeBreakAlliance with a single-player allySmallIds set for early-exit performance.
## Description:
When multiple nukes detonated in the same tick, clearTrail was called
once per dying unit. Each call scanned all remaining units to repaint
overlapping trail tiles — O(dead × alive × trail_len) per tick.
Replace with a deferred batch: dying units are queued into
pendingTrailClears during drawUnitsCells, then flushTrailClears()
processes them all at once after the draw pass. All trail tiles are
cleared in a single loop (skipping duplicates), followed by one repaint
scan of surviving units — O((dead + alive) × trail_len).
Also fixes a minor bug in the original: the surviving unit's
relationship is now used when repainting its trail (previously the dying
unit's relationship was used, which gave wrong colors in alternate-view
mode).
## 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:
The warship pathfinder operates on a 2x downscaled mini-map, and
upscaling mini-map paths back to full coordinates produces diagonal
interpolated steps. At close range (< 20 tiles), the entire path
consists of these diagonal moves, causing the warship to approach the
trade ship at an awkward angle and never converge cleanly.
## 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:
Updating TypeScript to 6.0.3.
Updating TypeScript-eslint to 8.59.1 for TS6 support.
Concurrently needed to get updated as well to remove deprecated warning.
Most things deleted are now just defaults.
## 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:
Babyboucher
Part of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661)
(split into 3 PRs so they are not too large..)
## Description:
Part 3/3 of
[#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661).
This PR adds the retreat control and override behavior for warships:
- Manual override: moving a warship manually cancels retreat and
suppresses auto-retreat for 5 seconds
- Aggro override: a retreating warship will aggro a nearby enemy
transport or warship before continuing retreat
- Heal-at-port command for sending a warship to a friendly port manually
- Friendly-port validation for HealAtPortExecution
- Regression tests for manual override, aggro override, and heal-at-port
behavior
## 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:
zixer._
---------
Co-authored-by: iamlewis <lewismmmm@gmail.com>
Co-authored-by: evanpelle <evanpelle@gmail.com>
## Description:
Version identifier within MLS: v5.3
[Changed languages]
- fr
- ru
- uk
[Change volume]
- Changed languages: 3
- Changed files: 3
- Changed lines: 6165
- metadata.json: unchanged
Final reviewer: meow02952
This PR was generated by the PR sender tool, then checked and submitted
by the final reviewer.
## 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