wire stream: compare a click's state id as its lag behind the server; hotbar_swap swings at the sky

A run's state ids count every change its server made since the login, so a click numbered 53 in one
run and 55 in the other were both up to date. hotbar_swap now also taps a number key with a swing at
nothing, where vanilla sends the slot only on the next tick.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Horizon agentandClaude Opus 5.5 committed 2026-11-05 19:43:04 +01:00
1 parent a14ca6acf4
commit b5ed12bc33
3 files changed
+37 -6

No files matched your search

+15
View File
@@ -72,6 +72,21 @@ class WireStreamTest(unittest.TestCase):
self.assertEqual(wire_stream.decode('player_action', varint(0) + as_long(29999999, 2047, -30000000)
+ b'\1' + varint(3))['pos'], [29999999, 2047, -30000000])
def test_a_click_s_state_id_compares_as_how_far_it_lags_the_server(self):
def set_slot(state):
return {'conn': 1, 'dir': 's2c', 'state': 'play', 'name': 'container_set_slot',
'data': (varint(0) + varint(state) + b'\0\x24\0').hex()}
def click(state):
return c2s('container_click', varint(0) + varint(state) + b'\0\x24\0' + varint(0) + b'\0\0')
# Two runs whose servers counted differently: both clicks are up to date, the third is one behind.
a = wire_stream.scenarios([LOGIN, set_slot(53), mark(), click(53), TICK_END])
b = wire_stream.scenarios([LOGIN, set_slot(55), mark(), click(55), TICK_END, set_slot(56), click(55),
TICK_END])
self.assertEqual(a[0][0]['packets'], b[0][0]['packets'])
self.assertEqual(a[0][0]['packets'][0][1]['stale'], 0)
self.assertEqual(b[0][1]['packets'][0][1]['stale'], 1)
def test_compare_reports_the_first_field_that_differs(self):
tick = lambda y: {'packets': [('move_player_pos', {'x': 0.5, 'y': y})], 'frame': [], 'misplaced': []} # noqa
diffs = wire_stream.compare([tick(-60.0)], [tick(-60.0000001)], 0)
+5 -2
View File
@@ -151,9 +151,12 @@ SCENARIOS = {
setup=[TARGET.format(x=0.5, y=FLOOR + 1, z=3.0)] +
[f'item replace entity {PLAYER} hotbar.{i} with minecraft:{item}' for i, item in
enumerate(['diamond_sword', 'stone_axe', 'golden_apple', 'stone', 'bow'])],
ticks=50, steps=merge({0: ['look 0 20']}, {t: [f'key {t % 5 + 1} tap'] for t in range(5, 15)},
# 52-54: a swing at the sky with a new slot sends no slot before its Punch (no ensureHasSentCarriedItem
# on a miss); the next tick's MultiPlayerGameMode.tick does.
ticks=60, steps=merge({0: ['look 0 20']}, {t: [f'key {t % 5 + 1} tap'] for t in range(5, 15)},
{16: ['key 2 tap', 'mouse left click'], 20: ['key 1 tap'], 21: ['key 3 tap'],
30: ['key 5 tap', 'mouse left click'], 35: ['key q tap'], 40: ['key f tap']})),
30: ['key 5 tap', 'mouse left click'], 35: ['key q tap'], 40: ['key f tap'],
52: ['look 0 -80'], 54: ['key 3 tap', 'mouse left click']})),
'dig_teleport': dict(
setup=[f'setblock 0 {FLOOR + 1} 2 minecraft:stone'],
ticks=60, steps={0: ['look 0 45'], 3: ['mouse left down'], 15: ['command tp @s ~ ~ ~'],
+17 -4
View File
@@ -130,13 +130,26 @@ def self_id(entries):
def scenarios(entries):
"""One list of ticks for each /wire-mark in the play stream, in order (the ticks after a mark run to the
next mark); a tick is {'packets': [(name, fields)], 'frame': [(name, fields)], 'misplaced': [name]}."""
next mark); a tick is {'packets': [(name, fields)], 'frame': [(name, fields)], 'misplaced': [name]}.
A click's state id becomes `stale`: how far it is behind the last one the server sent for that container
(0 = up to date). The ids themselves count every change the server made since the login, so two runs
whose earlier scenarios differed number the same click differently."""
me = self_id(entries)
c2s = [e for e in entries if e.get('dir') == 'c2s' and e.get('state') == 'play' and 'name' in e]
runs, current = [], None
for e in c2s:
play = [e for e in entries if e.get('state') == 'play' and 'name' in e and e.get('dir') in ('c2s', 's2c')]
runs, current, server_state = [], None, {}
for e in play:
name = e['name']
if e['dir'] == 's2c':
if name in ('container_set_content', 'container_set_slot'):
r = Reader(bytes.fromhex(e.get('data', '')))
container = r.varint()
server_state[container] = r.varint()
continue
fields = decode(name, bytes.fromhex(e.get('data', '')))
if name == 'container_click':
last = server_state.get(fields['container'], fields['state'])
fields = {'container': fields['container'], 'stale': last - fields.pop('state'), **fields}
if name == 'chat_command' and fields.get('command') == 'wire-mark':
current = [new_tick()]
runs.append(current)