14#include <simplesquirrel/simplesquirrel.hpp>
23std::set<std::string> csv(std::string_view
s) {
24 std::set<std::string> out;
25 std::stringstream in{std::string(
s)};
27 while (std::getline(in,
v,
','))
28 if (!
v.empty()) out.insert(
v);
31std::string
quote(std::string_view
s) {
35 if (
c ==
'"' ||
c ==
'\\') o <<
'\\';
40std::string join(
const std::set<std::string>&
s) {
43 if (!o.empty()) o +=
',';
48bool finite(
float v) {
return std::isfinite(
v); }
50template <
class Ref,
class Proxy,
class Release>
54 const Ref ref = std::move(
reference).takeValue();
55 auto object = eve::script::makeOwnedSquirrelInstance<Proxy>(
vm, std::make_unique<Proxy>(ref));
58 object.ignore(
"failed to create owned sensing proxy");
59 std::invoke(std::forward<Release>(
release), ref).ignore(
"rollback failed owned sensing allocation");
63 ssq::Object
owned = std::move(
object).takeValue();
65 result.set(
"value",
owned);
66 result.set(
"ownership", std::string(
"owned"));
67 result.set(
"ownerEpoch",
static_cast<std::int64_t
>(ref.ownerEpoch));
68 result.set(
"handle",
static_cast<std::int64_t
>(ref.packed()));
73std::vector<std::string> toVector(
const std::set<std::string>&
values) {
74 return std::vector<std::string>(
values.begin(),
values.end());
77bool insideCone(
float px,
float py,
const QueryCone& cone) {
78 const float dx =
px - cone.x;
79 const float dy =
py - cone.y;
81 if (
distSq > cone.range * cone.range)
return false;
82 if (
distSq == 0.f)
return true;
83 const float facingLen = std::hypot(cone.dirX, cone.dirY);
84 if (!(facingLen > 0.f))
return false;
85 const float invDist = 1.f / std::sqrt(
distSq);
86 const float nx =
dx * invDist;
87 const float ny =
dy * invDist;
88 const float fx = cone.dirX / facingLen;
89 const float fy = cone.dirY / facingLen;
90 const float dot = std::clamp(
nx * fx +
ny * fy, -1.f, 1.f);
91 return std::acos(dot) <= cone.halfAngle;
96 [&](
const auto&
value) ->
bool {
97 using T = std::decay_t<
decltype(
value)>;
98 if constexpr (std::is_same_v<T, std::monostate>) {
100 }
else if constexpr (std::is_same_v<T, QueryCircle>) {
104 }
else if constexpr (std::is_same_v<T, QueryBox>) {
115 std::string_view excludeTagsCsv, std::string_view includeFactionsCsv,
116 std::string_view excludeFactionsCsv, std::string_view visibleTo,
int limit) {
120 "circle query requires finite coordinates, non-negative radius and limit",
"query", {},
"sensing"));
123 spec.shape = QueryCircle{
x,
y,
radius};
124 spec.requiredTags = toVector(csv(requireTagsCsv));
125 spec.excludedTags = toVector(csv(excludeTagsCsv));
126 spec.includeFactions = toVector(csv(includeFactionsCsv));
127 spec.excludeFactions = toVector(csv(excludeFactionsCsv));
128 if (!visibleTo.empty()) spec.visibleTo = std::string(visibleTo);
132 spec.maxCount =
static_cast<std::uint32_t
>(
limit);
138eve::Result<QuerySpec> boxSpec(
float minX,
float minY,
float maxX,
float maxY, std::string_view requireTagsCsv,
139 std::string_view excludeTagsCsv, std::string_view includeFactionsCsv,
140 std::string_view excludeFactionsCsv, std::string_view visibleTo,
int limit) {
144 "query", {},
"sensing"));
147 spec.shape = QueryBox{minX, minY, maxX, maxY};
148 spec.requiredTags = toVector(csv(requireTagsCsv));
149 spec.excludedTags = toVector(csv(excludeTagsCsv));
150 spec.includeFactions = toVector(csv(includeFactionsCsv));
151 spec.excludeFactions = toVector(csv(excludeFactionsCsv));
152 if (!visibleTo.empty()) spec.visibleTo = std::string(visibleTo);
154 spec.maxCount =
static_cast<std::uint32_t
>(
limit);
164 "QuerySpec.minRange must be finite and >= 0",
165 "query.minRange", {},
"sensing"));
170 "query.maxRange", {},
"sensing"));
179 "requiredTags cannot contain empty keys",
180 "query.requiredTags", {},
"sensing"));
185 "excludedTags cannot contain empty keys",
186 "query.excludedTags", {},
"sensing"));
191 "includeFactions cannot contain empty keys",
192 "query.includeFactions", {},
"sensing"));
197 "excludeFactions cannot contain empty keys",
198 "query.excludeFactions", {},
"sensing"));
202 "visibleTo cannot be empty when set",
203 "query.visibleTo", {},
"sensing"));
205 const auto shapeOk = std::visit(
207 using T = std::decay_t<
decltype(
value)>;
208 if constexpr (std::is_same_v<T, QueryCircle>) {
212 "QueryCircle requires finite center and non-negative radius",
"query.shape", {},
"sensing"));
214 }
else if constexpr (std::is_same_v<T, QueryBox>) {
218 "QueryBox requires finite ordered bounds",
219 "query.shape", {},
"sensing"));
221 }
else if constexpr (std::is_same_v<T, QueryCone>) {
224 value.halfAngle > 3.14159265f ||
value.range < 0.f || !(std::hypot(
value.dirX,
value.dirY) > 0.f)) {
227 "QueryCone requires finite apex/dir, halfAngle in [0,pi], non-negative range, non-zero dir",
228 "query.shape", {},
"sensing"));
239 std::string_view
v) {
242 "subject id and finite coordinates are required",
243 "subject", {},
"sensing"));
247 if (spatialIndex_) indexSubject(subjects_[
subject.id]);
253 const std::string
key(
id);
254 if (subjects_.erase(
key) == 0)
262 const std::string
key(
id);
263 auto it = subjects_.find(
key);
264 if (it == subjects_.end())
267 auto zones = csv(zonesCsv);
268 for (
const auto& zone : zones) {
271 "zones cannot contain empty keys",
"subject.zones",
275 "zones entries must be valid LogicalId texts",
276 "subject.zones", {},
"sensing"));
278 it->second.zones = std::move(zones);
287 if (!
finite(cellSize) || !(cellSize > 0.f)) {
289 "spatial index cellSize must be finite and > 0",
290 "spatial.cellSize", {},
"sensing"));
293 spatialIndex_ = std::make_unique<eve::spatial::SpatialHash2D>(cellSize);
294 rebuildSpatialIndex();
300void SensingWorld::clearSpatialIndex() {
301 spatialIndex_.reset();
302 subjectSpatialIds_.clear();
303 spatialIdSubjects_.clear();
307void SensingWorld::rebuildSpatialIndex() {
308 subjectSpatialIds_.clear();
309 spatialIdSubjects_.clear();
311 if (!spatialIndex_)
return;
312 spatialIndex_->clear();
313 for (
const auto& [
id,
subject] : subjects_) {
319void SensingWorld::indexSubject(
const Subject&
subject) {
320 if (!spatialIndex_)
return;
321 auto it = subjectSpatialIds_.find(
subject.id);
322 if (it == subjectSpatialIds_.end()) {
323 const int sid = nextSpatialId_++;
324 subjectSpatialIds_.emplace(
subject.id, sid);
325 spatialIdSubjects_.emplace(sid,
subject.id);
332void SensingWorld::unindexSubject(
const std::string&
id) {
333 auto it = subjectSpatialIds_.find(
id);
334 if (it == subjectSpatialIds_.end())
return;
335 const int sid = it->second;
336 if (spatialIndex_) spatialIndex_->remove(sid);
337 spatialIdSubjects_.erase(sid);
338 subjectSpatialIds_.erase(it);
341bool SensingWorld::trySpatialBroadphase(
const QueryOrigin&
origin,
const QuerySpec& spec,
342 std::vector<const Subject*>& out)
const {
343 if (!spatialIndex_)
return false;
344 const bool ran = std::visit(
345 [&](
const auto&
value) ->
bool {
346 using T = std::decay_t<
decltype(
value)>;
347 if constexpr (std::is_same_v<T, std::monostate>) {
348 if (!(std::isfinite(spec.maxRange)))
return false;
349 spatialIndex_->queryCircle(
origin.x,
origin.y, spec.maxRange);
351 }
else if constexpr (std::is_same_v<T, QueryCircle>) {
354 }
else if constexpr (std::is_same_v<T, QueryBox>) {
363 if (!ran)
return false;
365 out.reserve(
static_cast<std::size_t
>(spatialIndex_->getResultCount()));
366 for (
int i = 0; i < spatialIndex_->getResultCount(); ++i) {
367 const int sid = spatialIndex_->getResultId(i);
368 const auto map = spatialIdSubjects_.find(sid);
369 if (map == spatialIdSubjects_.end())
continue;
370 const auto subject = subjects_.find(map->second);
371 if (
subject == subjects_.end())
continue;
372 out.push_back(&
subject->second);
377bool SensingWorld::accepts(
const Subject&
subject,
const QuerySpec& spec)
const {
378 for (
const auto&
tag : spec.requiredTags)
380 for (
const auto&
tag : spec.excludedTags)
382 if (!spec.includeFactions.empty()) {
383 bool included =
false;
384 for (
const auto& faction : spec.includeFactions)
385 if (
subject.faction == faction) {
389 if (!included)
return false;
391 for (
const auto& faction : spec.excludeFactions)
392 if (
subject.faction == faction) return false;
393 if (spec.visibleTo && !
subject.visibleTo.count(*spec.visibleTo))
return false;
394 return insideShape(
subject, spec.shape);
397void SensingWorld::publishResults(std::vector<RankedCandidate> ranked) {
399 results_.reserve(ranked.size());
400 for (
const auto& candidate : ranked)
401 results_.push_back(Candidate{candidate.
id, candidate.
x, candidate.
y, candidate.
distance});
402 lastQuery_.ranked = ranked;
403 lastQuery_.accepted =
static_cast<std::uint32_t
>(ranked.size());
412 "query.origin", {},
"sensing"));
415 std::vector<const Subject*> candidates;
416 const bool usedSpatial = trySpatialBroadphase(
origin, spec, candidates);
419 candidates.reserve(subjects_.size());
420 for (
const auto& [
id,
subject] : subjects_) {
422 candidates.push_back(&
subject);
426 std::vector<RankedCandidate> ranked;
427 ranked.reserve(candidates.size());
428 for (
const Subject* subjectPtr : candidates) {
433 if (distance < spec.minRange || distance > spec.
maxRange)
continue;
441 ranked.push_back(std::move(candidate));
446 if (a.distance != b.distance) return a.distance < b.distance;
450 std::sort(ranked.begin(), ranked.end(),
455 if (ranked.size() >
static_cast<std::size_t
>(spec.
maxCount)) ranked.resize(
static_cast<std::size_t
>(spec.
maxCount));
456 if (ranked.size() <
static_cast<std::size_t
>(spec.
minCount)) {
459 "candidate count is below QuerySpec.minCount after truncation",
"query.count", {},
"sensing"));
461 }
else if (ranked.size() <
static_cast<std::size_t
>(spec.
minCount) ||
462 ranked.size() >
static_cast<std::size_t
>(spec.
maxCount)) {
465 "candidate count violates QuerySpec count policy",
"query.count", {},
"sensing"));
468 lastQuery_.usedSpatial = usedSpatial;
469 lastQuery_.scanned =
static_cast<std::uint32_t
>(candidates.size());
470 lastQuery_.originX =
origin.x;
471 lastQuery_.originY =
origin.y;
472 lastQuery_.shapeKind = std::visit(
473 [](
const auto&
value) -> std::string {
474 using T = std::decay_t<
decltype(
value)>;
475 if constexpr (std::is_same_v<T, QueryCircle>)
return "circle";
476 if constexpr (std::is_same_v<T, QueryBox>)
return "box";
477 if constexpr (std::is_same_v<T, QueryCone>)
return "cone";
482 publishResults(ranked);
487 std::string_view i, std::string_view ef, std::string_view
v,
int limit) {
496 std::string_view i, std::string_view ef, std::string_view
v,
int limit) {
497 auto spec = boxSpec(
a,
b,
c,
d,
r, e, i, ef,
v,
limit);
499 const float originX = (
a +
c) * 0.5f;
500 const float originY = (
b +
d) * 0.5f;
501 auto result =
query(
QueryOrigin{originX, originY, std::nullopt}, std::move(spec).takeValue());
515 auto rankedResult = std::move(executed).takeValue();
516 std::vector<RankedCandidate> ranked(rankedResult.ranked().begin(), rankedResult.ranked().end());
517 const int count =
static_cast<int>(ranked.size());
518 publishResults(std::move(ranked));
527 std::ostringstream o;
528 o << std::setprecision(9);
529 o <<
"{\"schema\":\"eve.sensing.lastQuery\",\"version\":1"
530 <<
",\"origin\":{\"x\":" << lastQuery_.originX <<
",\"y\":" << lastQuery_.originY <<
'}'
531 <<
",\"shape\":" <<
quote(lastQuery_.shapeKind)
532 <<
",\"spatial\":{\"enabled\":" << (spatialIndex_ ?
"true" :
"false")
533 <<
",\"used\":" << (lastQuery_.usedSpatial ?
"true" :
"false")
534 <<
",\"scanned\":" << lastQuery_.scanned
535 <<
",\"accepted\":" << lastQuery_.accepted <<
'}'
538 for (
const auto& candidate : lastQuery_.ranked) {
539 if (!
first) o <<
',';
541 o <<
"{\"id\":" <<
quote(candidate.id) <<
",\"x\":" << candidate.x <<
",\"y\":" << candidate.y
542 <<
",\"distance\":" << candidate.distance <<
",\"score\":" << candidate.score
543 <<
",\"scoreReason\":" <<
quote(candidate.scoreReason) <<
'}';
550 std::ostringstream o;
551 o <<
"{\"schema\":\"eve.sensing.world\",\"version\":1,\"subjects\":[";
553 for (
auto& [
id,
s] : subjects_) {
554 if (!
first) o <<
',';
556 o <<
"{\"id\":" <<
quote(
id) <<
",\"x\":" << std::setprecision(9) <<
s.x <<
",\"y\":" <<
s.y
557 <<
",\"faction\":" <<
quote(
s.faction) <<
",\"tags\":" <<
quote(join(
s.tags))
558 <<
",\"visibleTo\":" <<
quote(join(
s.visibleTo)) <<
",\"zones\":" <<
quote(join(
s.zones)) <<
'}';
560 return o.str() +
"]}";
565 if (!
d.valid() || !
d.root().isObject() ||
d.root().getString(
"schema") !=
"eve.sensing.world" ||
566 d.root().getInt(
"version") != 1 || !
d.root().get(
"subjects").isArray()) {
571 auto a =
d.root().get(
"subjects");
572 for (
size_t n = 0;
n <
a.size(); ++
n) {
578 auto subject = next.upsert(
v.getString(
"id"),
float(
v.getDouble(
"x")),
float(
v.getDouble(
"y")),
579 v.getString(
"faction"),
v.getString(
"tags"),
v.getString(
"visibleTo"));
583 if (
v.has(
"zones")) {
584 auto zones = next.setZones(
v.getString(
"id"),
v.getString(
"zones"));
591 subjects_ = std::move(next.subjects_);
594 if (spatialIndex_) rebuildSpatialIndex();
598 return Sensing::create()->worlds_.emplace(std::make_unique<SensingWorld>());
602 Sensing*
module = ModuleManager::getInstance<Sensing>("Sensing");
603 return module ? module->worlds_.resolve(reference) : eve::script::Borrowed<SensingWorld>();
607 Sensing*
module = ModuleManager::getInstance<Sensing>("Sensing");
611 return module->worlds_.erase(reference);
616 Sensing*
module = ModuleManager::getInstance<Sensing>("Sensing");
617 return !
module || module->worlds_.isStale(reference);
637 std::vector<eve::SensingWorldQuery> queries;
642 queries.push_back(std::move(entry));
648void Sensing::expose(ssq::Table&
t) {
649 auto c =
t.addClass<
Candidate>(
"SensingCandidate", std::function<Candidate*()>([]() {
return nullptr; }),
false);
650 c.addFunc(
"getId", [](Candidate*
v) {
return v ?
v->id : std::string{}; });
651 c.addFunc(
"getX", [](Candidate*
v) {
return v ?
v->x : 0.f; });
652 c.addFunc(
"getY", [](Candidate*
v) {
return v ?
v->y : 0.f; });
653 c.addFunc(
"getDistance", [](Candidate*
v) {
return v ?
v->distance : 0.f; });
655 auto w =
t.addClass<ScriptSensingWorld>(
"SensingWorld",
656 std::function<ScriptSensingWorld*()>([]() {
return nullptr; }),
false);
657 w.addFunc(
"ownership", [](ScriptSensingWorld*) {
return std::string(
"owned"); });
658 w.addFunc(
"ownerEpoch", [](ScriptSensingWorld*
value) {
659 return value ?
static_cast<std::int64_t
>(
value->reference.ownerEpoch) :
std::int64_t{0};
661 w.addFunc(
"handle", [](ScriptSensingWorld*
value) {
662 return value ?
static_cast<std::int64_t
>(
value->reference.packed()) :
std::int64_t{0};
665 w.addFunc(
"release", [
vm](ScriptSensingWorld*
value) {
669 "sensing world proxy must not be null",
"world", {},
"sensing")));
672 w.addFunc(
"upsert", [
vm](ScriptSensingWorld*
value,
const std::string&
id,
float x,
float y,
673 const std::string& faction,
const std::string& tags,
const std::string& visibleTo) {
677 "sensing world proxy must not be null",
"world", {},
"sensing")));
679 if (!
world.isBound())
685 w.addFunc(
"remove", [
vm](ScriptSensingWorld*
value,
const std::string&
id) {
689 "sensing world proxy must not be null",
"world", {},
"sensing")));
691 if (!
world.isBound())
697 w.addFunc(
"setZones", [
vm](ScriptSensingWorld*
value,
const std::string&
id,
const std::string& zones) {
701 "sensing world proxy must not be null",
"world", {},
"sensing")));
703 if (!
world.isBound())
713 w.addFunc(
"circle", [
vm, projectCount](ScriptSensingWorld*
value,
float x,
float y,
float radius,
714 const std::string&
required,
const std::string& excluded,
715 const std::string& includedFactions,
const std::string& excludedFactions,
716 const std::string& visibleTo,
int limit) {
721 "sensing world proxy must not be null",
"world", {},
"sensing")),
724 if (!
world.isBound())
733 w.addFunc(
"box", [
vm, projectCount](ScriptSensingWorld*
value,
float minX,
float minY,
float maxX,
float maxY,
734 const std::string&
required,
const std::string& excluded,
735 const std::string& includedFactions,
const std::string& excludedFactions,
736 const std::string& visibleTo,
int limit) {
741 "sensing world proxy must not be null",
"world", {},
"sensing")),
744 if (!
world.isBound())
750 return projectCount(
world->box(minX, minY, maxX, maxY,
required, excluded, includedFactions, excludedFactions,
753 w.addFunc(
"executePreset", [
vm, projectCount](ScriptSensingWorld*
value,
const std::string& presetId,
float originX,
754 float originY,
float dirX,
float dirY) {
759 "sensing world proxy must not be null",
"world", {},
"sensing")),
762 if (!
world.isBound())
768 return projectCount(
world->executePreset(presetId, originX, originY, dirX, dirY));
770 w.addFunc(
"resultAt", [](ScriptSensingWorld*
value,
int i) -> Candidate* {
771 if (!
value)
return nullptr;
773 if (!
world.isBound())
return nullptr;
774 auto candidate =
world->resultAt(i);
775 return candidate ?
const_cast<Candidate*
>(&candidate->get()) : nullptr;
777 w.addFunc(
"snapshotJson", [
vm](ScriptSensingWorld*
value) {
782 "sensing world proxy must not be null",
"world", {},
"sensing")),
785 if (!
world.isBound())
792 [](std::string
text) { return eve::Value(std::move(text)); });
794 w.addFunc(
"restoreJson", [
vm](ScriptSensingWorld*
value,
const std::string& json) {
798 "sensing world proxy must not be null",
"world", {},
"sensing")));
800 if (!
world.isBound())
806 w.addFunc(
"setSpatialIndexEnabled", [
vm](ScriptSensingWorld*
value,
bool enabled,
float cellSize) {
810 "sensing world proxy must not be null",
"world", {},
"sensing")));
812 if (!
world.isBound())
818 w.addFunc(
"spatialIndexEnabled", [](ScriptSensingWorld*
value) {
819 if (!
value)
return false;
821 return world.isBound() &&
world->spatialIndexEnabled();
823 w.addFunc(
"debugLastQueryJson", [
vm](ScriptSensingWorld*
value) {
828 "sensing world proxy must not be null",
"world", {},
"sensing")),
831 if (!
world.isBound())
838 [](std::string
text) { return eve::Value(std::move(text)); });
840 auto cls =
t.addClass(
name, Sensing::create,
false);
843void Sensing::expose(ssq::Class&
c) {
845 c.addFunc(
"newWorld", [
vm =
c.getHandle()](
Sensing*) {
846 return makeOwnedProxy<SensingWorldHandleRef, ScriptSensingWorld>(
847 vm, Sensing::newWorld(), [](SensingWorldHandleRef reference) { return Sensing::release(reference); });
AuthorityStoreHandleRef reference
struct SQVM * HSQUIRRELVM
std::map< std::string, Var > values
wgpu::PopErrorScopeStatus status
Strong identity values shared by persistence, content and logical-name APIs.
const std::string * accepts
#define Module_IMPL(ModuleName, newExpr)
std::function< eve::Result< void >()> release
The single Squirrel projection for common Result, Status and Value.
Ordered Select/Filter/Sort task pipeline over SensingWorld candidates.
const VegetationPresetContext & context
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.
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
virtual std::string getName() const =0
Returns the name.
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Structured status and zero or more diagnostics for an operation.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
The canonical owning dynamic value used by data-facing protocols.
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Non-owning C++ observation with no implicit lifetime extension.
Owning ranked result; does not assign a primary target.
Gameplay-facing 2D candidate query service; it never values or selects targets.
eve::Result< CandidateQueryResult > query(const QueryOrigin &origin, const QuerySpec &spec)
Runs a configurable candidate query.
eve::Result< void > restoreJson(const std::string &json)
Restores a snapshot transactionally.
eve::Result< void > setSpatialIndexEnabled(bool enabled, float cellSize=64.f)
Enables or disables an optional SpatialHash2D broadphase (off by default).
eve::Result< int > circle(float x, float y, float radius, std::string_view requireTagsCsv, std::string_view excludeTagsCsv, std::string_view includeFactionsCsv, std::string_view excludeFactionsCsv, std::string_view visibleTo, int limit)
Queries a circle. Filters are CSV; empty fields disable that filter.
eve::Result< void > setZones(std::string_view id, std::string_view zonesCsv)
Replaces logical zone membership for an existing subject.
std::string snapshotJson() const
Exports deterministic JSON.
eve::Result< int > executePreset(std::string_view presetId, float originX, float originY, float dirX, float dirY)
Runs a registered TargetingPreset against this world and refreshes resultAt().
std::string debugLastQueryJson() const
Deterministic JSON dump of the last successful query for MCP/debug overlays.
eve::Result< void > upsert(std::string_view id, float x, float y, std::string_view faction, std::string_view tagsCsv, std::string_view visibleToCsv)
Inserts or replaces mirrored facts. CSV fields contain comma-separated stable keys.
eve::Result< int > box(float minX, float minY, float maxX, float maxY, std::string_view requireTagsCsv, std::string_view excludeTagsCsv, std::string_view includeFactionsCsv, std::string_view excludeFactionsCsv, std::string_view visibleTo, int limit)
Queries an axis-aligned box with the same filters as circle().
eve::Result< void > remove(std::string_view id)
Removes mirrored facts, or returns NotFound when the id is absent.
eve::OptionalRef< const Candidate > resultAt(int index) const
Returns a candidate from the most recent query, or null for an invalid index.
bool spatialIndexEnabled() const noexcept
Reports whether the optional spatial broadphase is active.
Script factory for independent sensing worlds.
~Sensing() override
Sensing.
static eve::Result< SensingWorldHandleRef > newWorld()
Script factory for independent sensing worlds.
static bool isStale(SensingWorldHandleRef reference) noexcept
Reports whether a world reference is stale.
int worldCount() const override
Number of live sensing worlds created by the project.
Sensing()
Registers the read-only sensing capability for automation hosts.
static eve::script::Borrowed< SensingWorld > resolve(SensingWorldHandleRef reference) noexcept
Resolves a live sensing world as a non-owning observation.
std::vector< eve::SensingWorldQuery > lastQueries() const override
Last completed query of every live world, in slot order.
static eve::Result< void > release(SensingWorldHandleRef reference)
Releases a module-owned sensing world and invalidates its handle.
static TargetingPipeline & sharedBuiltins()
Process-local builtins pipeline used by SensingWorld::executePreset. @ownership Process lifetime; cal...
Result< CandidateQueryResult > executePreset(TargetingSourceContext &context, std::string_view presetId) const
Looks up a preset by id and executes it.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
@ TruncateToMax
Keep the best maxCount candidates after sorting (legacy circle/box limit).
std::variant< std::monostate, QueryCircle, QueryBox, QueryCone > QueryShape
Optional broad-phase shape; monostate means range-only around the origin.
std::optional< std::reference_wrapper< T > > OptionalRef
Optional borrowed reference; it does not extend T's lifetime.
Last completed query of one live sensing world.
std::string lastQueryJson
A deterministic candidate returned by a spatial query.
World2D origin used for range tests and distance sorting.
Configurable candidate query against SensingWorld.
std::vector< std::string > excludedTags
std::vector< std::string > excludeFactions
eve::Result< void > validate() const
Validates ranges, counts, tags and shape invariants.
std::vector< std::string > requiredTags
std::optional< std::string > visibleTo
std::vector< std::string > includeFactions
One ranked candidate produced by SensingWorld::query.
~ScriptSensingWorld() noexcept
SensingWorldHandleRef reference
ScriptSensingWorld(SensingWorldHandleRef value)
Mirrored gameplay facts used by candidate queries.
Runtime source for one pipeline execution.