载入中...
搜索中...
未找到
ClimbingCodec.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <unordered_set>
7#include <utility>
8
9namespace eve::climbing {
10namespace {
11
12const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
13 const auto found = object.find(std::string(name));
14 return found == object.end() ? nullptr : &found->second;
15}
16
17bool finite(float value) { return std::isfinite(value); }
18
19bool readString(const eve::Value::Object& object, std::string_view name, std::string& output) {
20 const eve::Value* value = field(object, name);
21 const auto* text = value ? value->getIf<std::string>() : nullptr;
22 if (!text) return false;
23 output = *text;
24 return true;
25}
26
27bool readInt64(const eve::Value::Object& object, std::string_view name, std::int64_t& output) {
28 const eve::Value* value = field(object, name);
29 const auto* number = value ? value->getIf<std::int64_t>() : nullptr;
30 if (!number) return false;
31 output = *number;
32 return true;
33}
34
35bool readBool(const eve::Value::Object& object, std::string_view name, bool& output) {
36 const eve::Value* value = field(object, name);
37 const auto* boolean = value ? value->getIf<bool>() : nullptr;
38 if (!boolean) return false;
39 output = *boolean;
40 return true;
41}
42
43bool readFloat(const eve::Value::Object& object, std::string_view name, float& output) {
44 const eve::Value* value = field(object, name);
45 if (!value) return false;
46 double number = 0.0;
47 if (const auto* integer = value->getIf<std::int64_t>())
48 number = static_cast<double>(*integer);
49 else if (const auto* real = value->getIf<double>())
50 number = *real;
51 else
52 return false;
53 if (!std::isfinite(number) || number < -static_cast<double>(std::numeric_limits<float>::max()) ||
54 number > static_cast<double>(std::numeric_limits<float>::max()))
55 return false;
56 output = static_cast<float>(number);
57 return true;
58}
59
60bool readOptionalFloat(const eve::Value::Object& object, std::string_view name, float& output) {
61 return !field(object, name) || readFloat(object, name, output);
62}
63
64bool readOptionalInt64(const eve::Value::Object& object, std::string_view name, std::int64_t& output) {
65 return !field(object, name) || readInt64(object, name, output);
66}
67
68template <class Enum>
69bool readOptionalEnum(const eve::Value::Object& object, std::string_view name, Enum& output,
70 std::int64_t maximumInclusive) {
71 std::int64_t value = static_cast<std::int64_t>(output);
72 if (!readOptionalInt64(object, name, value) || value < 0 || value > maximumInclusive) return false;
73 output = static_cast<Enum>(value);
74 return true;
75}
76
77eve::Value stringArray(const std::vector<std::string>& values) {
78 eve::Value::Array result;
79 result.reserve(values.size());
80 for (const std::string& value : values) result.emplace_back(value);
81 return eve::Value(std::move(result));
82}
83
84bool readOptionalStringArray(const eve::Value::Object& object, std::string_view name,
85 std::vector<std::string>& output) {
86 const eve::Value* value = field(object, name);
87 if (!value) return true;
88 const auto* array = value->getIf<eve::Value::Array>();
89 if (!array) return false;
90 std::vector<std::string> candidate;
91 candidate.reserve(array->size());
92 for (const eve::Value& item : *array) {
93 const auto* text = item.getIf<std::string>();
94 if (!text) return false;
95 candidate.push_back(*text);
96 }
97 output = std::move(candidate);
98 return true;
99}
100
101bool readOptionalString(const eve::Value::Object& object, std::string_view name, std::string& output) {
102 return !field(object, name) || readString(object, name, output);
103}
104
105bool uniqueNonEmpty(const std::vector<std::string>& values) {
106 std::vector<std::string_view> sorted;
107 sorted.reserve(values.size());
108 for (const std::string& value : values) {
109 if (value.empty()) return false;
110 sorted.push_back(value);
111 }
112 std::sort(sorted.begin(), sorted.end());
113 return std::adjacent_find(sorted.begin(), sorted.end()) == sorted.end();
114}
115
116std::string_view kindName(ClimbingActionKind kind) {
117 switch (kind) {
118 case ClimbingActionKind::Vault: return "vault";
119 case ClimbingActionKind::Mantle: return "mantle";
120 case ClimbingActionKind::LedgeGrab: return "ledge_grab";
121 case ClimbingActionKind::ClimbUp: return "climb_up";
122 case ClimbingActionKind::Shimmy: return "shimmy";
123 case ClimbingActionKind::CornerInner: return "corner_inner";
124 case ClimbingActionKind::CornerOuter: return "corner_outer";
125 case ClimbingActionKind::LedgeJump: return "ledge_jump";
126 case ClimbingActionKind::ClimbDown: return "climb_down";
127 case ClimbingActionKind::LadderMount: return "ladder_mount";
128 case ClimbingActionKind::LadderClimb: return "ladder_climb";
129 case ClimbingActionKind::LadderDismount: return "ladder_dismount";
130 case ClimbingActionKind::WallRun: return "wall_run";
131 case ClimbingActionKind::Slide: return "slide";
132 case ClimbingActionKind::BeamBalance: return "beam_balance";
133 case ClimbingActionKind::PoleSwing: return "pole_swing";
134 case ClimbingActionKind::BarSwing: return "bar_swing";
135 }
136 return "unknown";
137}
138
139bool readKind(std::string_view text, ClimbingActionKind& output) {
140 if (text == "vault")
142 else if (text == "mantle")
144 else if (text == "ledge_grab")
146 else if (text == "climb_up")
148 else if (text == "shimmy")
150 else if (text == "corner_inner")
152 else if (text == "corner_outer")
154 else if (text == "ledge_jump")
156 else if (text == "climb_down")
158 else if (text == "ladder_mount")
160 else if (text == "ladder_climb")
162 else if (text == "ladder_dismount")
164 else if (text == "wall_run")
166 else if (text == "slide")
168 else if (text == "beam_balance")
170 else if (text == "pole_swing")
172 else if (text == "bar_swing")
174 else
175 return false;
176 return true;
177}
178
179eve::Result<const eve::Value::Object*> readRoot(const eve::Value& value, std::string_view schemaId,
180 std::int64_t schemaVersion) {
181 const auto* object = value.getIf<eve::Value::Object>();
182 if (!object)
184 eve::DiagnosticCode::ParseError, "climbing definition must be an object", {}, {}, "climbing.codec"));
185 std::string actualSchema;
186 std::int64_t actualVersion = 0;
187 if (!readString(*object, "schemaId", actualSchema) || actualSchema != schemaId)
189 eve::DiagnosticCode::ParseError, "climbing definition schemaId is missing or mismatched", "schemaId", {},
190 "climbing.codec"));
191 if (!readInt64(*object, "schemaVersion", actualVersion))
193 eve::DiagnosticCode::ParseError, "climbing definition schemaVersion must be an integer", "schemaVersion",
194 {}, "climbing.codec"));
195 if (actualVersion != schemaVersion && actualVersion != schemaVersion - 1)
197 eve::DiagnosticCode::UnknownVersion, "climbing definition schema version is unsupported", "schemaVersion",
198 {}, "climbing.codec"));
200}
201
202eve::Value::Object unknownFields(const eve::Value::Object& object, std::initializer_list<std::string_view> knownNames) {
203 std::unordered_set<std::string_view> known(knownNames.begin(), knownNames.end());
204 eve::Value::Object result;
205 for (const auto& [name, value] : object)
206 if (!known.contains(name)) result.emplace(name, value);
207 return result;
208}
209
210void writeActionFields(eve::Value::Object& object, const ClimbingActionDefinition& action) {
211 eve::Value::Array notifyValues;
212 notifyValues.reserve(action.requiredNotifies.size());
213 for (const std::string& notify : action.requiredNotifies) notifyValues.emplace_back(notify);
214 eve::Value::Array warpWindowValues;
215 warpWindowValues.reserve(action.warpWindows.size());
216 for (const ClimbingWarpWindow& window : action.warpWindows) {
217 warpWindowValues.push_back(eve::Value::object({{"start", window.start},
218 {"end", window.end},
219 {"horizontal", window.horizontal},
220 {"vertical", window.vertical},
221 {"facing", window.facing}}));
222 }
223 object["schemaId"] = eve::Value(std::string(ClimbingActionDefinition::SchemaId));
224 object["schemaVersion"] = eve::Value(ClimbingActionDefinition::SchemaVersion);
225 object["id"] = eve::Value(action.id);
226 object["kind"] = eve::Value(std::string(kindName(action.kind)));
227 object["minHeight"] = eve::Value(action.minHeight);
228 object["maxHeight"] = eve::Value(action.maxHeight);
229 object["minSpeed"] = eve::Value(action.minSpeed);
230 object["durationNs"] = eve::Value(action.duration.nanoseconds());
231 object["landingForward"] = eve::Value(action.landingForward);
232 object["apexHeight"] = eve::Value(action.apexHeight);
233 object["selectionBias"] = eve::Value(action.selectionBias);
234 object["hangBodyOffset"] = eve::Value(action.hangBodyOffset);
235 object["hangFeetBelowLedge"] = eve::Value(action.hangFeetBelowLedge);
236 object["handSpacing"] = eve::Value(action.handSpacing);
237 object["cancelWindowStart"] = eve::Value(action.cancelWindowStart);
238 object["cancelWindowEnd"] = eve::Value(action.cancelWindowEnd);
239 object["rootMotionScaleMin"] = eve::Value(action.rootMotionScaleMin);
240 object["rootMotionScaleMax"] = eve::Value(action.rootMotionScaleMax);
241 object["maxTranslationWarpPerTick"] = eve::Value(action.maxTranslationWarpPerTick);
242 object["maxYawWarpRadiansPerTick"] = eve::Value(action.maxYawWarpRadiansPerTick);
243 object["horizontalWarpBudget"] = eve::Value(action.horizontalWarpBudget);
244 object["verticalWarpBudget"] = eve::Value(action.verticalWarpBudget);
245 object["facingWarpBudgetRadians"] = eve::Value(action.facingWarpBudgetRadians);
246 object["warpWindows"] = eve::Value(std::move(warpWindowValues));
247 object["requiredNotifies"] = eve::Value(std::move(notifyValues));
248 object["tags"] = stringArray(action.tags);
249 object["sourceModes"] = eve::Value(static_cast<std::int64_t>(action.sourceModes));
250 object["requiredCommand"] = eve::Value(static_cast<std::int64_t>(action.requiredCommand));
251 object["probeRecipe"] = eve::Value(static_cast<std::int64_t>(action.probeRecipe));
252 object["minDepth"] = eve::Value(action.minDepth);
253 object["maxDepth"] = eve::Value(action.maxDepth);
254 object["minDistance"] = eve::Value(action.minDistance);
255 object["maxDistance"] = eve::Value(action.maxDistance);
256 object["minSurfaceNormalY"] = eve::Value(action.minSurfaceNormalY);
257 object["maxSlopeRadians"] = eve::Value(action.maxSlopeRadians);
258 object["maxCurvature"] = eve::Value(action.maxCurvature);
259 object["requiredSupportTags"] = stringArray(action.requiredSupportTags);
260 object["trajectory"] = eve::Value(static_cast<std::int64_t>(action.trajectory));
261 object["animation"] = eve::Value::object({{"clipId", action.animation.clipId},
262 {"graphNodeId", action.animation.graphNodeId},
263 {"rootBone", action.animation.rootBone},
264 {"mirrored", action.animation.mirrored}});
265 eve::Value::Array branchValues;
266 branchValues.reserve(action.branchWindows.size());
267 for (const ClimbingBranchWindow& window : action.branchWindows)
268 branchValues.push_back(eve::Value::object(
269 {{"start", window.start}, {"end", window.end}, {"comboTag", window.comboTag}}));
270 object["branchWindows"] = eve::Value(std::move(branchValues));
271 eve::Value::Array contactValues;
272 contactValues.reserve(action.contactConstraints.size());
273 for (const ClimbingContactConstraint& contact : action.contactConstraints)
274 contactValues.push_back(eve::Value::object({{"target", static_cast<std::int64_t>(contact.target)},
275 {"start", contact.start},
276 {"end", contact.end},
277 {"maxWeight", contact.maxWeight}}));
278 object["contactConstraints"] = eve::Value(std::move(contactValues));
279 object["landingPolicy"] = eve::Value(static_cast<std::int64_t>(action.landingPolicy));
280 object["terminalVelocityPolicy"] = eve::Value(static_cast<std::int64_t>(action.terminalVelocityPolicy));
281 object["comboTags"] = stringArray(action.comboTags);
282 object["repetitionPenalty"] = eve::Value(static_cast<std::int64_t>(action.repetitionPenalty));
283 object["requiredConditionTags"] = stringArray(action.requiredConditionTags);
284 object["staminaCost"] = eve::Value(action.staminaCost);
285 object["cameraCue"] = eve::Value(action.cameraCue);
286 object["eventMetadata"] = stringArray(action.eventMetadata);
287}
288
289} // namespace
290
292 if (action.id.empty())
294 eve::DiagnosticCode::InvalidArgument, "action id must not be empty", "id", {}, "climbing.codec"));
295 if (!finite(action.minHeight) || !finite(action.maxHeight) || action.minHeight < 0.f ||
296 action.maxHeight < action.minHeight)
298 eve::DiagnosticCode::InvalidArgument, "action height range must be finite, non-negative, and ordered",
299 "height", {}, "climbing.codec"));
300 if (!finite(action.minSpeed) || action.minSpeed < 0.f || !finite(action.landingForward) ||
301 action.landingForward < 0.f || !finite(action.apexHeight) || action.apexHeight < 0.f)
304 "action speed, landing distance, and apex must be finite and non-negative", {}, {}, "climbing.codec"));
305 if (action.duration.nanoseconds() <= 0)
307 "action duration must be positive", "durationNs", {},
308 "climbing.codec"));
309 if (!finite(action.hangBodyOffset) || action.hangBodyOffset < 0.f || !finite(action.hangFeetBelowLedge) ||
310 action.hangFeetBelowLedge <= 0.f || !finite(action.handSpacing) || action.handSpacing <= 0.f ||
311 !finite(action.cancelWindowStart) || !finite(action.cancelWindowEnd) || action.cancelWindowStart < 0.f ||
312 action.cancelWindowEnd > 1.f || action.cancelWindowEnd < action.cancelWindowStart)
314 eve::DiagnosticCode::InvalidArgument, "hang geometry and cancel window must be finite and ordered", {}, {},
315 "climbing.codec"));
316 if (!finite(action.rootMotionScaleMin) || !finite(action.rootMotionScaleMax) || action.rootMotionScaleMin <= 0.f ||
317 action.rootMotionScaleMax < action.rootMotionScaleMin || action.rootMotionScaleMax > 4.f ||
318 !finite(action.maxTranslationWarpPerTick) || action.maxTranslationWarpPerTick <= 0.f ||
319 !finite(action.maxYawWarpRadiansPerTick) || action.maxYawWarpRadiansPerTick <= 0.f ||
320 !finite(action.horizontalWarpBudget) || action.horizontalWarpBudget < 0.f ||
321 !finite(action.verticalWarpBudget) || action.verticalWarpBudget < 0.f ||
322 !finite(action.facingWarpBudgetRadians) || action.facingWarpBudgetRadians < 0.f)
325 "root-motion scale and warp budgets must be finite, positive, and bounded", "warp", {}, "climbing.codec"));
326 float previousEnd = 0.f;
327 for (std::size_t index = 0; index < action.warpWindows.size(); ++index) {
328 const ClimbingWarpWindow& window = action.warpWindows[index];
329 if (!finite(window.start) || !finite(window.end) || window.start < 0.f || window.end > 1.f ||
330 window.end <= window.start || window.start < previousEnd ||
331 (!window.horizontal && !window.vertical && !window.facing))
334 "warp windows must be ordered, non-overlapping, and enable a channel",
335 "warpWindows." + std::to_string(index), {}, "climbing.codec"));
336 previousEnd = window.end;
337 }
338 std::vector<std::string> notifyNames = action.requiredNotifies;
339 std::sort(notifyNames.begin(), notifyNames.end());
340 if (std::any_of(notifyNames.begin(), notifyNames.end(), [](const std::string& name) { return name.empty(); }) ||
341 std::adjacent_find(notifyNames.begin(), notifyNames.end()) != notifyNames.end())
343 eve::DiagnosticCode::InvalidArgument, "required animation notifies must be non-empty and unique",
344 "requiredNotifies", {}, "climbing.codec"));
345 const auto sourceBits = static_cast<std::uint8_t>(action.sourceModes);
346 if (sourceBits == 0 || (sourceBits & ~static_cast<std::uint8_t>(ClimbingSourceMode::Any)) != 0)
348 "sourceModes must contain a known mode", "sourceModes",
349 {}, "climbing.codec"));
350 if (!finite(action.minDepth) || !finite(action.maxDepth) || action.minDepth < 0.f ||
351 action.maxDepth < action.minDepth || !finite(action.minDistance) || !finite(action.maxDistance) ||
352 action.minDistance < 0.f || action.maxDistance < action.minDistance ||
353 !finite(action.minSurfaceNormalY) || action.minSurfaceNormalY < -1.f || action.minSurfaceNormalY > 1.f ||
354 !finite(action.maxSlopeRadians) || action.maxSlopeRadians < 0.f || action.maxSlopeRadians > 3.1415927f ||
355 !finite(action.maxCurvature) || action.maxCurvature < 0.f)
357 "action geometry constraints are invalid", "geometry",
358 {}, "climbing.codec"));
359 if (!uniqueNonEmpty(action.tags) || !uniqueNonEmpty(action.requiredSupportTags) ||
360 !uniqueNonEmpty(action.comboTags) || !uniqueNonEmpty(action.requiredConditionTags) ||
361 !uniqueNonEmpty(action.eventMetadata))
363 "action tags and event metadata must be unique",
364 "metadata", {}, "climbing.codec"));
365 if (action.animation.rootBone.empty())
367 "animation rootBone must not be empty",
368 "animation.rootBone", {}, "climbing.codec"));
369 for (std::size_t index = 0; index < action.branchWindows.size(); ++index) {
370 const auto& window = action.branchWindows[index];
371 if (!finite(window.start) || !finite(window.end) || window.start < 0.f || window.end > 1.f ||
372 window.end <= window.start || window.comboTag.empty())
375 "branchWindows." + std::to_string(index), {}, "climbing.codec"));
376 if (std::find(action.comboTags.begin(), action.comboTags.end(), window.comboTag) == action.comboTags.end())
378 eve::DiagnosticCode::InvalidArgument, "branch window comboTag must be declared by the action",
379 "branchWindows." + std::to_string(index) + ".comboTag", {}, "climbing.codec"));
380 }
381 for (std::size_t index = 0; index < action.contactConstraints.size(); ++index) {
382 const auto& contact = action.contactConstraints[index];
383 if (!finite(contact.start) || !finite(contact.end) || !finite(contact.maxWeight) || contact.start < 0.f ||
384 contact.end > 1.f || contact.end <= contact.start || contact.maxWeight < 0.f || contact.maxWeight > 1.f)
386 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "contact constraint is invalid",
387 "contactConstraints." + std::to_string(index), {}, "climbing.codec"));
388 }
389 if (action.repetitionPenalty < 0 || !finite(action.staminaCost) || action.staminaCost < 0.f)
391 eve::DiagnosticCode::InvalidArgument, "action cost metadata is invalid", "cost", {}, "climbing.codec"));
393}
394
396 if (!finite(profile.capsuleRadius) || !finite(profile.capsuleHeight) || profile.capsuleRadius <= 0.f ||
397 profile.capsuleHeight <= profile.capsuleRadius * 2.f)
399 "capsule height must exceed twice its positive radius",
400 "capsule", {}, "climbing.codec"));
401 if (!finite(profile.compactCapsuleHeight) ||
402 profile.compactCapsuleHeight <= profile.capsuleRadius * 2.f ||
403 profile.compactCapsuleHeight > profile.capsuleHeight)
406 "compact capsule height must exceed the diameter and not exceed standing height",
407 "compactCapsuleHeight", {}, "climbing.codec"));
408 if (!finite(profile.skin) || profile.skin < 0.f || !finite(profile.maxProbeDistance) ||
409 profile.maxProbeDistance <= 0.f || !finite(profile.maxObstacleHeight) || profile.maxObstacleHeight <= 0.f ||
410 !finite(profile.minTopNormalY) || profile.minTopNormalY < 0.f || profile.minTopNormalY > 1.f ||
411 !finite(profile.maxWarpResidual) || profile.maxWarpResidual < 0.f)
413 eve::DiagnosticCode::InvalidArgument, "profile probe, surface, skin, and warp limits are invalid", {}, {},
414 "climbing.codec"));
415 if (!finite(profile.maxPlatformSpeed) || profile.maxPlatformSpeed <= 0.f || !finite(profile.dropInitialSpeed) ||
416 profile.dropInitialSpeed < 0.f)
418 "platform and drop speed limits must be finite", {},
419 {}, "climbing.codec"));
420 if (profile.inputBufferTicks == 0 || profile.inputBufferTicks > 1000000 || profile.coyoteTicks > 1000000)
422 eve::DiagnosticCode::InvalidArgument, "input buffer must be non-zero and tick windows must be bounded",
423 "inputTicks", {}, "climbing.codec"));
424 if (profile.pathValidationSegments < 2 || profile.pathValidationSegments > 64 ||
425 !finite(profile.maxPelvisDeviation) || profile.maxPelvisDeviation < 0.f)
427 eve::DiagnosticCode::InvalidArgument, "path validation samples and pelvis deviation are invalid",
428 "motionValidation", {}, "climbing.codec"));
429 if (!finite(profile.groundAcceleration) || profile.groundAcceleration < 0.f || !finite(profile.groundBraking) ||
430 profile.groundBraking < 0.f || !finite(profile.airControl) || profile.airControl < 0.f ||
431 profile.airControl > 1.f || !finite(profile.gravity) || profile.gravity <= 0.f ||
432 !finite(profile.jumpSpeed) || profile.jumpSpeed <= 0.f || profile.probeSectors == 0 ||
433 profile.probeSectors > 64 || profile.maxCandidates == 0 || profile.maxCandidates > 8 ||
434 !finite(profile.autoAssistStrength) || profile.autoAssistStrength < 0.f || profile.autoAssistStrength > 1.f ||
435 !finite(profile.maxTotalWarpBudget) || profile.maxTotalWarpBudget < 0.f)
437 "profile locomotion and probe policies are invalid",
438 "policies", {}, "climbing.codec"));
439 if (!uniqueNonEmpty(profile.defaultActionIds) || !uniqueNonEmpty(profile.allowedActionTags) ||
440 !uniqueNonEmpty(profile.deniedActionTags))
442 "profile action ids and tags must be unique",
443 "actionPolicy", {}, "climbing.codec"));
445 if (weights.direction < 0 || weights.approachSpeed < 0 || weights.height < 0 || weights.distance < 0 ||
446 weights.warpTranslation < 0 || weights.warpRotation < 0 || weights.intentMismatch < 0)
448 "score weights must be non-negative", "scoreWeights",
449 {}, "climbing.codec"));
452 eve::DiagnosticCode::InvalidArgument, "RequireProvider stamina policy needs a stable adapter id",
453 "staminaAdapter", {}, "climbing.codec"));
454 std::unordered_set<std::string> ids;
455 for (const ClimbingActionDefinition& action : profile.actions) {
457 if (!valid) return eve::Result<void>::failure(valid.status());
458 if (std::max(action.horizontalWarpBudget, action.verticalWarpBudget) >
459 profile.maxTotalWarpBudget + 0.0001f)
461 eve::DiagnosticCode::InvalidArgument, "action translation warp budgets exceed profile maximum",
462 "actions." + action.id, {}, "climbing.codec"));
463 if (!ids.emplace(action.id).second)
465 "profile contains duplicate action id",
466 "actions." + action.id, {}, "climbing.codec"));
467 }
468 for (const std::string& id : profile.defaultActionIds)
469 if (!ids.contains(id))
471 eve::DiagnosticCode::NotFound, "default action id is not registered in the profile",
472 "defaultActionIds." + id, {}, "climbing.codec"));
474}
475
478 if (!valid) return eve::Result<eve::Value>::failure(valid.status());
479 eve::Value::Object object = action.extensionMetadata;
480 writeActionFields(object, action);
481 return eve::Result<eve::Value>::success(eve::Value(std::move(object)));
482}
483
487 const eve::Value::Object& object = *root.value();
488 ClimbingActionDefinition candidate;
489 std::string kind;
490 std::int64_t durationNs = 0;
491 std::int64_t selectionBias = 0;
492 const eve::Value* notifiesValue = field(object, "requiredNotifies");
493 const auto* notifies = notifiesValue ? notifiesValue->getIf<eve::Value::Array>() : nullptr;
494 const eve::Value* warpWindowsValue = field(object, "warpWindows");
495 const auto* warpWindows = warpWindowsValue ? warpWindowsValue->getIf<eve::Value::Array>() : nullptr;
496 if (!readString(object, "id", candidate.id) || !readString(object, "kind", kind) ||
497 !readKind(kind, candidate.kind) || !readFloat(object, "minHeight", candidate.minHeight) ||
498 !readFloat(object, "maxHeight", candidate.maxHeight) || !readFloat(object, "minSpeed", candidate.minSpeed) ||
499 !readInt64(object, "durationNs", durationNs) ||
500 !readFloat(object, "landingForward", candidate.landingForward) ||
501 !readFloat(object, "apexHeight", candidate.apexHeight) || !readInt64(object, "selectionBias", selectionBias) ||
502 selectionBias < std::numeric_limits<int>::min() || selectionBias > std::numeric_limits<int>::max() ||
503 !readFloat(object, "hangBodyOffset", candidate.hangBodyOffset) ||
504 !readFloat(object, "hangFeetBelowLedge", candidate.hangFeetBelowLedge) ||
505 !readFloat(object, "handSpacing", candidate.handSpacing) ||
506 !readFloat(object, "cancelWindowStart", candidate.cancelWindowStart) ||
507 !readFloat(object, "cancelWindowEnd", candidate.cancelWindowEnd) ||
508 !readOptionalFloat(object, "rootMotionScaleMin", candidate.rootMotionScaleMin) ||
509 !readOptionalFloat(object, "rootMotionScaleMax", candidate.rootMotionScaleMax) ||
510 !readOptionalFloat(object, "maxTranslationWarpPerTick", candidate.maxTranslationWarpPerTick) ||
511 !readOptionalFloat(object, "maxYawWarpRadiansPerTick", candidate.maxYawWarpRadiansPerTick) ||
512 !readOptionalFloat(object, "horizontalWarpBudget", candidate.horizontalWarpBudget) ||
513 !readOptionalFloat(object, "verticalWarpBudget", candidate.verticalWarpBudget) ||
514 !readOptionalFloat(object, "facingWarpBudgetRadians", candidate.facingWarpBudgetRadians) ||
515 (warpWindowsValue && !warpWindows) || !notifies)
518 "climbing action has missing or invalid known fields", {}, {}, "climbing.codec"));
519 candidate.duration = eve::Duration::fromNanoseconds(durationNs);
520 candidate.selectionBias = static_cast<int>(selectionBias);
521 candidate.requiredNotifies.reserve(notifies->size());
522 for (std::size_t index = 0; index < notifies->size(); ++index) {
523 const auto* notify = (*notifies)[index].getIf<std::string>();
524 if (!notify)
526 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "animation notify must be a string",
527 "requiredNotifies." + std::to_string(index), {}, "climbing.codec"));
528 candidate.requiredNotifies.push_back(*notify);
529 }
530 if (warpWindows) candidate.warpWindows.reserve(warpWindows->size());
531 for (std::size_t index = 0; warpWindows && index < warpWindows->size(); ++index) {
532 const auto* valueObject = (*warpWindows)[index].getIf<eve::Value::Object>();
533 ClimbingWarpWindow window;
534 if (!valueObject || !readFloat(*valueObject, "start", window.start) ||
535 !readFloat(*valueObject, "end", window.end) || !readBool(*valueObject, "horizontal", window.horizontal) ||
536 !readBool(*valueObject, "vertical", window.vertical) || !readBool(*valueObject, "facing", window.facing))
538 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "warp window has missing or invalid fields",
539 "warpWindows." + std::to_string(index), {}, "climbing.codec"));
540 candidate.warpWindows.push_back(window);
541 }
542 std::int64_t repetitionPenalty = candidate.repetitionPenalty;
543 if (!readOptionalStringArray(object, "tags", candidate.tags) ||
544 !readOptionalEnum(object, "sourceModes", candidate.sourceModes,
545 static_cast<std::int64_t>(ClimbingSourceMode::Any)) ||
546 !readOptionalEnum(object, "requiredCommand", candidate.requiredCommand,
547 static_cast<std::int64_t>(ClimbingCommandRequirement::Crouch)) ||
548 !readOptionalEnum(object, "probeRecipe", candidate.probeRecipe,
549 static_cast<std::int64_t>(ClimbingProbeRecipe::AnchorGraph)) ||
550 !readOptionalFloat(object, "minDepth", candidate.minDepth) ||
551 !readOptionalFloat(object, "maxDepth", candidate.maxDepth) ||
552 !readOptionalFloat(object, "minDistance", candidate.minDistance) ||
553 !readOptionalFloat(object, "maxDistance", candidate.maxDistance) ||
554 !readOptionalFloat(object, "minSurfaceNormalY", candidate.minSurfaceNormalY) ||
555 !readOptionalFloat(object, "maxSlopeRadians", candidate.maxSlopeRadians) ||
556 !readOptionalFloat(object, "maxCurvature", candidate.maxCurvature) ||
557 !readOptionalStringArray(object, "requiredSupportTags", candidate.requiredSupportTags) ||
558 !readOptionalEnum(object, "trajectory", candidate.trajectory,
559 static_cast<std::int64_t>(ClimbingTrajectoryKind::AnchorToAnchor)) ||
560 !readOptionalEnum(object, "landingPolicy", candidate.landingPolicy,
561 static_cast<std::int64_t>(ClimbingLandingPolicy::Stop)) ||
562 !readOptionalEnum(object, "terminalVelocityPolicy", candidate.terminalVelocityPolicy,
563 static_cast<std::int64_t>(ClimbingTerminalVelocityPolicy::Zero)) ||
564 !readOptionalStringArray(object, "comboTags", candidate.comboTags) ||
565 !readOptionalInt64(object, "repetitionPenalty", repetitionPenalty) ||
566 repetitionPenalty < std::numeric_limits<std::int32_t>::min() ||
567 repetitionPenalty > std::numeric_limits<std::int32_t>::max() ||
568 !readOptionalStringArray(object, "requiredConditionTags", candidate.requiredConditionTags) ||
569 !readOptionalFloat(object, "staminaCost", candidate.staminaCost) ||
570 !readOptionalString(object, "cameraCue", candidate.cameraCue) ||
571 !readOptionalStringArray(object, "eventMetadata", candidate.eventMetadata))
573 eve::DiagnosticCode::ParseError, "climbing action has invalid v4 metadata", {}, {}, "climbing.codec"));
574 candidate.repetitionPenalty = static_cast<std::int32_t>(repetitionPenalty);
575 if (const eve::Value* animationValue = field(object, "animation")) {
576 const auto* animation = animationValue->getIf<eve::Value::Object>();
577 if (!animation || !readString(*animation, "clipId", candidate.animation.clipId) ||
578 !readString(*animation, "graphNodeId", candidate.animation.graphNodeId) ||
579 !readString(*animation, "rootBone", candidate.animation.rootBone) ||
580 !readBool(*animation, "mirrored", candidate.animation.mirrored))
582 eve::DiagnosticCode::ParseError, "animation binding has missing or invalid fields", "animation", {},
583 "climbing.codec"));
584 }
585 if (const eve::Value* branchValue = field(object, "branchWindows")) {
586 const auto* branches = branchValue->getIf<eve::Value::Array>();
587 if (!branches)
589 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "branchWindows must be an array",
590 "branchWindows", {}, "climbing.codec"));
591 candidate.branchWindows.reserve(branches->size());
592 for (std::size_t index = 0; index < branches->size(); ++index) {
593 const auto* valueObject = (*branches)[index].getIf<eve::Value::Object>();
595 if (!valueObject || !readFloat(*valueObject, "start", window.start) ||
596 !readFloat(*valueObject, "end", window.end) ||
597 !readString(*valueObject, "comboTag", window.comboTag))
599 eve::DiagnosticCode::ParseError, "branch window has missing or invalid fields",
600 "branchWindows." + std::to_string(index), {}, "climbing.codec"));
601 candidate.branchWindows.push_back(std::move(window));
602 }
603 }
604 if (const eve::Value* contactsValue = field(object, "contactConstraints")) {
605 const auto* contacts = contactsValue->getIf<eve::Value::Array>();
606 if (!contacts)
608 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "contactConstraints must be an array",
609 "contactConstraints", {}, "climbing.codec"));
610 candidate.contactConstraints.reserve(contacts->size());
611 for (std::size_t index = 0; index < contacts->size(); ++index) {
612 const auto* valueObject = (*contacts)[index].getIf<eve::Value::Object>();
614 if (!valueObject ||
615 !readOptionalEnum(*valueObject, "target", contact.target,
616 static_cast<std::int64_t>(ClimbingContactTarget::Pelvis)) ||
617 !field(*valueObject, "target") || !readFloat(*valueObject, "start", contact.start) ||
618 !readFloat(*valueObject, "end", contact.end) ||
619 !readFloat(*valueObject, "maxWeight", contact.maxWeight))
621 eve::DiagnosticCode::ParseError, "contact constraint has missing or invalid fields",
622 "contactConstraints." + std::to_string(index), {}, "climbing.codec"));
623 candidate.contactConstraints.push_back(contact);
624 }
625 }
626 candidate.extensionMetadata = unknownFields(object, {"schemaId",
627 "schemaVersion",
628 "id",
629 "kind",
630 "minHeight",
631 "maxHeight",
632 "minSpeed",
633 "durationNs",
634 "landingForward",
635 "apexHeight",
636 "selectionBias",
637 "hangBodyOffset",
638 "hangFeetBelowLedge",
639 "handSpacing",
640 "cancelWindowStart",
641 "cancelWindowEnd",
642 "rootMotionScaleMin",
643 "rootMotionScaleMax",
644 "maxTranslationWarpPerTick",
645 "maxYawWarpRadiansPerTick",
646 "horizontalWarpBudget",
647 "verticalWarpBudget",
648 "facingWarpBudgetRadians",
649 "warpWindows",
650 "requiredNotifies",
651 "tags",
652 "sourceModes",
653 "requiredCommand",
654 "probeRecipe",
655 "minDepth",
656 "maxDepth",
657 "minDistance",
658 "maxDistance",
659 "minSurfaceNormalY",
660 "maxSlopeRadians",
661 "maxCurvature",
662 "requiredSupportTags",
663 "trajectory",
664 "animation",
665 "branchWindows",
666 "contactConstraints",
667 "landingPolicy",
668 "terminalVelocityPolicy",
669 "comboTags",
670 "repetitionPenalty",
671 "requiredConditionTags",
672 "staminaCost",
673 "cameraCue",
674 "eventMetadata"});
675 auto valid = validateClimbingActionDefinition(candidate);
677 return eve::Result<ClimbingActionDefinition>::success(std::move(candidate));
678}
679
682 if (!valid) return eve::Result<eve::Value>::failure(valid.status());
684 actions.reserve(profile.actions.size());
685 for (const ClimbingActionDefinition& action : profile.actions) {
687 if (!encoded) return eve::Result<eve::Value>::failure(encoded.status());
688 actions.push_back(std::move(encoded).takeValue());
689 }
690 eve::Value::Object filter{{"categoryBits", eve::Value(static_cast<std::int64_t>(profile.queryFilter.categoryBits))},
691 {"maskBits", eve::Value(static_cast<std::int64_t>(profile.queryFilter.maskBits))},
692 {"ignoredBodyId", eve::Value(profile.queryFilter.ignoredBodyId)},
693 {"ignoredShapeId", eve::Value(profile.queryFilter.ignoredShapeId)}};
694 eve::Value::Object object = profile.extensionMetadata;
695 object["schemaId"] = eve::Value(std::string(ClimbingProfileDefinition::SchemaId));
696 object["schemaVersion"] = eve::Value(ClimbingProfileDefinition::SchemaVersion);
697 object["capsuleRadius"] = eve::Value(profile.capsuleRadius);
698 object["capsuleHeight"] = eve::Value(profile.capsuleHeight);
699 object["compactCapsuleHeight"] = eve::Value(profile.compactCapsuleHeight);
700 object["skin"] = eve::Value(profile.skin);
701 object["maxProbeDistance"] = eve::Value(profile.maxProbeDistance);
702 object["maxObstacleHeight"] = eve::Value(profile.maxObstacleHeight);
703 object["minTopNormalY"] = eve::Value(profile.minTopNormalY);
704 object["maxWarpResidual"] = eve::Value(profile.maxWarpResidual);
705 object["maxPlatformSpeed"] = eve::Value(profile.maxPlatformSpeed);
706 object["dropInitialSpeed"] = eve::Value(profile.dropInitialSpeed);
707 object["inputBufferTicks"] = eve::Value(static_cast<std::int64_t>(profile.inputBufferTicks));
708 object["coyoteTicks"] = eve::Value(static_cast<std::int64_t>(profile.coyoteTicks));
709 object["pathValidationSegments"] = eve::Value(static_cast<std::int64_t>(profile.pathValidationSegments));
710 object["maxPelvisDeviation"] = eve::Value(profile.maxPelvisDeviation);
711 object["groundAcceleration"] = eve::Value(profile.groundAcceleration);
712 object["groundBraking"] = eve::Value(profile.groundBraking);
713 object["airControl"] = eve::Value(profile.airControl);
714 object["gravity"] = eve::Value(profile.gravity);
715 object["jumpSpeed"] = eve::Value(profile.jumpSpeed);
716 object["probeSectors"] = eve::Value(static_cast<std::int64_t>(profile.probeSectors));
717 object["maxCandidates"] = eve::Value(static_cast<std::int64_t>(profile.maxCandidates));
718 object["autoAssistStrength"] = eve::Value(profile.autoAssistStrength);
719 object["scoreWeights"] = eve::Value::object({{"direction", profile.scoreWeights.direction},
720 {"approachSpeed", profile.scoreWeights.approachSpeed},
721 {"height", profile.scoreWeights.height},
722 {"distance", profile.scoreWeights.distance},
723 {"warpTranslation", profile.scoreWeights.warpTranslation},
724 {"warpRotation", profile.scoreWeights.warpRotation},
725 {"intentMismatch", profile.scoreWeights.intentMismatch}});
726 object["maxTotalWarpBudget"] = eve::Value(profile.maxTotalWarpBudget);
727 object["defaultActionIds"] = stringArray(profile.defaultActionIds);
728 object["allowedActionTags"] = stringArray(profile.allowedActionTags);
729 object["deniedActionTags"] = stringArray(profile.deniedActionTags);
730 object["staminaPolicy"] = eve::Value(static_cast<std::int64_t>(profile.staminaPolicy));
731 object["staminaAdapter"] = eve::Value(profile.staminaAdapter);
732 object["cameraCueProfile"] = eve::Value(profile.cameraCueProfile);
733 object["queryFilter"] = eve::Value(std::move(filter));
734 object["actions"] = eve::Value(std::move(actions));
735 return eve::Result<eve::Value>::success(eve::Value(std::move(object)));
736}
737
741 const eve::Value::Object& object = *root.value();
743 const eve::Value* actionsValue = field(object, "actions");
744 const auto* actions = actionsValue ? actionsValue->getIf<eve::Value::Array>() : nullptr;
745 const eve::Value* filterValue = field(object, "queryFilter");
746 const auto* filter = filterValue ? filterValue->getIf<eve::Value::Object>() : nullptr;
747 std::int64_t categoryBits = 0;
748 std::int64_t maskBits = 0;
749 std::int64_t ignoredBodyId = 0;
750 std::int64_t ignoredShapeId = 0;
751 std::int64_t inputBufferTicks = 0;
752 std::int64_t coyoteTicks = 0;
753 std::int64_t pathValidationSegments = candidate.pathValidationSegments;
754 std::int64_t probeSectors = candidate.probeSectors;
755 std::int64_t maxCandidates = candidate.maxCandidates;
756 if (!readFloat(object, "capsuleRadius", candidate.capsuleRadius) ||
757 !readFloat(object, "capsuleHeight", candidate.capsuleHeight) ||
758 !readOptionalFloat(object, "compactCapsuleHeight", candidate.compactCapsuleHeight) ||
759 !readFloat(object, "skin", candidate.skin) ||
760 !readFloat(object, "maxProbeDistance", candidate.maxProbeDistance) ||
761 !readFloat(object, "maxObstacleHeight", candidate.maxObstacleHeight) ||
762 !readFloat(object, "minTopNormalY", candidate.minTopNormalY) ||
763 !readFloat(object, "maxWarpResidual", candidate.maxWarpResidual) ||
764 !readFloat(object, "maxPlatformSpeed", candidate.maxPlatformSpeed) ||
765 !readFloat(object, "dropInitialSpeed", candidate.dropInitialSpeed) ||
766 !readInt64(object, "inputBufferTicks", inputBufferTicks) || inputBufferTicks <= 0 ||
767 !readInt64(object, "coyoteTicks", coyoteTicks) || coyoteTicks < 0 ||
768 !readOptionalInt64(object, "pathValidationSegments", pathValidationSegments) || pathValidationSegments < 0 ||
769 pathValidationSegments > std::numeric_limits<std::uint32_t>::max() ||
770 !readOptionalFloat(object, "maxPelvisDeviation", candidate.maxPelvisDeviation) ||
771 !readOptionalFloat(object, "groundAcceleration", candidate.groundAcceleration) ||
772 !readOptionalFloat(object, "groundBraking", candidate.groundBraking) ||
773 !readOptionalFloat(object, "airControl", candidate.airControl) ||
774 !readOptionalFloat(object, "gravity", candidate.gravity) ||
775 !readOptionalFloat(object, "jumpSpeed", candidate.jumpSpeed) ||
776 !readOptionalInt64(object, "probeSectors", probeSectors) || probeSectors < 0 ||
777 probeSectors > std::numeric_limits<std::uint32_t>::max() ||
778 !readOptionalInt64(object, "maxCandidates", maxCandidates) || maxCandidates < 0 ||
779 maxCandidates > std::numeric_limits<std::uint32_t>::max() ||
780 !readOptionalFloat(object, "autoAssistStrength", candidate.autoAssistStrength) ||
781 !readOptionalFloat(object, "maxTotalWarpBudget", candidate.maxTotalWarpBudget) ||
782 !readOptionalStringArray(object, "defaultActionIds", candidate.defaultActionIds) ||
783 !readOptionalStringArray(object, "allowedActionTags", candidate.allowedActionTags) ||
784 !readOptionalStringArray(object, "deniedActionTags", candidate.deniedActionTags) ||
785 !readOptionalEnum(object, "staminaPolicy", candidate.staminaPolicy,
786 static_cast<std::int64_t>(ClimbingStaminaPolicy::UseWhenPresent)) ||
787 !readOptionalString(object, "staminaAdapter", candidate.staminaAdapter) ||
788 !readOptionalString(object, "cameraCueProfile", candidate.cameraCueProfile) || !actions || !filter ||
789 !readInt64(*filter, "categoryBits", categoryBits) || !readInt64(*filter, "maskBits", maskBits) ||
790 categoryBits < 0 || categoryBits > std::numeric_limits<std::uint32_t>::max() || maskBits < 0 ||
791 maskBits > std::numeric_limits<std::uint32_t>::max() || !readInt64(*filter, "ignoredBodyId", ignoredBodyId) ||
792 ignoredBodyId < std::numeric_limits<int>::min() || ignoredBodyId > std::numeric_limits<int>::max() ||
793 !readInt64(*filter, "ignoredShapeId", ignoredShapeId) || ignoredShapeId < std::numeric_limits<int>::min() ||
794 ignoredShapeId > std::numeric_limits<int>::max())
797 "climbing profile has missing or invalid known fields", {}, {}, "climbing.codec"));
798 candidate.queryFilter.categoryBits = static_cast<std::uint32_t>(categoryBits);
799 candidate.queryFilter.maskBits = static_cast<std::uint32_t>(maskBits);
800 candidate.queryFilter.ignoredBodyId = static_cast<int>(ignoredBodyId);
801 candidate.queryFilter.ignoredShapeId = static_cast<int>(ignoredShapeId);
802 candidate.inputBufferTicks = static_cast<std::uint64_t>(inputBufferTicks);
803 candidate.coyoteTicks = static_cast<std::uint64_t>(coyoteTicks);
804 candidate.pathValidationSegments = static_cast<std::uint32_t>(pathValidationSegments);
805 candidate.probeSectors = static_cast<std::uint32_t>(probeSectors);
806 candidate.maxCandidates = static_cast<std::uint32_t>(maxCandidates);
807 if (const eve::Value* weightsValue = field(object, "scoreWeights")) {
808 const auto* weights = weightsValue->getIf<eve::Value::Object>();
809 std::int64_t direction = 0, approachSpeed = 0, height = 0, distance = 0;
810 std::int64_t warpTranslation = 0, warpRotation = 0, intentMismatch = 0;
811 if (!weights || !readInt64(*weights, "direction", direction) ||
812 !readInt64(*weights, "approachSpeed", approachSpeed) || !readInt64(*weights, "height", height) ||
813 !readInt64(*weights, "distance", distance) ||
814 !readInt64(*weights, "warpTranslation", warpTranslation) ||
815 !readInt64(*weights, "warpRotation", warpRotation) ||
816 !readInt64(*weights, "intentMismatch", intentMismatch))
818 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "scoreWeights has missing or invalid fields",
819 "scoreWeights", {}, "climbing.codec"));
820 const auto inRange = [](std::int64_t value) {
821 return value >= std::numeric_limits<std::int32_t>::min() &&
822 value <= std::numeric_limits<std::int32_t>::max();
823 };
824 if (!inRange(direction) || !inRange(approachSpeed) || !inRange(height) || !inRange(distance) ||
825 !inRange(warpTranslation) || !inRange(warpRotation) || !inRange(intentMismatch))
827 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "scoreWeights exceeds int32 range",
828 "scoreWeights", {}, "climbing.codec"));
829 candidate.scoreWeights = {static_cast<std::int32_t>(direction),
830 static_cast<std::int32_t>(approachSpeed),
831 static_cast<std::int32_t>(height),
832 static_cast<std::int32_t>(distance),
833 static_cast<std::int32_t>(warpTranslation),
834 static_cast<std::int32_t>(warpRotation),
835 static_cast<std::int32_t>(intentMismatch)};
836 }
837 candidate.actions.reserve(actions->size());
838 for (std::size_t index = 0; index < actions->size(); ++index) {
839 auto decoded = decodeClimbingActionDefinition((*actions)[index]);
840 if (!decoded)
842 decoded.error()->code(), decoded.error()->message(),
843 "actions." + std::to_string(index) + "." + decoded.error()->path(), {}, "climbing.codec"));
844 candidate.actions.push_back(std::move(decoded).takeValue());
845 }
846 candidate.extensionMetadata =
847 unknownFields(object, {"schemaId", "schemaVersion", "capsuleRadius", "capsuleHeight",
848 "compactCapsuleHeight", "skin",
849 "maxProbeDistance", "maxObstacleHeight", "minTopNormalY", "maxWarpResidual",
850 "maxPlatformSpeed", "dropInitialSpeed", "inputBufferTicks", "coyoteTicks",
851 "pathValidationSegments", "maxPelvisDeviation", "groundAcceleration",
852 "groundBraking", "airControl", "gravity", "jumpSpeed", "probeSectors",
853 "maxCandidates", "autoAssistStrength", "scoreWeights", "maxTotalWarpBudget",
854 "defaultActionIds", "allowedActionTags", "deniedActionTags", "staminaPolicy",
855 "staminaAdapter", "cameraCueProfile", "queryFilter", "actions"});
858 return eve::Result<ClimbingProfileDefinition>::success(std::move(candidate));
859}
860
861} // namespace eve::climbing
double value
int root
Definition AnimSmr.cpp:119
std::string output
Versioned, lossless codecs for climbing definitions.
std::map< std::string, Var > values
glm::uvec4 ids
std::uint32_t height
std::string text
TokenKind kind
std::string name
bool valid
float distance
bool finite
std::string action
Definition PlayHost.cpp:117
std::vector< ActionSpec > actions
Definition PlayHost.cpp:126
RoadLaneDirection direction
double number
bool found
std::string filter
bool boolean
float weights[3]
Json object
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
static constexpr Duration fromNanoseconds(std::int64_t nanoseconds) noexcept
Construct an exact duration from nanoseconds.
Definition Time.h:55
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
static Value object(Object value)
Compatibility factory for an object value.
Definition Value.h:114
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Result< float > readFloat(const Accessor &accessor, std::uint32_t element, std::uint32_t component)
Read one bounded finite FLOAT component.
eve::Result< ClimbingActionDefinition > decodeClimbingActionDefinition(const eve::Value &value)
Decode and validate one action without partially mutating a registry.
eve::Result< eve::Value > encodeClimbingProfileDefinition(const ClimbingProfileDefinition &profile)
Encode one validated profile and all nested actions into canonical schema v4 data.
eve::Result< ClimbingProfileDefinition > decodeClimbingProfileDefinition(const eve::Value &value)
Decode and validate an owning profile candidate transactionally.
eve::Result< eve::Value > encodeClimbingActionDefinition(const ClimbingActionDefinition &action)
Encode one validated action into canonical schema v4 data.
eve::Result< void > validateClimbingActionDefinition(const ClimbingActionDefinition &action)
Validate every known field of one action definition.
ClimbingActionKind
Data-driven action family; stable action identity remains the definition id.
Definition Climbing.h:46
eve::Result< void > validateClimbingProfileDefinition(const ClimbingProfileDefinition &profile)
Validate every known field and nested action of one profile definition.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
bool readString(const eve::Value::Object &object, const char *name, std::string &output)
const EditorValue * field(const EditorValue &value, const char *name)
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Definition Widget.cpp:292
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Definition of one geometry-compatible climbing action.
Definition Climbing.h:160
ClimbingCommandRequirement requiredCommand
Definition Climbing.h:191
ClimbingTrajectoryKind trajectory
Definition Climbing.h:201
std::vector< ClimbingContactConstraint > contactConstraints
Definition Climbing.h:204
std::vector< ClimbingBranchWindow > branchWindows
Definition Climbing.h:203
static constexpr std::int64_t SchemaVersion
Current action schema version.
Definition Climbing.h:164
ClimbingAnimationBinding animation
Definition Climbing.h:202
std::vector< std::string > requiredSupportTags
Definition Climbing.h:200
eve::Value::Object extensionMetadata
Unknown schema fields retained across decode and encode.
Definition Climbing.h:214
ClimbingTerminalVelocityPolicy terminalVelocityPolicy
Definition Climbing.h:206
std::vector< std::string > tags
Definition Climbing.h:189
std::vector< std::string > requiredConditionTags
Definition Climbing.h:209
std::vector< std::string > comboTags
Definition Climbing.h:207
std::vector< std::string > requiredNotifies
Definition Climbing.h:188
std::vector< std::string > eventMetadata
Definition Climbing.h:212
std::vector< ClimbingWarpWindow > warpWindows
Definition Climbing.h:187
static constexpr std::string_view SchemaId
Schema id emitted by the canonical action codec.
Definition Climbing.h:162
ClimbingLandingPolicy landingPolicy
Definition Climbing.h:205
Normalized branch interval advertising one stable combo tag.
Definition Climbing.h:108
Normalized contact interval and maximum constraint weight.
Definition Climbing.h:118
Runtime profile controlling capsule geometry and probe limits.
Definition Climbing.h:218
static constexpr std::string_view SchemaId
Schema id emitted by the canonical profile codec.
Definition Climbing.h:220
std::vector< std::string > deniedActionTags
Definition Climbing.h:250
eve::Value::Object extensionMetadata
Unknown schema fields retained across decode and encode.
Definition Climbing.h:257
std::vector< std::string > defaultActionIds
Definition Climbing.h:248
physics::QueryFilter3D queryFilter
Definition Climbing.h:254
static constexpr std::int64_t SchemaVersion
Current profile schema version.
Definition Climbing.h:222
std::vector< ClimbingActionDefinition > actions
Definition Climbing.h:255
std::vector< std::string > allowedActionTags
Definition Climbing.h:249
ClimbingStaminaPolicy staminaPolicy
Definition Climbing.h:251
Quantized selection-cost weights stored with a profile.
Definition Climbing.h:126
Normalized action interval selecting which motion-warp channels may correct authored motion.
Definition Climbing.h:151