mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 10:38:11 +00:00
b5f1577cebb427175e0df0fb2bf5d13cf0e0675c
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
18fb513326 |
Pathfinding refinements (#2959)
## Description: ### Short path for multi-source HPA* Math was not mathing, increased the bounds to 260x260, it is a bit slower but should work better. The short path was breaking when player owned a lot of shores. This is because the bounding box of tiles with less than 120 distance + 10 padding could be as big as 260x260 and the optimized array was set to 140x140. I made mistake of calculating it as `2 * (60 + 10)` instead of `2 * (120 + 10)`. ### LoS path refinement Previously, we ran 2 passes of LoS smoothing on the path. However, since we are effectively tracing the same path, the line of sight is essentially the same. This PR makes second line of sight stop on water tiles with magnitude `n + 1` compared to first path. Practically, this means it'll attempt LoS exactly 1 tile after previous corner. See screenshot. <img width="1299" height="1151" alt="image" src="https://github.com/user-attachments/assets/726be236-1ff8-406c-896a-02902a762ab0" /> ### SendBoatAttackIntentEvent The flow of sending transport ships is currently strange. This PR makes the flow more sane. **Old flow** ``` - Player clicks TARGET tile, it can be deep inland - Client asks Worker for the best START tile to TARGET tile - Worker answers `false`, since the tile is inland - Client sends BoatAttackIntent with START=false and TARGET tiles set - Worker accepts BoatAttackIntent, computes DESTINATION as closest shore to TARGET - Worker re-computes best START to DESTINATION - Worker sends boat from START to DESTINATION ``` **New flow** ``` - Player clicks TARGET tile, it can be deep inland - Client sends BoatAttackIntent with TARGET - Worker accepts BoatAttackIntent, computes DESTINATION as closest shore to TARGET - Worker computes START as the best tile to DESTINATION - Worker sends boat from START to DESTINATION ``` ## 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: moleole |
||
|
|
c123adc0ef |
fix (pathfinding): prioritize best connected water neighbor in ShoreCoercingTransformer (#2937)
## Description: **Describe the PR.** This PR improves how pathfinding finds a starting water tile when launching a transport ship from a shore. Previously, the code simply picked the first water neighbor it found. This caused issues where, if a boat were traveling east, it might launch out of a northern tile from a shore. <img width="896" height="353" alt="image" src="https://github.com/user-attachments/assets/69d83012-3397-43b3-8ab0-9ebde6ffea97" /> <img width="342" height="219" alt="image" src="https://github.com/user-attachments/assets/a191f5cf-97da-4e34-a191-55ce14c794f0" /> The new logic checks all water neighbors and picks the "best" one by counting how many water tiles surround it. This ensures transport ships launch into the main body of water instead of suboptimal positions. If two tiles have water neighbors with the same score, they are tie-broken through a euclidean distance check. ## 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: Scisyph --------- Co-authored-by: WilliamT-byte <williamt2023@tamu.edu> Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
6bd95d4884 |
Pathfinding - optimize naval invasions (#2932)
# Pathfinding pt. 4 https://pf-pt-4.openfront.dev/ ## Description: Hello again! Pathfinding. It's fast, but inaccurate. This PR makes it more accurate and actually faster. Sadly it is _faster_ because of a blunder in previous PR (using BucketQueue where MinHeap would be better), not because of a new tech. More importantly, it is more accurate. And that's what people apparently want. ## What changed? Most of the functional changes relate to `SpatialQuery` module. This is the thingy that answers "we know the target, which tile of my territory is the best to launch an invasion". To make it compute a path from South America to the deep inland China river, it has to work on a coerced map, one with a very small resolution, so small in fact, that every 4096 map tiles gets compressed to just one pixel. I hope you see where this is going. Previously we selected a random coastal tile within this big pixel (honestly it wasn't random at all, but could very well be for the illustrative purposes). Now, we try to be a bit more deliberate. Since we already know the rough location of the probably best tile, we can exclude all other tiles from the computation. Imagine a player's territory spans both Americas on global map - that's a lot of shores. But since we already know the best tile is somewhere close to Miami, the problem space was greatly reduced, no need to consider all other shores. But pathing to the target in China from Miami is still crazy expensive. This is where second trick comes to play - instead of pathing all the way to China, we select a _waypoint_ in the rough direction of China, about 100 to 200 tiles away. This way we fairly cheaply select best tile to launch an invasion towards this abstract point. And chances are, this point is far enough, the newly computed path is very close to being optimal. When you throw a dart from far away, the difference between scoring 10 and missing is very small. This is why aiming in the general direction of the board - as opposed to the ceiling - is usually good enough. ## Okay, but what about the crazy paths when I send invasion to the opposed bank of a river?! Well, pathing from America to China is cool, but most players wouldn't notice the difference on such long paths, what about the short ones? We now try more accurate pathing first and defer to hierarchy only if it fails. This produces much better paths for short invasions. While the fix described above ensures the accuracy is improved also on medium-to-long routes. ## Playground Yes. https://github.com/user-attachments/assets/9cf9586f-c99a-416d-b856-8cf0a21c35ed ## CodeRabbit Grab a 🥕. Remember `tests/pathfinding/playground` is mostly generated code and go easy on it. It's enough for it to work and do it's job of visualizing the paths. No need for throughout review of these files. ## 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: moleole |
||
|
|
85def73bd9 |
Pathfinding Refinement (#2878)
# Pathfinding pt. 3 ## Description: This PR introduces final change to the pathfinding - path refinement. It optimizes Line of Sight refinement by searching with for the best tile with a binary search instead of linearly. And then spends the recovered budget on better refinement of the first and last 50 tiles of the journey - the place where user is most likely to look at. Additionally this PR re-introduces magnitude check and makes the ships prefer sailing close to the coast, but not too close. ## 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 ## What? | Before | After | | :--- | :--- | | <img width="1097" height="1117" alt="image" src="https://github.com/user-attachments/assets/4a0b300d-10ef-4151-b6dc-33acfb49f992" /> | <img width="1093" height="1119" alt="image" src="https://github.com/user-attachments/assets/cf81c515-c145-40f4-91e5-a4353986907b" /> | | <img width="1096" height="1129" alt="image" src="https://github.com/user-attachments/assets/21b46bce-f961-4259-88f6-fe4a66180270" /> | <img width="1098" height="1126" alt="image" src="https://github.com/user-attachments/assets/d92587d1-e6b6-4353-b4a4-1efe71bca43d" /> | ## Performance There is actually a severe performance impact of these changes. The path initial path takes almost 2x as long to generate - this is because pre processing can only do so much if the initial path is ugly. Luckily in real gameplay we only need to do this calculation once per edge, so the actual observed performance impact should be much smaller. Cache FTW. | | No Cache | Cache | | :--- | :--- | :--- | | Before | 277.04ms | 208.58ms | | After | 498.34ms | 264.27ms | ## DebugSpan Small utility, it allows any code to be easily instrumented for performance. The idea is the same as with [OTEL Spans](https://opentelemetry.io/docs/concepts/signals/traces/). Produce a span, create sub-spans, measure whatever you need. Works only when `globalThis.__DEBUG_SPAN_ENABLED__ === true`, otherwise no-op. Cool stuff, try it out: ```ts // Convenient wrapper, small performance impact return DebugSpan.wrap('add', () => a + b) // Synchronous API, basically free DebugSpan.start('work') work() DebugSpan.end() // Create sub spans DebugSpan.wrap('complex', () => { const aPlusB = DebugSpan.wrap('add', () => a + b) DebugSpan.set('additionResult', () => aPlusB) // Store data return aPlusB * c }) // Access spans, data and timing const span = DebugSpan.getLast() const compelxSpan = DebugSpan.getLast('complex') console.log(complexSpan.duration, complexSpan.data['additionResult']) ``` These are virtually free and can be enabled on-demand **in production** and available in the devtools. Under the hood devtools integration is just a wrapper around [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API). For clarity data keys not prefixed by `$` are omitted from the integration. Every key prefixed with `$` must be fully JSON serializable. <img width="977" height="799" alt="image" src="https://github.com/user-attachments/assets/b4d43506-1639-4f78-a611-30e61de12a07" /> |
||
|
|
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 |