跳转到正文

动作、战斗与死亡观察 ​

使用这些接口可以记录玩家挥手、地面状态、血量变化,追踪特定生物与箭的观察证据,并检查机器人自己的死亡记录和实际出站动作。每条证据都应保留连接、游标、身份和时间;发送成功、挥手、受伤、命中和所有权是不同事实。

先选择证据来源 ​

需求入口结果能证明什么
玩家动作与血量线索CombatObservations已绑定玩家的包字段和血量变化
特定生物受伤线索MobCombatObservations / MobObservationPump在本流中 Add 绑定后的目标证据
箭生命周期与 owner 字段ProjectileObservations收到的箭和 owner 元数据,不是射手或命中归因
自身死亡与相邻库存记录framework.self.death.read本连接机器人收到的有序证据
实际出站动作、最近输入及动作前检查framework.self.action_audit.read / framework.self.auth_input.observe / framework.self.action_guard.observe本地入队、编码和读取状态,不是服务器接受证明

前三种 Python 投影不自行联网,需要调用方提供一个新打开的 OriginalPackets 流及其初始快照。流需要 Host 独立授权所选包、规范化玩家状态和 Text 的组合访问;players: list / observe 不能替代此授权。其余接口通过操作系统调用,分别申请同名 permissions.operations 权限。权限存在还需能力目录和实际来源可用。

OriginalPackets 的连续消费 ​

python
from neomega_runtime.original_packets import OriginalPackets

stream, snapshot = await OriginalPackets.open(
    peer, session,
    required_packet_ids=[19, 27, 29, 44],
    exact_selection=True,
    text_profile="selected_only",
)
event = await stream.next()
event_or_none = await stream.poll()
await stream.close()

以上参数都可省略;exact_selection 默认为 False,设置为 True 时必须提供 required_packet_ids。所选 ID 是 1–32 个不同的非零 uint32;目标 Host 可支持更小的授权范围,不支持的选择会被拒绝。text_profile 只接受 selected_only,不增加权限。原始包接口、支持的表示和发送接口应通过协议接口核对。

每个事件含 sequence、state_revision,并有且仅有 update、text、packet 之一。packet 为 {"id": int, "format": str, "data": bytes};这里已经是 Python 不可变字节,不要再次做 base64 解码。格式可为 neomega-json-v1 或 tooldelta-custom-v1,下述投影要求相关包使用 neomega-json-v1,它不是网络 wire 格式。单包解码上限 1 MiB,整个编码文档上限 32 MiB。

必须把流的每一条事件按顺序交给投影,包括 Text、身份更新和无关包。序号重复、跳跃、epoch 改变、身份歧义或非法字段会使投影失效;不能过滤后再 apply,也不能自动重连后沿用旧投影。消费者提供的 received_at 必须是非递减 time.monotonic(),它不是服务器时间。

玩家动作投影 ​

python
from neomega_runtime.combat_observations import CombatObservations

projection = CombatObservations(snapshot)
signals = projection.apply(event, epoch=snapshot["epoch"], received_at=now)
projection.close()

CombatObservations.packet_ids == (19, 27, 29, 44)。projection.players 是内部使用的 PlayerState,可用 snapshot() 读取副本;不要在投影之外修改它。玩家 runtime ID 必须经当前状态唯一绑定到 UUID,未知或歧义身份不产出归属信号。

输入线索输出 kind 与附加字段
UpdateAttributes 中的 healthhealth_observed:health;比上次低时再产生 health_decreased:previous、health
Animate 的 ActionType=1swing_observed
ActorEvent 的 EventType 为 1、2、3actor_event_observed:event_type,保留协议值,不转换为业务跳跃或击杀事件
MovePlayerground_state_observed:on_ground

每项还含 epoch、sequence、state_revision、player_uuid、entity_runtime_id、packet_id、received_at、time_source="consumer_monotonic"、coverage_verified=False。重复的同值 health 属性保留一份观察;冲突值和非法值使投影失败。相同内容在不同 sequence 出现仍是不同观察,不自动按包内容去重。

下面的函数只读一个有限窗口,返回信号,不发送游戏动作:

python
import asyncio
import time
from neomega_runtime.original_packets import OriginalPackets
from neomega_runtime.combat_observations import CombatObservations

async def collect_combat(ctx, seconds=3):
    stream, snapshot = await OriginalPackets.open(
        ctx.peer, ctx.operations.session,
        required_packet_ids=list(CombatObservations.packet_ids),
        exact_selection=True, text_profile="selected_only",
    )
    projection = None
    try:
        projection = CombatObservations(snapshot)
        result = []
        end = time.monotonic() + seconds
        while time.monotonic() < end and len(result) < 1000:
            event = await stream.poll()
            if event is None:
                await asyncio.sleep(0.01)
                continue
            result.extend(projection.apply(
                event, epoch=snapshot["epoch"], received_at=time.monotonic(),
            ))
        return result
    finally:
        if projection is not None:
            projection.close()
        await stream.close()

窗口与数量上限只控制采集,不证明事件全覆盖。流关闭、错误、撤权或数据丢失时应废弃投影;正常停止等待在途读取结束再 close。对业务日志应逐条保存原事件、信号和游标,而不是只保留最后一个血量。

特定生物与持久化 Pump ​

text
MobCombatObservations(snapshot, *, target_unique_id)
projection.apply(event, *, epoch, received_at) -> list[dict]
projection.state() -> dict
projection.close()

MobObservationPump(stream, projection, *, sink, sink_timeout=0.5)
await pump.run()
await pump.ready.wait()
pump.guard_state(*, max_age=0.5) -> dict
pump.request_stop()
pump.invalidate()

这些类位于 neomega_runtime.mob_combat_observations。packet_ids == (13, 14, 27, 29);target_unique_id 是有符号 int64,必须由调用方明确掌握目标身份。先打开流,再生成目标,投影只接受该流真实收到的 AddActor 绑定,不能用旧缓存 runtime ID 或 querytarget 结果伪造 Add 证据。

输出 health_observed、health_decreased、hurt_observed,含 epoch、sequence、state_revision、目标 unique/runtime ID、received_at、time_source 与 coverage_verified=False。首次 Add 中附带的 health 也可建立血量。移除、明确死亡、零血量、ID 复用、玩家/生物身份冲突、流异常均永久封闭投影,不把它们转换成成功击杀。

state() 含 valid、guard_ready、bound、epoch、entity_unique_id、entity_runtime_id、health、health_received_at、coverage_verified。未绑定时 runtime/health 可为空。最多跟踪 4096 个已见 runtime 身份;超过界限失败,不默默丢弃历史继续绑定。

Pump 的 sink(event, signals) 必须是异步持久化函数,默认等待上限 0.5 秒。在 sink 完成前,动作准入视图被封闭;超时、流缺口和背压会使投影失效。ready 只说明 run 已启动,不等于目标已绑定、血量新鲜或来源完整。guard_state() 在 state 字段外增加 sampled_monotonic,结合 Pump 是否运行、停止以及采样年龄收紧 guard_ready;调用方仍须同时要求 bound,并分别核对健康证据时效、身份、库存和 tick。

创建并监管 run() 的任务是调用方责任。正常停止调用 request_stop() 后等待同一个 run 任务完成,不取消它来模拟成功关闭;request_stop 会立即封闭新动作准入,完成已受理读写后释放流。invalidate() 立即使投影失效。任何退出都会关闭流。不能完成取消的 sink 可能稍后写完原记录,但不会重新启用动作准入。

箭与 owner 元数据 ​

python
from neomega_runtime.projectile_observations import ProjectileObservations

projection = ProjectileObservations(snapshot)
signals = projection.apply(event, epoch=epoch, received_at=now)
projection.close()

packet_ids == (13, 14, 39),对应 AddActor、RemoveActor、SetActorData。只有 entity_type="minecraft:arrow" 才产出信号,但所有相关 actor 身份仍参与冲突和复用检查。需要在箭出现前订阅;初始玩家快照不包含既有箭快照。

输出 kind 是 projectile_added_observed、projectile_owner_observed、projectile_removed_observed。每项含 entity_unique_id、entity_runtime_id、entity_type、owner_unique_id、owner_sequence、owner_received_at,以及流游标、packet_id、消费者时间、coverage_verified=False、hit_verified=False。

owner 来自 metadata key 5 的有符号 unique ID,不是 runtime ID。缺失保留 None,包括哨兵在内的已收到数值原样返回,不自动绑定到玩家。增量包没带 owner 时保留旧数据但不产生新 owner 观察;RemoveActor 只说明移除,不能判为命中。重复或复用 actor ID 即使发生在移除之后也会封闭投影;actor 历史与玩家历史各有 4096 项界限。

读取自身死亡记录 ​

当前 SDK 没有 ctx.death 对象,用公开通用操作入口:

python
intent = ctx.operations.prepare(
    "framework.self.death.read",
    {"limit": 8},
    idempotency_key=key, deadline=deadline,
)

参数为可选 limit(1–32,默认 8)和 after={"connection_id": str, "revision": int}。身份固定来自当前登录机器人,没有读取其他玩家死亡库存的参数。通过 submit/wait 得到成功回执后读取 receipt["result"]["result"];完整调用顺序可参考实体观察示例。

成功结果字段:

字段含义
source固定 native_self_death_journal
connection_id、connected_at、self_runtime_id本连接身份;runtime ID 为十进制字符串
sampled_at、last_packet_at读取与最新来源包时间
revision、next_revision、has_more当前最新修订、本页下一游标、是否有下一页
dropped_through、overflow、gap丢弃历史界限、历史淘汰、当前读取缺口
complete_inventory、ownership_verified始终为 false
records按顺序返回 Content、Slot、自身明确死亡等记录

记录含 revision、observed_at、packet_id、kind、window_id、slot、actor_runtime_id、event_data、invalid,通常还有 items。无效物品带 reason="invalid_item_payload";无法在大小限制内返回物品时用 items_omitted=true 并设置 gap,不能把缺失当空库存。未知 -1 标记保留 known=false、unchanged=true。库存窗口覆盖 0、119、120;额外盔甲槽只作证据,不授权写入。

底层至多保留 256 条、8 MiB 历史,单页物品记录有约 192 KiB 编码预算。先持久化完整本页,再用返回的 connection_id 与 next_revision 组成下一次 after。has_more=true 时使用全局 revision 会跳过未读页。连接变化、过期游标、无效或省略条目需要保留缺口,不能继续声称完整。

死亡前先到的清空包仍保留原顺序,重复死亡通知也保留独立 revision。消费者不能据包次数重复结算,不自动选择“死亡前库存”,更不能据此分配地面物品所有者。

实际出站动作审计 ​

操作 framework.self.action_audit.read 参数为 {},或 {"after": {"connection_id": str, "sequence": int}}。它读取本地真实编码的出站记录,和调用意图做对照,不重新构造一次请求来当审计证据。

返回字段包括 source="conn_outbound_audit"、connection_id、connected_at、after、sequence、sampled_at、monotonic_nanos、complete、gap、closed、events、auth_input_fence、auth_input_tick_high_water;complete_activity_coverage 与 server_accepted 都为 false。events 至多 256 项,总编码事件预算 128 KiB。

事件含 sequence、observed_at、monotonic_nanos、packet_id、classification、source(typed / raw / unknown)、local_admitted、flushed,可选 projection。local_admitted 只证明进入本地发送缓冲;flushed 只证明编码器处理到该序号,不保证网络交付、服务器接受或游戏动作生效。分类 jump、attack、item_use、airborne 是出站线索;unknown 不能当无动作。

目前只有 UseItemOnEntity 与 PlayerAuthInput 提供选定字段投影。成功投影有 status="decoded" 和 kind="use_item_on_entity" / "player_auth_input";非法目标包只返回 status="undecodable"。投影保留目标、槽位、数值物品标识、位置、tick、角度、移动向量、flags 等,不暴露 NBT、聊天、命令串或原始字节。uint64 tick 和 ID 必须保持精确整数。

stack_network_id_nonzero 是从已解码 stack ID 推导的布尔值,不是原始 wire 标志;零不能区分“未提供”与“显式为零”。审计也无法还原 HeldItemRef.ObservedRevision,不能替代库存引用校验。同一 Core 解码与编码一致不单独证明服务端协议接受。

出站 connection_id 与库存或实体观察命名空间分别比较,不要混用。截断、历史缺失或 epoch/游标异常会设置 gap 并阻止 complete;部分无关出站记录被过滤,因此返回 events 的序号不必逐一连续,不能自行把所有过滤空位当成丢包。

auth_input_fence 含 connection_id、revision、state、available,state 为 none、typed 或 unknown。它描述最新本地输入的提交位置;available=true 且 state 为 none 仍表示尚无 AuthInput,不能当成已观察到一个可用 tick。

最近 AuthInput 与 tick 高水位 ​

framework.self.auth_input.observe 参数是可选 connection_id(期望的出站连接)和 max_age_ms(1–10000,默认 1000)。成功结果含:

text
source = "conn_outbound_auth_input"
connection_id, connected_at, revision, tick
rotation = {Pitch, Yaw, HeadYaw, InteractPitch, InteractYaw, PitchRepeat, YawRepeat}
monotonic_age_ms, history_gap, latest_suffix_complete
local_admitted, flushed
server_accepted = false
usable_as_movement_clock = false

它保留最近可用的 typed AuthInput 原始年龄和精确 tick;tick=0 也是观察值,不能丢弃它后拿更早非零 tick 冒充最新输入。缺失、过旧、关闭、连接不符,以及后续 raw/非法输入或未知包头会以 self_auth_input_observation_unavailable 失败,不回退到旧帧。历史 ring 淘汰可能使 history_gap=true,但候选到最新序列的后缀仍可完整。

动作审计中的 auth_input_tick_high_water 则包含 connection_id、tick、available、source="connection_lifetime_local_admission"、server_accepted=false、usable_as_server_clock=false。它是连接期间已解码 typed 输入的最大 tick,不是最新姿态或服务器时钟;最新 Tick 0 不降低此前最大值。raw/不可解码 AuthInput 会使连接期间覆盖不可用,后续合法帧不能恢复这一覆盖保证。ring 淘汰不自动删除这份独立高水位,因此 gap 与 available 可同时为 true。

高水位不分配 tick、不保留动作位置,也不锁住其他写入者。选后继时仍须检查 uint64 溢出、完整序列空间和当前连接/revision 的提交条件,不能因为发现一个最大值就直接发送移动。

一次操作读取动作前检查证据 ​

text
from neomega_runtime.action_guard import (
    prepare_action_guard_observation, action_guard_components,
)

prepare_action_guard_observation(
    operations, *, idempotency_key, deadline,
    connection_id="", max_age_ms=500, retained_fence=None,
) -> OperationIntent
action_guard_components(result) -> tuple  # death, inventory, auth_input

申请 framework.self.action_guard.observe。它固定按 death(limit=1)、inventory(window=0)、auth_input 顺序读取,只减少往返,不授予三项独立读取或任何写操作。max_age_ms 为 1–500,默认 500;没有任意窗口、provider 列表或通用批量参数。

成功结果 source="sequential_native_action_guard"、atomic=false、order=["death","inventory","auth_input"],components 对应每项的 observation={state, reason, result}、started_at、completed_at、started_offset_nanos、completed_offset_nanos。先保存确认成功的原回执,再把其业务结果交给 action_guard_components;它验证顺序、状态和间隔,返回三个原 provider 结果,不更新时间或合并命名空间。

确认失败的组件不阻止其他独立读取,但组合操作失败。unknown 会停止后续组件并标记 not_read_after_stop,整个操作保留 unknown。单组件编码上限 64 KiB,超限或非法数据被省略并使操作不能成功。不得自动回退到三个独立操作重新尝试不确定结果。

可选 retained_fence={connection_id, revision, tick, neutral?} 用来验证已保留 typed baseline 的当前本地提交关系:connection 非空,revision 非零,revision/tick 是精确 uint64;外层 connection 若非空必须一致。未给 neutral 时,baseline 后任何新的 144 都会拒绝;给定 neutral 时必须恰好有一次后续 typed144,与完整显式包及 tick+1 匹配。调用方仍需已确认成功的原 neutral 回执;不能自己假定下一 revision。

此模式保留原帧年龄,不发“刷新包”,也不把旧 tick 变成新时钟。调用方还需把新的组合读取及原流检查约束在 500 ms 内,保留原意图和回执。连接变化、缺失 baseline、raw/未知尾部、不完整后缀或溢出都会拒绝。无论采用哪条路径,顺序读取都不是原子快照,读后到动作提交之间仍可能变化。

业务判断的边界 ​

挥手包不等于命中;Hurt 和 health 减少也不携带可靠攻击者归因。箭 owner 字段、RemoveActor、自身死亡与掉落物外观不能单独组成所有权或奖励结算证明。需要结算时,保留独立动作意图、回执、目标当前身份、时间窗口与实际读回结果;缺失证据保持未知。

观察范围取决于机器人接收范围、维度、距离及服务器转发行为;未收到的数据应保持未知。需要执行动作时,使用物品动作、移动与导航或目标与战斗。

Neomega · 使用、开发与参考文档