载入中...
搜索中...
未找到
CrowdDocument.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <set>
6#include <utility>
7
9namespace {
10
11const EditorValue* field(const EditorValue& value, const char* name) {
12 const auto* object = value.getIf<EditorValue::Object>();
13 if (!object) return nullptr;
14 const auto found = object->find(name);
15 return found == object->end() ? nullptr : &found->second;
16}
17
18EditorValue pointValue(const CrowdWaypointRecord& point) {
19 return EditorValue::Object{{"id", point.id.value()}, {"x", point.x}, {"y", point.y},
20 {"arriveRadius", point.arriveRadius}, {"waitSeconds", point.waitSeconds}};
21}
22
24 const auto string = [&](const char* key) -> const std::string* {
25 const auto* entry = field(value, key); return entry ? entry->getIf<std::string>() : nullptr;
26 };
27 const auto number = [&](const char* key) -> const double* {
28 const auto* entry = field(value, key); return entry ? entry->getIf<double>() : nullptr;
29 };
30 const auto* id = string("id"); const auto* x = number("x"); const auto* y = number("y");
31 const auto* radius = number("arriveRadius"); const auto* wait = number("waitSeconds");
32 if (!id || id->empty() || !x || !y || !radius || !wait || !std::isfinite(*x) || !std::isfinite(*y) ||
33 !std::isfinite(*radius) || !std::isfinite(*wait) || *radius <= 0.0 || *wait < 0.0)
34 return eve::editing::failed<CrowdWaypointRecord>(
35 EditorStatus::Rejected, RuleId("editor.crowd.invalid-waypoint"),
36 "Waypoint requires finite coordinates, positive radius and nonnegative wait");
37 return eve::editing::applied<CrowdWaypointRecord>({StableId(*id), *x, *y, *radius, *wait});
38}
39
40EditorValue pathValue(const CrowdPathRecord& path) {
42 for (const auto& point : path.points) points.push_back(pointValue(point));
43 return EditorValue::Object{{"id", path.id.value()}, {"name", path.name}, {"loop", path.loop},
44 {"points", std::move(points)}};
45}
46
48 const auto* idEntry = field(value, "id"); const auto* nameEntry = field(value, "name");
49 const auto* loopEntry = field(value, "loop"); const auto* pointsEntry = field(value, "points");
50 const auto* id = idEntry ? idEntry->getIf<std::string>() : nullptr;
51 const auto* name = nameEntry ? nameEntry->getIf<std::string>() : nullptr;
52 const auto* loop = loopEntry ? loopEntry->getIf<bool>() : nullptr;
53 const auto* points = pointsEntry ? pointsEntry->getIf<EditorValue::Array>() : nullptr;
54 if (!id || id->empty() || !name || name->empty() || !loop || !points || points->empty())
55 return eve::editing::failed<CrowdPathRecord>(EditorStatus::Rejected, RuleId("editor.crowd.invalid-path"),
56 "Path requires identity, name and at least one waypoint");
57 CrowdPathRecord result{StableId(*id), *name, *loop, {}};
58 std::set<StableId> ids;
59 for (const auto& entry : *points) {
60 auto point = parsePoint(entry);
61 if (!point.ok() || !ids.insert(point.value().id).second)
62 return eve::editing::failed<CrowdPathRecord>(EditorStatus::Rejected,
63 RuleId("editor.crowd.duplicate-waypoint"),
64 "Path waypoint ids must be unique and valid");
65 result.points.push_back(std::move(point).value());
66 }
67 if (result.loop && result.points.size() < 2)
68 return eve::editing::failed<CrowdPathRecord>(EditorStatus::Rejected, RuleId("editor.crowd.loop-point-count"),
69 "Looping paths require at least two waypoints");
70 return eve::editing::applied<CrowdPathRecord>(std::move(result));
71}
72
73EditorValue agentValue(const CrowdAgentRecord& agent) {
74 return EditorValue::Object{{"id", agent.id.value()}, {"archetype", agent.archetype}, {"x", agent.x},
75 {"y", agent.y}, {"heading", agent.heading}, {"radius", agent.radius},
76 {"maximumSpeed", agent.maximumSpeed}, {"behavior", agent.behavior}, {"path", agent.path.value()}};
77}
78
80 const auto string = [&](const char* key) -> const std::string* { const auto* v = field(value, key); return v ? v->getIf<std::string>() : nullptr; };
81 const auto number = [&](const char* key) -> const double* { const auto* v = field(value, key); return v ? v->getIf<double>() : nullptr; };
82 const auto* id = string("id"); const auto* archetype = string("archetype"); const auto* behavior = string("behavior");
83 const auto* path = string("path"); const auto* x = number("x"); const auto* y = number("y");
84 const auto* heading = number("heading"); const auto* radius = number("radius"); const auto* speed = number("maximumSpeed");
85 static const std::set<std::string> behaviors{"idle", "flow", "seek", "boids", "path"};
86 if (!id || id->empty() || !archetype || !behavior || !behaviors.contains(*behavior) || !path || !x || !y ||
87 !heading || !radius || !speed || *radius <= 0.0 || *speed < 0.0)
88 return eve::editing::failed<CrowdAgentRecord>(
89 EditorStatus::Rejected, RuleId("editor.crowd.invalid-agent"),
90 "Agent requires identity, supported behavior and valid movement limits");
91 for (const double* component : {x, y, heading, radius, speed})
92 if (!std::isfinite(*component))
93 return eve::editing::failed<CrowdAgentRecord>(EditorStatus::Rejected,
94 RuleId("editor.crowd.nonfinite-agent"),
95 "Agent numeric properties must be finite");
96 if (*behavior == "path" && path->empty())
97 return eve::editing::failed<CrowdAgentRecord>(EditorStatus::Rejected, RuleId("editor.crowd.missing-agent-path"),
98 "Path behavior requires a path reference");
99 return eve::editing::applied<CrowdAgentRecord>({StableId(*id), *archetype, *x, *y, *heading,
100 *radius, *speed, *behavior, StableId(*path)});
101}
102
103EditorValue zoneValue(const CrowdZoneRecord& zone) {
105 for (const auto& point : zone.points) points.push_back(EditorValue::Array{point[0], point[1]});
106 return EditorValue::Object{{"id", zone.id.value()}, {"name", zone.name}, {"kind", zone.kind},
107 {"points", std::move(points)}, {"weight", zone.weight}, {"enabled", zone.enabled}};
108}
109
110Result<CrowdZoneRecord> parseZone(const EditorValue& value) {
111 const auto* idEntry = field(value, "id"); const auto* nameEntry = field(value, "name");
112 const auto* kindEntry = field(value, "kind"); const auto* pointsEntry = field(value, "points");
113 const auto* weightEntry = field(value, "weight"); const auto* enabledEntry = field(value, "enabled");
114 const auto* id = idEntry ? idEntry->getIf<std::string>() : nullptr;
115 const auto* name = nameEntry ? nameEntry->getIf<std::string>() : nullptr;
116 const auto* kind = kindEntry ? kindEntry->getIf<std::string>() : nullptr;
117 const auto* points = pointsEntry ? pointsEntry->getIf<EditorValue::Array>() : nullptr;
118 const auto* weight = weightEntry ? weightEntry->getIf<double>() : nullptr;
119 const auto* enabled = enabledEntry ? enabledEntry->getIf<bool>() : nullptr;
120 static const std::set<std::string> kinds{"avoid", "slow", "goal", "sense", "spawn"};
121 if (!id || id->empty() || !name || name->empty() || !kind || !kinds.contains(*kind) || !points ||
122 points->size() < 3 || !weight || !std::isfinite(*weight) || *weight < 0.0 || !enabled)
123 return eve::editing::failed<CrowdZoneRecord>(
124 EditorStatus::Rejected, RuleId("editor.crowd.invalid-zone"),
125 "Zone requires identity, supported kind, weight and at least three points");
126 CrowdZoneRecord result{StableId(*id), *name, *kind, {}, *weight, *enabled};
127 for (const auto& pointEntry : *points) {
128 const auto* point = pointEntry.getIf<EditorValue::Array>();
129 const auto* x = point && point->size() == 2 ? (*point)[0].getIf<double>() : nullptr;
130 const auto* y = point && point->size() == 2 ? (*point)[1].getIf<double>() : nullptr;
131 if (!x || !y || !std::isfinite(*x) || !std::isfinite(*y))
132 return eve::editing::failed<CrowdZoneRecord>(EditorStatus::Rejected,
133 RuleId("editor.crowd.invalid-zone-point"),
134 "Zone vertices must contain two finite coordinates");
135 result.points.push_back({*x, *y});
136 }
137 return eve::editing::applied<CrowdZoneRecord>(std::move(result));
138}
139
140DomainOperation operation(const char* type, const char* inverseType, const std::string& target,
141 EditorValue payload, EditorValue inverse, const StableId& affected) {
142 DomainOperation result;
143 result.type = type; result.inverseType = inverseType; result.target = TargetId(target);
144 result.payload = std::move(payload); result.inverse = std::move(inverse); result.hasInverse = true;
145 result.affectedObjects.push_back({TargetId(target), affected.value(), 0}); return result;
146}
147
148template <class Record>
149std::vector<Record> values(const std::map<StableId, Record>& records) {
150 std::vector<Record> result; result.reserve(records.size());
151 for (const auto& [id, record] : records) { (void)id; result.push_back(record); }
152 return result;
153}
154
155} // namespace
156
157CrowdDocumentTarget::CrowdDocumentTarget(std::string id) : id_(std::move(id)) {}
158
160 TargetDescriptor result; result.id = TargetId(id_); result.type = "crowd-document";
161 result.revision = revisionValue();
162 result.capabilities = {editorCapabilityId()};
163 return result;
164}
165
167 return capability == editorCapabilityId() ? static_cast<CrowdDocumentTarget*>(this) : nullptr;
168}
169
171 if (op.target != TargetId(id_))
172 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.wrong-target"),
173 "Operation targets another crowd document");
174 auto apply = [&](const std::string& type, const EditorValue& payload) -> Result<void> {
175 if (type == "crowd.agent.set.v1") { auto value = parseAgent(payload); if (!value.ok()) return Result<void>::failure(value.status()); agents_.insert_or_assign(value.value().id, value.value()); }
176 else if (type == "crowd.zone.set.v1") { auto value = parseZone(payload); if (!value.ok()) return Result<void>::failure(value.status()); zones_.insert_or_assign(value.value().id, value.value()); }
177 else if (type == "crowd.path.set.v1") { auto value = parsePath(payload); if (!value.ok()) return Result<void>::failure(value.status()); paths_.insert_or_assign(value.value().id, value.value()); }
178 else if (type == "crowd.agent.delete.v1" || type == "crowd.zone.delete.v1" || type == "crowd.path.delete.v1") {
179 const auto* id = payload.getIf<std::string>();
180 if (!id || id->empty())
181 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.invalid-delete"),
182 "Delete payload requires a stable id");
183 const StableId stable(*id); std::size_t erased = type[6] == 'a' ? agents_.erase(stable) : (type[6] == 'z' ? zones_.erase(stable) : paths_.erase(stable));
184 if (!erased)
185 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.crowd.object-not-found"),
186 "Crowd object was not found");
187 } else
188 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.unsupported-operation"),
189 "Unsupported crowd document operation");
190 bumpRevision();
191 widenDirty(0, 0);
192 return eve::editing::applied<void>();
193 };
194 return apply(op.type, op.payload);
195}
196
197std::unique_ptr<IDomainOperationTarget> CrowdDocumentTarget::cloneDomainState() const { return std::make_unique<CrowdDocumentTarget>(*this); }
198Result<void> CrowdDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate) {
199 auto* typed = dynamic_cast<CrowdDocumentTarget*>(candidate.get());
200 if (!typed || typed->id_ != id_)
201 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.invalid-staging-state"),
202 "Staged state belongs to another target type or id");
203 *this = *typed; return eve::editing::applied<void>();
204}
205
206std::vector<CrowdAgentRecord> CrowdDocumentTarget::agents() const { return values(agents_); }
207std::vector<CrowdZoneRecord> CrowdDocumentTarget::zones() const { return values(zones_); }
208std::vector<CrowdPathRecord> CrowdDocumentTarget::paths() const { return values(paths_); }
209
211 auto parsed = parseAgent(agentValue(record));
212 if (!parsed.ok())
213 return eve::editing::failed<DomainOperation>(parsed.code(), RuleId("editor.crowd.invalid-agent"),
214 "Cannot plan invalid agent");
215 const auto found = agents_.find(record.id); return eve::editing::applied<DomainOperation>(operation("crowd.agent.set.v1", found == agents_.end() ? "crowd.agent.delete.v1" : "crowd.agent.set.v1", id_, agentValue(record), found == agents_.end() ? EditorValue(record.id.value()) : agentValue(found->second), record.id));
216}
218 const auto found = agents_.find(id);
219 if (found == agents_.end())
220 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.crowd.agent-not-found"),
221 "Agent was not found");
222 return eve::editing::applied<DomainOperation>(
223 operation("crowd.agent.delete.v1", "crowd.agent.set.v1", id_, id.value(), agentValue(found->second), id));
224}
226 auto parsed = parseZone(zoneValue(record));
227 if (!parsed.ok())
228 return eve::editing::failed<DomainOperation>(parsed.code(), RuleId("editor.crowd.invalid-zone"),
229 "Cannot plan invalid zone");
230 const auto found = zones_.find(record.id);
231 return eve::editing::applied<DomainOperation>(
232 operation("crowd.zone.set.v1", found == zones_.end() ? "crowd.zone.delete.v1" : "crowd.zone.set.v1", id_,
233 zoneValue(record), found == zones_.end() ? EditorValue(record.id.value()) : zoneValue(found->second),
234 record.id));
235}
237 const auto found = zones_.find(id);
238 if (found == zones_.end())
239 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.crowd.zone-not-found"),
240 "Zone was not found");
241 return eve::editing::applied<DomainOperation>(
242 operation("crowd.zone.delete.v1", "crowd.zone.set.v1", id_, id.value(), zoneValue(found->second), id));
243}
245 auto parsed = parsePath(pathValue(record));
246 if (!parsed.ok())
247 return eve::editing::failed<DomainOperation>(parsed.code(), RuleId("editor.crowd.invalid-path"),
248 "Cannot plan invalid path");
249 const auto found = paths_.find(record.id);
250 return eve::editing::applied<DomainOperation>(
251 operation("crowd.path.set.v1", found == paths_.end() ? "crowd.path.delete.v1" : "crowd.path.set.v1", id_,
252 pathValue(record), found == paths_.end() ? EditorValue(record.id.value()) : pathValue(found->second),
253 record.id));
254}
256 const auto found = paths_.find(id);
257 if (found == paths_.end())
258 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.crowd.path-not-found"),
259 "Path was not found");
260 for (const auto& [agentId, agent] : agents_) {
261 (void)agentId;
262 if (agent.path == id)
263 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.crowd.path-in-use"),
264 "Path is referenced by an agent");
265 }
266 return eve::editing::applied<DomainOperation>(
267 operation("crowd.path.delete.v1", "crowd.path.set.v1", id_, id.value(), pathValue(found->second), id));
268}
269
270std::vector<EditorDiagnostic> CrowdDocumentTarget::validate() const {
271 std::vector<EditorDiagnostic> result;
272 for (const auto& [id, agent] : agents_) if (!agent.path.empty() && !paths_.contains(agent.path)) result.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::NotFound, RuleId("editor.crowd.dangling-path"), DiagnosticSeverity::Error, "Agent " + id.value() + " references a missing path"));
273 for (const auto& [id, path] : paths_) for (std::size_t i=1;i<path.points.size();++i) if(path.points[i-1].x==path.points[i].x && path.points[i-1].y==path.points[i].y) result.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::PreconditionViolation, RuleId("editor.crowd.zero-path-segment"),DiagnosticSeverity::Warning,"Path " + id.value() + " contains a zero-length segment"));
274 return result;
275}
276
278 CrowdOverlayResult result;
279 result.revision = revisionValue();
280 result.diagnostics = validate();
281 if (budget <= 0) { result.status=EditorStatus::Rejected; result.diagnostics.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument, RuleId("editor.crowd.invalid-overlay-budget"),DiagnosticSeverity::Error,"Overlay primitive budget must be positive")); return result; }
282 auto add=[&](CrowdOverlayPrimitive primitive){ if(static_cast<int>(result.primitives.size())>=budget) return false; result.primitives.push_back(std::move(primitive)); return true; };
283 for(const auto& [id,path]:paths_) for(std::size_t i=1;i<path.points.size();++i) if(!add({"line",id,{path.points[i-1].x,path.points[i-1].y,path.points[i].x,path.points[i].y},path.name})) goto exhausted;
284 for(const auto& [id,zone]:zones_) { for(std::size_t i=0;i<zone.points.size();++i) { const auto& a=zone.points[i]; const auto& b=zone.points[(i+1)%zone.points.size()]; if(!add({"line",id,{a[0],a[1],b[0],b[1]},zone.kind})) goto exhausted; } }
285 for(const auto& [id,agent]:agents_) if(!add({"circle",id,{agent.x,agent.y,agent.radius,agent.heading},agent.behavior})) goto exhausted;
286 result.status=EditorStatus::Applied; return result;
287exhausted:
288 result.status=EditorStatus::Rejected; result.diagnostics.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::PreconditionViolation, RuleId("editor.crowd.overlay-budget"),DiagnosticSeverity::Error,"Crowd overlay exceeds the primitive budget")); return result;
289}
290
292 EditorValue::Array agents, zones, paths; for(const auto& [id,value]:agents_){(void)id;agents.push_back(agentValue(value));} for(const auto& [id,value]:zones_){(void)id;zones.push_back(zoneValue(value));} for(const auto& [id,value]:paths_){(void)id;paths.push_back(pathValue(value));}
293 return EditorValue::Object{{"schemaVersion",int64_t{1}},{"agents",std::move(agents)},{"zones",std::move(zones)},{"paths",std::move(paths)}};
294}
295
297 const auto* versionEntry=field(snapshot,"schemaVersion"); const auto* agentsEntry=field(snapshot,"agents"); const auto* zonesEntry=field(snapshot,"zones"); const auto* pathsEntry=field(snapshot,"paths");
298 const auto* version=versionEntry?versionEntry->getIf<int64_t>():nullptr; const auto* agents=agentsEntry?agentsEntry->getIf<EditorValue::Array>():nullptr; const auto* zones=zonesEntry?zonesEntry->getIf<EditorValue::Array>():nullptr; const auto* paths=pathsEntry?pathsEntry->getIf<EditorValue::Array>():nullptr;
299 if (!version || *version != 1 || !agents || !zones || !paths)
300 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.invalid-snapshot"),
301 "Crowd snapshot requires schema version one and all collections");
302 CrowdDocumentTarget candidate(id_);
303 for (const auto& entry : *paths) {
304 auto value = parsePath(entry);
305 if (!value.ok())
306 return eve::editing::failed<void>(value.code(), RuleId("editor.crowd.invalid-snapshot-path"),
307 "Snapshot contains an invalid path");
308 if (!candidate.paths_.emplace(value.value().id, value.value()).second)
309 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.duplicate-path"),
310 "Snapshot path ids must be unique");
311 }
312 for (const auto& entry : *zones) {
313 auto value = parseZone(entry);
314 if (!value.ok())
315 return eve::editing::failed<void>(value.code(), RuleId("editor.crowd.invalid-snapshot-zone"),
316 "Snapshot contains an invalid zone");
317 if (!candidate.zones_.emplace(value.value().id, value.value()).second)
318 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.duplicate-zone"),
319 "Snapshot zone ids must be unique");
320 }
321 for (const auto& entry : *agents) {
322 auto value = parseAgent(entry);
323 if (!value.ok())
324 return eve::editing::failed<void>(value.code(), RuleId("editor.crowd.invalid-snapshot-agent"),
325 "Snapshot contains an invalid agent");
326 if (!candidate.agents_.emplace(value.value().id, value.value()).second)
327 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.duplicate-agent"),
328 "Snapshot agent ids must be unique");
329 }
330 for (const auto& diagnostic : candidate.validate())
331 if (diagnostic.severity() == DiagnosticSeverity::Error)
332 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.crowd.invalid-snapshot-reference"),
333 "Snapshot contains dangling references");
334 agents_ = std::move(candidate.agents_);
335 zones_ = std::move(candidate.zones_);
336 paths_ = std::move(candidate.paths_);
337 bumpRevision();
338 widenDirty(0, 0);
339 return eve::editing::applied<void>();
340}
341
342} // namespace eve::crowd_editing
LogicalId target
ActionParameterOperation operation
double value
Value::Object payload
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::map< std::string, Var > values
std::uint32_t key
glm::uvec4 ids
float v
TokenKind kind
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float radius
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
double number
bool found
std::string string
V3 heading
Definition TreeMesh.cpp:292
glm::vec3 point
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
UI-neutral agent/zone/path document with reversible domain operations.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
std::vector< EditorDiagnostic > validate() const
Validate geometry, numeric limits and cross references.
Result< DomainOperation > makeDeletePath(const StableId &id) const
Plan reversible path deletion when no agent references it.
std::vector< CrowdPathRecord > paths() const
Enumerate paths in stable-id order.
EditorValue snapshotValue() const
Capture deterministic schema-version-one authoring data.
std::vector< CrowdZoneRecord > zones() const
Enumerate zones in stable-id order.
Result< DomainOperation > makeDeleteZone(const StableId &id) const
Plan reversible zone deletion.
CrowdOverlayResult overlay(int primitiveBudget=100000) const
Produce deterministic path, zone and agent overlay primitives.
Result< DomainOperation > makeDeleteAgent(const StableId &id) const
Plan reversible agent deletion.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load validated authoring data.
Result< DomainOperation > makeSetPath(const CrowdPathRecord &record) const
Plan reversible path creation or replacement.
Result< DomainOperation > makeSetAgent(const CrowdAgentRecord &record) const
Plan reversible agent creation or replacement.
std::vector< CrowdAgentRecord > agents() const
Enumerate agents in stable-id order.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
static CapabilityId editorCapabilityId()
Stable capability id for crowd agent, zone and path editing.
Result< DomainOperation > makeSetZone(const CrowdZoneRecord &record) const
Plan reversible zone creation or replacement.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
TargetDescriptor describe() const override
Describe.
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
editing::RuleId RuleId
editing::TargetId TargetId
editing::StableId StableId
editing::Value EditorValue
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
bool enabled
Stable authoring record for one crowd agent.
Renderer-neutral line, circle and label primitives for AI overlays.
Revision-tagged deterministic viewport overlay.
std::vector< CrowdOverlayPrimitive > primitives
std::vector< EditorDiagnostic > diagnostics
Stable ordered path shared by multiple agents.
Stable waypoint used by a crowd path.
Stable polygonal sensing, avoidance or gameplay zone.
std::vector< std::array< double, 2 > > points
DomainOperation public API.
std::vector< ObjectRefValue > affectedObjects
Stable discovery metadata exposed by every editable target.
std::vector< CapabilityId > capabilities