Commit Graph
3 Commits
Author SHA1 Message Date
Navaneeth PrabhaandGitHub 1a34321ac8 Fix transport ships targeting unreachable inland-lake shores (#4577)
Resolves #4555

## Description:

Fixes the bug where transport boats could not be sent to certain targets
(e.g. between the arrows on the Four Islands map) when the attacker's
territory was far enough away that it approached the target from its
inland-lake side.

**Root cause:** The transport landing tile was selected purely by
distance. `targetTransportTile()` called `SpatialQuery.closestShore()`,
which returns the nearest shore owned by the target using a
Manhattan-distance BFS and only checks `isShore && isLand && owned` — it
never considers whether that shore is reachable by water. Water bodies
are tracked as connected components (an inland lake is a separate
component from the surrounding ocean), and the *source* selection
(`closestShoreByWater()`) correctly requires the attacker to have a
shore in the same water component as the destination. So when the
nearest owned shore happened to face a disconnected inland lake, the
source search found no shore in the lake's component and the transport
silently failed — even though the same target had an ocean-facing shore
that the attacker's boats could actually reach.

**Fix:** Make destination selection reachability-aware so a lake-facing
shore is never chosen when a reachable one exists.

- Added `SpatialQuery.closestReachableShore(targetOwner, attacker,
tile)`: it first collects the set of water components adjacent to the
attacker's own shoreline, then returns the nearest target-owned shore
whose water component is in that reachable set. Shores that only border
a disconnected water body (an inland lake) are skipped. It returns
`null` only when the target has no reachable shore at all.
- `targetTransportTile()` now takes the attacker and delegates to
`closestReachableShore()` instead of `closestShore()`. Its two callers —
`canBuildTransportShip()` and `TransportShipExecution.init()` — already
have the attacker in hand and pass it through, so both the build-time
check and the actual execution agree on the same reachable destination.
- `closestShore()` is left unchanged; this adds a reachability-aware
variant rather than altering existing behavior.

**Testing performed:** Reproduced the bug first on a generated map with
an inland lake enclosed by a thick land moat (confirmed the lake
resolves to a separate water component and that `closestShoreByWater()`
returns `null` for the lake-facing destination while a reachable ocean
shore exists). Added unit tests for `closestReachableShore()` (picks the
reachable ocean shore; returns `null` when every target shore is in an
unreachable water body) and a `canBuildTransportShip()` regression test
for the lake scenario. Full suite: 1878 tests pass; `tsc --noEmit`,
ESLint, and Prettier are all clean.

## Please complete the following:

- [x] I have added screenshots for all UI updates — no UI changes in
this PR
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file — no user-facing text added in
this PR
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:
Navaneeth Prabha#0825
2026-07-14 14:21:59 -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
Arkadiusz SygulskiandGitHub 0e3ced3bfa Pathfinding Refactor pt. 2 (#2866)
## Playtest

https://pf-pt-2.openfront.dev/

## Pathfinding Refactor pt. 2

<img width="1536" height="1024" alt="image"
src="https://github.com/user-attachments/assets/9477958e-54b7-4c83-b317-ba789e809e9e"
/>


This is a follow-up to a previous PR introducing pathfinding changes.
This time, it introduces a complete refactor of `pathfinding` directory
and breakdown into composable pieces.

### Unified PathFinder interface

`PathFinder<T>` and `SteppingPathFinder<T>` are introduced to unify
**all** pathfinding across the application. First one exposes complete
path, while stepping variant allows the callee to iterate over the path
by calling `.next`. All pathfinders share this one common interface,
which makes them easy to use in any scenario -
`PathFinding.Water(game).search(from, to)`.

`SteppingPathFinder<T>` extends `PathFinder<T>` with an ability to
iterate over the path. It handles caching, storing current index and
invalidation. This allows the units to not care about the inner workings
of the pathfinder and just call `pf.next(current, target)` and receive
instructions on what to do next.

### Common entry point

All pathfinders are now exposed from common `PathFinding` entrypoint:

- `PathFinding.Water`
- `PathFinding.Rail`
- `PathFinding.Stations`
- `PathFinding.Rail`

Additional entry point is introduced for pathfinders which need to work
both in the worker, but also on the frontend, which lacks `Game`
interface. Currently only `UniversalPathFinding.Parabola` is available.

### Spatial Query

New module has been introduced close to `pathfinding` - `SpatialQuery`.
It aims to resolve any questions game may have about finding tiles
meeting criteria. Currently `SpatialQuery.closestShore(player, target)`
and `SpatialQuery.closestShoreByWater(player, target)` are available -
they help answering questions about naval invasion: "What is the best
landing location from user's click?" and "Which our tile should be used
to launch the transport ship?". Under the hood they use very similar
mechanics to pathfinding, so it felt right to put them close by.

### Modular architecture

Pathfinders now support transformers: `MiniMapTransformer`,
`ShoreCoercingTransformer`, `ComponentCheckTransformer`,
`SmoothingTransformer`. Transformers functions like a middleware in the
pathfinding chain. They wrap around the pathfinder and provide
additional functionality. This allows the pathfinder to focus on
actually finding the path instead of doing unrelated things.

Example chain for simple (A*) water pathfinding:
```ts
static WaterSimple(game: Game): SteppingPathFinder<TileRef> {
  const miniMap = game.miniMap();
  const pf = new AStarWater(miniMap);

  return PathFinderBuilder.create(pf)
    .wrap((pf) => new ShoreCoercingTransformer(pf, miniMap))
    .wrap((pf) => new MiniMapTransformer(pf, game.map(), miniMap))
    .buildWithStepper(tileStepperConfig(game));
}
```

The Pathfinder - here `AStarWater` - does not care about the conversion
between minimap and main map tiles. It also does not care if the source
or destination is a land tile. The transformers take care of that. The
pathfinder gets a set of valid coordinates and produces the path -
that's it.

Modular approach makes working on a particular set of utilities much
easier - for example map upscaling is handled consistently across all
pathfinders. Additionally, the pathfinders are not tied to the
particular map resolution used. Pass them a different map and they will
work the same.

### Algorithms

Algorithms used are neatly organized inside
`src/core/pathfinding/algorithms`. They are prefixed with the algorithm
name and suffixed with the use case. File without suffix exposes generic
version ready to traverse any graph with adapters. Specialized versions
either use an adapter or inline logic when performance is critical -
using adapters leads to 20-30% performance loss.

The directory includes `A*` and `BFS` but also other useful utils, such
as `AbstractGraph` used to generate... an abstract graph on top of the
tile map and `ConnectedComponents` helping to identify whether two tiles
are connected by a path without actually computing the path.

### Playground

The playground have been updated with new algorithms, including tweaked
very greedy `A*`.

<img width="2175" height="1424" alt="image"
src="https://github.com/user-attachments/assets/1f833651-0024-4299-bf86-882f5368358c"
/>

### Tests

Yeah, there are some, a little too many if I say so myself. But there
are no useless tests. I had to ensure refactored code works somehow
reliably. This PR comes with trust me bro guarantee, but I would
appreciate someone confirming **naval invasions, nukes (esp. MIRV) and
warships**.

### Discord
`moleole`

GL & HF
2026-01-11 20:11:14 -08:00