13using Cell = std::pair<int, int>;
14constexpr std::array<Cell, 4>
directions{{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}};
16std::optional<Cell> cellAt(WorldPosition
point,
const NavigationGrid& grid) {
17 const double x = (
static_cast<double>(
point.x) -
grid.originX) /
grid.cellSize;
18 const double y = (
static_cast<double>(
point.y) -
grid.originY) /
grid.cellSize;
19 constexpr double limit =
static_cast<double>(std::numeric_limits<int>::max()) - 4096.0;
20 if (!std::isfinite(
x) || !std::isfinite(
y) || std::abs(
x) >
limit || std::abs(
y) >
limit)
return std::nullopt;
21 return Cell{
static_cast<int>(std::lround(
x)),
static_cast<int>(std::lround(
y))};
24bool isNarrow(
const map::Pathfinder& pathfinder, Cell
cell) {
25 if (!pathfinder.isWalkable(
cell.first,
cell.second))
return false;
37Result<Corridor> discoverCorridor(
const map::Pathfinder& pathfinder, Cell
seed) {
39 result.cells.insert(
seed);
41 for (std::size_t i = 0; i <
pending.size(); ++i) {
43 int narrowNeighbors = 0;
46 if (!pathfinder.isWalkable(
next.first,
next.second))
continue;
47 if (!isNarrow(pathfinder, next)) {
48 result.exits.insert(next);
52 if (result.cells.contains(next))
continue;
53 if (result.cells.size() >= 1024)
56 "RTS corridor exceeds the 1024-cell reservation budget",
"traffic.corridor"));
57 result.cells.insert(next);
60 if (narrowNeighbors <= 1) result.exits.insert(
cell);
65std::optional<WorldPosition> evacuationStep(
const map::Pathfinder& pathfinder,
const NavigationGrid& grid,
66 const Corridor& corridor, Cell exit, Cell
current, WorldPosition
position,
67 float radius, std::set<Cell>& parking) {
68 if (corridor.cells.contains(exit))
return std::nullopt;
70 return WorldPosition{
grid.originX +
static_cast<float>(
cell.first) *
grid.cellSize,
71 grid.originY +
static_cast<float>(
cell.second) *
grid.cellSize};
73 std::optional<Cell> bay;
75 if (!corridor.cells.contains({exit.first - dx, exit.second - dy}))
continue;
78 for (
int sign : {-1, 1}) {
81 if (!parking.contains(candidate) && !corridor.cells.contains(candidate) &&
91 if (!bay)
return std::nullopt;
94 if (corridor.cells.contains(
current)) {
95 std::map<Cell, Cell> towardExit;
96 towardExit.emplace(exit, exit);
97 std::vector<Cell>
pending{exit};
98 for (std::size_t i = 0; i <
pending.size() && !towardExit.contains(
current); ++i) {
101 if (corridor.cells.contains(next) && towardExit.emplace(next,
pending[i]).second)
106 if (next == towardExit.end())
return std::nullopt;
120 if (!std::isfinite(grid.cellSize) || grid.cellSize <= 0.f || !std::isfinite(grid.originX) ||
121 !std::isfinite(grid.originY))
124 "RTS traffic grid requires finite origin and positive cell size",
"traffic.grid"));
136 std::map<Cell, Corridor> corridors;
137 std::map<Cell, Cell> owners;
138 std::map<Cell, std::vector<Candidate>> contenders;
139 std::vector<Unit::Navigation*> recovering;
140 std::size_t processed = 0;
142 ecs::View<Unit, Unit::Identity, Unit::Motion, Unit::Navigation, Unit::Orders, Unit::Containment, Unit::Crowd>();
143 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
144 auto [identity, motion, navigation, orders, containment, crowd] = *it;
148 navigation->trafficWaiting =
false;
149 if (navigation->trafficRecoveryTarget) recovering.push_back(navigation);
150 navigation->trafficRecoveryTarget.reset();
151 if (containment->container.isBound())
continue;
154 const auto&
order = currentOrder.value();
156 const bool hasPath = navigation->plannedOrderId ==
order->id;
157 const auto current = cellAt({motion->x, motion->y}, grid);
158 const auto goal = cellAt(hasPath ? navigation->plannedGoal :
order->target, grid);
161 "RTS traffic coordinates exceed the grid index range",
162 "traffic.position"));
164 std::optional<Cell>
seed;
166 const std::size_t
begin = hasPath ? navigation->waypointIndex : navigation->waypoints.size();
168 const auto cell = cellAt(navigation->waypoints[
index], grid);
172 auto owner = owners.find(*
seed);
173 if (owner == owners.end()) {
174 auto discovered = discoverCorridor(pathfinder, *
seed);
176 auto corridor = std::move(discovered).takeValue();
177 const Cell
key = *corridor.cells.begin();
178 for (
auto cell : corridor.cells) owners.emplace(
cell,
key);
179 corridors.emplace(
key, std::move(corridor));
180 owner = owners.find(*
seed);
182 const Cell
key = owner->second;
183 const auto& corridor = corridors.at(
key);
185 if (!corridor.exits.empty()) {
186 exit = *std::min_element(corridor.exits.begin(), corridor.exits.end(), [&](Cell
left, Cell
right) {
187 const auto distance = [&](Cell cell) {
188 return std::hypot(static_cast<double>(cell.first) - goal->first,
189 static_cast<double>(cell.second) - goal->second);
196 if (begin < navigation->waypoints.size()) {
197 const auto point = cellAt(navigation->waypoints[
begin], grid);
200 contenders[
key].push_back({navigation,
202 navigation->movementPriority,
206 corridor.cells.contains(*
current),
207 {motion->x, motion->y},
212 if (left.occupant != right.occupant) return left.occupant;
213 if (left.priority != right.priority) return left.priority > right.priority;
214 return left.subject.format() < right.subject.format();
217 std::set<Cell> reserved;
218 std::set<Cell> parking;
221 const bool narrowNext = isNarrow(pathfinder,
value.next);
222 value.navigation->trafficWaiting = !sameDirection || (narrowNext && !reserved.insert(
value.next).second);
224 value.navigation->trafficRecoveryTarget =
226 value.radius, parking);
229 for (
auto* navigation : recovering) {
230 if (navigation->trafficRecoveryTarget)
continue;
231 navigation->plannedOrderId.clear();
232 navigation->trafficWaiting =
true;
239 auto* currentTable = ecs::current();
240 for (
const auto&
handle : units_) {
241 if (
handle.table != currentTable)
continue;
243 if (!
unit || !
unit->navigation()->trafficRecoveryTarget)
continue;
244 unit->navigation()->trafficRecoveryTarget.reset();
245 unit->navigation()->trafficWaiting =
false;
246 unit->navigation()->plannedOrderId.clear();
248 pathfinder_ = pathfinder;
249 navigationGrid_ = grid;
250 navigationEvent_ = std::move(unreachable);
std::vector< QuestEvent > pending
std::map< std::string, Var > values
std::array< float, 3 > position
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
RTS module owner and phase-one composition profile entry point.
RoadLaneDirection direction
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Strong, domain-neutral reference to a runtime subject.
Pathfinding facade. Single-agent: A*. Group (same goal): Flow Field + follow. Grid/topology internals...
static Result< std::size_t > step(const map::Pathfinder &pathfinder, const NavigationGrid &grid)
Reserve corridor direction, then next narrow cells, by occupancy, priority and stable identity.
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
@ Cell
A cell was removed; the out-parameter holds it.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
constexpr uint32_t Corridor
Result< std::optional< OrderRecord > > readCurrent(OrderComponent &orders)
bool isMovementOrder(OrderKind kind)
EVENGINE_API_DOMAINS bool isFormationSegmentClear(const map::Pathfinder &pathfinder, const NavigationGrid &grid, WorldPosition from, WorldPosition to, float radius)
std::function< void(Unit &, const OrderRecord &)> NavigationEvent
Notification emitted once when a newly planned order has no canonical map route.
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
StatusCode
Stable outcome category for an operation.
World/grid conversion used when an RTS composition consumes a canonical map Pathfinder.
RTS projection of a canonical map path; the map provider remains the grid authority.
Two-dimensional deterministic RTS world position.