Commit Graph
669 Commits
Author SHA1 Message Date
EvanandGitHub 20bc311caf Add Graphics Settings modal with live name-label tuning (#4065)
## Description:

- Add a user-facing **Graphics Settings** modal accessible from the
in-game Settings menu, with live preview as sliders change.
- First two knobs: **Name Scale** and **Minimum Name Size** (the
name-cull threshold).
- Overrides stored as a single JSON blob in `localStorage` under
`settings.graphics`, validated by a Zod schema
(`GraphicsOverridesSchema`). Future graphics knobs just extend the
schema + slider list.

## How it fits together

- `generateRenderSettings(overrides)` (`RenderSettings.ts`) — pure
function: clones `render-settings.json` defaults, layers overrides on
top, returns a fresh `RenderSettings`.
- `UserSettings.graphicsOverrides()` / `setGraphicsOverrides()` —
read/write the blob; falls back to `{}` on a missing/corrupt entry.
- `ClientGameRunner` listens for
`USER_SETTINGS_CHANGED_EVENT:settings.graphics`, regenerates, and
`Object.assign`s each category into the live `view.getSettings()` slice
so passes pick up the new values on the next frame (no renderer
reconstruction).
- Modal reads defaults straight from `render-settings.json` so there's
no duplication.


<img width="599" height="515" alt="Screenshot 2026-05-28 at 11 18 43 AM"
src="https://github.com/user-attachments/assets/263d7d91-10d8-4a66-a069-10015c735d60"
/>

## 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
2026-05-28 13:06:43 -07:00
EvanandGitHub aa3959bffe feat: territory png based skins (#4006)
## Description:

Add image-based territory skins as a new cosmetic type, rendered
alongside the existing 1-bit patterns. Skins render a single PNG
centered on each player's spawn tile — opaque pixels show the skin
(multiplied by team color in team games, raw colors in FFA), transparent
pixels and tiles outside the image bounds fall through to the regular
player palette color.

**Cosmetic plumbing**
- `SkinSchema` in `CosmeticSchemas.ts`, optional `skins` map on
`CosmeticsSchema`
- `PlayerSkin`, `PlayerCosmetics.skin`, `PlayerCosmeticRefs.skinName` in
`Schemas.ts`
- Server-side resolution: `PrivilegeCheckerImpl.isSkinAllowed` (gated by
`skin:*` / `skin:<name>` flares)
- Client persistence: stored under `PATTERN_KEY` (`pattern:` and `skin:`
share one slot — they're mutually exclusive)
- `getPlayerCosmeticsRefs` only emits a `skinName` when cosmetics are
loaded, the skin exists in the catalog, and the user has the right flare
— otherwise drops the ref and clears storage

**Renderer**
- `SkinAtlasArray` — fixed `TEXTURE_2D_ARRAY`, 1024×1024 per layer,
exact layer count allocated once at game start from the locked-in player
set. No resize, no callbacks, no retained `HTMLImageElement`. Zero GPU
cost when no players have skins (1×1 placeholder).
- `skinLayerTex` (R8UI 4096×1) — per-player `layer + 1` (`0` = no skin)
- `skinAnchorTex` (RG16UI 4096×1) — per-player spawn tile, so the PNG
center anchors at each player's spawn (re-uploads when the player
re-picks during spawn phase)
- `WebGLFrameBuilder.syncPlayers` collects unique skin URLs on first
sync and calls `view.initSkinAtlas(urls)` once; `clearCaches()` resets
so seek/replay re-initializes
- `territory.frag.glsl`: skin branch is mutually exclusive with
patterns; bounds-checks UVs against `[0, 1]` so the image is a single
stamp, not tiled; alpha-blends against the player palette color so
transparent pixels and out-of-bounds tiles render as the regular player
color

**Hover highlight (global UX change, not skin-scoped)**
- Existing hover highlight changed from "brighten toward white" to
"saturation boost." Applies to all players regardless of
skin/pattern/flat-color — looks better across the board.

**UI**
- `CosmeticButton` renders skins as a single `<img>` (object-contain)
- `TerritoryPatternsModal` merges patterns + skins into one grid; single
"default" tile clears both
- Selecting a pattern clears the skin and vice versa (mutually
exclusive)
- `Store` pattern tab includes skin entries (purchasable, not-yet-owned)
- `PatternInput` lobby button previews the active skin when one is set

**Memory**
- 0 skin players → ~4 bytes (placeholder) + ~40 KB fixed per-player
tables
- 1 skin player → ~5.6 MB GPU
- 5 skin players → ~28 MB GPU
- 10 skin players → ~56 MB GPU

**Tests**
- `tests/Privilege.test.ts`: 13 new cases covering `isSkinAllowed`
(wildcard, exact-match, missing flare, missing skin, forged refs) and
`isAllowed` integration (allowed/forbidden paths, short-circuit when
invalid skin is paired with valid other cosmetics)

## 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
- [x] I have added relevant tests to the test directory
- [ ] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

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

evan
2026-05-27 13:00:07 -07:00
172113193f Add May Labyrinth (#4002)
## Description:

Labyrinth is a maze type map. My attempt at making a more chess style
board for play. Games with bots appear stable at over 45min average run
times. The map has been setup for team spawn zones for 2, 3, 4, 5, 6,
and 7 teams. Some of the team spawns for odd numbers are experimental
and I would like to see how they play out with live players. Additional
nation names included. There are other design factors like each of the
large squares being within the blast radius of a hydro; small islands
are within the blast radius of nukes.

This is meant as a slower playing game. My intentions are to get some
sort of literal rotation of the map in the future if easily implemented.
That way every time players load the game there would be some
randomization.

As an additional note one of my last edit to the map was the "+" shape
to the islands to allow train passage. Zooming out I can see now that
the pattern is squares and + through out. Did not fully intend on that,
but it felt like good vibes.


https://discord.com/channels/1284581928254701718/1293201128858587207/threads/1497062552784605316
https://www.youtube.com/watch?v=e8c-TylT4hs
https://www.youtube.com/watch?v=0-yqrfr3nv0


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

---------

Co-authored-by: RickD004 <realtacoco@gmail.com>
2026-05-26 19:25:43 -07:00
b086881a4e Add Korea Map (#3977)
## Description:

Add map of korean peninsula. Size 1092x2149
Nations: 35
based on provinces

## 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
crunchybbbbb @crunchybbbbb_59469


<img width="1092" height="2149" alt="Korea-2"
src="https://github.com/user-attachments/assets/874100e8-4a68-4f57-b2f7-49aa87b8194d"
/>

two teams nations war video https://www.youtube.com/watch?v=n4h7GAfAHTM

---------

Co-authored-by: Ricky G.P. <realtacoco@gmail.com>
2026-05-26 19:11:13 -07:00
RickD004andGitHub 5a2c0504eb adds map of the Balkans (using Additional nations feature) (#3998)
## Description:

Adds map of the Balkan Peninsula and surroundings. Heavily requested map
with multiple posts on the Discord all with over 10 or 20 upvotes.

23 NPC/Nations based on countries and relevant regions of the area. Adds
an extra 39 nations for crowded Humans vs Nations gamemode for a total
of 62 NPCs, based on regions of multiple countries. Also some flags for
some regions.

Source from NASA DEM, already credited

Photo of base map, and 62 HvN:


<img width="614" height="588" alt="Captura de pantalla 2026-05-24
030105"
src="https://github.com/user-attachments/assets/5742a4c3-1b1f-4ca7-858d-91529861dd81"
/>

<img width="548" height="547" alt="image"
src="https://github.com/user-attachments/assets/758d8ad0-1515-41b8-8d42-14e76cdd54ed"
/>

This map completes the quartet row of "polemic" maps for v32

<img width="678" height="119" alt="image"
src="https://github.com/user-attachments/assets/9e6f4ef1-f0cc-48ea-a59f-b7ff69033b73"
/>

## 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
2026-05-24 16:09:37 +01:00
EvanandGitHub db501c68d2 Put friends on the same team (#3994)
Fixes #3911

## Description:

- Server captures `publicId` and `friends` from `getUserMe()` and
includes each player's in-game friend `clientID`s in `PlayerSchema` on
game start
- Team assignment treats friends as a **soft preference** (best-effort):
a non-clan player goes to the team where the most of their friends
already are; if that team is full they spill to the next-emptiest team
rather than getting kicked
- Clans remain strict (kick overflow) since clan membership is an
explicit opt-in; friends are implicit, so a friend-of-friend chain that
doesn't fit shouldn't bench anyone
- Friendship is symmetric — an edge from either direction counts, which
keeps things working when one side's `getUserMe` is stale
- Lobby preview unchanged — friend grouping only takes effect once the
game actually starts (avoids exposing friend lists in the lobby payload)


## 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
2026-05-23 18:02:41 +01:00
RickD004andGitHub b1ec3ac70f Adds Indian Subcontinent map (#3975)
## Description:

Adds Map of the Indian Subcontinent, with indian and pakistani states
and surrounding countries, important rivers like the Ganges, Brahmaputra
and Indus, and Tibet/ theHimalayas

2M land pixels and 52 Nations (i think its fitting that India has the
most nations of a regional map, only continental maps have more)

Should be nice to boost whatever indian playerbase this game might have.
This region also doesnt have any representation aside from continental
maps

<img width="584" height="598" alt="image"
src="https://github.com/user-attachments/assets/4089049a-800b-4e37-ab34-2afc5de821e8"
/>
<img width="418" height="462" alt="image"
src="https://github.com/user-attachments/assets/a68e2424-5972-4105-86c9-0312ab095024"
/>

Elevation data from NASA DEM, already credited in CREDITS.md

No reference test is needed, the test suite automatically iterates over
all GameMapType enum values — no map is hardcoded by name in the tests

## 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
2026-05-23 17:38:03 +01:00
VariableVinceandGitHub 2ee2fb97e3 WebGL: return of factory/defence post radii, and railroad highlighting when placing city/port right on top (#3981)
## Description:

Show factory and defence post radius for ghost structure when placing
structures from build bar (unitdisplay).

Show when city/port is placed directly over existing railroad, by
highlighting the railroad green. The railroad is not highlighted when
instead a city/port nearby the ghost structure will be upgraded instead
of placing it on the railroad. This works with the existing code in
buildableUnits in PlayerImpl: it would already return an empty array []
for overlappingRailroads and for ghostRailPaths when canUpgrade is
false. So the old checks for uiState for Canvas2D in
BuildPreviewController weren't even needed per se, they followed the
same logic as buildableUnits in PlayerImpl already did.

Both changes emulate how it worked before the move to WebGL. 

- OverlappingRailroads now returns TileRefs instead of a railroad ID,
and it does so with less allocations than the previous code. It's a
determistic outcome, sorted and deduplicated. In doubt about this a bit,
because it's better also in case we ever do desync checks using this
data, but for the rendering it isn't needed per se and could be more
performant without allocations.
- Also: Cleanup obsolete Canvas2D rail highlighting state (UIState) that
was superseded by GhostPreviewData.

## 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
2026-05-22 10:33:09 +01:00
EvanandGitHub 41ef675e98 Improve Notification Panel (#3913)
Resolves #3910

## Description:

- Split the events HUD into two components: a new
**`<actionable-events>`** that owns alliance prompts (request / renew)
and a slimmed-down **`<events-display>`** for everything else.
- Reworked `<events-display>` into two visual tiers: dim/scrolling tier
2 on top (trade results, unit losses, donations, alliance status),
prominent tier 1 anchored at the bottom (inbound nukes, naval invasion,
attack requests, alliance broken, conquered player, chat). Tier 2 caps
at the 4 newest entries; events expire after 8s.
- Added a transient **+gold pip** above the gold pill in
`<control-panel>`, animated with a small fade-in. Fires for trade ships,
trains, donations, and conquest. Trade-ship and train arrivals are
removed from the events scroll since they're surfaced here instead.
- New `MessageType.NUKE_DETONATED` and a server-side emission in
`NukeExecution.detonate` — once an inbound nuke lands or gets
intercepted, the inbound warning vanishes and a "detonated" entry takes
its place.
- `displayMessage` gained optional `unitID` and `focusPlayerID` params
so events can link to a unit or a player. Unit captures and destructions
now navigate to the unit's last tile when clicked; donations navigate to
the other player.
- ActionableEvents card width matches `<events-display>`; cards persist
until the user clicks Accept/Reject/Renew/Ignore or the server-side
request timeout expires.
- Removed the in-events category filter UI and the gold-amount banner —
`<events-display>` is now a lightweight log that hides entirely when
empty.

<img width="570" height="444" alt="Screenshot 2026-05-21 at 1 42 30 PM"
src="https://github.com/user-attachments/assets/f103efb3-0e11-4b72-a11b-91ff6896177c"
/>

<img width="430" height="296" alt="Screenshot 2026-05-21 at 1 41 34 PM"
src="https://github.com/user-attachments/assets/ae58475a-b252-4aa6-9ce5-99dea7575ce3"
/>

## 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
2026-05-21 19:50:10 +01:00
RickD004andGitHub 6591b055c3 Adds map of Venice 🛶 (#3935)
## Description:

Adds map of Venice. A relatively small map (similar land area to World)
for heavy trade and lots of boating.

Because of the very low difference of elevation of the zone, terrain is
instead used to show buildings.

Map source from OpenStreetMap, already credited in CREDITS.md

Very requested map, with 2 discord posts suggesting it with +15 upvotes
each

<img width="794" height="569" alt="image"
src="https://github.com/user-attachments/assets/ca7d44f2-cfc9-4e93-b7d4-43dbe62f74d4"
/>

## 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
2026-05-18 19:43:02 -07:00
evanpelle 17e3ac4b05 make spawn glow follow the player's currently selected spawn tile
Plumb spawnTile through PlayerUpdate / PlayerState / applyStateUpdate
so the WebGL spawn overlay can read it directly. The glow was reading
nameData.x/y (territory centroid for label placement) which only
recomputes every 2 ticks and only when largestClusterBoundingBox has
been updated by PlayerExecution — both lag the player's actual spawn
click. Using spawnTile updates the same tick setSpawnTile() fires.

Also adds spawnTile to diffPlayerUpdate / applyStateUpdate so changes
after the initial full snapshot actually propagate (the recent
diff-only PlayerUpdate path silently dropped any field not enumerated
in those helpers).
2026-05-18 19:15:01 -07:00
EvanandGitHub 62e15d2794 Cut worker→main bandwidth ~3.3× by switching PlayerUpdate to deltas (#3967)
## Description:

Cut worker→main bandwidth ~3.3× by switching PlayerUpdate from a full
per-tick snapshot to a field-level diff. PlayerImpl.toUpdate() now
caches the last sent update and returns only changed fields, or null if
nothing changed. The client-side applyStateUpdate() merges instead of
overwriting.

Per-tick total dropped from ~297 KB to ~89 KB; the Player bucket alone
went from 258 KB/tick to 50 KB/tick. Diff/apply logic lives in a new
GameUpdateUtils.ts module with unit tests.

## 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
2026-05-18 17:07:40 -07:00
evanpelle 5b663fae14 refactor: share renderer state shapes between game and WebGL renderer
PlayerView/UnitView now wrap renderer-shaped state objects (PlayerState,
PlayerStatic, UnitState) directly instead of holding engine wire types.
GameView owns a long-lived FrameData object kept in sync each tick:
players/units/tiles/trail/railroad are mutated in place; derived buffers
(playerStatus, relationMatrix, allianceClusters, nukeTelegraphs,
attackRings) and events are recomputed in a final populateFrame() pass.

The renderer reads gameView.frameData() and the same byte-identical
state objects PlayerView/UnitView wrap. WebGLFrameBuilder shrinks from
~270 to ~70 LOC: palette management + a single uploadFrameData() call,
no per-frame UnitState allocation on the hot path.

Wiring: maxPlayers=1024 on RendererConfig (pre-sizes NamePass/palette/
relation matrix textures); NamePass disabled so HTML NameLayer remains
the only on-screen player names.

Also: 39 new tests covering PlayerView/GameView/FrameData behavior;
replace .data field access in three layer call sites with accessor
methods (betrayals(), type(), getTraitorRemainingTicks()).
2026-05-16 13:27:31 -07:00
evanpelle 53cf2d43f8 migrate away from canvas 2026-05-16 08:55:02 -07:00
RickD004andGitHub 7dc5d472a7 Change name of map "The Straits" into "Danish Straits" (#3929)
## Description:

Renames TheStraits map. The people that suggested this map told me they
would prefer a more specific name for the map, rather than the generic
one it has right now. So im renaming it into Danish Straits

This map is for v32, it has not been released, it should be fine to
rename


## 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
2026-05-15 08:59:28 +00:00
RickD004andGitHub 7359e2bc3b Adds Northwest Passage map (using new additionalNations feature) (#3920)
## Description:

Adds map "Northwest Passage", map of the Canadian Arctic Archipelago ,
Greenland and surroundings. "Northwest Passage" (NWP) is the sea lane
between the Atlantic and Pacific oceans
(https://en.wikipedia.org/wiki/Northwest_Passage) .

21 default nations, based on the towns of the region.

This map uses the brand new additionalNations feature made by FloPinguin
https://github.com/openfrontio/OpenFrontIO/pull/3902 . Adds 39 extra
nations for a total of 60 nations (so that in gamemodes like Humans vs
Nations all the nations have names of real places)

Comparison: 
- Map with default nations
- Map with extra named nations, tested by raising the number of nations
in Solo

<img width="1050" height="412" alt="image"
src="https://github.com/user-attachments/assets/12ed94f1-0615-4fb3-b0d0-dcecb65006ea"
/>
<img width="1089" height="436" alt="image"
src="https://github.com/user-attachments/assets/6e7c11bf-7382-4e36-9433-229a9d463b68"
/>

Terrain source from OpenTopography, already credited in CREDITS.md

## 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
2026-05-14 19:27:46 -07:00
RickD004andGitHub 834a9757d7 Adds map "The Straits" (#3896)
## Description:

**Adds "The Straits" map:**

A map located around Denmark and the many surrounding straits: Kattegat,
Skagerrak and the Danish straits (thus the name, meant to be a creative
name like "Between Two Seas" and "Gateway to the Atlantic"). This map is
themed in the early 1900s, the nations/NPCs are traditional and
historical regions of Sweden-Norway, Denmark and the Germany.

Relatively small map with ~700k land tiles, similar to World

Inspired by this Discord thread with nearly 20 upvotes:
https://canary.discord.com/channels/1284581928254701718/1482089104110911634/1482089104110911634

<img width="365" height="506" alt="image"
src="https://github.com/user-attachments/assets/5ee16218-34c0-4b8b-9f9b-d33f219760b0"
/>

## 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
2026-05-11 21:35:41 +00:00
FrederikJAandGitHub 89d330cf64 Make setting for go to player on spawn (default on) (#3874)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #3847 

## Description:
Makes a setting to turn off go to player on spawn.

Most players i have played with are really annoyed about this feature. I
understand that some people like it, which is why i have made it default
on. But i feel like we should be able to turn it off.

## Please complete the following:

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

FrederikJA
2026-05-11 13:04:00 -07:00
FloPinguinandGitHub 19280c0b37 Allow mappers to define additionalNations 🗺️ (#3902)
## Description:

Adds an optional `additionalNations` array to map manifests (info.json /
manifest.json), used as a name pool when a game requests more nations
than the map defines (HvN, private lobbies, solo games).

Suggested by mapmaker PatrickPlaysBadly.

When the requested nation count exceeds `nations.length`:
1. The deficit is filled by random picks from `additionalNations`
(collisions with manifest names are skipped).
2. If `additionalNations` still does not cover the deficit, the
remainder is generated procedurally as before.

Each entry supports `name`, optional `flag` and optional `coordinates`.
If `coordinates` are provided, the picked nation gets a spawn cell
(otherwise it spawns like the procedurally generated ones, with no fixed
location).

`Nation.flag` is also relaxed to optional, since many existing manifest
entries already omit it.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

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

FloPinguin
2026-05-11 12:58:34 -07:00
AotumuriandGitHub f1d162825e feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 

## Description:

Remove the singleplayer spawn countdown so the game starts when the
player spawns, spawn nations immediately after player spawn, and align
game timer/max-timer timing with the new start point.

Added a singleplayer regression test for spawn-immunity timing
(GameImpl.test.ts) and updated spawn-phase loop tests to use gameType:
GameType.Public where singleplayer behavior is not under test (e.g.
MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase()
timeout hangs after the new singleplayer start logic.


https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756

## 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
2026-05-11 12:44:44 -07:00
RickD004andGitHub 36c8fc0394 Add map of Taiwan Strait (#3878)
## Description:

Map of the Taiwan and the Chinese Mainland. 

Team heavy map like Baikal and Hormuz. Terrain Source from
OpenTopography, already credited

<img width="1800" height="1511" alt="image"
src="https://github.com/user-attachments/assets/45954469-8199-4882-9efe-899c5df87ce4"
/>

I also took the chance to standarize and sort alphabetically the map
lists in main.go and MapPlaylist.ts.

## 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.star 1011

NOTE: If the map gets added, please give contributor to
crunchybbbbb_59469 for this map on Discord. Every file was made by him,
his PR just had weird bugs that didnt allow the PR to be review
automatically

Original PR: https://github.com/openfrontio/OpenFrontIO/pull/3853 by
crunchybbb2-hash
2026-05-08 18:08:17 -07:00
Sky ElderandGitHub 38cb3027cb Feature Add retaliate keybind (#3801)
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
2026-05-06 12:08:31 -06:00
RickD004andGitHub 3744027e94 Adds map of the Middle East (#3829)
## 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
2026-05-05 22:12:42 -06:00
EvanandGitHub f90e73c2f7 conqueror receives 50% of gold when conquering a human player (#3818)
## 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
2026-05-02 09:23:49 -06:00
babyboucherandGitHub 4f20d2b332 TypeScript update to 6.0.3 (#3806)
## 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
2026-04-30 15:49:24 -06:00
742a544a69 2661 PR 3/3 Warship Manual Override, Aggro Override, and Heal-at-Port Command (#3501)
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>
2026-04-30 13:54:28 -06:00
evanpelle 3476bfc674 Changed the default in UserSettings.ts:386 from 1 to 0. New users (or anyone who hasn't touched the slider) will now start with sound effects muted. 2026-04-28 21:39:31 -06:00
RickD004andGitHub a989bcbeb0 Rename Mediterranean to Mare Nostrum 🏛️ (#3768)
## Description:

Change inspired by Territorial.io . Renames the Mediterranean sea map to
Mare Nostrum, the ancient name of the sea given by the Romans. The NPCs
of the map are already roman provinces, so this change is more thematic
and also adds a creative name like "Gateway to the Atlantic" and
"Between Two Seas"

## 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
2026-04-28 19:15:29 -06:00
FloPinguinandGitHub 7654537a00 Much better river handling for nations and tribes! 🏞️ (#3786)
## Description:

In this example, the two nations DONT see each other as neighbors, but
as ISLANDERS. Because they dont have a direct border connection, there
is water in between.

<img width="526" height="329" alt="image"
src="https://github.com/user-attachments/assets/cf2c15b5-7793-4445-afd2-920d6cd50a2a"
/>

This is a big problem, because most of the logic in AiAttackBehavior
gets ignored. Only the "islander" strategy runs (late, because its a
not-important strat).

### Summary

- `PlayerImpl.neighbors()` now includes cross-water neighbors: a new
`shoreReachableNeighbors()` helper samples every 10th shore border tile
and looks up to 5 tiles in each cardinal direction across water, finding
land owners on the other side (covers rivers up to 4 tiles wide).

- `AiAttackBehavior.maybeAttack()` extends the `hasNonNukedTerraNullius`
check to also trigger on TN detected via `player.neighbors()`, so
nations notice and pursue TN that is only reachable across a river.

- `sendAttack()` uses a new `hasLandBorderWithTerraNullius()` land-only
adjacency check to decide between a land attack and a boat attack for
TN, rather than `sharesBorderWith()` which includes water tiles.

- Added `sendBoatAttackToNearbyTerraNullius()`: when no TN land is
directly adjacent, the AI scans its shore border tiles for unowned land
across water and dispatches a transport ship.

### Also works for Tribes!

Tribes can boat rivers now, really cool.


https://github.com/user-attachments/assets/382e85aa-c437-4e0c-afc2-0c381432da3d

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

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

FloPinguin
2026-04-28 18:25:11 -06:00
79ac66df3c Add map los angeles (with roads) (#3708)
## Description:

Add Map - Los Angeles (With Roads)
Whats NY without LA? What is LA without its roads? Here is a larger map
where I have turned LAs road network into rivers. Nations named to areas
of the city or surrounding area. Map geo data is from OpenTopography. I
stenciled the highways from screenshots of online maps and some artistic
choice.


https://discord.com/channels/1284581928254701718/1482605671697613081/1482605671697613081
https://www.youtube.com/watch?v=6td9wiQKE9s

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

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
2026-04-27 22:05:17 -06:00
Zixer1andGitHub c0febacb8e 2661 PR 2/3 Warship Port Healing, Docking Capacity, and Waiting Behavior (#3499)
Part of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661)
(split into 3 PRs so they are not too large..)

## Description:

Part 2/3 of
[#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661).

This PR adds port-based healing and docking behavior:
- Passive healing near friendly ports
- Active docked healing pool scaled by port level and shared across
docked ships
- Docking radius and capacity-by-port-level behavior
- Waiting behavior near full ports until a slot opens
- Auto-undock once fully healed

For the active healing, it works like `ActiveHeal = (PortLevel * 5) /
DockedShipsAtThatPort`
Ex:
1 ship at level 1 port -> +5 HP/tick
1 ship at level 2 port → +10 HP/tick
2 ships at level 3 port → +7.5 HP/tick each

Includes regression tests covering healing math and docking/waiting
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._
2026-04-26 21:42:13 +00:00
RickD004andGitHub 32a254b375 add Baja California map 🏜️ (#3759)
## Description:

Adds map of the Baja California peninsula in Mexico. Majority of this
map is brown terrain to simulate the (Sonora) desert
Mexican and American states as NPCs. The peninsula while having little
landmass is balanced by the fact it will be very easy to pirate from it

<img width="503" height="583" alt="Captura de pantalla 2026-04-18
151337"
src="https://github.com/user-attachments/assets/b1acd93a-fbe4-4d17-95b6-5f4c86ec872f"
/>

Relief data from OpenTopography, already credited

## 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
2026-04-25 15:27:30 -06:00
Zixer1andGitHub 37079e6a05 2661 PR 1/3 Warship Retreat Core, Blue UI Signal, and Transport-First Target Priority (#3498)
Part of #2661 (split into 3 PRs so they are not too large..)

## Description:

Part 1/3 of #2661.

This PR adds warship retreat basics, a blue retreating UI state, and
updates target priority.

Added:
- Retreat state handling
- Blue visual for retreating warships
- Target priority: transport > warship > trade 
- Tests for retreat and target priority

Example video:
https://youtu.be/2hE2qeOeY48
Ship retreating:
<img width="630" height="488" alt="image"
src="https://github.com/user-attachments/assets/56d3e6d5-08af-453d-afe5-ee21dd6f3414"
/>
Ship healing:
<img width="483" height="311" alt="image"
src="https://github.com/user-attachments/assets/aeaf2239-bb81-444f-84ef-62dbcb48fddf"
/>
Back to being deployed:
<img width="585" height="358" alt="image"
src="https://github.com/user-attachments/assets/875828a2-8a24-4593-ac76-26426bb81057"
/>

## 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._
2026-04-24 08:26:14 -06:00
RickD004andGitHub aa70d6742a add Archipelago Sea map (#3749)
## Description:

Adds Archipelago Sea map. The island map to end all island maps.

This is a real location in Southwest Finland, famous for being the
archipelago with the most islands in the world.

This map is made for the very loud playerbase who enjoys naval maps.
This is probably the most heavy naval map we are gonna get. Given this
is a very experimental and unorthodox map like amazon or 4islands, it
has a rotation of 3 , low but still should pop in for people who prefer
island maps.


https://github.com/user-attachments/assets/19b32a4f-825b-4b82-aec3-b24c387b9cdd

## 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
2026-04-24 08:07:48 -06:00
ec6e14051a Add an Antarctica map 🐧 (#3740)
## Description:

(re-submission of older PR because the old one had too many merge
conflicts)

Re-adds and reworks the previously April Fools "Reglaciated Antarctica"
as a new map, this time with proper elevation data: Map of Antarctica
centered in the South Pole.

The "appeal" of this a map is that it has no green terrain. (As such it
has a really low rotation number (of 1). )

This also completes the Continental map category (antarctica was the
only continent out of the traditional 7 missing).


https://github.com/user-attachments/assets/28302464-c533-483e-8a1b-2699093921ff

The base map image is a composite of 2 relief maps from 2 different
sources, both properly credited

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

Co-authored-by: iamlewis <lewismmmm@gmail.com>
2026-04-22 16:04:56 -07:00
JarifaandGitHub 0801798fbd Feat: Alliance and betrayal hotkeys (#3110)
Original Feature request by @FloPinguin  
Resolves #3077 

## Description:

Adds hotkeys for Requesting alliances and breaking alliances. This
allows for players to send or break alliances whose tile is under the
cursor, without opening the radial menu.

Keybinds:
New "Ally Keybinds" section in Settings -> Keybinds
Request alliance: Default: K - sends an alliance request to the
player/bot/nation under the cursor
Break alliance: Default: L - breaks the alliance with the player at the
cursor

Behavior:
- Cursor must be over a tile owned by the target player. The action runs
only when the game allows it, following the same logic as the radial
menu. (canSendAllianceRequest and canBreakAlliance)
- When an alliance request is sent, the events log shows: "Alliance
request sent to [target]" for confirmation. No extra message for
breaking an alliance (betrayal/debuff message already exists and is sent
upon breaking an alliance)

## Screenshots:
Keybind menu:
<img width="739" height="595" alt="image"
src="https://github.com/user-attachments/assets/ee958eab-fd50-4971-85c5-dfd49c6f0bdc"
/>
In game logs:
<img width="373" height="232" alt="image"
src="https://github.com/user-attachments/assets/2cf6bb07-5f0d-425a-82d3-65a44fef99c5"
/>

## 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:
_Dave9595_
2026-04-21 14:34:51 -07:00
Ivan BatsulinandGitHub 29a1e8dfda feat: multi-warship selection with Shift+drag box (#3677)
Resolves #3666

## Description:

Adds RTS-style box selection for warships. Hold Shift and drag (desktop)
or long-press and drag (touch/mobile) to draw a selection rectangle —
all player-owned warships inside get selected at once. A subsequent
click/tap on water sends them all to that location.

- `SelectionBoxLayer` — pixel-dashed rectangle in world-space, player
territory color; shared between desktop and touch
- `UILayer` — same pulsing selection outline on each box-selected
warship; clears correctly when switching between single/multi selection
- `UnitLayer` — finds warships in screen rect, filters inactive ships
before sending; touch support included
- `InputHandler` — Shift+drag and touch long-press+drag both emit
selection box events; cursor becomes crosshair on Shift; discards active
ghost structure on Shift press; configurable via `shiftKey` keybind
- `Transport` — single atomic `move_multiple_warships` intent (no split
on socket drop)
- `Schemas` + `ExecutionManager` + `MoveMultipleWarshipsExecution` —
server fans out atomic intent into individual `MoveWarshipExecution` per
ship
- `DynamicUILayer` — `MoveIndicatorUI` chevron animation on target tile
for both single and multi move
- `UnitDisplay` — warship tooltip Shift hint via `translateText`
- `HelpModal` — new hotkey row: Shift + drag → select multiple warships

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

## UI update
### Mouse + Keyboard


https://github.com/user-attachments/assets/3f35ab5e-1f3c-4c5d-bc4f-aabccf64dc60

### Touch


https://github.com/user-attachments/assets/0d6aec3f-44fa-4fee-b5c6-b267b9b14d79

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

fghjk_60845
2026-04-21 14:06:07 -07:00
RickD004andGitHub ec8884410c Add new Bering Sea map, reduce Strait rotation (#3706)
## Description:

Re-submission of older PR:
https://github.com/openfrontio/OpenFrontIO/pull/3617
<img width="612" height="396" alt="image"
src="https://github.com/user-attachments/assets/955a0b2e-1df9-4fa3-a389-235f46a90f69"
/>

Regarding Bering Sea and Bering strait maps:
<img width="576" height="87" alt="image"
src="https://github.com/user-attachments/assets/f57031eb-f9c0-4e1e-83ff-204df51bb6c2"
/>
<img width="589" height="93" alt="image"
src="https://github.com/user-attachments/assets/629e71ba-489f-41cf-b67e-2476af8704f2"
/>

Because of mixed opinions about replacing the maps, i propose at least
this temporary compromise: Bering Sea gets added as a new map and Bering
Strait gets its rotation lowered to a small number. Since the classic
maps are probably being taken off rotation, we should have some small
breathing space for the 2 maps.

In the near future we should discuss between mapmakers in the dev server
whether we keep, replace or completely take Bering Strait off rotation,
and/or a community poll.

## 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
2026-04-17 17:36:38 -07:00
FloPinguinandGitHub bcd9cd6af5 Cache shared-water computation for nation port placement 💧 (#3696)
## Description:

- Cache `sharedWaterComponents` globally with a 30-tick (~3s) TTL so all
nations share one `O(total_border_tiles)` pass instead of each nation
re-scanning every other player's border on every call.
- Treat ocean as always-shared: any ocean neighbor short-circuits as a
valid port site, skipping the `getWaterComponent` lookup in both the
build pass and the per-tile port check.
- Exclude bots and mutually-embargoed players from the trade-partner
candidate set, so nations no longer avoid port sites that only "share"
water with a player they can never trade with.

Port placement is not time-critical, so the 3-second staleness is
acceptable and lets the expensive build amortize across many attack
cycles.

### Performance
Benchmarked on World map (2000×1000, 61 nations) with the realistic call
pattern of ~3 nations invoking `sharedWaterComponents` per tick:

- **Before (main):** ~414 μs per tick
- **After:** ~8 μs per tick amortized (29/30 ticks hit the warm cache;
1/30 rebuilds)
- **~50× faster** on this AI hot path

## 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
2026-04-17 17:34:45 -07:00
FloPinguinandGitHub 0801cad0b5 Fix NaN coordinates in Warship patrol logic 🚢 (#3697)
## Description:

This PR fixes the `Invalid coordinates: NaN,NaN` crash during Warship
patrol execution.

### Root cause

`WarshipExecution.randomTile` picks a patrol destination inside
`warshipPatrolRange / 2` of the current patrol tile. When a search fails
to find a valid tile, the range expands by 50% per retry (`100 → 150 →
225 → 337`) and becomes odd. Once odd, `warshipPatrolRange / 2` is a
float (e.g. `112.5`), which is handed straight to
`PseudoRandom.nextInt`:

```ts
Math.floor(this.rng() * (max - min)) + min;
```

With a float `min`, this returns `integer + float` - a float. Despite
its name, `nextInt` was silently returning a non-integer. From there:

- `x = mg.x(patrolTile) + floatOffset` → float
- `mg.isValidCoord(floatX, floatY)` → `true` (only bounds were checked)
- `mg.ref(floatX, floatY)` → `yToRef[floatY] + floatX` → `undefined +
float` → `NaN`
- `hasWaterComponent(NaN, …)` → `miniMap.ref(NaN, NaN)` → **throw**

### Why this only started crashing recently

The float‑leaking `nextInt` bug has been latent since at least the
pathfinding refactor (#2866, January), which introduced the
`hasWaterComponent` check. It was invisible because the guard directly
above it short‑circuited on `NaN`:

```ts
if (!this.mg.isOcean(tile) || (!allowShoreline && this.mg.isShoreline(tile))) continue;
```

For a `NaN` tile ref, `terrain[NaN]` is `undefined`, so:

- `isOcean(NaN)` → `Boolean(undefined & OCEAN_BIT)` → **`false`**
- `isLand(NaN)` → **`false`**
- `isWater(NaN)` → `!isLand(NaN)` → **`true`**

Before: `!isOcean(NaN)` was `true`, execution hit `continue`, and the
poisoned ref never reached `hasWaterComponent`. The "Trading in lakes"
PR (#3653) relaxed that single line to allow patrol on lakes:

```diff
- if (!this.mg.isOcean(tile) || ...) continue;
+ if (!this.mg.isWater(tile) || ...) continue;
```

Because `isWater(NaN)` is `true`, `!isWater(NaN)` is now `false` -
execution falls through to `hasWaterComponent(NaN, …)` and crashes.
#3653 didn't introduce the bug; it just happened to remove the
accidental NaN filter that was hiding it.

### Changes

- **`PseudoRandom.nextInt`** - root‑cause fix. Floors both `min` and
`max` so `nextInt` always returns an integer regardless of what callers
pass. Future callers can't re‑trip this trap.
- **`WarshipExecution.randomTile`** - replaced the unsafe
`this.warship.patrolTile()!` non‑null assertion with a proper
`undefined` guard that returns early.
- **`GameMap.isValidCoord`** - defense in depth: also requires
`Number.isInteger(x)` and `Number.isInteger(y)`. Non‑integer coords can
still be produced outside `nextInt` (trig, arithmetic); this makes
`ref()` fail loudly at the boundary instead of silently producing `NaN`
refs.

### Original stacktrace

Please paste the following in your bug report in Discord:
Game crashed!
game id: gGicMpDh
client id: wXE5SpT2
Error: Invalid coordinates: NaN,NaN
Message: Error: Invalid coordinates: NaN,NaN
at at.ref
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:64853)
at r_.hasWaterComponent
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:243326)
at l_.hasWaterComponent
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:260740)
at b1.randomTile
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:92634)
at b1.patrol
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:91728)
at b1.tick
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:89996)
at
https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:251463
    at Array.forEach (<anonymous>)
at l_.executeNextTick
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:251383)
at p_.executeNextTick
(https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:271256)

Discord:
https://discord.com/channels/1284581928254701718/1494336024740888667

## 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
2026-04-17 17:34:12 -07:00
FloPinguinandGitHub ae96eb7e98 Fix nation nuke crash when attacker has no remaining tiles 🛡️ (#3703)
## Description:

Fixes a game crash (`Error: array must not be empty`) thrown from
`PseudoRandom.randElement` when a nation tries to pick a nuke target
whose territory no longer exists.

## Root cause

`NationNukeBehavior.findBestNukeTarget` calls
`findIncomingAttackPlayer`, which iterates the player's
`_incomingAttacks`. `AttackImpl` instances can linger in this array past
the point where the attacker has lost all tiles — the attack is only
removed on explicit `delete()` and `removeOnDeath` cleanup isn't
guaranteed to run before other executions tick within the same turn. A
dead attacker gets returned as the nuke target, `randTerritoryTileArray`
samples their (empty) territory, and `randElement` throws on the empty
array.

## Fix

- `Player.incomingAttacks()` now filters out attacks whose attacker is
no longer alive, so consumers can't observe stale references from
mid-tick deaths.
- `randTerritoryTile` guards against `numTilesOwned() === 0` before
falling back to `randElement(p.tiles())` as a defense-in-depth safeguard
at the util level.
- `PlayerImpl.toUpdate()` now uses the `incomingAttacks()` /
`outgoingAttacks()` accessors (rather than the raw `_` arrays) so
serialized client state stays consistent with the server-side view.

`outgoingAttacks()` is intentionally left unfiltered, the engine relies
on seeing in-flight attacks during their retreat phase after a target is
conquered (attack merging/cancellation in `AttackExecution.init`).

### Bug report from discord

Please paste the following in your bug report in Discord:
Game crashed!
game id: LQDSWbh6
client id: JjwysSLN
Error: array must not be empty
Message: Error: array must not be empty
at sa.randElement
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:28:39166)
at H1
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116429)
at Jc
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116135)
at G1.maybeSendNuke
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:117597)
at n5.tick
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:171764)
at https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251463
    at Array.forEach (<anonymous>)
at c.executeNextTick
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251383)
at b.executeNextTick
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:271256)
at S_
(https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:366356)

## 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
2026-04-17 17:32:49 -07:00
FloPinguinandGitHub f1f63ec9b4 Partial Pathfinding Rebuild 💧 (#3689)
## Description:

Another big water nukes performance improvement.

Performance measurements (water-nuke-detonation on GWM):

<img width="203" height="103" alt="image"
src="https://github.com/user-attachments/assets/b3d62575-d4bd-43c9-a5af-af127d73a9c5"
/>

I did a lot of testing with throwing water nukes and sending boats in
the area, paths are looking clean & correct!

## 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
2026-04-15 16:17:40 -07:00
FloPinguinandGitHub 9821e8e041 Add host cheats for streamers (Specifically Enzo) (#3671)
## Description:

- Adds a "Host Cheats" toggle in the private lobby options section that
reveals a dedicated section with four host-only cheats: infinite gold,
infinite troops, gold multiplier, and starting gold
- Only the lobby creator receives the cheat effects in-game (checked via
`isLobbyCreator` in DefaultConfig)
- Joining players see active host cheats displayed as yellow badges in
the lobby UI
- Adds `hostCheats` optional object to `GameConfigSchema` and wires it
through the server config update whitelist
- Raises the intent size limit for `update_game_config` messages
(lobby-only, not stored in turn history) to prevent rate-limiter kicks
(I always got too-much-data-kicked after selecting "host cheats" lol)

<img width="861" height="525" alt="image"
src="https://github.com/user-attachments/assets/51e51ec4-c2e8-46ca-b258-11a93487964f"
/>


<img width="933" height="825" alt="image"
src="https://github.com/user-attachments/assets/5acbd38d-2097-42e1-ba78-0fb17d6afe82"
/>

## 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
2026-04-15 15:20:08 -07:00
AotumuriandGitHub d05f5e748c Fix external train trade attribution in match results (#3589)
## Description:

A player reported that income received from opponents' trains was
missing from match result logs.
Fix external train trade attribution to the station owner and add
regression tests for train income stats.

## 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
2026-04-15 02:35:32 +00:00
FloPinguinandGitHub 35a64fa0d9 Big Water-Nukes Performance Improvements 💧 (#3668)
## Description:

### 1. Water Magnitude Calculation Optimization (WaterManager.ts)
* Boxed BFS Approach: Refactored the water magnitude recomputation to
use "Dirty" and "Seed" boxes. Instead of a global update, the system now
only recalculates magnitudes within a specific radius of the affected
area, significantly reducing CPU load after water-nuke-explosions
* Shoreline Bit Optimization: Narrowed the scope for updating shoreline
bits to a 2-ring neighborhood around converted tiles, avoiding
unnecessary checks across the entire map.

Performance test on the world map:
- AtomBomb (r=30): 24ms (was 344ms with global BFS), 2,993 changed tiles
(was 630k)
- Massive (r=200): 178ms (was 378ms), 130k changed tiles (was 654k)

### 2. Pathfinding Rebuild Staggering (PathFinder.ts,
TradeShipExecution.ts, TransportShipExecution.ts)
* Distributed Rebuilds: Introduced a staggering mechanism in
WaterPathFinder. Ship pathfinders now wait a randomized/distributed
number of ticks (0 - 5 seconds) before rebuilding after a water graph
change.
* CPU Spike Mitigation: By spreading out these expensive A* rebuilds
over time, we prevent lag when hundreds of ships attempt to re-path
simultaneously
* Like Mole said it: "Pretty realistic I;d say the capitan needs a
second to realize the big nuke on the left opened a new path"

From a performance test on the big new Luna map:
Graph rebuild: 256.4ms
Pathfinder-Rebuild of 329 ships (Including other Executions): 1564.4ms
(No longer noticeable, spread over 5s)

###  3. Performance Refinements
* Simplified deep ocean magnitude logic within the optimized BFS flow.
   * Improved memory efficiency by utilizing clipped BFS wavefronts.

## 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
2026-04-14 16:17:15 -07:00
RickD004andGitHub 897a3a3566 adds map of the Caucasus (#3661)
## Description:

Adds map of the Caucasus. This map is made especifically because of the
new update that allows ports to be placed on smaller bodies of water,
rather than just the largest one.

This map has 2 disconnected, large bodies of water of similar size
(Black and Caspian seas), which would create 2 independent trade
systems, creating a more dynamic economy.

<img width="667" height="541" alt="Captura de pantalla 2026-04-13
195810"
src="https://github.com/user-attachments/assets/d980b831-d920-4a2e-9d92-40a7be96ead6"
/>
<img width="539" height="439" alt="Captura de pantalla 2026-04-13
200005"
src="https://github.com/user-attachments/assets/e973a66a-8796-40dc-b8b3-39b49f687609"
/>

## 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
2026-04-13 20:02:11 -07:00
VariableVinceandGitHub 318d1e2c44 Refactor/Fix(UserSettings): last localstorage calls now use UserSettings, and Keybinds fixes (#3619)
## Description:

1) Have last localstorage calls for keybinds and attack ratio also use
UserSettings cache instead, after #3481. Remaining calls to localstorage
are for different things than user settings, so they are left as is.

2) Consolidate and centralize keybinds logic. And three fixes for it.

- **UnitDisplay** and **UserSettingsModal**: _parsedUserKeybinds_ is
introduced in **UserSettings** to centralize their logic. It is also
used by _normalizedUserKeybinds_, see point below.

- **UserSettingsModal**
-- replaced unwanted cast `as SettingKeybind` by a typed QuerySelector.
-- renamed this.keybinds to this.userKeybinds for more clarity, and
distinction from defaultKeybinds.
-- state private _userKeybinds_: remove type string[] since
loadKeybindsFromStorage replaces a value array by its first string
element, so it can not contain string[] anymore.
-- _handleKeybindChange_ and _getKeyValue_: no need to check for
Array.isArray anymore, see above reason.
-- **Fix**: checks after calling _parsedUserKeybinds_ are improved a
bit: don't delete all keybinds and print a console warning when finding
just one invalid keybind and (i think i have seen people complaining
about things being removed). Instead it now migrates or throws out the
invalid ones but keeps the valid ones. Also works with the "Null" value
expected and removed within
**UserSettingsModal**._handleyKeybindChange_() and in **HelpModal**.
When legacy value is an array and key is empty, don't put value as key
but get first array element or empty string as key name. So that check
on line 68 is true.

- **HelpModal** and **InputHandler**: Also centralize/consolidate their
logic more, by having __keybinds()_ from **UserSettings** perform
fetching _getDefaultKeybinds_ and _normalizedUserKeybinds_.
-- Functionality in _normalizedUserKeybinds_ is the same: Where
HelpModal did return [k, v.value] if typeof (v as any).value ===
"string", this is now handled by lines 309-310 of normalizedKeybinds
still the same but with less lines. Same for old HelplModal if (typeof v
=== "string") return [k, v], this is stil returned by line 112 of
normalizedKeybinds. And return [k, undefined] when (typeof val !==
"string") as was done in InputHandler, isn't needed as values that
weren't strings were already filtered out right after which we still do
on line 314 of normalizedKeybinds.
-- **Fix** in _normalizedUserKeybinds_: added one extra thing that was a
discrepancy between **HelpModal**/**InputHandler** and
**UserSettingsModal** before: **UserSettingsModal** would handle array
values, and normalize them by picking only the first value if it is a
string. Now have _normalizedKeybinds_ do the same. Otherwise it would
have thrown those values out while **UserSettingsModal** would have kept
the first value. This may still help a returning player who hasn't
played in the last version (i think i have seen people complaining about
things being removed, but that may not have been about this). And makes
the logic more consistent between **UserSettingsModal** and
**HelpModal**/**InputHandler**.

- **UserSettings**: 
-- _getDefaultKeybinds_: centralized/consolidated logic, accepts
Platform.isMac parameter. In **HelpModal**, **InputHandler** and
**UserSettingsModal** the same list with default keybinds was hardcoded.
Now they all read from _getDefaultKeybinds_. The list of default
keybinds in **HelpModal** was a little shorter, but that doesn't matter
since its _render_() function has hardcoded which of the hotkeys
**HelpModal** shows. Have thought about putting default keybinds in
**DefaultConfig** but with all the logic handled through
**UserSettings**, this seemed the better place in the current refactor.
-- _removeCached_: make public, now that **InputHandler.test.ts** needs
to be able to call it. We could instead make a public function like
removeKeybinds() and keep removeCached() private, but went with this for
now.
-- _parsedUserKeybinds_: centralized/consolidated logic for
**UserSettingsModal**/**UserDisplay**. Always returns an object, even an
empty one if the JSON wasn't parsable.
-- _normalizedKeybinds_: centralized/consolidated logic. Used by
_keybinds_() which is now called by **HelpModal**/**InputHandler**.
-- _keybinds_: now uses getDefaultKeybinds() and normalizedKeybinds() to
get the default and user changed keybinds.
-- **Fix** in _keybinds_: it now removes a key if it is Unbound by the
user in **UserSettingsModal**. Instead of first loading the
parsedUserKeybinds, removing "Null" keys from it, and then merging that
with defaultKeybinds (so default key would overwrite an unbound key), we
now merge parsedUserKeybinds with defaultKeybinds and after that remove
"Null" keys from it (so that unbound key stays removed).
For example if Boat Attack Up is set to "None" ("Null") by clicking
Unbind, there is now no hotkey working for it anymore. Even when the
default is "B".
Why? This prevents the user from being confused, they have deliberately
Unbound it, they don't understand why it still works (have seen bug
reports and game feedback about this)? Also more importantly: they used
to now be able to bind "B" to another action. Effectively making key "B"
bound to two actions: the user choosen one and Boat Attack. This also
makes the logic more consistent. Because building hotkeys in
**UnitDisplay** already didn't work when unbound, eg. when Build Missile
Silo was Unbound, the "5" key did not do anything anymore (there is a
fallback in **UnitDisplay** in case the key is actually null, but it
does respect "Null" as it should).
-- _setKeybinds_: have it accept an object, it stringifies it itself.
Callers UserSettingsModal and InputHandler.test.ts now just send either
a string or an object.

- **InputHandler.test.ts**: 
-- use **UserSettings** functions instead of localStorage for more
real-world testing.
-- change test "ignores non-string values and preserves defaults,
removes 'Null' for unbound keys". As explained above, as a fix we no
longer preserve unbound ("Null") keys within InputHandler.
UserSettings.keybinds() now removes "Null" keys as explained above.

- ControlPanel: use UserSettings to fetch initial attack ratio.

## 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
2026-04-13 15:56:32 -07:00
Patrick Plays BadlyandGitHub 3704914676 Add map conakry (#3656)
## Description:

Add Map - Conakry

Dataset from OpenTopography. Map rotated 45 degrees from north. This is
a 'long' map in a similar catagory as Amazon River with public rotation
adjusted to match. Different than Amazon in that its stubbier, one
sided, and has various terrain obstacles. Also its a really cool looking
piece of geography.

https://www.youtube.com/watch?v=OsMDbnnOOko


https://discord.com/channels/1284581928254701718/1481689305960288477/1481689305960288477

I removed an additional bot from the far left to help balance the map.
Some rivers were extended past realism to help section off the map in
areas. Size of map kept below average intentionally.


## 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
2026-04-12 19:22:33 -07:00
17c1a6300f Trading in lakes 🚤 (#3653)
## Description:

- Widened port placement and warship spawn/patrol checks from
`isOcean`/`isOceanShore` to `isWater`/`isShore`, so ports can be built
on lake shores and ships can operate on lakes, we discussed it here:

<img width="996" height="423" alt="image"
src="https://github.com/user-attachments/assets/acf1e970-9631-4848-a0ed-6d0470616e1d"
/>

- Filtered `tradingPorts()` by water component so ports only attempt
trades with reachable ports - prevents silent path-not-found failures
across disconnected water bodies
- Applied the same water component filter when a captured trade ship
reroutes to its new owner's nearest port
- Removed the `WaterManager` fallback that force-marked isolated
water-nuked-tiles as ocean (no longer needed since lakes are now
navigable)
- Added a check to prevent nations from building ports on water bodies
that aren't accessible to other players

## 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>
2026-04-12 17:18:52 -07:00
51716e9dc4 Add map luna (the moon) (#3635)
## Description:
Add map Luna (The Moon).

https://youtu.be/6L6vS9VvD8k


https://discord.com/channels/1284581928254701718/1490394299785805944/1490394299785805944

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

---------

Co-authored-by: FloPinguin <25036848+FloPinguin@users.noreply.github.com>
2026-04-11 13:17:43 -07:00