载入中...
搜索中...
未找到
Settlement.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <exception>
6#include <limits>
7#include <memory>
8#include <utility>
9
10namespace eve::settlement {
11namespace {
12
15}
16
17eve::Result<void> validateFiniteNonNegative(double value, std::string_view name) {
18 if (!std::isfinite(value) || value < 0.0)
20 std::string(name) + " must be finite and non-negative",
21 std::string(name)));
23}
24
25bool isTerminalDisposition(SettlementDisposition disposition) noexcept {
26 switch (disposition) {
34 }
35 return true;
36}
37
38} // namespace
39
41 if (!request.target.isValid())
43 eve::DiagnosticCode::InvalidArgument, "settlement target must be a valid SubjectRef", "target"));
44 if (request.kind.empty())
46 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement kind must not be empty", "kind"));
47
48 auto magnitude = validateFiniteNonNegative(request.magnitude, "magnitude");
49 if (!magnitude) {
50 const auto status = magnitude.status();
52 }
53 if (!request.causation.isCanonical())
56 "settlement causation must use a canonical event or command id", "causation"));
57 if (!request.correlation.isCanonical())
59 eve::DiagnosticCode::InvalidArgument, "settlement correlation must use a canonical id", "correlation"));
60 for (std::size_t index = 0; index < request.decisions.size(); ++index) {
61 const auto& decision = request.decisions[index];
62 const auto path = "decisions[" + std::to_string(index) + "]";
63 if (!decision.stream.isValid())
66 "settlement random decision stream must be a valid LogicalId", path + ".stream"));
67 if (decision.sequence > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()))
70 "settlement random decision sequence exceeds the event encoding range", path + ".sequence"));
71 if (!std::isfinite(decision.sample) || decision.sample < 0.0 || decision.sample > 1.0)
73 eve::DiagnosticCode::InvalidArgument, "settlement random decision sample must be in [0,1]",
74 path + ".sample"));
75 if (!std::isfinite(decision.threshold) || decision.threshold < 0.0 || decision.threshold > 1.0)
77 eve::DiagnosticCode::InvalidArgument, "settlement random decision threshold must be in [0,1]",
78 path + ".threshold"));
79 for (std::size_t previous = 0; previous < index; ++previous)
80 if (request.decisions[previous].stream == decision.stream &&
81 request.decisions[previous].sequence == decision.sequence)
84 "settlement random decision stream and sequence must be unique", path + ".sequence"));
85 }
86 if (request.chain.depth == 0) {
87 if (!request.trigger.empty() || request.chain.emittedCount != 0 || !request.chain.triggerPath.empty())
90 "root settlement requests must not contain derived-chain metadata", "chain"));
91 } else {
92 if (request.trigger.empty())
95 "derived settlement requests require a non-empty trigger key", "trigger"));
96 if (request.chain.triggerPath.size() != request.chain.depth || request.chain.triggerPath.back() != request.trigger)
99 "derived settlement trigger path must match its depth and current trigger", "chain"));
100 if (request.chain.emittedCount == 0)
103 "derived settlement requests require a positive emitted count", "chain.emittedCount"));
104 }
106}
107
108namespace {
109
110const char* traceLevelName(SettlementTraceLevel level) noexcept {
111 switch (level) {
112 case SettlementTraceLevel::Off: return "off";
113 case SettlementTraceLevel::Summary: return "summary";
114 case SettlementTraceLevel::Full: return "full";
115 }
116 return "unknown";
117}
118
119eve::Result<void> validateFrame(const SettlementContext& context) {
120 auto magnitude = validateFiniteNonNegative(context.magnitude(), "working magnitude");
121 if (!magnitude) {
122 const auto status = magnitude.status();
124 }
125 auto absorbed = validateFiniteNonNegative(context.projectedResult().absorbed, "absorbed");
126 if (!absorbed) {
127 const auto status = absorbed.status();
129 }
130 auto resisted = validateFiniteNonNegative(context.projectedResult().resisted, "resisted");
131 if (!resisted) {
132 const auto status = resisted.status();
134 }
136}
137
138eve::Diagnostic stageDiagnostic(std::string_view stage, const eve::Status& status) {
140 details.emplace_back("stage", std::string(stage));
141 details.emplace_back("status", std::string(eve::statusCodeName(status.code())));
143 "settlement stage failed: " + std::string(stage),
144 "settlement.stage." + std::string(stage), details);
145}
146
147eve::Status stageFailureStatus(std::string_view stage, const eve::Status& status) {
148 auto diagnostics = status.diagnostics();
149 diagnostics.emplace_back(stageDiagnostic(stage, status));
150 return eve::Status(status.code(), std::move(diagnostics));
151}
152
153eve::Result<game_event::GameEvent> makeEvent(const SettlementContext& context) {
154 const auto& request = context.request();
155 const auto& result = context.projectedResult();
156
157 auto schema = eve::LogicalId::parse("settlement:result");
158 if (!schema)
161 "settlement result schema id is not a valid LogicalId", "event.schemaId"));
162
164 payload["kind"] = request.kind;
165 payload["resource"] = request.resource;
166 payload["requested"] = result.requested;
167 payload["applied"] = result.applied;
168 payload["absorbed"] = result.absorbed;
169 payload["resisted"] = result.resisted;
170 payload["clamped"] = result.clamped;
171 payload["critical"] = result.critical;
172 payload["disposition"] = settlementDispositionName(result.disposition);
173 payload["tick"] = static_cast<std::int64_t>(result.tick.value());
174 payload["context"] = request.context;
175
177 tags.reserve(request.tags.size());
178 for (const auto& tag : request.tags) tags.emplace_back(tag);
179 payload["tags"] = eve::Value(std::move(tags));
180
181 eve::Value::Array decisions;
182 decisions.reserve(request.decisions.size());
183 for (const auto& decision : request.decisions) {
185 value["stream"] = decision.stream.format();
186 value["sequence"] = static_cast<std::int64_t>(decision.sequence);
187 value["sample"] = decision.sample;
188 value["threshold"] = decision.threshold;
189 value["accepted"] = decision.accepted;
190 decisions.emplace_back(eve::Value(std::move(value)));
191 }
192 payload["decisions"] = eve::Value(std::move(decisions));
193
194 eve::Value::Array triggerPath;
195 triggerPath.reserve(request.chain.triggerPath.size());
196 for (const auto& trigger : request.chain.triggerPath) triggerPath.emplace_back(trigger);
197 payload["trigger"] = request.trigger;
199 {"depth", eve::Value(static_cast<std::int64_t>(request.chain.depth))},
200 {"emitted_count", eve::Value(static_cast<std::int64_t>(request.chain.emittedCount))},
201 {"trigger_path", eve::Value(std::move(triggerPath))},
202 });
203 payload["trace_level"] = traceLevelName(request.trace);
204
205 eve::Value::Array stages;
206 stages.reserve(result.stages.size());
207 for (const auto& stage : result.stages) {
208 eve::Value::Object stageValue;
209 stageValue["name"] = stage.name;
210 stageValue["kind"] = stageKindName(stage.kind);
211 stageValue["status"] = std::string(eve::statusCodeName(stage.status));
212 stageValue["before"] = stage.before;
213 stageValue["after"] = stage.after;
214 stageValue["details"] = stage.details;
215 stages.emplace_back(eve::Value(std::move(stageValue)));
216 }
217 payload["stages"] = eve::Value(std::move(stages));
218
219 auto encoded = eve::Value(std::move(payload)).toJson();
220 if (!encoded) {
221 const auto status = encoded.status();
223 }
224
225 game_event::GameEvent envelope;
226 envelope.type = "settlement.result";
227 envelope.source = request.source.isValid() ? request.source.format() : std::string{};
228 envelope.subject = request.target.format();
229 envelope.causation = request.causation;
230 envelope.correlation = request.correlation;
231 envelope.schemaId = *schema;
232 envelope.schemaVersion = eve::SchemaVersion(1);
233 envelope.tick = request.tick;
234 envelope.flags = result.critical ? 1u : 0u;
235 envelope.payload = std::move(encoded).takeValue();
236 return eve::Result<game_event::GameEvent>::success(std::move(envelope));
237}
238
239} // namespace
240
241const char* stageKindName(StageKind kind) noexcept {
242 switch (kind) {
243 case StageKind::Validate: return "validate";
244 case StageKind::Decision: return "decision";
245 case StageKind::SourceModifiers: return "source_modifiers";
246 case StageKind::TargetMitigation: return "target_mitigation";
247 case StageKind::ArmorShield: return "armor_shield";
248 case StageKind::Clamp: return "clamp";
249 case StageKind::Apply: return "apply";
250 case StageKind::Event: return "event";
251 case StageKind::Trigger: return "trigger";
252 }
253 return "unknown";
254}
255
256const char* settlementDispositionName(SettlementDisposition disposition) noexcept {
257 switch (disposition) {
258 case SettlementDisposition::Applied: return "applied";
259 case SettlementDisposition::NoOp: return "no_op";
260 case SettlementDisposition::Immune: return "immune";
261 case SettlementDisposition::Resisted: return "resisted";
262 case SettlementDisposition::PartiallyApplied: return "partially_applied";
263 case SettlementDisposition::Blocked: return "blocked";
264 case SettlementDisposition::InvalidTarget: return "invalid_target";
265 }
266 return "unknown";
267}
268
269bool SettlementResult::hasStage(std::string_view name) const noexcept { return stage(name) != nullptr; }
270
271std::size_t SettlementResult::ruleEvaluationCount() const noexcept {
272 return static_cast<std::size_t>(std::count_if(stages.begin(), stages.end(), [](const auto& stage) {
273 return stage.name.starts_with("zz_rule.");
274 }));
275}
276
277std::size_t SettlementResult::ruleMatchCount() const noexcept {
278 return static_cast<std::size_t>(std::count_if(stages.begin(), stages.end(), [](const auto& stage) {
279 return stage.name.starts_with("zz_rule.") && stage.status == eve::StatusCode::Applied;
280 }));
281}
282
283const SettlementStageResult* SettlementResult::stage(std::string_view name) const noexcept {
284 const auto it = std::find_if(stages.begin(), stages.end(), [&](const auto& value) { return value.name == name; });
285 return it == stages.end() ? nullptr : &*it;
286}
287
289 : commit_(std::move(commit)), rollback_(std::move(rollback)) {}
290
292 : commit_(std::move(other.commit_)),
293 rollback_(std::move(other.rollback_)),
294 committed_(other.committed_),
295 rolledBack_(other.rolledBack_) {
296 other.commit_ = {};
297 other.rollback_ = {};
298 other.committed_ = true;
299 other.rolledBack_ = true;
300}
301
303 if (this == &other) return *this;
304 rollback();
305 commit_ = std::move(other.commit_);
306 rollback_ = std::move(other.rollback_);
307 committed_ = other.committed_;
308 rolledBack_ = other.rolledBack_;
309 other.commit_ = {};
310 other.rollback_ = {};
311 other.committed_ = true;
312 other.rolledBack_ = true;
313 return *this;
314}
315
317 // A successful commit remains published. The pipeline invokes rollback
318 // explicitly only when a later event append fails.
319 if (!committed_) rollback();
320}
321
322bool PreparedApply::isValid() const noexcept {
323 return static_cast<bool>(commit_) && static_cast<bool>(rollback_) && !rolledBack_;
324}
325
327 if (committed_) return success(eve::StatusCode::Applied);
328 if (!isValid())
330 eve::DiagnosticCode::InvariantViolation, "settlement mutation is empty or already rolled back", "apply"));
331
332 try {
333 auto outcome = commit_();
334 const bool passed = outcome.ok();
335 if (passed) {
336 committed_ = true;
337 return outcome;
338 }
339 const auto status = outcome.status();
341 } catch (const std::exception& exception) {
343 eve::DiagnosticCode::Failed, std::string("settlement apply commit threw: ") + exception.what(), "apply"));
344 } catch (...) {
346 eve::DiagnosticCode::Failed, "settlement apply commit threw an unknown exception", "apply"));
347 }
348}
349
350void PreparedApply::rollback() noexcept {
351 if (rolledBack_ || !rollback_) return;
352 try {
353 rollback_();
354 } catch (...) {
355 // A rollback exception would make the public failure contract false:
356 // the caller could observe a failure while domain state remained
357 // partially applied. There is no safe recovery path from noexcept
358 // rollback (including the destructor path), so fail closed.
359 std::terminate();
360 }
361 rolledBack_ = true;
362}
363
364SettlementContext::SettlementContext(const SettlementRequest& request, SettlementResult& result,
365 ISettlementPolicy& policy)
366 : request_(request), result_(result), policy_(policy), magnitude_(request.magnitude) {}
367
369 if (applyPrepared_) {
370 recordMutationViolation("settlement magnitude cannot change after apply preparation", "magnitude");
372 eve::DiagnosticCode::Conflict, "settlement magnitude is frozen after apply preparation", "magnitude"));
373 }
374 auto valid = validateFiniteNonNegative(value, "working magnitude");
375 if (!valid) {
376 const auto status = valid.status();
378 }
379 magnitude_ = isTerminalDisposition(disposition_) ? 0.0 : value;
381}
382
384 if (applyPrepared_) {
385 recordMutationViolation("settlement absorbed amount cannot change after apply preparation", "absorbed");
387 eve::DiagnosticCode::Conflict, "settlement absorbed amount is frozen after apply preparation", "absorbed"));
388 }
389 auto valid = validateFiniteNonNegative(value, "absorbed amount");
390 if (!valid) {
391 const auto status = valid.status();
393 }
394 if (value > std::numeric_limits<double>::max() - absorbed_)
396 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "absorbed amount overflowed", "absorbed"));
397 absorbed_ += value;
399}
400
402 if (applyPrepared_) {
403 recordMutationViolation("settlement resisted amount cannot change after apply preparation", "resisted");
405 eve::DiagnosticCode::Conflict, "settlement resisted amount is frozen after apply preparation", "resisted"));
406 }
407 auto valid = validateFiniteNonNegative(value, "resisted amount");
408 if (!valid) {
409 const auto status = valid.status();
411 }
412 if (value > std::numeric_limits<double>::max() - resisted_)
414 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "resisted amount overflowed", "resisted"));
415 resisted_ += value;
417}
418
420 if (applyPrepared_) {
421 recordMutationViolation("settlement clamped amount cannot change after apply preparation", "clamped");
423 eve::DiagnosticCode::Conflict, "settlement clamped amount is frozen after apply preparation", "clamped"));
424 }
425 auto valid = validateFiniteNonNegative(value, "clamped amount");
426 if (!valid) {
427 const auto status = valid.status();
429 }
430 if (value > std::numeric_limits<double>::max() - clamped_)
432 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "clamped amount overflowed", "clamped"));
433 clamped_ += value;
435}
436
438 if (applyPrepared_) {
439 recordMutationViolation("settlement disposition cannot change after apply preparation", "disposition");
441 eve::DiagnosticCode::Conflict, "settlement disposition is frozen after apply preparation",
442 "disposition"));
443 }
444 disposition_ = disposition;
445 dispositionExplicit_ = true;
446 switch (disposition) {
450 case SettlementDisposition::InvalidTarget: magnitude_ = 0.0; break;
454 }
456}
457
459 if (applyPrepared_) {
460 recordMutationViolation("settlement clamp cannot change after apply preparation", "clamp");
462 eve::DiagnosticCode::Conflict, "settlement clamp is frozen after apply preparation", "clamp"));
463 }
464 if (value) {
465 auto valid = validateFiniteNonNegative(*value, "clamp maximum");
466 if (!valid) {
467 const auto status = valid.status();
469 }
470 }
471 clampMax_ = value;
473}
474
476 if (key.empty()) return;
477 if (request_.trace != SettlementTraceLevel::Full) return;
478 if (eventPrepared_) {
479 recordMutationViolation("settlement stage details cannot change after event preparation", "stage.details");
480 return;
481 }
482 stageDetails_[std::move(key)] = std::move(value);
483}
484
486 double next = std::max(0.0, magnitude_);
487 if (clampMax_) next = std::min(next, *clampMax_);
488 const double lost = magnitude_ - next;
489 if (lost > 0.0) {
490 auto recorded = addClamped(lost);
491 if (!recorded) {
492 const auto status = recorded.status();
494 }
495 }
496 magnitude_ = next;
498}
499
501 if (applyPrepared_)
503 eve::DiagnosticCode::Conflict, "settlement apply preparation may run only once", "apply"));
504 auto prepared = policy().prepareApply(*this);
505 const bool preparedOk = prepared.ok();
506 if (!preparedOk) {
507 const auto status = prepared.status();
509 }
510 auto mutation = std::move(prepared).takeValue();
511 if (!mutation.isValid())
513 eve::DiagnosticCode::InvariantViolation, "settlement policy returned an invalid mutation", "apply"));
514 pendingApply_.emplace(std::move(mutation));
515 applyPrepared_ = true;
517}
518
520 if (eventPrepared_)
522 eve::DiagnosticCode::Conflict, "settlement event preparation may run only once", "event"));
524 auto event = makeEvent(*this);
525 const bool eventOk = event.ok();
526 if (!eventOk) {
527 const auto status = event.status();
529 }
530 pendingEvent_.emplace(std::move(event).takeValue());
531 eventPrepared_ = true;
533}
534
536 result_.applied = magnitude_;
537 result_.absorbed = absorbed_;
538 result_.resisted = resisted_;
539 result_.clamped = clamped_;
540 result_.critical = critical_;
541 if (dispositionExplicit_) {
542 result_.disposition = disposition_;
543 } else if (magnitude_ > 0.0 && (absorbed_ > 0.0 || resisted_ > 0.0 || clamped_ > 0.0)) {
545 } else if (magnitude_ > 0.0) {
547 } else if (resisted_ > 0.0) {
549 } else if (absorbed_ > 0.0) {
551 } else {
553 }
554}
555
557 if (request.trigger.empty())
560 "derived settlement request requires a non-empty trigger key", "derived.trigger"));
561 if (request.chain.depth != 0 || request.chain.emittedCount != 0 || !request.chain.triggerPath.empty())
564 "derived settlement request must not pre-populate chain metadata", "derived.chain"));
565 result_.derived.push_back(std::move(request));
567}
568
569void SettlementContext::recordMutationViolation(std::string message, std::string path) noexcept {
570 if (mutationViolation_) return;
571 try {
572 mutationViolation_.emplace(
574 } catch (...) {
575 // A late mutation is a contract violation. If recording its
576 // diagnostic itself cannot allocate, fail closed instead of allowing
577 // a possibly stale successful result to escape.
578 std::terminate();
579 }
580}
581
583
588
590 auto install = [&](StageKind kind, const char* name, StageFunction function, bool terminal = false) {
591 const int priority = terminal ? 0 : std::numeric_limits<int>::min();
592 stages_.push_back(StageEntry{kind, name, priority, nextRegistration_++, true, terminal, std::move(function)});
593 };
594
595 install(StageKind::Validate, "validate", [](SettlementContext& context) {
596 auto generic = validateSettlementRequest(context.request());
597 const bool genericOk = generic.ok();
598 if (!genericOk) {
599 const auto status = generic.status();
601 }
602 return context.policy().validate(context);
603 });
604 install(StageKind::Decision, "decision",
605 [](SettlementContext& context) { return context.policy().decide(context); });
606 install(StageKind::SourceModifiers, "source_modifiers",
607 [](SettlementContext& context) { return context.policy().sourceModifiers(context); });
608 install(StageKind::TargetMitigation, "target_mitigation",
609 [](SettlementContext& context) { return context.policy().targetMitigation(context); });
610 install(StageKind::ArmorShield, "armor_shield",
611 [](SettlementContext& context) { return context.policy().armorShield(context); });
612 install(
613 StageKind::Clamp, "clamp",
615 auto configured = context.policy().clamp(context);
616 const bool configuredOk = configured.ok();
617 if (!configuredOk) {
618 const auto status = configured.status();
620 }
621 return context.applyClamp();
622 },
623 true);
624 install(
625 StageKind::Apply, "apply",
627 context.synchronizeResult();
628 return context.prepareApply();
629 },
630 true);
631 install(
632 StageKind::Trigger, "trigger",
634 context.synchronizeResult();
635 auto derived = context.policy().prepareTrigger(context, context.projectedResult());
636 if (!derived) return eve::Result<void>::failure(derived.status());
637 for (auto& request : std::move(derived).takeValue()) {
638 auto emitted = context.emitDerived(std::move(request));
639 if (!emitted) return emitted;
640 }
641 return eve::Result<void>::success(context.result_.derived.empty()
644 },
645 true);
646 install(StageKind::Event, "event", [](SettlementContext& context) { return context.prepareEvent(); }, true);
647}
648
650 if (name.empty())
652 eve::DiagnosticCode::InvalidArgument, "settlement stage name must not be empty", "stage.name"));
653 if (!function)
655 eve::DiagnosticCode::InvalidArgument, "settlement stage function must not be empty", "stage.function"));
656 const auto duplicate =
657 std::find_if(stages_.begin(), stages_.end(), [&](const auto& stage) { return stage.name == name; });
658 if (duplicate != stages_.end())
660 eve::DiagnosticCode::AlreadyExists, "settlement stage name is already registered", "stage.name"));
661 if (nextRegistration_ == std::numeric_limits<std::uint64_t>::max())
663 eve::DiagnosticCode::InvariantViolation, "settlement stage registration sequence exhausted", "stage"));
664 stages_.push_back(StageEntry{kind, std::move(name), priority, nextRegistration_++, false, false,
665 std::move(function)});
667}
668
669eve::Result<void> SettlementPipeline::prepare(SettlementContext& context, SettlementResult& result) const {
670 std::vector<const StageEntry*> ordered;
671 ordered.reserve(stages_.size());
672 for (const auto& stage : stages_) ordered.push_back(&stage);
673 std::sort(ordered.begin(), ordered.end(), [](const auto* left, const auto* right) {
674 const auto leftKind = static_cast<std::uint8_t>(left->kind);
675 const auto rightKind = static_cast<std::uint8_t>(right->kind);
676 if (leftKind != rightKind) return leftKind < rightKind;
677 // Terminal preparation is an unregistrable phase boundary. It must
678 // sort after every custom stage in the same phase, even when a custom
679 // stage deliberately uses a larger priority or a lexicographically
680 // later name.
681 if (left->terminal != right->terminal) return !left->terminal && right->terminal;
682 if (left->canonical != right->canonical) return left->canonical && !right->canonical;
683 if (left->priority != right->priority) return left->priority < right->priority;
684 if (left->name != right->name) return left->name < right->name;
685 return left->registration < right->registration;
686 });
687
688 auto rollback = [&]() noexcept {
689 if (auto* pending = context.pendingApply()) pending->rollback();
690 };
691
692 for (const StageEntry* entry : ordered) {
693 context.beginStage();
694 const double before = context.magnitude();
695 auto outcome = entry->function(context);
696 const bool outcomeOk = outcome.ok();
697 const auto outcomeStatus = outcome.status();
698
699 context.synchronizeResult();
700 auto frame = validateFrame(context);
701 const bool frameOk = frame.ok();
702 const auto frameStatus = frame.status();
703 const auto* violation = context.mutationViolation();
704 const bool violationPresent = violation != nullptr;
705 const eve::Status violationStatus =
706 violationPresent ? eve::Status::failure(*violation) : eve::Status::success();
707
708 if (context.request().trace != SettlementTraceLevel::Off) {
709 SettlementStageResult stageResult;
710 stageResult.kind = entry->kind;
711 stageResult.name = entry->name;
712 stageResult.status = !outcomeOk ? outcomeStatus.code()
713 : !frameOk ? frameStatus.code()
714 : violationPresent ? violationStatus.code()
715 : outcomeStatus.code();
716 stageResult.before = before;
717 stageResult.after = context.magnitude();
718 stageResult.details = Value(context.stageDetails());
719 result.stages.push_back(std::move(stageResult));
720 }
721
722 if (!outcomeOk) {
723 rollback();
724 return eve::Result<void>::failure(stageFailureStatus(entry->name, outcomeStatus));
725 }
726 if (!frameOk) {
727 rollback();
728 return eve::Result<void>::failure(stageFailureStatus(entry->name, frameStatus));
729 }
730 if (violationPresent) {
731 rollback();
732 return eve::Result<void>::failure(stageFailureStatus(entry->name, violationStatus));
733 }
734 }
735
736 context.synchronizeResult();
737 if (context.pendingApply() == nullptr)
739 eve::DiagnosticCode::InvariantViolation, "settlement pipeline did not prepare an apply mutation",
740 "apply"));
742}
743
746 SettlementResult result;
747 result.requested = request.magnitude;
748 result.tick = request.tick;
749 SettlementContext context(request, result, policy);
750
751 auto prepared = prepare(context, result);
752 if (!prepared) return eve::Result<SettlementResult>::failure(prepared.status());
753
754 auto rollback = [&]() noexcept {
755 if (auto* pending = context.pendingApply()) pending->rollback();
756 };
757 auto* pending = context.pendingApply();
758
759 auto committed = pending->commit();
760 const bool committedOk = committed.ok();
761 if (!committedOk) {
762 const auto status = committed.status();
763 rollback();
765 }
766
767 if (const auto* preparedEvent = context.pendingEvent()) {
768 game_event::GameEvent envelope = *preparedEvent;
769 result.event = envelope;
770 if (events != nullptr) {
771 try {
772 auto appended = events->append(envelope);
773 const bool appendedOk = appended.ok();
774 if (!appendedOk) {
775 const auto status = appended.status();
776 rollback();
778 }
779 const auto sequence = std::move(appended).takeValue();
780 envelope.sequence = sequence;
781 if (const auto* stored = events->find(sequence)) {
782 result.event = *stored;
783 } else {
784 result.event = envelope;
785 }
786 } catch (const std::exception& exception) {
787 rollback();
790 std::string("settlement result event append threw: ") + exception.what(), "event"));
791 } catch (...) {
792 rollback();
794 eve::DiagnosticCode::Failed, "settlement result event append threw an unknown exception", "event"));
795 }
796 }
797 }
798
799 const auto status = result.applied == 0.0 ? eve::StatusCode::NoOp : eve::StatusCode::Applied;
801}
802
803} // namespace eve::settlement
double value
Value::Object payload
std::vector< QuestEvent > pending
int priority
std::vector< std::uint8_t > stored
Definition Evpack.cpp:171
std::string message
DiagnosticCode code
const GltfImportRequest & request
std::uint32_t key
wgpu::PopErrorScopeStatus status
float u
Definition Grass.cpp:233
HexVec3 left
HexVec3 right
TokenKind kind
std::string name
bool valid
int level
const std::string * tag
graphics::Canvas * previous
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string path
Definition PlayHost.cpp:110
MigrationFunction function
Battle::Events events
uint32_t index
const VegetationPresetContext & context
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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 std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
StatusCode code() const noexcept
Stable operation outcome.
Definition Status.h:98
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
const std::vector< Diagnostic > & diagnostics() const noexcept
All diagnostics in stable insertion order.
Definition Status.h:124
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
Deterministic in-memory stream of topic-neutral event envelopes.
Definition GameEvent.h:261
Domain-owned policy hooks used by the common settlement pipeline.
Definition Settlement.h:497
virtual eve::Result< std::vector< SettlementRequest > > prepareTrigger(const SettlementContext &context, const SettlementResult &result)
Produce owning derived requests without mutating domain state.
virtual eve::Result< void > decide(SettlementContext &context)
Resolve deterministic hit, immunity, block or other supplied decisions.
virtual eve::Result< PreparedApply > prepareApply(const SettlementContext &context)=0
Build an atomic candidate mutation without publishing it.
Prepared domain mutation used to make settlement application atomic.
Definition Settlement.h:281
std::function< eve::Result< void >()> CommitFunction
Definition Settlement.h:283
PreparedApply & operator=(const PreparedApply &)=delete
void rollback() noexcept
Restore or discard the staged candidate; safe to call repeatedly.
bool isValid() const noexcept
Return whether both transaction callbacks are available.
PreparedApply()=default
Construct an empty invalid mutation.
eve::Result< void > commit()
Publish the staged candidate; failure must leave state unchanged.
std::function< void()> RollbackFunction
Definition Settlement.h:284
Mutable calculation frame visible to policy and custom stage code.
Definition Settlement.h:338
SettlementDisposition disposition() const noexcept
Return the current semantic outcome.
Definition Settlement.h:391
eve::Result< void > addAbsorbed(double value)
Add an amount absorbed by a barrier or shield; late writes are rejected.
void setStageDetail(std::string key, Value value)
Attach one serializable explanation field to the current stage.
const SettlementRequest & request() const noexcept
Return the immutable request currently being settled.
Definition Settlement.h:341
eve::Result< void > setDisposition(SettlementDisposition disposition)
Set an explicit semantic outcome during calculation.
eve::Result< void > setClampMax(std::optional< double > value)
Set or clear the maximum amount used by the generic clamp stage.
eve::Result< void > emitDerived(SettlementRequest request)
Append one owning derived request during the Trigger phase.
eve::Result< void > prepareEvent()
Build the canonical GameEvent without appending it.
eve::Result< void > addClamped(double value)
Add an amount removed by the final clamp stage; late writes are rejected.
void synchronizeResult() noexcept
Copy the current frame counters into SettlementResult.
eve::Result< void > addResisted(double value)
Add an amount removed by target mitigation; late writes are rejected.
ISettlementPolicy & policy() const noexcept
Return the policy participating in this synchronous call.
Definition Settlement.h:344
eve::Result< void > applyClamp()
Apply the adapter-configured max bound and record any clamp loss.
eve::Result< void > setMagnitude(double value)
Set the current amount; late writes after Apply are rejected.
eve::Result< void > prepareApply()
Prepare the adapter mutation without publishing domain state.
std::function< eve::Result< void >(SettlementContext &)> StageFunction
Definition Settlement.h:544
eve::Result< void > addStage(StageKind kind, std::string name, int priority, StageFunction function)
Add one named custom stage; duplicate names are rejected.
eve::Result< SettlementResult > settle(const SettlementRequest &request, ISettlementPolicy &policy, game_event::GameEventLog *events=nullptr) const
Execute one settlement transaction against a domain policy.
SettlementPipeline()
Construct a pipeline with the canonical stages installed.
const char * settlementDispositionName(SettlementDisposition disposition) noexcept
Return the stable lowercase spelling of a settlement disposition.
StageKind
Kind of ordered calculation stage in a settlement pipeline.
Definition Settlement.h:36
const char * stageKindName(StageKind kind) noexcept
Return the stable lowercase spelling of a stage kind.
SettlementTraceLevel
Amount of stage explanation retained by one settlement request.
Definition Settlement.h:60
eve::Result< void > validateSettlementRequest(const SettlementRequest &request)
Validate the canonical semantic invariants of a settlement request.
SettlementDisposition
Semantic outcome of a completed settlement, distinct from its numeric amount.
Definition Settlement.h:49
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
constexpr std::string_view statusCodeName(StatusCode code) noexcept
Return the stable protocol spelling of a status code.
Definition Status.h:45
Domain-neutral, deterministic settlement pipeline.
Envelope metadata shared by event domains.
Definition GameEvent.h:183
EventSequence sequence
Sequence assigned by the owning stream; it is never global identity.
Definition GameEvent.h:187
std::vector< std::string > triggerPath
Definition Settlement.h:85
Domain-neutral input to one settlement operation.
Definition Settlement.h:107
std::string trigger
Stable re-entry key for a derived request; empty only for a root request.
Definition Settlement.h:119
SettlementChainContext chain
Definition Settlement.h:120
Complete observable outcome of one successful settlement.
Definition Settlement.h:163
const SettlementStageResult * stage(std::string_view name) const noexcept
Return the first stage explanation with this name, or null.
std::vector< SettlementRequest > derived
Owning derived requests emitted by the policy in deterministic order.
Definition Settlement.h:175
std::size_t ruleEvaluationCount() const noexcept
Count declarative rules evaluated in this retained trace.
std::size_t ruleMatchCount() const noexcept
Count declarative rules whose filter and condition matched.
std::vector< SettlementStageResult > stages
Definition Settlement.h:172
std::optional< game_event::GameEvent > event
Definition Settlement.h:173
SettlementDisposition disposition
Definition Settlement.h:170
bool hasStage(std::string_view name) const noexcept
Return whether a named stage explanation exists.
Explanation emitted for one executed stage.
Definition Settlement.h:145