mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 08:00:43 +00:00
Show rail ghost for initial factory 🚂 (#4294)
## Problem Fixes #4284. When you build a factory in an area with **no pre-existing factory** (e.g. just a city nearby), no rail ghost preview appeared — even though building the factory *would* lay rail lines connecting it to that city. ## Root cause `computeGhostRailPaths` in `RailNetworkImpl.ts` had two factory-hostile assumptions: 1. It bailed out early unless a `Factory` was already in range (`hasUnitNearby(..., UnitType.Factory)`). 2. It only matched neighbors that were *already* train stations (`findStation(...)` → skipped if null). But a **Factory** always becomes a station itself and *promotes* nearby City/Port/Factory into the rail network (see `FactoryExecution`). So it needs no pre-existing factory, and its neighbors won't be stations yet on first build. A **City/Port** only joins the network when a factory already exists (`CityExecution`/`PortExecution`) — so their behavior is correctly left unchanged. ## Fix - Skip the "factory must be nearby" gate when the placed unit is itself a `Factory`. - For a factory build, pathfind to nearby City/Port/Factory even if they aren't stations yet. City/Port keep connecting only to existing stations. ## Tests Added two cases to `RailNetwork.test.ts` (factory connects with no pre-existing factory; city still doesn't without one). All 25 tests pass. ## Note on scope As @Katokoda noted on the issue, a fully build-exact preview (neighboring structures also connecting to *each other*, merging existing networks, etc.) is larger and order-dependent. This PR resolves the reported bug — the initial factory now shows its rail ghost — and leaves the exact-match cascade as a separate follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -376,5 +376,36 @@ describe("RailNetworkImpl", () => {
|
||||
expect(pathService.findTilePath).toHaveBeenCalledTimes(1);
|
||||
expect(pathService.findTilePath).toHaveBeenCalledWith(tile, 100);
|
||||
});
|
||||
|
||||
test("factory connects to nearby structures with no pre-existing factory", () => {
|
||||
const tile = 42 as any;
|
||||
const railGridMock = { query: vi.fn(() => new Set()) };
|
||||
(network as any).railGrid = railGridMock;
|
||||
|
||||
// No factory in range, and the nearby city is not a station yet.
|
||||
game.hasUnitNearby.mockReturnValue(false);
|
||||
stationManager.findStation.mockReturnValue(null);
|
||||
|
||||
const cityUnit = { id: 1, tile: vi.fn(() => 100) };
|
||||
game.nearbyUnits.mockReturnValue([{ unit: cityUnit, distSquared: 400 }]);
|
||||
|
||||
const mockPath = [42, 50, 60, 100];
|
||||
pathService.findTilePath.mockReturnValue(mockPath);
|
||||
|
||||
const result = network.computeGhostRailPaths(UnitType.Factory, tile);
|
||||
expect(result).toEqual([mockPath]);
|
||||
expect(pathService.findTilePath).toHaveBeenCalledWith(tile, 100);
|
||||
});
|
||||
|
||||
test("city does not connect to non-station neighbors without a factory", () => {
|
||||
const tile = 42 as any;
|
||||
const railGridMock = { query: vi.fn(() => new Set()) };
|
||||
(network as any).railGrid = railGridMock;
|
||||
|
||||
game.hasUnitNearby.mockReturnValue(false);
|
||||
|
||||
const result = network.computeGhostRailPaths(UnitType.City, tile);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user