Commit Graph

18 Commits

Author SHA1 Message Date
Aotumuri 300bcaec44 fix TestConfig 2026-01-11 12:59:32 +09:00
Aotumuri 1c31f8397d fix TestConfig 2026-01-11 09:58:43 +09:00
Arkadiusz Sygulski b090f2f624 HPA* Pathfinding (#2815)
## Pathfinding with HPA*

Hi! The primary objective of this PR is to replace per-tile A* with
hierarchical pathfinding - HPA*. In practice, this means we create an
abstract graph on top of the actual map with far fewer points and use it
to decide on general path structure. Only then we go back to tile-level
and build path between selected waypoints. This speeds up long distance
pathfinding by over 1000x in some cases. To make the review easier, it
comes with a benchmark and visual playground.

## PREPROCESSING

H part of HPA* means "hierarchical" and requires preprocessing.

This PR includes pre-processing as part inside `new Game()` constructor.
It takes about 135ms for `giantworldmap` on my machine, which increases
the effective initialization from ~95ms to ~230ms. This time could be
reduced in different ways, which are **out of scope** for this PR.

After confirming the initialization time is bearable on low-end devices,
I argue merging this PR as-is is acceptable tradeoff. It creates small
lag at the beginning of a round but pays for itself in the first minute
of the match.

## Nerdy details

**Architecture**
- HPA*-style hierarchical pathfinding
- 32×32 sectors on minimap with gateway nodes on borders
- Gateway graph built via BFS during preprocessing
- Water component optimization skips unreachable gateway pairs
- A* on gateway graph → local A* within sectors → Bresenham path
smoothing
- Minimap upscaling identical to currently used in MiniAStar

**Key Optimizations**
- Typed arrays instead of high-level primitives
- Stamp-based visited tracking (no need to recreate buffers, O(1)
clearing)
- Optional - enabled by default - caching of tile paths between gateways
- Line of sight smoothing for the final path

## Review Focus

Play with included tools, benchmark and visualization. Pathfinding
should be safe to merge as a black box - you do not need to understand
the details. Outcomes can be tested empirically in-game. Visualize (and
share!) edge cases with included playground. Confirm the 100x speedup is
real with benchmark.

If you plan to dive into the code, I suggest the following order:
- Pathfinding abstraction in `src/core/pathfinding/`
- Pathfinding tests in `tests/core/pathfinding/`
- NavMesh in `src/core/pathfinding/navmesh/` + integration with
`Game.ts`
- Benchmark in `tests/pathfinding/benchmark/`

Do not look at playground's code, it has been created with a clanker.
The design is 100% mine and I spent way too long polishing it, but I
haven't even once edited the code manually. There is probably no
abstraction whatsoever, just do not look at the code, let it play.

## Core Changes

#### Pathfinding (`src/core/pathfinding/navmesh/`)
- HPA* + refinement -> three phased pathfinding: A* over the graph ->
naive path -> refinement
- comes with A* and BFS optimized for for specific needs

#### Pre-Processing (`src/core/pathfinding/navmesh/`)
- identify water bodies to avoid pathfinding between disconnected nodes
- create high-level graph of gateways on top of tile map

#### Abstraction (`src/core/pathfinding/`)

- common `PathFinder` interface that can return full path and also act
as state machine (`.next()`)
- adapters for both new and legacy algorithm with fallback to legacy if
navigation mesh not available

#### Benchmark (`tests/pathfinding/benchmark/`)

- `npx tsx tests/pathfinding/benchmark/run.ts` - no guesswork, numbers
- `npx tsx tests/pathfinding/benchmark/run.ts --synthetic` - 1000s of
synthetic paths
- `npx tsc tests/pathfinding/benchmark/generate.ts` - generate more as
needed, test new maps
- includes ONE synthetic scenario to avoid PR bloat, generate more
locally / later

#### Playground (`tests/pathfinding/playground/`)

- `npx tsx tests/pathfinding/playground/server.ts` - visualize paths
with both new and legacy algorithm

## Benchmarks

### Compared with legacy in default - hand picked - scenario:
```
Initialization: 95.95ms -> 227.29ms
Pathfinding: 3038.43ms -> 6.45ms
Distance: 26972 -> 26810 tiles
```

### 42,000 synthetic routes across all maps
```
Running 42 synthetic scenarios with hpa.cached adapter...

 synthetic/achiran                   | Init:    93.42ms | Path:    139.07ms | Dist: 1481630 tiles | Routes: 1000/1000
 synthetic/africa                    | Init:    87.14ms | Path:    155.08ms | Dist: 1829414 tiles | Routes: 1000/1000
 synthetic/asia                      | Init:    57.60ms | Path:    112.55ms | Dist: 1204082 tiles | Routes: 1000/1000
 synthetic/australia                 | Init:    78.18ms | Path:     77.12ms | Dist:  978375 tiles | Routes: 1000/1000
 synthetic/baikal                    | Init:    78.26ms | Path:    152.14ms | Dist: 1600016 tiles | Routes: 1000/1000
 synthetic/baikalnukewars            | Init:    81.44ms | Path:    165.90ms | Dist: 1699283 tiles | Routes: 1000/1000
 synthetic/betweentwoseas            | Init:    29.29ms | Path:    114.99ms | Dist: 1338075 tiles | Routes: 1000/1000
 synthetic/blacksea                  | Init:    30.66ms | Path:     93.14ms | Dist:  949217 tiles | Routes: 1000/1000
 synthetic/britannia                 | Init:    74.12ms | Path:     85.62ms | Dist:  866752 tiles | Routes: 1000/1000
 synthetic/deglaciatedantarctica     | Init:   105.49ms | Path:    192.93ms | Dist: 1574684 tiles | Routes: 1000/1000
 synthetic/didier                    | Init:    81.51ms | Path:    153.70ms | Dist: 1734876 tiles | Routes: 1000/1000
 synthetic/eastasia                  | Init:    49.29ms | Path:    128.63ms | Dist: 1410270 tiles | Routes: 1000/1000
 synthetic/europe                    | Init:    92.55ms | Path:    178.35ms | Dist: 1525216 tiles | Routes: 1000/1000
 synthetic/europeclassic             | Init:    33.50ms | Path:    104.40ms | Dist: 1209759 tiles | Routes: 1000/1000
 synthetic/falklandislands           | Init:    63.00ms | Path:    107.41ms | Dist: 1080251 tiles | Routes: 1000/1000
 synthetic/faroeislands              | Init:    71.91ms | Path:     49.52ms | Dist:  604613 tiles | Routes: 1000/1000
 synthetic/fourislands               | Init:    45.75ms | Path:     78.91ms | Dist:  937439 tiles | Routes: 1000/1000
 synthetic/gatewaytotheatlantic      | Init:    81.00ms | Path:    257.06ms | Dist: 2555551 tiles | Routes: 1000/1000
 synthetic/giantworldmap             | Init:   214.25ms | Path:    220.42ms | Dist: 1976693 tiles | Routes: 1000/1000
 synthetic/gulfofstlawrence          | Init:    45.16ms | Path:     96.05ms | Dist: 1014604 tiles | Routes: 1000/1000
 synthetic/halkidiki                 | Init:    74.68ms | Path:    149.39ms | Dist: 1546781 tiles | Routes: 1000/1000
 synthetic/iceland                   | Init:    58.72ms | Path:     78.16ms | Dist: 1001554 tiles | Routes: 1000/1000
 synthetic/italia                    | Init:    29.78ms | Path:    139.93ms | Dist: 1412024 tiles | Routes: 1000/1000
 synthetic/japan                     | Init:   161.07ms | Path:    118.65ms | Dist: 1154393 tiles | Routes: 1000/1000
 synthetic/lemnos                    | Init:    52.59ms | Path:    136.69ms | Dist: 1481101 tiles | Routes: 1000/1000
 synthetic/lisbon                    | Init:    49.27ms | Path:     86.53ms | Dist: 1032011 tiles | Routes: 1000/1000
 synthetic/manicouagan               | Init:    53.74ms | Path:    110.52ms | Dist: 1307630 tiles | Routes: 1000/1000
 synthetic/mars                      | Init:    29.39ms | Path:     80.55ms | Dist: 1091702 tiles | Routes: 1000/1000
 synthetic/mena                      | Init:    26.37ms | Path:    120.09ms | Dist: 1272751 tiles | Routes: 1000/1000
 synthetic/montreal                  | Init:    26.08ms | Path:    106.77ms | Dist: 1187736 tiles | Routes: 1000/1000
 synthetic/newyorkcity               | Init:    56.60ms | Path:    181.19ms | Dist: 1753875 tiles | Routes: 1000/1000
 synthetic/northamerica              | Init:    96.29ms | Path:    123.02ms | Dist: 1217221 tiles | Routes: 1000/1000
 synthetic/oceania                   | Init:    52.81ms | Path:     51.96ms | Dist:  482373 tiles | Routes: 1000/1000
 synthetic/pangaea                   | Init:    21.29ms | Path:     56.58ms | Dist:  716189 tiles | Routes: 1000/1000
 synthetic/pluto                     | Init:    53.89ms | Path:    141.62ms | Dist: 1304362 tiles | Routes: 1000/1000
 synthetic/southamerica              | Init:    85.19ms | Path:    123.03ms | Dist: 1301403 tiles | Routes: 1000/1000
 synthetic/straitofgibraltar         | Init:    76.68ms | Path:    108.30ms | Dist: 1304592 tiles | Routes: 1000/1000
 synthetic/straitofhormuz            | Init:    38.97ms | Path:     67.78ms | Dist:  754920 tiles | Routes: 1000/1000
 synthetic/surrounded                | Init:    95.35ms | Path:     90.18ms | Dist: 1017142 tiles | Routes: 1000/1000
 synthetic/svalmel                   | Init:    60.58ms | Path:    104.75ms | Dist: 1235501 tiles | Routes: 1000/1000
 synthetic/twolakes                  | Init:    62.05ms | Path:     94.54ms | Dist: 1140807 tiles | Routes: 1000/1000
 synthetic/world                     | Init:    41.43ms | Path:     93.42ms | Dist:  873406 tiles | Routes: 1000/1000

Completed 42 scenarios
Total Initialization Time: 2796.32ms
Total Pathfinding Time: 5026.64ms
Total Distance: 53160274 tiles
```

## Playground

**That's the fun part**. Watch NavMesh running circles around legacy
`PathFinder.Mini` in real time. Debug inner workings, test edge cases,
share URLs for debugging.


https://github.com/user-attachments/assets/34e2e3f5-fbc1-4b1f-917d-820766e98d5d

## Discord Tag
`moleole`
2026-01-08 13:34:18 -08:00
DevelopingTom af0b8a8d50 Configurable immunity timer (#2763)
## Description:

Resolve discussions about stalled PR
https://github.com/openfrontio/OpenFrontIO/pull/2460

<img width="724" height="348" alt="image"
src="https://github.com/user-attachments/assets/c2c9fa79-cace-431a-9ca4-b3656612fa9d"
/>

Changes:
- Added a `Player::canAttackPlayer(other)` function to determine whether
a player can be attacked.
- This function is now used in most places where a fight can occur:
    - AttackExecution (land attacks)
    - Naval invasion
    - Warship fight
- Nukes can't be thrown during the truce
- Immunity only affect human players. Nations and bot will fight as
usual, and can be fought against.
- The immunity timer uses minutes in the modal window.

UI:

- The immunity phase is displayed with a timer bar at the top. This is
from the original PR, to be discussed if it's not deemed visible enough:

<img width="632" height="215" alt="image"
src="https://github.com/user-attachments/assets/f5ab9aa0-bd4f-4503-b8d6-b40b121fba65"
/>


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

---------

Co-authored-by: newyearnewphil <git@nynp.dev>
2026-01-03 20:04:48 -08:00
Danny 7db8d51bf7 Remove RNG from SAM launchers (#2665)
## Description:
SAMs will now always hit their target instead of missing sometimes.

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:

Lavodan
2025-12-21 22:49:10 +00: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
evanpelle d16a2485e3 Merge branch 'v26' 2025-11-07 19:54:27 -08:00
Philipp Allweyer 5dde4cc16d Extend SAM Range to cover Hydros when stacked (#2351)
## Description:

Implements SAM range extension for stacked SAMs to cover hydros as
requested in #2347 and many times from users in discord.
This implementation is as simple as possible: At level 5 and higher,
SAMs extend their range to the range of a hydrogen bomb + 10 for a small
safety margin. Levels 2-4 are interpolated between.

Screenshot to show the sizes compared to a hydro:
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/a857d66c-e3d4-467f-855f-3539cc90b719"
/>

Everything works together with the new range UI, although I might need
to unify with / rebase on #2350. Not yet tested with #2348, but
shouldn't be an issue.

## Input needed:
- Should I add tests for this?
- This is in effect a massive buff to SAMs, might be too strong. Popular
ideas / suggestions from Discord to balance things:
  - Cap the SAM upgrade level at the maximum range (easy to do)
- Alternative, instead of capping the level, decrease the range when
missiles are reloading
- Increase the cost scaling for SAMs per stack, and scale way higher
(e.g. 1.5M > 3M > 4.5M > 6M or something like that) (UI integration
unclear, breaks with existing cost logic)
  - Decrease SAM hit probability for Hydros

I'm happy to implement any of these paths, or just roll with the simple
way it's set up now, just let me know.

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

newyearnewphil

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-05 11:15:00 -08:00
evanpelle 1b113873d6 Revert "AFK team mate: better ship handling + tests + bugfix (#2203)"
This reverts commit 896a8ebe92.
2025-11-05 08:56:51 -08:00
Vivacious Box e7c49d57d2 Add deletion duration and indicators (#2216)
Adds a timer before self deleting units
Adds a loading bar under deleting units
Adds a timer in radial menu for clarity purposes

![deletecd](https://github.com/user-attachments/assets/613bf742-ef90-42b5-a258-b928daae6aaa)

- [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
regression is found:

Mr.Box

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-11-01 11:02:32 -07:00
evanpelle 02b4702a11 Merge branch 'v26' 2025-11-01 10:49:35 -07:00
VariableVince 896a8ebe92 AFK team mate: better ship handling + tests + bugfix (#2203)
## Description:

Have AFK player's Warships not attack team members ships, like Transport
Ships boating in. If team mate conquers the AFK player, transfer over
Warships and Transport Ships to conqueror. The transfered Transport
ships attack in the name of the new owner when landing, and when they
are retreated they move back to a new owner shore tile if they have any.

Added tests. Expectation is this PR will be merged in v26 as the real
solution for the temporary workaround of deleting warships.

**Currently:**
- An AFK player can be attacked without troop loss by their team
members. For this purpose, isFriendly now returns false if the other
player isDisconnected
- But that meant Warships would get False from isFriendly too, and
attack the ships and boats of their team members.
- [Temporary workaround was to delete
warships](https://github.com/openfrontio/OpenFrontIO/commit/eea8db7a06aed50c005db35ad55ece026f7a3643)
as soon as the player was deemed AFK. But this is a disadvantage to the
team. For example the AFK player could have 6 warships in the waters,
either defending team land or helping the team cross over to the enemy
team.
- Transport Ships that were on the way to attack, were still deleted
after the AFK player was conquered. But this is also a disadvantage, if
say a transport ship has just managed to breach through to the enemy
lands despite warships all around. That could have made the win for the
team.

(Left to think about: do we want to transfer part of the defender troops
to the isOnSameTeam attacker? Defender looses less troops in the attack
from their team mate. You'd expect troops to lay down their arms mostly,
if the attacker is on the same team and doesn't loose troops themselves.
Those troops that they loose less than normal, are then added to the
attacker once they've been deemed conqueror. The enemy team can still
attack and do normal damage, and can still also be the conqueror so the
team members have to be fast.)

**Changes in this PR:**
- GameImpl > conquerPlayer: Transfer ownership of the warships to the
conquerer of their lands. If the conqueror is not a team member (other
team can still attack, in their case with troop loss), they won't get
the warships and the ships will be deleted like normal. If the conqueror
is a team member, have them capture the warships.
(Captures need to happen in conquerPlayer since this is right before the
last tiles are conquered and PlayerExecution finds out the player is
dead and deletes its units. Captures will be recorded in the stats just
like normal. Things like this add an extra incentive to be the fastest
to conquer the AFK player, next to getting their gold which is also
recorded in stats. The normal Event Box messages are also displayed.)

- GameImpl > conquerPlayer: Also transfer Transport Ships. As a note:
the limit of 3 transport ships concurrently out on water for one player,
can be exceeded in this specific case (the boatMaxNumber is only checked
for canBuild via TransportShipUtils, and in the init of a
TransportShipExecution, not for an existing TransportShipExecution with
a changing owner). This keeps the situation even for the team in terms
of ships that are already out to attack, which is fair.
captureUnit/setOwner won't do the full job here though, so changes in
TransportShipExecution are needed.
- TransportShipExecution: Added originalOwner. So we can check within
the execution itself if the Original owner disconnected, and if so if
the new owner is on the same team. Only in that case change private
this.attacker. This.attacker can still not be changed from the outside
in this way. Find new src of new owner to retreat boat to if needed, and
if new owner has no shore set it to null so the boat will be deleted
upon retreat. To find new src tile of new owner, use
bestTransportShipSpawn instead of canBuild because canBuild checks for
max boats = 3 etcera but the boat is already on its way so those checks
don't apply (we could get false back from canBuild because there's 3
ships out, while we only need to find the source tile so use
bestTransportShipSpawn).
- TransportShipUtils: to use bestTransportShipSpawn to find new owner
source tile to retreat to, we need to make sure it can handle a new
owner without shore tiles. When the new owner has no shore tiles
(candidates.length === 0), return false. This way it won't go on to call
MiniAStar which would have SerialAStar error on an empty this.sources
array.


- WarshipExecution: Changed isFriendly. This makes sure we have the
wanted behavior: allied/team ships should not attack each other once one
of their owners goes AFK.

- AttackExecution: added one more test specifically to check if attack
on AFK teammate is still witthout troop loss "Player can attack
disconnected team mate without troop loss". Also a bugfix that I left in
after removing a related change from this PR: Add a check for
removeTroops === false in the retreat() function, so at the end of the
attack we don't add troops back to owner troops if they were never
removed in the init. This check in retreat() is actually a bug fix
because removeTroops is in the constructor and can be set to True, but
in retreat() troops would then have been given back after not being
removed at init.

- DefaultConfig: small addition to comment.

- Disconnected.test.ts: added tests. Added useRealAttackLogic because at
least "Player can attack disconnected team mate without troop loss"
needs to use the real attackLogic.
- TestConfig: new class useRealAttackLogic extends TestConfig class, so
a test setup can use the real attackLogic from DefaultConfig instead of
the mock function in TestConfig.
- Setup.ts: for the test setup, add parameter to accept
useRealAttackLogic extension class. Defaults to TestConfig.

## 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: Evan <evanpelle@gmail.com>
2025-11-01 10:48:56 -07:00
Vivacious Box dddf54be0b Add deletion duration and indicators (#2216)
## Description:

Adds a timer before self deleting units
Adds a loading bar under deleting units
Adds a timer in radial menu for clarity purposes


![deletecd](https://github.com/user-attachments/assets/613bf742-ef90-42b5-a258-b928daae6aaa)

## Please complete the following:

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

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

Mr.Box

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-10-21 10:07:14 -07:00
Vivacious Box ad42dc0ee8 Fix sam targetting everything (#1280)
## Description:

There was a regression on how sam targets nukes.
This fixes 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
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

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

Vivacious Box
2025-06-26 16:47:16 -07:00
Ilan Schemoul 11791719e4 nukes now reduce attacking troops and transport ships (same as rest of pop) (#350)
Co-authored-by: Evan <evanpelle@gmail.com>
2025-03-31 12:39:18 -07:00
Ilan Schemoul f2193edc7c priortize ally ports and close ports (#335)
2x more likely to trade with ports belonging to an ally OR close.
3x more likely to trade with ports belonging to an ally AND close.
Add trading tests
2025-03-27 16:09:58 -07:00
Ilan Schemoul 68621f326a sam do not target twice same nuke (#270) 2025-03-21 10:17:33 -07:00
evanpelle cd1f8b9586 add testing infrastructure and example test (#276) 2025-03-17 12:20:23 -07:00