载入中...
搜索中...
未找到
RTSReplay.cpp
浏览该文件的文档.
1#include "rts/RTSReplay.h"
2
3#include "rts/RTS.h"
4
5#include <algorithm>
6#include <cmath>
7#include <iomanip>
8#include <limits>
9#include <locale>
10#include <sstream>
11
12namespace eve::rts {
13namespace {
14
15Result<void> replayFailure(std::string message, std::string path) {
18}
19
20SubjectRef parseSubject(const std::string& text) {
21 auto parsed = PersistentId::parse(text);
22 return parsed ? SubjectRef::fromPersistentId(*parsed) : SubjectRef{};
23}
24
25ecs::Entity* resolveTarget(RTS& world, SubjectRef subject) {
26 if (!subject.isValid()) return nullptr;
27 if (auto* unit = world.findUnit(subject)) return unit;
28 if (auto* building = world.findBuilding(subject)) return building;
29 return world.findResourceNode(subject);
30}
31
32bool normalizedLess(SubjectRef left, SubjectRef right) { return left.format() < right.format(); }
33
34} // namespace
35
37 if (command.tick.value() < currentTick.value())
38 return replayFailure("RTS replay command cannot target a past tick", "command.tick");
39 if (static_cast<unsigned>(command.operation) > static_cast<unsigned>(RTSReplayOperation::MovementGroup))
40 return replayFailure("RTS replay operation is invalid", "command.operation");
41 if (!std::isfinite(command.point.x) || !std::isfinite(command.point.y))
42 return replayFailure("RTS replay point must be finite", "command.point");
43 auto validFormation = command.formation.validate();
44 if (!validFormation) return validFormation;
45 if (!std::isfinite(command.groupLeadDistance) || command.groupLeadDistance <= 0.f)
46 return replayFailure("Movement-group lead distance must be finite and positive", "command.groupLeadDistance");
49 auto validCommand = command.command.validate();
50 if (!validCommand) return validCommand;
51 if (command.units.empty()) return replayFailure("RTS replay command requires units", "command.units");
52 if (ecs::try_get(command.command.targetEntity) != nullptr && !command.targetEntity.isValid())
53 return replayFailure("RTS replay entity targets require a stable SubjectRef", "command.targetEntity");
54 command.command.targetEntity = {};
55 std::sort(command.units.begin(), command.units.end(), normalizedLess);
56 command.units.erase(std::unique(command.units.begin(), command.units.end()), command.units.end());
58 (command.units.size() < 2 || command.command.kind != OrderKind::Move || command.command.append ||
59 command.command.priority != 0 || command.command.timeoutSeconds != 0.0 ||
60 !command.command.definitionId.empty() || command.targetEntity.isValid() || command.command.radius != 0.f ||
61 command.command.secondaryTarget.x != 0.f || command.command.secondaryTarget.y != 0.f))
62 return replayFailure("Movement-group replay requires two units, immediate Move and positive lead distance",
63 "command.group");
64 } else if (command.operation == RTSReplayOperation::Construction) {
65 if (command.units.size() != 1 || !command.faction.isValid() || !command.resultSubject.isValid() ||
66 !command.definition.isValid())
67 return replayFailure("RTS construction replay requires builder, faction, result, and definition",
68 "command.construction");
69 } else if (command.operation == RTSReplayOperation::Production ||
71 if (!command.producer.isValid() || !command.resultSubject.isValid() || !command.definition.isValid())
72 return replayFailure("RTS production replay requires producer, result, and definition",
73 "command.production");
74 } else if (command.operation == RTSReplayOperation::Research) {
75 if (!command.producer.isValid() || command.value.empty())
76 return replayFailure("RTS research replay requires producer and upgrade", "command.research");
77 } else if (command.operation == RTSReplayOperation::Ability &&
78 (command.units.size() != 1 || command.value.empty())) {
79 return replayFailure("RTS ability replay requires caster and ability", "command.ability");
80 } else if (command.operation == RTSReplayOperation::CancelProduction &&
81 (!command.producer.isValid() || command.priority < -1)) {
82 return replayFailure("RTS production cancellation requires producer and queue index",
83 "command.cancelProduction");
84 } else if (command.operation == RTSReplayOperation::CancelAbility && command.units.empty()) {
85 return replayFailure("RTS ability cancellation requires units", "command.cancelAbility");
88 !command.producer.isValid()) {
89 return replayFailure("RTS building lifecycle command requires a building", "command.producer");
91 (!command.producer.isValid() || !std::isfinite(command.command.radius) ||
92 command.command.radius <= 0.0f || command.priority <= 0)) {
93 return replayFailure("RTS fire-support request requires requester, radius, and shot budget",
94 "command.fireSupport");
95 } else if (command.operation == RTSReplayOperation::CancelFireSupport && !command.producer.isValid()) {
96 return replayFailure("RTS fire-support cancellation requires a requester", "command.fireSupport");
97 } else if (command.operation == RTSReplayOperation::SuppressArea &&
98 (command.units.empty() || command.priority < 0 || !std::isfinite(command.command.target.x) ||
99 !std::isfinite(command.command.target.y) || !std::isfinite(command.command.secondaryTarget.x) ||
100 !std::isfinite(command.command.secondaryTarget.y) || !std::isfinite(command.command.radius) ||
101 command.command.radius <= 0.0f ||
102 std::hypot(command.command.secondaryTarget.x - command.command.target.x,
103 command.command.secondaryTarget.y - command.command.target.y) <= 1e-3f)) {
104 return replayFailure("RTS suppression requires units, corridor, width, and non-negative shots",
105 "command.suppression");
106 } else if (command.operation == RTSReplayOperation::Escort &&
107 (command.units.empty() || !command.targetEntity.isValid() || !std::isfinite(command.command.radius) ||
108 command.command.radius <= 0.0f || !std::isfinite(command.formation.spacing) ||
109 command.formation.spacing <= 0.0f)) {
110 return replayFailure("RTS escort requires units, target, guard radius, and spacing", "command.escort");
111 }
115 std::sort(command.units.begin(), command.units.end(), normalizedLess);
116 command.units.erase(std::unique(command.units.begin(), command.units.end()), command.units.end());
117 }
118 if (std::any_of(command.units.begin(), command.units.end(), [](SubjectRef value) { return !value.isValid(); }))
119 return replayFailure("RTS replay unit subjects must be valid", "command.units");
120 queued_[command.tick.value()].push_back(command);
121 history_.push_back(std::move(command));
123}
124
126 auto found = queued_.find(tick.value());
127 if (found == queued_.end()) return Result<std::size_t>::success(0, Status::success(StatusCode::NoOp));
128 std::size_t applied = 0;
129 for (const auto& replay : found->second) {
130 if (replay.operation == RTSReplayOperation::Construction) {
131 auto* faction = world.findFaction(replay.faction);
132 auto* builder = world.findUnit(replay.units.front());
133 if (faction == nullptr || builder == nullptr)
135 DiagnosticCode::NotFound, "RTS replay construction actor cannot be resolved", "command.construction"));
136 auto result = world.startScriptConstruction(*faction, replay.resultSubject, replay.definition,
137 replay.point, *builder);
138 if (!result) return Result<std::size_t>::failure(result.status());
139 ++applied;
140 continue;
141 }
142 if (replay.operation == RTSReplayOperation::Production ||
144 replay.operation == RTSReplayOperation::Research) {
145 auto* producer = world.findBuilding(replay.producer);
146 if (producer == nullptr)
148 DiagnosticCode::NotFound, "RTS replay producer cannot be resolved", "command.producer"));
149 if (replay.operation == RTSReplayOperation::Research) {
150 auto result = world.queueScriptResearch(*producer, replay.value, replay.priority);
151 if (!result) return Result<std::size_t>::failure(result.status());
152 } else if (replay.operation == RTSReplayOperation::ReinforcementProduction) {
153 auto result = world.queueScriptReinforcement(
154 *producer, replay.resultSubject, replay.definition, replay.priority);
155 if (!result) return Result<std::size_t>::failure(result.status());
156 } else {
157 auto result = world.queueScriptUnit(
158 *producer, replay.resultSubject, replay.definition, replay.priority);
159 if (!result) return Result<std::size_t>::failure(result.status());
160 }
161 ++applied;
162 continue;
163 }
164 if (replay.operation == RTSReplayOperation::Ability) {
165 auto* caster = world.findUnit(replay.units.front());
166 if (caster == nullptr)
168 DiagnosticCode::NotFound, "RTS replay ability caster cannot be resolved", "command.units"));
169 auto result = world.castScriptAbility(*caster, replay.value, replay.targetEntity, replay.point);
170 if (!result) return Result<std::size_t>::failure(result.status());
171 ++applied;
172 continue;
173 }
174 if (replay.operation == RTSReplayOperation::CancelProduction) {
175 auto* producer = world.findBuilding(replay.producer);
176 if (producer == nullptr)
178 DiagnosticCode::NotFound, "RTS replay producer cannot be resolved", "command.producer"));
179 auto result = world.cancelScriptProduction(*producer, replay.priority);
180 if (!result) return Result<std::size_t>::failure(result.status());
181 ++applied;
182 continue;
183 }
184 if (replay.operation == RTSReplayOperation::CancelAbility) {
185 for (SubjectRef subject : replay.units) {
186 auto* caster = world.findUnit(subject);
187 if (caster == nullptr)
189 DiagnosticCode::NotFound, "RTS replay ability caster cannot be resolved", "command.units"));
190 auto result = world.cancelScriptAbility(*caster);
191 if (!result) return Result<std::size_t>::failure(result.status());
192 ++applied;
193 }
194 continue;
195 }
196 if (replay.operation == RTSReplayOperation::CancelConstruction ||
197 replay.operation == RTSReplayOperation::SellBuilding) {
198 auto* building = world.findBuilding(replay.producer);
199 if (building == nullptr)
201 DiagnosticCode::NotFound, "RTS replay building cannot be resolved", "command.producer"));
202 auto result = replay.operation == RTSReplayOperation::CancelConstruction
203 ? world.cancelScriptConstruction(*building)
204 : world.sellScriptBuilding(*building);
205 if (!result) return Result<std::size_t>::failure(result.status());
206 ++applied;
207 continue;
208 }
209 if (replay.operation == RTSReplayOperation::RequestFireSupport ||
210 replay.operation == RTSReplayOperation::CancelFireSupport) {
211 auto* requester = world.findUnit(replay.producer);
212 if (requester == nullptr)
214 DiagnosticCode::NotFound, "RTS fire-support requester cannot be resolved",
215 "command.producer"));
216 auto result = replay.operation == RTSReplayOperation::RequestFireSupport
217 ? world.requestFireSupport(*requester, replay.point, replay.command.radius,
218 replay.priority, replay.limit)
219 : world.cancelFireSupport(*requester);
220 if (!result) return Result<std::size_t>::failure(result.status());
221 applied += result.value();
222 continue;
223 }
224 if (replay.operation == RTSReplayOperation::SuppressArea) {
225 auto result = world.suppressArea(replay.units, replay.command.target,
226 replay.command.secondaryTarget,
227 replay.command.radius, replay.priority);
228 if (!result) return Result<std::size_t>::failure(result.status());
229 applied += result.value().accepted;
230 continue;
231 }
232 if (replay.operation == RTSReplayOperation::Escort) {
233 auto result = world.escortUnits(replay.units, replay.targetEntity,
234 replay.command.radius, replay.formation.spacing);
235 if (!result) return Result<std::size_t>::failure(result.status());
236 applied += result.value().accepted;
237 continue;
238 }
239 if (replay.operation == RTSReplayOperation::MovementGroup) {
240 MovementGroupBatch batch;
241 batch.units = replay.units;
242 batch.target = replay.command.target;
243 batch.formation = replay.formation;
244 batch.leadDistance = replay.groupLeadDistance;
245 auto receipt = world.submitMovementGroup(batch);
246 if (!receipt) return Result<std::size_t>::failure(receipt.status());
247 applied += receipt.value().accepted;
248 continue;
249 }
250 std::vector<ecs::EntityHandle> handles;
251 handles.reserve(replay.units.size());
252 for (SubjectRef subject : replay.units) {
253 auto* unit = world.findUnit(subject);
254 if (unit == nullptr)
256 DiagnosticCode::NotFound, "RTS replay unit cannot be resolved", "command.units"));
257 handles.push_back(ecs::handle_of(unit));
258 }
259 auto command = replay.command;
260 if (replay.targetEntity.isValid()) {
261 auto* target = resolveTarget(world, replay.targetEntity);
262 if (target == nullptr)
264 DiagnosticCode::NotFound, "RTS replay target cannot be resolved", "command.targetEntity"));
265 command.targetEntity = ecs::handle_of(target);
266 }
267 auto receipt = CommandFanOutSystem::fanOut(handles, command, replay.formation);
268 if (!receipt) return Result<std::size_t>::failure(receipt.status());
269 applied += receipt.value().accepted;
270 }
271 queued_.erase(found);
273}
274
275std::string RTSCommandLog::exportText() const {
276 std::ostringstream out;
277 out.imbue(std::locale::classic());
278 const bool grouped = std::any_of(history_.begin(), history_.end(), [](const auto& value) {
279 return value.operation == RTSReplayOperation::MovementGroup || value.groupLeadDistance != 2.f;
280 });
281 const bool oriented =
282 grouped || std::any_of(history_.begin(), history_.end(), [](const auto& value) {
283 return value.formation.rotationRadians != 0.f || value.formation.kind > FormationKind::Wedge;
284 });
285 const bool extended = oriented || std::any_of(history_.begin(), history_.end(), [](const auto& value) {
286 return value.operation != RTSReplayOperation::UnitCommand;
287 });
288 const bool fireSupport = std::any_of(history_.begin(), history_.end(), [](const auto& value) {
289 return value.operation == RTSReplayOperation::RequestFireSupport ||
290 value.operation == RTSReplayOperation::CancelFireSupport;
291 });
292 const unsigned version = grouped ? 5u : (oriented ? 4u : (fireSupport ? 3u : (extended ? 2u : 1u)));
293 out << "EVERTS_COMMANDS " << version << '\n'
294 << std::setprecision(std::numeric_limits<double>::max_digits10);
295 for (const auto& value : history_) {
296 if (extended) {
297 out << value.tick.value() << ' ' << static_cast<unsigned>(value.operation) << ' '
298 << std::quoted(value.producer.isValid() ? value.producer.format() : std::string{}) << ' '
299 << std::quoted(value.faction.isValid() ? value.faction.format() : std::string{}) << ' '
300 << std::quoted(value.resultSubject.isValid() ? value.resultSubject.format() : std::string{}) << ' '
301 << std::quoted(value.definition.isValid() ? value.definition.format() : std::string{}) << ' '
302 << std::quoted(value.value) << ' ' << value.priority << ' ';
303 if (version >= 3) out << value.limit << ' ';
304 out << value.point.x << ' ' << value.point.y << ' ';
305 }
306 out << value.tick.value() << ' ' << static_cast<unsigned>(value.command.kind) << ' '
307 << std::quoted(value.command.definitionId) << ' ' << value.command.priority << ' '
308 << value.command.timeoutSeconds << ' ' << value.command.target.x << ' ' << value.command.target.y << ' '
309 << value.command.secondaryTarget.x << ' ' << value.command.secondaryTarget.y << ' ' << value.command.radius
310 << ' ' << value.command.append << ' ' << static_cast<unsigned>(value.formation.kind) << ' '
311 << value.formation.spacing << ' ' << value.formation.columns << ' ';
312 if (version >= 4) out << value.formation.rotationRadians << ' ';
313 if (version >= 5) out << value.groupLeadDistance << ' ';
314 out << std::quoted(value.targetEntity.isValid() ? value.targetEntity.format() : std::string{}) << ' '
315 << value.units.size();
316 for (SubjectRef subject : value.units) out << ' ' << std::quoted(subject.format());
317 out << '\n';
318 }
319 return out.str();
320}
321
322Result<void> RTSCommandLog::importText(std::string_view text, SimulationTick currentTick, bool clearExisting) {
323 std::istringstream in{std::string(text)};
324 in.imbue(std::locale::classic());
325 std::string magic;
326 unsigned version = 0;
327 if (!(in >> magic >> version) || magic != "EVERTS_COMMANDS" || (version < 1 || version > 5))
328 return replayFailure("invalid RTS command log header", "commandLog.header");
329 std::vector<RTSReplayCommand> parsed;
330 while (in >> std::ws && !in.eof()) {
332 std::uint64_t tick = 0;
333 if (version >= 2) {
334 unsigned operation = 0;
335 std::string producer, faction, result, definition;
336 if (!(in >> tick >> operation >> std::quoted(producer) >> std::quoted(faction) >> std::quoted(result) >>
337 std::quoted(definition) >> std::quoted(value.value) >> value.priority) ||
338 operation > static_cast<unsigned>(version >= 5 ? RTSReplayOperation::MovementGroup
340 return replayFailure("malformed extended RTS command log entry", "commandLog.entry");
341 if (version >= 3 && !(in >> value.limit))
342 return replayFailure("malformed RTS command limit", "commandLog.entry");
343 if (!(in >> value.point.x >> value.point.y))
344 return replayFailure("malformed RTS command point", "commandLog.entry");
345 value.operation = static_cast<RTSReplayOperation>(operation);
346 const auto decode = [](const std::string& text) { return text.empty() ? SubjectRef{} : parseSubject(text); };
347 value.producer = decode(producer);
348 value.faction = decode(faction);
349 value.resultSubject = decode(result);
350 if ((!producer.empty() && !value.producer.isValid()) || (!faction.empty() && !value.faction.isValid()) ||
351 (!result.empty() && !value.resultSubject.isValid()))
352 return replayFailure("invalid extended RTS command subject", "commandLog.entry");
353 if (!definition.empty()) {
354 auto parsedDefinition = LogicalId::parse(definition);
355 if (!parsedDefinition) return replayFailure("invalid RTS command definition", "commandLog.definition");
356 value.definition = *parsedDefinition;
357 }
358 }
359 unsigned kind = 0, formation = 0;
360 std::string target;
361 std::size_t unitCount = 0;
362 std::uint64_t commandTick = 0;
363 if (!(in >> commandTick >> kind >> std::quoted(value.command.definitionId) >> value.command.priority >>
364 value.command.timeoutSeconds >> value.command.target.x >> value.command.target.y >>
365 value.command.secondaryTarget.x >> value.command.secondaryTarget.y >> value.command.radius >>
366 value.command.append >> formation >> value.formation.spacing >> value.formation.columns))
367 return replayFailure("malformed RTS formation", "commandLog.entry");
368 if (version >= 4 && !(in >> value.formation.rotationRadians))
369 return replayFailure("malformed RTS formation rotation", "commandLog.entry");
370 if (version >= 5 && !(in >> value.groupLeadDistance))
371 return replayFailure("malformed RTS movement-group lead distance", "commandLog.entry");
372 if (!(in >> std::quoted(target) >> unitCount) || kind > static_cast<unsigned>(OrderKind::SupplyRelay) ||
373 formation > static_cast<unsigned>(version >= 4 ? FormationKind::Dispersed : FormationKind::Wedge) ||
374 unitCount > 100000)
375 return replayFailure("malformed RTS command log entry", "commandLog.entry");
376 if (version >= 2 && commandTick != tick)
377 return replayFailure("extended RTS command tick mismatch", "commandLog.tick");
378 tick = commandTick;
379 value.tick = SimulationTick{tick};
380 value.command.kind = static_cast<OrderKind>(kind);
381 value.formation.kind = static_cast<FormationKind>(formation);
382 if (!target.empty()) {
383 value.targetEntity = parseSubject(target);
384 if (!value.targetEntity.isValid()) return replayFailure("invalid target subject", "commandLog.target");
385 }
386 value.units.reserve(unitCount);
387 for (std::size_t index = 0; index < unitCount; ++index) {
388 std::string unit;
389 if (!(in >> std::quoted(unit))) return replayFailure("missing unit subject", "commandLog.units");
390 auto subject = parseSubject(unit);
391 if (!subject.isValid()) return replayFailure("invalid unit subject", "commandLog.units");
392 value.units.push_back(subject);
393 }
394 RTSCommandLog validator;
395 auto valid = validator.queue(value, currentTick);
396 if (!valid) return valid;
397 parsed.push_back(std::move(value));
398 }
399 if (clearExisting) clear();
400 for (auto& value : parsed) {
401 auto accepted = queue(std::move(value), currentTick);
402 if (!accepted) return accepted;
403 }
405}
406
407void RTSCommandLog::clear() noexcept {
408 queued_.clear();
409 history_.clear();
410}
411
413 auto duration = Duration::fromSeconds(1.0 / 60.0);
414 if (duration) fixedStep_ = duration.value();
415}
416
418 if (value.nanoseconds() <= 0) return replayFailure("RTS lockstep interval must be strictly positive", "fixedStep");
419 fixedStep_ = value;
421}
422
425 if (fixedStep_.nanoseconds() <= 0)
427 DiagnosticCode::InvariantViolation, "RTS lockstep has no valid fixed interval", "fixedStep"));
428 std::size_t processed = 0;
429 for (std::uint64_t index = 0; index < count; ++index) {
430 const SimulationTick next{tick_.value() + 1};
431 auto applied = commands_.apply(next, world);
432 if (!applied) return Result<std::size_t>::failure(applied.status());
433 auto stepped = world.step({next, fixedStep_}, executor);
434 if (!stepped) return Result<std::size_t>::failure(stepped.status());
435 processed += applied.value() + stepped.value();
436 tick_ = next;
437 }
439}
440
441void RTSLockstep::reset(SimulationTick tick, bool clearCommands) noexcept {
442 tick_ = tick;
443 if (clearCommands) commands_.clear();
444}
445
446} // namespace eve::rts
LogicalId target
ActionParameterOperation operation
double value
float duration
int subject
Definition AnimSmr.cpp:163
std::string message
HexVec3 left
HexVec3 right
std::string text
TokenKind kind
bool valid
World3D * world
std::string path
Definition PlayHost.cpp:110
Stable tick-addressed RTS command recording and replay.
RTS module owner and phase-one composition profile entry point.
bool found
std::uint32_t count
TacticalUnit * unit
SimulationTick tick
uint32_t index
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.
Definition Diagnostic.h:125
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
constexpr std::int64_t nanoseconds() const noexcept
Return the exact signed nanosecond representation.
Definition Time.h:69
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
bool isValid() const noexcept
Returns whether this value contains a valid namespace and name.
Definition Identity.h:433
void value() const
Mark a successful void operation as checked.
Definition Result.h:526
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Strong, domain-neutral reference to a runtime subject.
Definition SubjectRef.h:26
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
std::string format() const
Return the canonical lower-case UUID spelling.
Definition SubjectRef.h:44
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
static Result< FanOutReceipt > fanOut(std::span< const ecs::EntityHandle > unitHandles, const CommandSpec &command, const FormationSpec &formation)
Submit one command to every selected live Unit.
Interface isolating RTS orders from the shared action runtime.
Definition RTSAction.h:38
Deterministic lockstep input buffer and portable command log.
Definition RTSReplay.h:63
Result< void > importText(std::string_view text, SimulationTick currentTick={}, bool clearExisting=true)
Parse and atomically import a command log.
Result< std::size_t > apply(SimulationTick tick, RTS &world)
Apply all commands for one tick through the existing RTS order boundary.
void clear() noexcept
Remove queued and retained commands.
Result< void > queue(RTSReplayCommand command, SimulationTick currentTick={})
Validate, normalize, queue, and retain one future command.
Definition RTSReplay.cpp:36
std::string exportText() const
Export retained commands in a locale-independent text format.
Result< void > setFixedStep(Duration value)
Set a finite, strictly positive fixed interval.
Result< std::size_t > step(RTS &world, IRTSActionExecutor &executor, std::uint64_t count=1)
Advance an exact number of deterministic ticks.
void reset(SimulationTick tick={}, bool clearCommands=true) noexcept
Reset the timeline and optionally retain the command history/queue.
RTSLockstep()
Construct with a 60 Hz fixed simulation interval.
Owns RTS root entities and coordinates the phase-one systems.
Definition RTS.h:63
OrderKind
Commands shared by movement, combat, construction and gathering.
Definition RTSTypes.h:64
RTSReplayOperation
Stable operation kinds accepted by the shared deterministic RTS input stream.
Definition RTSReplay.h:20
FormationKind
Deterministic formation layout strategies.
Definition RTSSystems.h:25
::eve::SubjectRef SubjectRef
Strong reference to a runtime subject.
Definition Targeting.h:76
std::string definitionId
Definition RTSTypes.h:104
ecs::EntityHandle targetEntity
Definition RTSTypes.h:107
Result< void > validate() const
Validate coordinates and command metadata without mutating state.
Definition RTSTypes.cpp:201
WorldPosition target
Definition RTSTypes.h:103
WorldPosition secondaryTarget
Definition RTSTypes.h:108
EVENGINE_API_DOMAINS Result< void > validate() const
Validate layout kind, spacing, grid columns and finite rotation.
Caller-built admission batch for an immediate coordinated Move command. @cost Linear in unit count; c...
Definition RTSSystems.h:48
float leadDistance
Positive world-space allowance before leaders slow for lagging members.
Definition RTSSystems.h:52
std::vector< SubjectRef > units
Owned stable identities; no entity pointers are retained.
Definition RTSSystems.h:49
One process-independent command using stable subjects instead of ECS handles.
Definition RTSReplay.h:39
std::vector< SubjectRef > units
Definition RTSReplay.h:42
RTSReplayOperation operation
Definition RTSReplay.h:41
FormationSpec formation
Definition RTSReplay.h:45