* docs(sensing): propose UE5-aligned targeting pipeline design Document gaps between EVEngine sensing/Targeting* and UE5 GameplayTargetingSystem, and a phased Preset/Task plan that keeps candidate scoring out of domain semantics. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * docs(sensing): make QuerySpec the unified SensingWorld query surface Revise the targeting design so SensingWorld owns one configurable QuerySpec path; Provider/Resolver become compatibility adapters, and Phase 1 targets semantic unification instead of a thin bridge only. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(sensing): unify SensingWorld queries behind QuerySpec Add SensingWorld::query with CountPolicy/top-K ranking, route circle/box through it, and adapt Targeting via SensingWorldCandidateProvider so RTS and Action share one candidate fact source without inventing domain relations. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(sensing): add TargetingPipeline presets, cone geometry, executePreset Phase 2 of the UE5-aligned targeting design: registerable Select/Filter/Sort tasks, QueryCone + WorldArea::cone2D, script executePreset, and usr docs. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(sensing): migrate RTS acquisition to QuerySpec; add PerceptionFact bridge Phase 3 consumer work: CombatFireSystem builds QuerySpec instead of circle sugar while keeping RTS priority selection local. Add perceptionFactsFrom for npc_ai adapters; weapon lock-on remains deferred until that path exists. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(sensing): optional SpatialHash2D broadphase and last-query debug JSON Add SensingWorld::setSpatialIndexEnabled for an opt-in SpatialHash2D cull before QuerySpec filtering, plus debugLastQueryJson for MCP/debug overlays. Keep sorting/filtering QuerySpec-owned; document Phase 4 and cover parity with linear scan in tests. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(sensing): mirror zone membership onto TargetCandidate.zones Add SensingWorld::setZones for LogicalId membership facts, project them through SensingWorldCandidateProvider, and honor TargetingSpec.zone filters. Snapshot/restore and script bindings round-trip zones. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * feat(npc_ai): adapt sensing PerceptionFact into PerceptionMemory Add npc_ai/SensingPerception.h helpers that copy ranked sensing facts into adapter-owned perception memories without creating a sensing→npc_ai dependency. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> * docs(sensing): document Candidate and owned-world script bindings Cover getId/getX/getY/getDistance, getName, and ownership/ownerEpoch/handle so Source quality --strict binding checks pass for the sensing module. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
5.9 KiB
Sensing 模块
脚本入口: eve.Sensing()
Sensing 持有独立的 SensingWorld 事实镜像,并提供可配置的候选查询(QuerySpec)与
可组合的 Targeting Preset 管线。模块只返回有序候选与分数,不会选定 primary /
“最佳目标”;技能、武器、RTS 命令等消费者保留最终选择语义。
基本用法
local sensing = eve.Sensing();
local created = sensing.newWorld();
local world = created.value;
world.upsert("hero", 0.0, 0.0, "player", "unit", "");
world.upsert("foe", 5.0, 0.0, "enemy", "unit,combat-target", "player");
// 糖:内部转为 QuerySpec(TruncateToMax + distance sort)
local n = world.circle(0.0, 0.0, 12.0, "combat-target", "", "", "player", "", 8).value;
local first = world.resultAt(0);
// Preset 管线(内置 cone 选择)
local m = world.executePreset("sensing.builtin.coneSelect", 0.0, 0.0, 1.0, 0.0).value;
QuerySpec 与形状
SensingWorld.query(origin, spec) 是唯一过滤/排序实现面。circle / box 是糖。
| 字段 | 含义 |
|---|---|
shape |
QueryCircle / QueryBox / QueryCone / 空(仅按 origin 距离) |
minRange / maxRange |
相对 origin 的距离窗 |
requiredTags / excludedTags |
标签包含 / 排除 |
includeFactions / excludeFactions |
阵营字符串过滤 |
visibleTo |
观察者键(事实可见性,不是几何 LOS) |
minCount / maxCount + countPolicy |
TruncateToMax 或 FailIfOutOfRange |
sortKey |
None 或 DistanceAscending(分数为 -distance) |
QueryCone:顶点 (x,y),朝向 (dirX,dirY),halfAngle 为弧度 [0, π],range 为非负距离。
Targeting Preset 管线
TargetingPipeline 把有序 Select / Filter / Sort 任务组成 Preset:
| Task id | 行为 |
|---|---|
sensing.select.world |
从 SensingWorld 广相(复用 QuerySpec) |
sensing.filter.spec |
按 QuerySpec 字段子集再过滤 |
sensing.filter.cone |
朝向 + 半角 + 距离 |
sensing.filter.los |
需要注入的 ILineOfSightQuery capability |
sensing.sort.distance |
近→远,写入 score |
sensing.sort.truncate |
top-K |
内置 Preset:sensing.builtin.coneSelect(广相 → 45°锥 → 距离排序 → 截断 8)。
脚本 world.executePreset(id, originX, originY, dirX, dirY) 走共享 builtins 管线,并刷新 resultAt 缓存。
C++ 可自建管线:
auto pipeline = eve::sensing::TargetingPipeline::withBuiltins();
eve::sensing::TargetingSourceContext ctx;
ctx.world = &world;
ctx.origin = {x, y, std::nullopt};
ctx.dirX = dirX;
ctx.dirY = dirY;
auto ranked = pipeline.executePreset(ctx, "sensing.builtin.coneSelect");
与 Targeting* 协议的关系
SensingWorld= 候选事实 + QuerySpec / Preset 查询面。SensingWorldCandidateProvider/TargetingResolver= 兼容壳(Capability / Action)。visibleTo与几何 LOS 正交;domain≠Any需要注入阵营关系,否则Unsupported。- 结果从不隐式设置 primary。
常见问题
- 把
visibleTo当成 LOS——那是事实键;几何遮挡走ILineOfSightQuery。 - 期望 Preset 选出“最佳目标”——sensing 只排序;primary 由消费者显式
setPrimary。 - 在非 World2D 事实上跑 2D QuerySpec——World 事实面目前是 2D。
API 快查
| 对象 | API | 说明 |
|---|---|---|
Sensing |
getName() / newWorld() |
查询模块名,或创建模块拥有的 SensingWorld Result。 |
| 拥有型 World | ownership() / ownerEpoch() / handle() / isStale() / release() |
查询世代生命周期或释放 World。 |
| World | upsert / remove / setZones |
写入/删除主体事实,或更新逻辑 Zone 成员。 |
| World | query / circle / box / executePreset / resultAt |
查询候选并读取缓存结果。 |
| World | setSpatialIndexEnabled / spatialIndexEnabled / debugLastQueryJson |
可选空间广相与上次查询调试 JSON。 |
| World | snapshotJson / restoreJson |
确定性快照或事务性恢复 Result。 |
SensingCandidate |
getId() / getX() / getY() / getDistance() |
resultAt 返回的只读候选字段。 |
| 管线 | TargetingPipeline::withBuiltins / registerTask / registerPreset / execute / executePreset |
C++ Preset 注册与执行。 |
源码: src/modules/sensing/
设计文档: docs/dev/感知与目标选择系统设计.md
相关测试: test/sensing.cpp、test/targeting.cpp
消费者迁移(Phase 3)
- RTS
CombatFireSystem自动索敌已改为组装QuerySpec(QueryCircle+ tags/factions +TruncateToMax);目标 priority / stance 仍在 RTS。 perceptionFactsFrom(CandidateQueryResult)把有序候选投影为中立PerceptionFact;npc_ai 侧perceptionMemoriesFrom/perceptionMemoryFrom(npc_ai/SensingPerception.h)再写入PerceptionMemory。sensing 不依赖 npc_ai。- Weapon soft-lock / lock-on 获取路径尚未在 weapon 模块落地;落地时应走同一 QuerySpec/preset,aim assist 留游戏侧。
性能与调试(Phase 4)
setSpatialIndexEnabled(true, cellSize)打开可选SpatialHash2D广相(默认关)。过滤/排序仍由QuerySpec负责;索引只做候选裁剪。debugLastQueryJson()返回上次成功查询的确定性 JSON(eve.sensing.lastQuery):origin、shape、spatial.enabled/used/scanned/accepted、ranked scores。可供 MCP/debug overlay 消费;sensing 不依赖 editor。- 当前查询路径保持同步;异步 request 未引入。
Zone 成员
setZones(id, "ns:name,...") 写入主体的逻辑 Zone 成员(LogicalId 文本)。SensingWorldCandidateProvider 将其投影为 TargetCandidate.zones,并支持 TargetingSpec.zone 过滤。几何 Zone 文档仍由 crowd/editor 等模块拥有;sensing 只镜像成员关系。