Commit Graph

2341 Commits

Author SHA1 Message Date
Wraith 7658c67662 optimize(RailroadLayer): throttle color scans, cull blits, and remove (#2565)
## Description:

Render optimizations were applied for RailroadLayer: limiting color
updates, skipping off-screen work, drawing only the visible region, and
preventing O(n) ray tile removals.

## 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:

wraith4081
2025-12-07 19:35:07 +00:00
bibizu 97e6c1cd77 feat: Nuke Trajectory SAM intercept prediction (#2541)
## Description:

Overhaul to nuke previews:
- Nuke previews now show when they become invulnerable (if
invulnerability setting enabled).
- Nuke previews now show if a SAM defense could intercept it, and where
the intercept occurs.
- SAMs range is now color-coded and outlined to be able to know if
missile will be intercepted by it.

<img width="1326" height="862" alt="image"
src="https://github.com/user-attachments/assets/4cc43a0b-ebac-4707-85bb-4d422f23da28"
/>

Colors/outlines aren't meant to be final, and there are constants to
play around with at the start of both files.

I also kept the naive implementation since the performance cost isn't
too high from my testing and any improvements I can think of are small
and require a bigger rewrite.

## 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:
bibizu
2025-12-06 11:21:44 -08:00
VariableVince 6ca81211ea Alert frame: add to in-game settings, orange for attack instead of red (#2561)
## Description:

The red alert frame for betrayals was added in
https://github.com/openfrontio/OpenFrontIO/pull/1195. It also flashes
red for incoming land attacks since
https://github.com/openfrontio/OpenFrontIO/pull/2358.

The same color for betrayals and attacks leads to confusion. And
possibly red alert fatigue. But when players find themselves fatigued
and want to shut it off for awhile, they can't because the setting
doesn't exist in-game. Also, the setting description on the homepage
settings didn't yet reflect that the alert frame flashes for attacks
too.

This PR fixes this by:

- making the color for land attacks orange. This is well discernable
from red for various colorblindness types, while still looking alarming.
- adding the setting to in-game SettingsModal 
- adding land attack to setting description

Reference to comments on it on Dev Discord:

https://discord.com/channels/1359946986937258015/1381347989464809664/1441232666065240064


https://discord.com/channels/1359946986937258015/1360078040222142564/1434574256704061523

Orange alert frame on being attacked over land:

https://github.com/user-attachments/assets/e0772d62-5b25-4213-a393-dd5af13e8bc9

Settings description change and addition to in-game toggles:
<img width="560" height="160" alt="Added to description what was added
in PR 2358"
src="https://github.com/user-attachments/assets/bc6e2206-b7ac-498d-9009-d2b6e302d3cf"
/>

<img width="665" height="425" alt="In SettingsModal and with attacks
added to description"
src="https://github.com/user-attachments/assets/d489830c-e359-4a5f-8eb4-3caa7d0c21b2"
/>

## 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
2025-12-04 13:26:55 -08:00
VariableVince ec0bf079ef Fix spacing in player team label display (#2560)
## Description:

Add a space after "Your team:"

Before:
<img width="237" height="117" alt="image"
src="https://github.com/user-attachments/assets/60c0821f-a188-44bc-bcd5-e810a741b297"
/>

After:
<img width="243" height="122" alt="image"
src="https://github.com/user-attachments/assets/99b3ff5a-167d-4bae-b8f6-b1b199d4946a"
/>

## 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
2025-12-03 16:37:17 -08:00
VariableVince 8f32746bb2 Special bot names (#2552)
## Description:

Special bot names. If the solution seems convoluted for such an easy
thing, that is because: not all bots find a spawn position, so only
assign a candidate name after finding a spawn. And the first few are
almost always overwritten by Nation spawns so the first 20 just get a
random name. Only then do we assign from the provided lists. For the
random names, some might get the same name but that's not an issue as
no-one will notice and they're off the map quite fast anyway.

## 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
2025-12-03 16:01:05 -08:00
Lavodan 4ff2ca3315 Fix: firefox back button not working (#2557)
## Description:

Fixes an issue on Firefox based browsers, which caused the back button
to not work when in a game.

This was caused because the renderer always appended the canvas to the
document, even when the canvas was already in the document. Chrome
handles this by moving the canvas to the end of the document, whereas
firefox refreshes the whole page. This made it lose important context,
specifically the pushed \#refresh history changes, which caused the back
button to not work properly.

Additionally, Firefox threw out all but the last instance of
history.pushState in certain cases, so using history.replaceState fixes
that issue.

Functionality is preserved for Chrome.

## 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
2025-12-03 15:41:58 -08:00
VariableVince 997cfea730 Fixes lobby team preview: clan players aren't assigned a team + add nation count + other small fixes (#2536)
## Description:

Fixes for v28. In https://github.com/openfrontio/OpenFrontIO/pull/2444,
lobby team preview was added. But for players with clan tags, this
doesn't work correctly. They don't get assigned to the same team in the
preview, while they do get assigned to the same team in the actual game.
Also added some small fixes and QoL improvements like showing the number
of Nations next to the number of Players. Since we needed that info
anyway. Did not choose to show and assign Nations to teams (just the
numbers), see why under CONSIDERED OPTIONS THAT I DIDN'T WENT WITH.

**BEFORE:**
https://youtu.be/AV_aDJ4PgOk

<img width="767" height="117" alt="Malformed argument because of double
accolades for remove_player"
src="https://github.com/user-attachments/assets/7de1114e-7ce1-4a8f-95cc-6b0528a61e3b"
/>

**AFTER:**
https://youtu.be/aDCKkwedqes

Cause of bug:
maxTeamSize is a number in assignTeams, only used to assign clan
players. It uses the length of the players array as input. At actual
game start, the Nations are also in the players array. But at lobby
preview the Nations aren't yet fetched. So when 2 players want to do a 3
Teams private lobby with them using clan tags to be in the same team.
maxTeamCount would do Math.ceil(2/3)=1. So only 1 clan player per team
as a result. While actually there could be 10 Nations which would result
in maxTeamCount Math.ceil(12/3)=4 so in the game they would actually be
assigned to the same team.

Fix for bug: 
fetch Nations count in HostLobbyModal and pass on to LobbyTeamView. Add
it to the number of players for maxTeamCount that
assignTeamsLobbyPreview uses for its calculation. Also added nation
count to the similar teamMaxSize in LobbyTeamView itself, to display the
same and correct number of max players. For random maps, we now need to
know the random map before the game starts to get its Nation count. So
made some changes for that too in HostLobbyModal.

Also fixed:
- willUpdate ran comptuteTeamPreview every second, now checks if
properties like 'client' actually changed. PollPlayers in HostLobbyModal
'changes' the clients property every second even if there are no actual
changes. Checks if the other properties are actually changed too, to
make it more future proof.
- cache teamsList so it is only fetched once instead of first in
computeTeamPreview and then again for showTeamColors.
- don't show the "Empty Teams" header if there are no empty teams.
- console error ICU format error SyntaxError: MALFORMED ARGUMENT.
Because of double accolades around remove_player in translation value.
- remove fallback for comparing clients on clientID, which used client
name. Players may have the same names so it's not safe to compare based
on name.

Also show number of Nations next to number of Players: 
now we now the nationCount since it is needed for the fix, show number
of Nations next to number of Players. It's handy and it prevents
confusion as to why it says 2/32 for two teams if there are only 2
players; it is because there are 61 Nations as well on the World map for
example.

Also determine number of teams based on Players + Nations: 
now we now the nationCount since it is needed for the fix, use it to
determine the number of teams. Just like populateTeams in GameImpl does.
This means for Duos on the World map, a minimum of 31 teams will be
shown since there are 61 Nations. This is better than just show two
teams based on 1 or 2 humans in the lobby. Also it makes more clear how
many teams there'll be the game and how the players and nations are
divided over the teams. Choose to not show the Nations' team assignments
though. That could be for another PR. See explanation under CONSIDERED
OPTIONS THAT I DIDN'T WENT WITH.

Also show Nations team as pre-filled for HumansVSNations: 
now we now the nationCount since it is needed for the fix, for
HumansVSNations, show the Nations team as fully assigned and non-empty.
For example for World map it shows Nations 61/61. Don't show them as
Empty Team but as Assigned Team. Although i choose not to show the
actual Nations (see CONSIDERED OPTIONS THAT I DIDN'T WENT WITH), this
makes it clear their team is pre-filled and how many Nations you're
actually up against. Whereas for other Team game types like 7 Teams or
Duos, it will display the teams that the Nations will fill up as empty.

CONSIDERED OPTIONS THAT I DIDN'T WENT WITH
- Use an optional param 'nationsCount' to assignTeams with default = 0.
And simply add nationsCount to the players.length count for maxTeamSize.
This would be error prone; 'nationsCount' should then never be assigned
a value except when called from LobbyTeamView. But in the future someone
might assign it a value even when called from somewhere else. Then you
could say, check if there are Nations in the players array and if so, do
not use Nationscount because we know they are already counted from
players.length. But if Disable Nations is enabled, there would be no
Nations in the players array but if nationsCount was somehow given a
value we still should not use it. So again, too developer error prone.

- Not only fetch the number of Nations, but actually get the Nations
themselves to show them in the team assign preview as well. They are
shuffled on team assignment but of course deterministicly (nation player
ID assigned based on Pseudorandom seeded with GameID in GameRunner, then
shuffled in TeamAssignment with Pseudorandom seeded with map's first
Nation's playerID), so we could replicate it. But then, how to
distinguish humans and Nations in the UI? This feels like something for
a follow-up PR.

FOR A FUTURE PR
- change the way Clan team overflow is handled. Now they're "kicked" as
in not assigned to a team without their knowing, are loaded into the
game but cannot spawn. This UX could need some improvement. And the
logic can be improved too, ie. don't "kick" too soon, check the number
of Clans in the lobby and the number of teams to decide if we can assign
the 'overflowing' Clan player to another team that doesn't have
rivalling Clan players. Far out of scope for this 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:

tryout33
2025-12-01 20:44:07 -08:00
jachisc 96cf177a5f Add the Lisbon Map and Credit Copernicus DEM (#2545)
## Description:

Adds a map based on Lisbon and the surrounding area. Also credits the
ESA's Copernicus Digital Elevation Model, which was used to create this
map and the Gulf of St. Lawrence map.

<img width="1257" height="1257" alt="screenshot of the new Lisbon map"
src="https://github.com/user-attachments/assets/39fa73da-c77d-4d5c-8d00-7ee2794d0298"
/>

## Checklist:

- [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:

sehentsin
2025-12-01 15:35:47 -08:00
Lavodan fe8c0f961a Fix clients able to join above max players (#2547)
Resolves #698

## Description:


This PR fixes clients being able to join games which are already full.
This caused several bugs and glitches, like incorrect team sizes in team
games, and being stuck in spectator mode.

This fix checks for number of active clients in GameServer, and if it
sees that the lobby is full, it does not put the client in the game, and
sends an error with error key being "full-lobby"

ClientGameRunner then checks to see this error, and overrides the
default implementation of showing a popup. Instead it will leave the
lobby for the user by dispatching a leave-lobby event into the document,
and the user can then reqeue into a new game.

Here is a video showcasing how full games are handled.


https://github.com/user-attachments/assets/dc6220ea-590f-4bd1-8ca5-38c0d24ae792

## Note on testing

I wasn't able to figure out how to properly overwrite lobbyMaxPlayers
from the default config using the devconfig, so the video shows just a
hardcoded version of defaultconfig, therefore testing solo is probably
not really possible.

I just changed the function in defaultconfig for my testing to this:
```ts
  lobbyMaxPlayers(
    map: GameMapType,
    mode: GameMode,
    numPlayerTeams: TeamCountConfig | undefined,
  ): number {
	  return 1; 
	}
```

## Notes

This PR does not necessarily resolve all cases which cause 698, as for
example joining too late while there is still space is not changed at
all. For most public games, this shouldn't be an issue as the timer is
long enough for a majority to be filled up before the timer hits 0.

Additionally, spectating ongoing games should work fine, but as local
server spectating is buggy in general, I was not able to test and
confirm this 100%.

## 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
2025-12-01 15:33:24 -08:00
jachisc 8f3e09c6a3 Add Gulf of St. Lawrence Map and Related Flags (#2524)
## Description:

Adds the Gulf of St. Lawrence map and the flags of New Brunswick
(ca_nb), Nova Scotia (ca_ns), and Prince Edward Island (ca_pe).

## 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

<img width="1380" height="1150" alt="Screenshot 2025-11-26 201008"
src="https://github.com/user-attachments/assets/77531058-b429-4c68-b645-a3e59033458b"
/>
<img width="1434" height="1195" alt="Screenshot 2025-11-26 202128"
src="https://github.com/user-attachments/assets/9c3e2bc2-882f-4662-a32b-16e17db852f2"
/>

## Discord username

sehentsin
2025-11-30 20:17:29 -08:00
Lavodan 55e413c528 Fix Territory Skins option (#2538)
Resolves #2517 

## Description:

Fixes territory skin option not doing anything.
Also changes some instances of directly accessing
"this.cosmetics.pattern" to use the precalculated "pattern" variable.

## 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
2025-11-30 19:38:35 -08:00
Roan 5d513dfe5d Remove border around in game time (#2544)
## Description:

Removes the border around the in game time because it looks better this
way. Sorry my screenshots are a bit washed out, windows doesn't like my
HDR monitor...

Before:
<img width="201" height="145" alt="image"
src="https://github.com/user-attachments/assets/ef399dcd-72bc-4ee7-ac67-42a0e4b2b793"
/>

After:
<img width="213" height="143" alt="image"
src="https://github.com/user-attachments/assets/bdb211af-d2c6-4b5e-8946-1cb88bf707c2"
/>


## 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:

rovi.
2025-11-30 19:00:36 -08:00
FloPinguin ab53ee687f Alliance icon does no longer stretch/disappear 🖌️ (#2527)
Resolves #2521

## Description:

Small CSS fix so the new alliance icon does not stretch when there are
multiple icons.

## 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
2025-11-26 19:50:58 -08:00
FloPinguin 41deef9e96 Nations no longer send random boats to their bordering enemies 🚢 (#2526)
## Description:

Nations no longer send random boats to their bordering enemies.

Usually it looked a bit stupid when nations did that ("Why don't you
attack your bordering enemy directly?")

## 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
2025-11-26 19:42:50 -08:00
DevelopingTom c38c25ab1e Add factory & train emojis (#2522)
## Description:

Add 🏭 & 🚂 emojis, which is commonly requested. Also 3 other emojis to
fill the new emoji line: 🫴🙏

To be discussed: this adds a new line in the emoji list: is it too much?
Eventually we could use categories to filter the emojis.

<img width="199" height="465" alt="image"
src="https://github.com/user-attachments/assets/cb6a44e9-30cd-48f7-90ab-8a7c32dcd180"
/>

## 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:

IngloriousTom
2025-11-26 16:39:52 -08:00
CrackeRR11 8f53785a80 BUG FIX: Gold double deduction + Rmoval of UnitType.Construction (#2378)
## Description:

- Removed the temporary UnitType.Construction and embedded construction
state into real units via isUnderConstruction().
- Centralized non-structure spawning to perform a single validation
right before unit creation/launch.
- Updated UI layers to render construction state without relying on the
removed enum.
- Adjusted and created tests to match the new flow and to cover the
no-refundscenarios.

# Tests updated 
- tests/economy/ConstructionGold.test.ts: covers structure cost
deduction and income, tolerant of passive income; ensures no refunds
during construction.
- tests/nukes/HydrogenAndMirv.test.ts: accounts for single-check launch
flow; MIRV test targets a player-owned tile; ensures launch after
payment.
- tests/client/graphics/UILayer.test.ts: mocks now provide
isUnderConstruction and real type strings;

## 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:

CrackeRR1

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-26 14:45:14 -08:00
VariableVince 7d8c1c2822 Fix: prevent desync after clan team assignment for profane username (#2511)
## Description:

Clan tag was removed when overwriting profane username. The local player
still sees the name they put in though, and are assigned to a team based
on the clan tag. Other player's browsers don't assign them to their team
because the clan tag isn't visible to them.

**Fixes:**
- GameRunner.ts > username.ts: fetch clan tag before potentially
overwriting bad username. Prepend non-profane clan tag back to the name
string afterwards.

- Util.ts: added getClanTagOriginalCase; we can't use getClanTag in
censorNameWithClanTag because it returns all caps and we needed to
retain the orginal capitalization. Explained in code comment.

- Game.ts: no changes. By keeping the getClanTag in PlayerInfo
contructor, TeamAssignment still gets clan param to correctly assign
clan teams, other players get to see the clan tag of the "BeNicer"
player, and GameServer archivegame() and LocalServer endGame() can still
do getClanTag to have the same data at the end of the game, and test
files keep working.

**Screencap after fix:**

https://github.com/user-attachments/assets/564c0ffd-577e-4653-ba33-155d2135a9f0

## 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
2025-11-25 20:08:30 -08:00
VariableVince 9b125c8cfe Bugfix: nation strength undefined in only place it is used (#2498)
## Description:

In commit
https://github.com/openfrontio/OpenFrontIO/commit/bbf72bd14f7f31146c687523aea8fc0aff31bbe1#diff-ee2fcbca50d87cc09d2c7d2667210defe2e3e111239820c89c40283be5385b64
it was added that startManpower in DefaultConfig used
playerInfo.nation.strength to set the starting troops for a Nation. In
ExecutionManager, param nation of PlayerInfo was set during the
instantiation of a new FakeHumanExecution.

However in commit
https://github.com/openfrontio/OpenFrontIO/commit/d6a412aa50dd86d474d80c216fd9ba36e7426ef9#diff-2d0a5d8b171d8b504f934891025e42742e142ef0964d6e17712bfdcd30bf050c
the changes made it so that **param nation of PlayerInfo was never
set**. While startManpower in DefaultConfig still checked for
playerinfo.nation.strength. Since it was always undefined, it would use
a multiplier of 1 instead of the actual nation strength.

This PR fixes it by passing the nation strength to param nationStrength
in PlayerInfo. Removing param strength from class Nation. Strength isn't
used anywhere else so this isn't a problem and it also consolidates
human player info and nation player info even more. We could have also
used the Nation.strength directly, but that would have required more
code in addPlayers and addPlayer in GameImpl, especially for Teams
games. So this PR has the simplest solution.

- I did add a config setting useNationStrengthForStartManpower with a
comment that explains its reason for being. Namely that in the months
that startManpower didn't get to use nation strength because of the bug,
FakeHumans have become much harder to fight. Re-enabling higher starting
troops from this fix would make them even harder to fight, and i think
rebalancing is needed before that.

- Or we could decide to scrap Nation strength altogether, as it is only
ever used to set starting troops anyway. This would make map making a
little easier as a bycatch.


## 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
2025-11-24 10:30:18 -08:00
NOBODY 049485cd39 Fix: Correct percentage (%) placement in Persian UI (RTL handling) (#2501)
## Description:

Fixes incorrect RTL rendering where the percentage symbol (%) appeared
before the label in Persian (fa-IR).
The UI now correctly displays values as `20%`.

**Changes:**
- Updated `controlpanel.ts` to fix percentage position.
- 16 additions, 7 deletions.

**Testing:**
1. Switch UI language to Persian (fa-IR).
2. Open control panel with percentage display.
3. Confirm `%` appears after numeric value.
4. Verified no changes in LTR languages.

## 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:

nobodyiran
2025-11-24 10:29:08 -08:00
Rj Manhas a5cdd23c00 feat: added retaliate button (#2426)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #495 

## Description:

Adds a button to quickly retaliate against a incoming attack

<img width="464" height="212" alt="image"
src="https://github.com/user-attachments/assets/4764d261-a408-4d61-a2d2-2685018aa698"
/>


## 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:

notifxy (1379678982676676639)
2025-11-24 09:34:04 -08:00
evanpelle 930a79e31c Merge branch 'v27' 2025-11-21 20:22:40 -08:00
Evan 0b651b6941 Update copyright notice & footer (#2496)
## 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
2025-11-21 20:22:17 -08:00
scamiv 33810e41c5 Optimize edge lookup railnetwork (#2493)
## 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
2025-11-21 22:33:08 +00:00
Loacky 024285389a Implement donation troops/gold between human players after forming an alliance (#2450)
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>
2025-11-21 14:25:41 -08:00
evanpelle 92372b770b Merge branch 'v26' into v27 2025-11-21 14:08:43 -08:00
evanpelle 7d7ef40ae4 Merge branch 'v26' 2025-11-21 14:08:27 -08:00
evanpelle 44920f030b Bugfix: Kick player in private lobby not working
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.
2025-11-21 14:06:31 -08:00
Aotumuri b0e6661c97 mls (v4.9) (#2487)
## 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
2025-11-21 11:51:14 -08:00
evanpelle 26b965468f Only open news modal if the user has seen an existing version. This prevents brand new players from seeing the news popup.
Move NewsButton into NewsModal.ts
Refactor & simplify NewsButton
2025-11-21 10:57:07 -08:00
evanpelle 9bd2b54ad7 Merge branch 'v27' 2025-11-20 20:11:56 -08:00
evanpelle c5b1bca6c6 fix z-index of homepage modals & buttons so modals always appear above the buttons 2025-11-20 19:35:07 -08:00
evanpelle cf7e658d3d Remove yenisei because there is no source in map-generate/assets so the map cannot be rebuilt 2025-11-20 19:18:37 -08:00
Lavodan f4a7788cdf Change wiki site from miraheze to openfront.wiki (#2483)
## 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
2025-11-20 16:50:10 -08:00
evanpelle 9287d0323d bugfix: emoji table was too small on firefox 2025-11-20 16:49:45 -08:00
Lavodan 8f04a60b89 Change wiki site from miraheze to openfront.wiki (#2483)
## 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
2025-11-20 16:34:29 -08:00
FloPinguin 46bbc8f296 New Map! "Four Islands" 🏝️ (#2482)
## 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>
2025-11-19 19:33:05 -08:00
Abdallah Bahrawi 4463236e8b Lobby Team Preview UI (#2444)
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>
2025-11-19 19:27:41 -08:00
unique-coder-124 599342995c fix(2388): troop penalty applied to boat retreat (#2389)
Description:
There is a boating exploit where players could repeatedly send and
retreat boats to effectively increase troop regeneration and maintain
almost double the max troop cap. This PR fixes #2388.

Summary
This pr adds a troop penalty to the boat retreats in
src/core/execution/TransportShipExecution.ts of 25 percent (currently
the same as land attacks, but may require fine tuning). this prevents,
to some degree, the ability to stockpile large amounts of troops above
your max cap.

Testing
I tested the change locally and confirmed that the troop penalty is
applied at the correct times and under the correct conditions, i.e. it
is only applied on successful retreat (applying on reteat initiation may
be confusing if you are told you have lost troops if the transport ship
never arrives anyway). The boating exploit is far less effective with
this fix in place.

Notes
- This is a exploit and does not introduce any new features.
- Existing game behavior outside the retreat penalty remains unchanged.
- The attack message is the same as the land attack, so the translation
should already be present.

Checklist
- [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

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-19 12:37:48 -08:00
Hauke12345 dcf5d1b103 Fading handshake (#2474)
## Description:
Add dynamic alliance icon with time-based fill and extension request
indicator

- Implement bottom-up green fill on alliance icon proportional to
remaining time
- Use AllianceIconFaded.svg as base layer with green overlay clipped
from top
- Add 20-82.40% clip range to account for icon vertical offset 

## 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


<img width="1132" height="631" alt="Screenshot 2025-11-18 205205"
src="https://github.com/user-attachments/assets/4af71ddc-f847-4460-9046-167275efc773"
/>
<img width="1387" height="792" alt="Screenshot 2025-11-18 205532"
src="https://github.com/user-attachments/assets/9dd0e018-323f-4de1-bae8-2633c09fe867"
/>

## Please put your Discord username so you can be contacted if a bug or
regression is found:
hauke4707

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-19 12:32:01 -08:00
evanpelle 9840306753 fix translation for humans vs nations in public lobby 2025-11-19 12:12:29 -08:00
evanpelle 807151b723 Revert "Improve random spawn (#2465)"
This reverts commit 2b2200c808.
2025-11-19 10:58:49 -08:00
Evan 0ba709c40d Create clan stats modal (#2479)
Resolves #2452

## Description:

Created a Clan Stats PR to show top clans. In another PR we can show the
player leaderboard to show top players.

Based on PR from https://github.com/Geekyhobo

<img width="659" height="792" alt="Screenshot 2025-11-19 at 10 00 40 AM"
src="https://github.com/user-attachments/assets/9333b7e2-2357-47a6-a7c8-788cf81e9be3"
/>


## 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

Co-authored-by: Geekyhobo <geekyhobo@users.noreply.github.com>
2025-11-19 10:34:23 -08:00
Mykola 2b2200c808 Improve random spawn (#2465)
## Description:

After the v27 playtest, some players experienced instant death on spawn.
The issue was that the human random spawn occasionally coincided with a
bot’s spawn. Previously, bot spawns didn’t account for human spawn
locations and could appear on the same tile, now they don’t.

## Please complete the following:

- [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: Evan <evanpelle@gmail.com>
2025-11-18 15:07:25 -08:00
FloPinguin 90b73451a8 Added NameLayer-Icons to PlayerInfoOverlay (#2446)
Resolves #1686

## Description:

Above or behind the player names on the map, there are icons (images
and/or emojis).
This PR also adds these icons to the PlayerInfoOverlay (on the right
side of the player name).
To share the logic, a new file PlayerIcons.ts has been created.

<img width="215" height="355" alt="Screenshot 2025-11-14 024435"
src="https://github.com/user-attachments/assets/2e581ef9-0330-4c9d-9c52-5f943a58e64b"
/>
<img width="203" height="337" alt="Screenshot 2025-11-14 024731"
src="https://github.com/user-attachments/assets/0c2bf278-b8ca-43c2-b466-ea7a83577b25"
/>
<img width="193" height="288" alt="Screenshot 2025-11-14 024639"
src="https://github.com/user-attachments/assets/be114bc6-f3a8-4b8d-b267-025587c9eafe"
/>

The alliance icon is NOT shown because it's already on the left side of
the alliance timer.

### Why is this change needed?

Sometimes you can't quickly find the nametag of a player on the map.
Especially if a player's territory is scattered around the map, maybe
even on several small islands.
But you still want to know if the player is AFK, has a traitor debuff,
etc.
So it's very useful to get this information by just hovering over a
player instead of needing to search for the floating nametag.

## 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>
2025-11-18 15:03:52 -08:00
VariableVince 9c24d29824 AFK team mate v2: better ship handling + tests + bugfix (#2396)
## Description:

See PR https://github.com/openfrontio/OpenFrontIO/pull/2203

It was reverted. This unreverts it, with an added fix for boat troops
not getting returned to owner. And small comment updates. And a const
for boatOwner to re-use.

The added bugfix is check for this.sourceTile === null in the retreat()
function in AttackExecution. A boat attack always sets removeTroops to
false because the troops were already removed from owner troops when the
boat departed. They don't have to be removed again in AttackExecution
init, when the boat lands and the attack starts. But at the end of the
attack, in retreat() in AttackExecution, the starting/boat troops still
need to be returned to the owner. That's why even if removeTroops is
false, when sourceTile is not null (only when it's a boat attack) we add
back the troops to the owner.

## 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: Fx Morin <28154542+FxMorin@users.noreply.github.com>
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2025-11-18 14:57:33 -08:00
Mohamad Reza be75d2a6b5 fixed Pahlavi Iran flag not showing in game (#2469)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

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:

DISCORD_USERNAME
2025-11-17 14:16:25 -08:00
Mike Harris 2b44b68362 Feature - Improve Structure Color Contrast (#2454)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #2447

## Description:

This PR updates the logic used to generate structure fill and border
colors. Currently, (v0.26.16 and earlier), some light territory colors
have structures that are difficult to see and identify. This PR ensures
that all territory colors have structures that are easily visible.

Instead of using `Colord.lighten()` and `Colord.darken()` to generate
structure colors, the logic now:
- queries the territory color and border color of the structure owner
- Converts these colors to the [LAB color
space](https://en.wikipedia.org/wiki/CIELAB_color_space) (which is a
human-perception-uniform color space).
- Darkens the border color (by decreasing LAB luminance) and sometimes
lightens the territory color (by increasing LAB luminance) until a
specific `Color Delta` is achieved (currently `delta > 0.5`)
- This ensures contrast between the structure and the territory
background.

Additionally, this PR re-organizes colors in the `Colors.ts` file for
better visibility and removes redundant colors from the `nationColors`
list.

This PR is an implementation of the proposed mock-up posted on imgur in
issue #2447. Screenshots of the original, final, and side-by-side
comparison of structure colors (for all available player colors) are in
the [imgur
album](https://imgur.com/a/openfront-color-playground-4cxSbbj).

I'd recommend inclusion as a feature/fix 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:

GlacialDrift
2025-11-16 20:58:34 -08:00
scamiv 7373a28c99 Feature/frame profiler (#2467)
## Description:

Adds a reusable FrameProfiler utility, and a way to export profiling
data for offline analysis.


### What this PR changes in the existing performance monitor

This PR enhances the performance monitor by:

- **Introducing a reusable `FrameProfiler` utility**
- New `FrameProfiler` singleton in
`src/client/graphics/FrameProfiler.ts`.
- Profiling is only active when the performance overlay is visible
(toggled via user settings), to avoid unnecessary overhead.

- **Per-layer and span-level timing integration**
  - `GameRenderer.renderGame` now:
    - Clears the profiler at the start of each frame.
- Wraps each `layer.renderLayer?.(this.context)` call with
`FrameProfiler.start()/end()`, keyed by the layer’s constructor name.
- Consumes the recorded timings at the end of the frame and passes them
into `PerformanceOverlay.updateFrameMetrics(frameDuration,
layerDurations)`.
  - `TerritoryLayer` instruments key operations:
    - `renderTerritory`
    - `putImageData`
    - Drawing the main canvas
    - Drawing the highlight canvas during spawn
- These show up in the performance overlay as additional entries (e.g.
`TerritoryLayer:renderTerritory`).

- **JSON export of performance snapshots**
- `PerformanceOverlay` can now build a full performance snapshot
(`buildPerformanceSnapshot`) containing:
    - FPS and frame time stats (current, 60s average, 60s history).
    - Tick metrics (avg/max execution and delay, plus raw samples).
- Layer breakdown (EMA-smoothed avg, max, total time per layer/span).
  - A new “Copy JSON” button:
- Uses `navigator.clipboard.writeText` when available and falls back to
a hidden `<textarea>` + `document.execCommand("copy")`.
- Provides user feedback via a transient status ("Copy JSON" → "Copied!"
or "Failed to copy").

- **Enable/disable functionality hooked into the UI**
  - `FrameProfiler.setEnabled(visible)` is invoked:
    - When the overlay visibility is toggled (`init` → `setVisible`).
- When the overlay re-checks visibility in `updateFrameMetrics`, so the
profiler state stays in sync with user settings.
- When disabled, `FrameProfiler` becomes a no-op (returns `0` from
`start`, ignores `record`/`end`, and `consume` returns an empty object),
ensuring minimal overhead when performance monitoring is off.

- **Performance overlay UX and i18n improvements**
  - New controls:
    - **Reset** button to clear all FPS/tick/layer stats.
    - **Copy JSON** button with a tooltip and transient status text.
  - Visual enhancements:
- Wider overlay (`min-width: 420px`) and extra padding for readability.
    - Layer breakdown section with:
      - A list that is now sorted by total accumulated time.
      - A horizontal bar per entry, scaled by average cost.
      - Avg / max time display per layer/span.
- All new text is routed through `translateText` and backed by
`en.json`:
    - `performance_overlay.reset`
    - `performance_overlay.copy_json_title`
    - `performance_overlay.copy_clipboard`
    - `performance_overlay.copied`
    - `performance_overlay.failed_copy`
    - `performance_overlay.fps`
    - `performance_overlay.avg_60s`
    - `performance_overlay.frame`
    - `performance_overlay.tick_exec`
    - `performance_overlay.tick_delay`
    - `performance_overlay.layers_header`

---

### How to set up profiling for new functions / code paths

For any function or code block you want to profile during a frame:

```ts
import { FrameProfiler } from "../FrameProfiler"; 

function heavyOperation() {
  const spanStart = FrameProfiler.start();
  // ... your existing work ...
  FrameProfiler.end("MyFeature:heavyOperation", spanStart);
}
```

Guidelines:

- Use descriptive, stable names:
  - Prefix with the component or layer name, e.g.:
    - `"TerritoryLayer:prepareTiles"`
    - `"GameRenderer:resolveVisibility"`
    - `"FooFeature:fetchData"`
- The same name can be called multiple times per frame; the profiler
accumulates the durations in that frame.
- The accumulated values will appear:
  - In `layerDurations` consumed at the end of the frame.
  - In the overlay “Layers (avg / max, sorted by total time)” section.
  - In the exported JSON under `layers` with `avg`, `max`, and `total`.

**3. Record pre-computed durations (optional)**

If you already have a measured duration and just want to attach it:

```ts
FrameProfiler.record("MyFeature:step1", someDurationInMs);
```

- This is equivalent to calling `start`/`end` but with your own timing
logic.
- Again, multiple calls with the same name in one frame will be summed.

---

<img width="466" height="823" alt="image"
src="https://github.com/user-attachments/assets/354b249a-25eb-4c3f-bd2e-9906372f761b"
/>


## 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

- [ ] 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

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-16 19:10:20 -08:00
Fx Morin 1aaf7bd2c5 Performance: Remove un-optimized blurring (#2466)
## Description:
I haven't been able to play openfront for a while now, so today I took
some time to fix the performance issue.
It turns out its `backdrop-filter: blur(5px);` being used on a
background element, causing the entire website to re-paint on nearly
every change.
This causes the game to run at 8 fps on my computer, with chrome
reporting 700ms of INP presentation delay.

The solution here was simply to pre-blur the background image.

<details>
<summary>Here's the instructions on how to pre-blur exactly like it
currently does</summary>

Install sharp
`npm install sharp`  

Run blur
```ts
sharp(`./resources/images/EuropeBackground.webp`)
  .blur(5)
  .toFile(`./resources/images/EuropeBackgroundBlurred.webp`);
```
This could be automated if you plan to do more backgrounds.  
</details>
  

The surprising part is that I'm running a A5000 with a Ryzen 9 5950X. So
I usually never lag from anything, which is what made this kinda
interesting. I assume the issue is caused by my 4K display or Kubuntu.

## Video:
This video showcases the 700+ms of delay on the main menu buttons. Video
is in real-time.


https://github.com/user-attachments/assets/ad5ff509-6bc7-48ff-8cba-85409be774f0



## 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

<details>
<summary>Discord Username</summary>

fx.morin

</details>
2025-11-16 13:32:57 -08:00
Fx Morin a883d612e0 Performance: Remove un-optimized blurring (#2466)
## Description:
I haven't been able to play openfront for a while now, so today I took
some time to fix the performance issue.
It turns out its `backdrop-filter: blur(5px);` being used on a
background element, causing the entire website to re-paint on nearly
every change.
This causes the game to run at 8 fps on my computer, with chrome
reporting 700ms of INP presentation delay.

The solution here was simply to pre-blur the background image.

<details>
<summary>Here's the instructions on how to pre-blur exactly like it
currently does</summary>

Install sharp
`npm install sharp`  

Run blur
```ts
sharp(`./resources/images/EuropeBackground.webp`)
  .blur(5)
  .toFile(`./resources/images/EuropeBackgroundBlurred.webp`);
```
This could be automated if you plan to do more backgrounds.  
</details>
  

The surprising part is that I'm running a A5000 with a Ryzen 9 5950X. So
I usually never lag from anything, which is what made this kinda
interesting. I assume the issue is caused by my 4K display or Kubuntu.

## Video:
This video showcases the 700+ms of delay on the main menu buttons. Video
is in real-time.


https://github.com/user-attachments/assets/ad5ff509-6bc7-48ff-8cba-85409be774f0



## 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

<details>
<summary>Discord Username</summary>

fx.morin

</details>
2025-11-16 13:32:30 -08:00