外观
容器、末影箱与批量转移
ctx.containers 在机器人附近已有容器上执行原生开窗、读取、转移和关窗。它不会建箱、传送或操作任意玩家的背包。所有操作都通过 Operations 留下回执;成功 ACK 与独立读回是两种证据。
独立入口是 neomega_runtime.containers.Containers(operations)。ContainerKind 为 Literal["chest", "ender_chest", "double_chest", "barrel", "shulker_box"];TransferDirection 为 Literal["deposit", "withdraw"],可从该模块导入。
公共参数与权限
| 参数 | 要求 |
|---|---|
pos | 三个整数的方块坐标,SDK 转为列表;目标必须在机器人 6 格内且已有可核验方块。 |
dimension: int | 0..2,必须与实际交互维度一致。 |
kind | 默认 "chest";实际方块必须与指定类型相符,不自动切换。 |
idempotency_key: str | 独立业务动作的持久唯一标识,所有意图方法必传。 |
deadline: str | 带时区的绝对截止时间,所有意图方法必传。 |
inspect、transfer、merge 分别需要同名 operation 权限 framework.container.inspect、framework.container.transfer、framework.container.merge,均须清单声明、管理员授权和世界写入门允许。inspect 也会真实开关窗口,不是被动缓存查询。
| 类型 | 槽位 | 额外约束 |
|---|---|---|
chest | 0..26 | 单箱;遇到双箱不会自动扩展。 |
double_chest | 0..53 | 要求配对方块证据和本次完整 54 槽窗口内容。 |
ender_chest | 0..26 | 内容属于当前连接机器人,不能指定其他玩家。 |
barrel | 0..26 | 使用木桶原生窗口类型。 |
shulker_box | 0..26 | 使用潜影盒原生窗口类型。 |
检查内容与保存会话证据
text
ctx.containers.inspect(pos, *, dimension: int,
kind: ContainerKind = "chest", idempotency_key: str, deadline: str) -> OperationIntent该方法只准备意图;执行时 Host 打开真实容器、读取按序内容并关闭窗口。成功为 succeeded/verified,结果提供本次容器内容。保留完整结果,而不只保存 slots;原生连接绑定是否可用仍须检查 native_connection_verified。
python
intent = ctx.containers.inspect(
container_position, dimension=dimension, kind="ender_chest",
idempotency_key=inspection_key, deadline=deadline,
)
await save_intent(intent.payload()) # 调用方的持久化函数
operation_id = await ctx.operations.submit(intent)
await save_operation_id(operation_id)
receipt = await ctx.operations.wait(operation_id)
if receipt["state"] == "succeeded":
observation = receipt["result"]["result"]
await save_observation(observation)容器原生有序内容与结果中的 inventory_slots 不是同一种来源:后者是 bot_resource_cache,不能独立证明主背包的新鲜状态。主背包对账另用 ctx.inventory.observe(0, ...)。已知空槽须有明确空证据;没有返回槽位或观察缺失均不能当空气。
容器槽记录含 slot、stack_id;空槽有 empty: true,非空槽有 item、fingerprint、attributes_fingerprint、nbt_types、reconciliation_supported。它不是 InventorySlot,没有主背包的 known/state 字段。
结果还可能附带 native_inventory 原生背包快照,缺失时为 null。只有 native_connection_verified: true 及相应 connection_id、connected_at、observation_started_at、sampled_at 才提供连接绑定证据;不能把未绑定观察提升为严格对账依据。
分别保存 Host 会话标识与原生 connection_id / connected_at,不要互相替代。重新连接后,旧物品栈 ID、修订号及容器证据不能用于新动作。末影箱也需要重新观察,不能因为仍站在同一方块旁就沿用旧引用。
移入空槽、拆堆与合堆
text
ctx.containers.transfer(pos, *, dimension: int,
direction: TransferDirection, inventory_slot: int, container_slot: int,
count: int, expected_source_stack_id: int,
expected_destination_stack_id: int, expected_source_fingerprint: str,
idempotency_key: str, deadline: str,
kind: ContainerKind = "chest") -> OperationIntent
ctx.containers.merge(pos, *, dimension: int,
direction: TransferDirection, inventory_slot: int, container_slot: int,
count: int, expected_source_stack_id: int,
expected_destination_stack_id: int, expected_source_fingerprint: str,
expected_destination_fingerprint: str, expected_source_count: int,
expected_destination_count: int, destination_count_limit: int,
idempotency_key: str, deadline: str,
kind: ContainerKind = "chest") -> OperationIntent| 项目 | transfer | merge |
|---|---|---|
| 目标 | 已观察空槽,目的栈 ID 必须为 0。 | 已观察非空兼容栈,两端栈 ID 均大于 0。 |
| 源与目的 | deposit 为背包 → 容器;withdraw 为容器 → 背包。 | 方向相同,指纹和数量始终按实际源/目的填写。 |
| 数量 | count: 1..64,不超过源数量;部分数量形成拆堆。 | 同样要求,另需精确的两端原数量及目的数量上界。 |
| 指纹 | 原生源完整指纹。 | 原生两端完整指纹;不自行重建或用属性指纹替换。 |
| 槽编号 | inventory_slot: 0..35;容器槽由种类决定。 | 相同。 |
destination_count_limit 为 1..64,来自已知原数量或玩法约束,不是自动识别的物品最大堆叠数。必须满足 count <= expected_source_count 及 count + expected_destination_count <= destination_count_limit。SDK 不会在合堆失败后自动退回空槽转移,也不选择替代槽。
python
# source 是新鲜主背包槽,destination 是同连接容器检查所得空槽。
# 业务先验证数量、来源、连接、新鲜度,并保存两份完整观察。
if not (source["known"] and source["state"] == "occupied"
and destination.get("empty") is True
and destination.get("stack_id") == 0 and "item" not in destination):
raise ValueError("转移需要真实源物品和明确空目的槽")
intent = ctx.containers.transfer(
container_position, dimension=dimension, kind="chest", direction="deposit",
inventory_slot=source["slot"], container_slot=destination["slot"], count=1,
expected_source_stack_id=source["stack_id"],
expected_destination_stack_id=destination["stack_id"],
expected_source_fingerprint=source["fingerprint"],
idempotency_key=transfer_key, deadline=deadline,
)
# 保存意图后提交、保存 operation_id,再等待回执;流程同 inspect 示例。转移保留原物品,不能用命令生成一个“看起来相同”的物品替代。成功通常是 succeeded/acknowledged,结果含 server_acknowledged: true、independent_readback: false;inventory_before 是供读回核对使用的游标,可能为空,不是完整物品快照。下一步重新观察主背包与容器,结合typed NBT 比较验证数量和属性。
顺序批量执行与部分完成
text
await ctx.containers.batch(intents, *, journal, timeout: float = 30) -> dict
await neomega_runtime.container_batch.execute_batch(
operations, intents, *, journal, timeout=30
) -> dictintents 是 1..32 个已准备的 transfer / merge 意图,各 key 必须不同。不是 OperationIntent 的元素抛 TypeError;数量、种类、重复 key、journal 或超时参数不合法时抛 ValueError。timeout 为有限正数,不能超过 300 秒。它不是单条 Host 原子操作:SDK 顺序提交、等待、记录每步,第一项失败、未知或等待超时便停止,没有回滚。
journal(record) 必须是异步持久化函数。调用前由业务保存一次性准入;SDK 在提交前、取得 ID 后、取得回执后等待 journal 完成。重启、取消或重复业务请求都读取原 journal,不再次执行整个 batch。
python
# intents 只能包含彼此不依赖已变化栈 ID 的已准备操作。
# save_batch 必须持久化;本次业务已完成一次性准入。
batch_result = await ctx.containers.batch(
intents, journal=save_batch, timeout=30,
)
await save_batch(batch_result)返回对象含 state、completed、steps。每步保留 intent、状态以及已取得的 operation_id 和 receipt;未提交项为 not_started。全部成功为 succeeded,中途停止为 partial;completed 只统计成功回执,不表示独立读回通过或整批原子提交。取消会保存未知步骤后传播取消异常。journal 失败也不会授权重跑。
相邻步骤若复用刚变化的栈,需要重新观察再准备后续意图;不能预先猜测下一步栈 ID。此类依赖链应由业务逐步执行与核对,而不是把推测引用塞进一个 batch。
核对未声明变动的容器槽
text
from neomega_runtime.item_reconciliation import compare_container_unchanged
compare_container_unchanged(*, before, after, changed_slots,
session_before, session_after) -> bool这是纯比较函数,仅支持完整 27 槽 chest / ender_chest 的 ordered_inventory_content 观察。前后必须有 native_connection_verified: true,原生连接及起点、种类、位置、维度一致;session_before、session_after 是业务保留的同一非空 Host 会话标识。
python
from neomega_runtime.item_reconciliation import compare_container_unchanged
others_unchanged = compare_container_unchanged(
before=container_before, after=container_after,
changed_slots=[container_slot],
session_before=host_session_before, session_after=host_session_after,
)该函数检查所有未列入 changed_slots 的槽位保持不变。它不检查列入的槽位,所以还要用 compare_transfer 验证实际源/目的变化;它也不检查 inventory_slots 缓存、不认证来源、不证明因果。不支持 double_chest、barrel、shulker_box,不能裁成 27 槽后套用。格式或支持范围不符抛 ValueError;有效证据下发现差异返回 False。
失败与恢复
| 原因 | 意义与后续动作 |
|---|---|
container_observation_failed | 未取得可用观察;检查目标、范围、窗口与连接状态,不推断为空。 |
container_transfer_not_sent / container_merge_not_sent | 未进入转移;核对旧引用是否已变化。 |
container_transfer_rejected / container_merge_rejected | 服务端明确拒绝,保留 ACK。 |
container_transfer_uncertain / container_merge_uncertain | 转移已进入但结果不确定,保留原 key 和 ID,重新读回对账。 |
container_lifecycle_uncertain | 窗口开关生命周期不明,保留恢复隔离,停止后续写入。 |
权限拒绝、未进服、未连接及世界写入门关闭也可能阻止动作。不要捕获所有异常后直接返回“箱子为空”或自动换 key 重试。
管理员端 POST /runtime/admin/v1/recovery-container-inspection 是资源恢复接口,不是 ctx.containers 方法。在旧容器入口范围内,请求只接受明确的 confirm: true 与全部待恢复记录的精确 operation_ids,仅覆盖旧 Host 会话中 framework.container.inspect 的 container_lifecycle_uncertain、无部分效果且原参数有效的记录,排除 transfer/merge。
恢复还要求新连接独占、没有当前动作、屏幕已关闭等证明。它只释放旧屏幕资源,不修改原 unknown,不证明旧检查成功,更不证明物品未变化。插件作者应保留回执供管理员处理;业务物品结果仍按错误与结果核对单独核对。
原生窗口计划与租约
本节接口自配套 1.0.0 Host 与 SDK 提供。先从 Bus 目录取得 major 1 的完整合同;下列成员由同一原生窗口模块提供。托管插件必须分别声明并获准 permissions.services 中实际调用的 window.run、window.open、window.read、window.move、window.close;旧容器 operation 权限和 window.run 授权不会自动覆盖其他成员。
window.run 接受 typed Plan{target, steps}:目标包含 kind、三个 int32 坐标及 int32 维度;非空 steps 使用完整的 Read | Transfer | Merge union 合同,不能拿普通 JSON 或只有一个变体的类型代替。Transfer 要求已观察的空目的槽;Merge 要求两端真实 stack ID、指纹、精确数量及明确的目的数量上界。后续步骤不会自动从前一步 ACK 推导新 stack ID。
一次计划只开一个目标窗口,依次执行步骤并等待实际关闭。纯 Read 计划同样开关窗口,需要世界写入条件。接纳只返回持久 operation ID;最终回执 result.steps 记录逐步的 index、kind、entered 与 receipt。首个步骤错误后停止,已成功的写入不回滚;失败时的 partial_effects.acknowledged_steps 保留已确认写入索引。转移或合并的 ACK 仍不等于独立读回。明细超过记录上限时会明确给出 steps_recorded 和 results_omitted: true。
原有 ctx.containers.inspect/transfer/merge 保留原权限、参数与回执结构;ctx.containers.batch 仍是多个独立操作和 ID,不会自动变为单个 window.run 计划,也不提供原子回滚。
读后再决定下一步
| 成员 | 请求与结果 |
|---|---|
window.open | OpenArgs{target};成功回执的 result.handle 返回窗口句柄。 |
window.read | HandleArgs{handle};读取当前窗口,保留 typed NBT 与物品证据,不会自动关闭窗口。 |
window.move | MoveArgs{handle, step};使用完整 Step union,但只接受 Transfer 或 Merge。 |
window.close | HandleArgs{handle};明确关闭,确认实际关闭或实际连接 epoch 已变化后释放租约。 |
完整保存句柄的 lease_id、epoch、owner 和 generation,不要只存 lease ID,也不能修改字段转交给另一安装。Host 另行核对可信调用者;这里的 epoch 是实际游戏连接身份。每次调用各自持久接纳并查询原回执,先确认 open,再 read、按证据决定 move,最后显式 close。
租约存在时,其他会发送的操作返回 resource_busy 与占用句柄,持有者自己的其他发包或主动命令查询也不例外;纯缓存读取与 库存 View 不受该租约检查影响。取消等待不会关闭窗口或释放租约。开关窗或发送结果为 unknown 时,保留原意图、完整句柄和 operation ID 先对账,不重新开窗或重放未知动作;恢复成功也不会改写历史 unknown。详见错误与结果核对。
候选中的容器检查恢复还接受合同精确匹配 window.run major 1、schema digest 一致且参数有效的纯 Read 计划。仍要求明确确认、完整原 operation IDs、暂停接纳、实际排空、新连接与关闭证明;写入计划及带 partial_effects 的记录不在此恢复范围,恢复不修改历史 unknown。
多个受管动作需要连续占用同一组资源时,可使用 1.1.0 的资源会话 Scope。关闭会话与动作结果分开处理,资源占用不会增加本页成员权限,也不会回滚游戏效果。