XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFE.SpaceEngineers.AgentBridge

【SpaceEngineer】AI调试插件

公开
关注 0 Fork 0 Star 0
返回提交历史

SpaceEngineersModDev/XFE.SpaceEngineers.AgentBridge

增强网格/方块完整度与连接器遥测统计

为 grids.get、blocks.get、telemetry.snapshot 增加完整度与损伤统计,支持返回受损方块详情。补充文档说明完整度字段及用法。显著增强连接器遥测信息,便于诊断与自动化测试。

0a8b350
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

4 个文件 +125 -8
Modified README.md +4 -2
@@ -56,13 +56,13 @@ $cli = './src/XFE.SeAgent.Cli/bin/Release/net10.0/xfe-se.exe'
56 56 | --- | --- |
57 57 | `agent.ping` / `agent.capabilities` / `agent.events` | 协议探测、能力查询、带序号的事件日志 |
58 58 | `world.status` / `world.load` / `world.save` / `world.pause` / `world.exit` | 世界就绪状态、测试副本加载/保存、由插件持有的暂停,以及保存成功后正常退出游戏 |
59 | `grids.list` / `grids.get` | 网格标识、位置和朝向、包围盒、速度及物理状态 |
59 | `grids.list` / `grids.get` | 网格标识、位置和朝向、包围盒、速度及物理状态;`grids.get` 另含装甲在内的船体完整度与损伤汇总 |
60 60 | `blocks.list` / `blocks.get` | 设备、所属刚性网格、CustomData 与详细信息 |
61 61 | `blocks.actions` / `blocks.action` | 枚举和执行实际终端动作 |
62 62 | `blocks.properties` / `blocks.setProperty` | 枚举并设置受支持的终端属性类型 |
63 63 | `pb.read` / `pb.inspect` | 源码、哈希、配置、持久数据、Echo、编译错误、运行预算以及受限的脚本实例字段快照 |
64 64 | `pb.deploy` / `pb.run` | 带预期哈希校验和备份的源码部署、真实编译、运行参数 |
65 | `telemetry.snapshot` | 电池、气罐、推力和覆盖、陀螺、连接器、相机、库存、文本屏幕、编程块、舰船质量和速度 |
65 | `telemetry.snapshot` | 电池、气罐、推力和覆盖、陀螺、连接器约束点与吸附状态、相机、库存、文本屏幕、编程块、舰船质量、速度和完整度 |
66 66 | `cameras.scan` | 使用指定实际摄像机的充能和视场进行探测,返回命中位置与实体 |
67 67 | `grid.stop` | 测试网格清速度、推力/陀螺覆盖;可同时禁用其编程块,防止下一帧再次施力 |
68 68 | `debug.screenshot` | 请求引擎输出游戏截图,便于与遥测核对 |
@@ -71,6 +71,8 @@ PB 部署备份保存在测试存档的 `Storage/XFE.AgentBridge/Backups` 中,
71 71
72 72 `pb.inspect` 只遍历脚本程序集自身字段和有限集合,深度最多 3 层、总节点和字符串均有限制。不会执行任意 C#、调用用户指定的反射方法或遍历整个游戏对象图。
73 73
74 对接诊断可读取连接器的实际 `constraintPosition`、约束对端和距离、可连接/吸附状态及交易保护,避免把方块中心当作连接点。船体 `integrity` 区分已生效损伤、待结算伤害和未完成建造,并记录变形数量;每网格最多检查 20000 块,最多列出 32 个受损位置,超出时明确标记截断。普通 `grids.list` 不进行完整度汇总。用相同网格的飞行前后基线核对方块数量与完整度,具体字段见 [API 参考](docs/API.md)。
75
74 76 ## 通信和执行语义
75 77
76 78 管道名为 `XFE.SE.Agent.<PID>`,ACL 仅允许当前 Windows 用户并拒绝网络登录身份,不开 TCP 端口。每个连接发送一行 UTF-8 JSON,获得一行响应:
Modified docs/API.md +44 -3
@@ -73,7 +73,7 @@ JSON 字段名区分大小写。请求最大深度 32,不接受重复属性名
73 73 | 结构 | 字段 |
74 74 | --- | --- |
75 75 | `Grid` | `entityId`, `name`, `isStatic`, `gridSize`, `blockCount`, `pose`, `bounds`;存在物理对象时还有 `linearVelocity`, `angularVelocity`, `speed`。 |
76 | `Block` | `entityId`, `gridId`, `name`, `type`, `subtype`, `functional`, `working`, `ownerId`, `positionInGrid`, `pose`, `bounds`, `inventoryCount`;功能方块另有 `enabled`。 |
76 | `Block` | `entityId`, `gridId`, `name`, `type`, `subtype`, `functional`, `working`, `ownerId`, `positionInGrid`, `pose`, `bounds`, `inventoryCount`;功能方块另有 `enabled`,可取得底层方块时另有下述 `integrity`。 |
77 77 | 详细 `Block` | `Block` 加 `customData`, `detailedInfo`, `customInfo`。后两项各保留前 32768 字符,超出追加 `…`;`customData` 不截断,仍受响应总大小限制。 |
78 78
79 79 ## 3. Agent 与世界生命周期
@@ -99,7 +99,7 @@ JSON 字段名区分大小写。请求最大深度 32,不接受重复属性名
99 99 | 方法 | 参数 | `result` |
100 100 | --- | --- | --- |
101 101 | `grids.list` | `name`:可选名称子串;`offset`:默认 0、负数按 0;`limit`:默认 256、夹在 1–2048 | `{grids:[Grid],total,offset,truncated}`。过滤后的网格按实体 ID 排序;名称不区分大小写。 |
102 | `grids.get` | `entityId`:必填网格 ID | `Grid`。 |
102 | `grids.get` | `entityId`:必填网格 ID | `Grid` 加下述网格 `integrity` 汇总,包含普通装甲块。 |
103 103 | `blocks.list` | 下述选择参数;`offset`、`limit` 同 `grids.list` | `{blocks:[Block],total,offset,truncated}`。只列终端方块,不包含普通装甲块。 |
104 104 | `blocks.get` | `entityId`:必填终端方块 ID | 详细 `Block`。 |
105 105 | `grid.stop` | `entityId`:必填网格 ID;`disableProgrammableBlocks`:可选布尔,默认 false;写权限 | `Grid` 加 `clearedThrusters`, `clearedGyros`, `disabledProgrammableBlocks`, `note`。清零该网格推进器覆盖,清零陀螺仪 yaw/pitch/roll 并关闭覆盖,清除该网格速度;选项为 true 时禁用该网格 PB。 |
@@ -117,6 +117,32 @@ JSON 字段名区分大小写。请求最大深度 32,不接受重复属性名
117 117
118 118 `grid.stop` 是一次性操作,不会持续冻结网格,不会禁用所有推进器,也不会停止其他相连网格。仍在运行的控制器/PB 可以再次施力;调试时按需显式设置 `disableProgrammableBlocks:true` 并查询相关网格。
119 119
120 ### 完整度与船体损伤
121
122 单个 `Block.integrity` 直接读取游戏方块的完整度与损伤,数值保留游戏精度,不是百分比:
123
124 | 字段 | 含义 |
125 | --- | --- |
126 | `current`, `build`, `maximum` | 当前完整度、已经建造的组件完整度、完整建造时的上限。 |
127 | `currentDamage` | `build - current`,已生效的损伤;不会把尚未建造的组件算作损伤。 |
128 | `accumulatedDamage` | 已累积、尚待游戏结算的伤害。 |
129 | `hasDeformation` | 是否存在骨骼变形。 |
130
131 `grids.get` 和 `telemetry.snapshot.result.grids[]` 返回网格 `integrity`。它遍历该**单个网格**的所有方块类型,包含装甲,字段如下:
132
133 | 字段 | 含义 |
134 | --- | --- |
135 | `inspectedBlockCount`, `truncated` | 本次实际检查数量,以及是否存在未检查方块。每网格最多检查 **20000 块**;`truncated:true` 时以下总量只代表已检查部分。完整方块数仍见 `Grid.blockCount`。 |
136 | `current`, `build`, `maximum`, `currentDamage`, `accumulatedDamage` | 已检查方块对应数值的累加。 |
137 | `damagedBlockCount` | `currentDamage > 0` 或 `accumulatedDamage > 0` 的方块数量。 |
138 | `deformedBlockCount`, `incompleteBlockCount` | 发生变形的数量,以及 `build < maximum` 的数量;同一块可能同时计入多个类别。 |
139 | `damageLocations` | 最多 32 个受损或变形方块;每项为单块 `integrity` 字段加 `positionInGrid`。以网格坐标定位,不依赖返回顺序。 |
140 | `damageLocationsTruncated` | 已检查部分中,是否还有未列出的受损/变形位置;网格扫描是否完整另查 `truncated`。 |
141
142 普通 `grids.list` 和 `grid.stop` 的 `Grid` 不进行这项汇总。遥测只汇总本次实际返回方块所属的网格,不自动包含连接器或机械连接的其他网格。高频观察时用精确 `entityIds` 或 `gridId` 限定范围。
143
144 核对飞行损伤时,先保存相同网格 ID 的基线,再比较 `blockCount`、完整度、伤害和变形数量;被摧毁并移除的方块不会继续出现在当前损伤列表。完整度没有变化只能说明这些指标未记录到损伤,不能单独证明没有发生接触。
145
120 146 ## 5. 终端动作和属性
121 147
122 148 | 方法 | 参数 | `result` |
@@ -233,7 +259,7 @@ JSON 字段名区分大小写。请求最大深度 32,不接受重复属性名
233 259 | `battery` | `storedMWh`, `maxStoredMWh`, `inputMW`, `outputMW`, `chargeMode`, `charging`。 |
234 260 | `thrust` | `currentN`, `maximumN`, `maxEffectiveN`, `overrideN`, `overrideRatio`, `gridDirection`, `forceDirection`。`forceDirection` 为该推进器世界矩阵的 Backward。 |
235 261 | `gyro` | `override`, `power`, `yaw`, `pitch`, `roll`。 |
236 | `connector` | `status`, `otherConnectorId`(无对端为 null), `throwOut`, `collectAll`, `pullStrength`。 |
262 | `connector` | `status`, `connected`, `connectable`, `otherConnectorId`(无对端为 null), `throwOut`, `collectAll`, `pullStrength`;实际游戏连接器另有下述约束点、吸附和交易字段。 |
237 263 | `camera` | `enabledRaycast`, `availableScanRange`, `coneLimitDegrees`, `distanceLimit`。 |
238 264 | `gasTank` | `capacity`, `filledRatio`, `stockpile`。 |
239 265 | `flight` | `linearVelocity`, `angularVelocity`, `speed`, `naturalGravity`, `artificialGravity`, `totalMassKg`, `physicalMassKg`, `baseMassKg`, `centerOfMass`, `dampeners`, `underControl`, `controlThrusters`, `moveIndicator`, `rotationIndicator:{x,y}`, `rollIndicator`。仅船舶控制器提供。 |
@@ -242,6 +268,21 @@ JSON 字段名区分大小写。请求最大深度 32,不接受重复属性名
242 268 | `cargoInventory` | 有库存方块的分类标记;仅货箱、钻头、连接器为 true。反应堆、氢氧制造机等仍可返回库存,但此标记为 false;统计矿机待卸货库存时可据此排除燃料/生产库存。 |
243 269 | `screens` | 每方块最多 16 个屏幕,每项 `index`, `name`, `displayName`, `contentType`, `text`, `script`, `surfaceSize:{x,y}`;`text` 最多 8192 字符后加 `…`。没有屏幕时可缺省整个字段。 |
244 270
271 ### 连接器精确遥测
272
273 | 字段 | 含义 |
274 | --- | --- |
275 | `connected`, `connectable` | 分别表示 `status` 为 `Connected`、`Connectable`。 |
276 | `constraintPosition` | `MyShipConnector.ConstraintPositionWorld()` 返回的世界坐标,单位米;用于比较连接约束点,不能用 `pose.position` 的方块中心替代。 |
277 | `inConstraint` | 游戏的 `InConstraint` 标志。 |
278 | `magnetized` | 本桥计算的 `inConstraint && !connected`,表示已进入约束但还未锁定;不表示存在磁力范围内的所有候选接口。 |
279 | `constraintOtherConnectorId` | 游戏实际 `Other` 的实体 ID,无对端时为 null;吸附阶段也可能已有值。原有 `otherConnectorId` 来自脚本接口的 `OtherConnector`,不应用它代替本字段判断吸附对端。 |
280 | `otherConstraintPosition`, `constraintDistance` | 仅实际 `Other` 非空时返回,对端约束点及两个约束点的欧氏距离(米)。还未吸附时要在同一次查询中明确选择两端连接器,比较各自 `constraintPosition`。 |
281 | `isSmallConnector` | 游戏的小型接口类别标志,不等同于“安装在小网格上”。 |
282 | `tradingEnabled`, `protectedFromLockingByTrading` | 实际交易开关,以及游戏当前是否因交易保护而阻止锁定。 |
283
284 这些字段通过固定的游戏 API 只读取得,不主动寻找、吸附或连接接口。约束点距离只是捕获条件之一;还须结合状态、功能/供电、所有者、接口类型、方向与交易保护判断。捕获阈值属于当前游戏实现,插件不将某个固定距离当成通用的“可以连接”结论。
285
245 286 `screens[].text` 是文本接口读到的字符串,不是 Sprite/图形画面的 OCR。遥测是一次游戏线程采样;需要趋势时用 CLI `watch`。库存物品、屏幕文本或大量详细对象可能触发 4 MiB 响应上限,先减少数量或关闭可选展开项。
246 287
247 288 ## 9. CLI 调用
Modified src/XFE.SeAgent.Plugin/Game/GameDebugApi.cs +1 -1
@@ -45,7 +45,7 @@ namespace XFE.SeAgent.Plugin.Game
45 45 case "world.exit": RequireWritableWorld(); return ExitWorld(args);
46 46 case "world.pause": RequireWritableWorld(); return PauseWorld(args);
47 47 case "grids.list": RequireWorld(); return ListGrids(args);
48 case "grids.get": RequireWorld(); return DescribeGrid(Grid(Id(args, "entityId")));
48 case "grids.get": RequireWorld(); return DescribeGridWithIntegrity(Grid(Id(args, "entityId")));
49 49 case "blocks.list": RequireWorld(); return ListBlocks(args);
50 50 case "blocks.get": RequireWorld(); return DescribeBlock(Block(Id(args, "entityId")), true);
51 51 case "blocks.actions": RequireWorld(); return ListActions(args);
Modified src/XFE.SeAgent.Plugin/Game/WorldQueries.cs +76 -2
@@ -7,6 +7,8 @@ using Sandbox.ModAPI.Ingame;
7 7 using VRage.Game.ModAPI.Ingame;
8 8 using VRageMath;
9 9 using Terminal = Sandbox.ModAPI.Ingame.IMyTerminalBlock;
10 using SlimBlock = Sandbox.Game.Entities.Cube.MySlimBlock;
11 using ShipConnector = Sandbox.Game.Entities.Cube.MyShipConnector;
10 12
11 13 namespace XFE.SeAgent.Plugin.Game
12 14 {
@@ -35,6 +37,49 @@ namespace XFE.SeAgent.Plugin.Game
35 37 return result;
36 38 }
37 39
40 private static JObject DescribeGridWithIntegrity(MyCubeGrid grid)
41 {
42 var result = DescribeGrid(grid);
43 // Include armor as well as terminal blocks. Bound work on unusually large grids;
44 // ordinary grids.list queries do not perform this scan.
45 const int maximumBlocks = 20000;
46 int inspected = 0, damaged = 0, deformed = 0, incomplete = 0, affected = 0;
47 double integrity = 0, buildIntegrity = 0, maxIntegrity = 0, currentDamage = 0, pendingDamage = 0;
48 var damageLocations = new JArray();
49 foreach (var block in grid.GetBlocks())
50 {
51 if (inspected >= maximumBlocks) break;
52 inspected++;
53 integrity += block.Integrity; buildIntegrity += block.BuildIntegrity; maxIntegrity += block.MaxIntegrity;
54 currentDamage += block.CurrentDamage; pendingDamage += block.AccumulatedDamage;
55 bool isDamaged = block.CurrentDamage > 0 || block.AccumulatedDamage > 0;
56 if (isDamaged) damaged++;
57 if (block.HasDeformation) deformed++;
58 if (block.BuildIntegrity < block.MaxIntegrity) incomplete++;
59 if (isDamaged || block.HasDeformation) affected++;
60 if ((isDamaged || block.HasDeformation) && damageLocations.Count < 32)
61 {
62 var detail = DescribeIntegrity(block);
63 detail["positionInGrid"] = Vec(block.Position);
64 damageLocations.Add(detail);
65 }
66 }
67 result["integrity"] = new JObject { ["inspectedBlockCount"] = inspected,
68 ["truncated"] = inspected < grid.GetBlocks().Count, ["current"] = integrity,
69 ["build"] = buildIntegrity, ["maximum"] = maxIntegrity, ["currentDamage"] = currentDamage,
70 ["accumulatedDamage"] = pendingDamage, ["damagedBlockCount"] = damaged,
71 ["deformedBlockCount"] = deformed, ["incompleteBlockCount"] = incomplete,
72 ["damageLocations"] = damageLocations, ["damageLocationsTruncated"] = affected > damageLocations.Count };
73 return result;
74 }
75
76 private static JObject DescribeIntegrity(SlimBlock block)
77 {
78 return new JObject { ["current"] = block.Integrity, ["build"] = block.BuildIntegrity,
79 ["maximum"] = block.MaxIntegrity, ["currentDamage"] = block.CurrentDamage,
80 ["accumulatedDamage"] = block.AccumulatedDamage, ["hasDeformation"] = block.HasDeformation };
81 }
82
38 83 private IEnumerable<Terminal> SelectBlocks(JObject args)
39 84 {
40 85 if (args["entityIds"] is JArray ids)
@@ -65,6 +110,8 @@ namespace XFE.SeAgent.Plugin.Game
65 110 ["working"] = block.IsWorking, ["ownerId"] = Sid(block.OwnerId), ["positionInGrid"] = Vec(block.Position), ["pose"] = Pose(block.WorldMatrix),
66 111 ["bounds"] = Bounds(block.WorldAABB), ["inventoryCount"] = block.InventoryCount };
67 112 if (block is IMyFunctionalBlock functional) result["enabled"] = functional.Enabled;
113 var slim = (block.CubeGrid as MyCubeGrid)?.GetCubeBlock(block.Position);
114 if (slim != null) result["integrity"] = DescribeIntegrity(slim);
68 115 if (details) { result["customData"] = block.CustomData ?? ""; result["detailedInfo"] = Clip(block.DetailedInfo, 32768); result["customInfo"] = Clip(block.CustomInfo, 32768); }
69 116 return result;
70 117 }
@@ -82,7 +129,7 @@ namespace XFE.SeAgent.Plugin.Game
82 129 catch (Exception error) { item["telemetryError"] = error.GetType().Name + ": " + error.Message; }
83 130 result.Add(item);
84 131 }
85 var grids = blocks.Select(b => b.CubeGrid.EntityId).Distinct().Select(id => DescribeGrid(Grid(id)));
132 var grids = blocks.Select(b => b.CubeGrid.EntityId).Distinct().Select(id => DescribeGridWithIntegrity(Grid(id)));
86 133 return new JObject { ["utc"] = DateTime.UtcNow, ["frame"] = RequireWorld().GameplayFrameCounter, ["grids"] = new JArray(grids), ["blocks"] = result, ["total"] = selected.Count, ["truncated"] = selected.Count > limit };
87 134 }
88 135
@@ -91,7 +138,34 @@ namespace XFE.SeAgent.Plugin.Game
91 138 if (block is IMyBatteryBlock battery) item["battery"] = new JObject { ["storedMWh"] = battery.CurrentStoredPower, ["maxStoredMWh"] = battery.MaxStoredPower, ["inputMW"] = battery.CurrentInput, ["outputMW"] = battery.CurrentOutput, ["chargeMode"] = battery.ChargeMode.ToString(), ["charging"] = battery.IsCharging };
92 139 if (block is IMyThrust thrust) item["thrust"] = new JObject { ["currentN"] = thrust.CurrentThrust, ["maximumN"] = thrust.MaxThrust, ["maxEffectiveN"] = thrust.MaxEffectiveThrust, ["overrideN"] = thrust.ThrustOverride, ["overrideRatio"] = thrust.ThrustOverridePercentage, ["gridDirection"] = Vec(thrust.GridThrustDirection), ["forceDirection"] = Vec(thrust.WorldMatrix.Backward) };
93 140 if (block is IMyGyro gyro) item["gyro"] = new JObject { ["override"] = gyro.GyroOverride, ["power"] = gyro.GyroPower, ["yaw"] = gyro.Yaw, ["pitch"] = gyro.Pitch, ["roll"] = gyro.Roll };
94 if (block is IMyShipConnector connector) item["connector"] = new JObject { ["status"] = connector.Status.ToString(), ["otherConnectorId"] = connector.OtherConnector == null ? null : Sid(connector.OtherConnector.EntityId), ["throwOut"] = connector.ThrowOut, ["collectAll"] = connector.CollectAll, ["pullStrength"] = connector.PullStrength };
141 if (block is IMyShipConnector connector)
142 {
143 var status = connector.Status;
144 var value = new JObject { ["status"] = status.ToString(),
145 ["connected"] = status == MyShipConnectorStatus.Connected,
146 ["connectable"] = status == MyShipConnectorStatus.Connectable,
147 ["otherConnectorId"] = connector.OtherConnector == null ? null : Sid(connector.OtherConnector.EntityId),
148 ["throwOut"] = connector.ThrowOut, ["collectAll"] = connector.CollectAll, ["pullStrength"] = connector.PullStrength };
149 item["connector"] = value;
150 if (block is ShipConnector actual)
151 {
152 var point = actual.ConstraintPositionWorld();
153 value["constraintPosition"] = Vec(point);
154 value["inConstraint"] = actual.InConstraint;
155 value["magnetized"] = actual.InConstraint && status != MyShipConnectorStatus.Connected;
156 value["isSmallConnector"] = actual.IsSmallConnector;
157 value["tradingEnabled"] = actual.TradingEnabled.Value;
158 value["protectedFromLockingByTrading"] = actual.IsProtectedFromLockingByTrading();
159 var other = actual.Other;
160 value["constraintOtherConnectorId"] = other == null ? null : Sid(other.EntityId);
161 if (other != null)
162 {
163 var otherPoint = other.ConstraintPositionWorld();
164 value["otherConstraintPosition"] = Vec(otherPoint);
165 value["constraintDistance"] = Vector3D.Distance(point, otherPoint);
166 }
167 }
168 }
95 169 if (block is IMyCameraBlock camera) item["camera"] = new JObject { ["enabledRaycast"] = camera.EnableRaycast, ["availableScanRange"] = camera.AvailableScanRange, ["coneLimitDegrees"] = camera.RaycastConeLimit, ["distanceLimit"] = camera.RaycastDistanceLimit };
96 170 if (block is IMyGasTank tank) item["gasTank"] = new JObject { ["capacity"] = tank.Capacity, ["filledRatio"] = tank.FilledRatio, ["stockpile"] = tank.Stockpile };
97 171 if (block is IMyShipController controller)