parity: world_reopen and server_player_sculk count ticks, not request round trips

On playhorizon (frames drawn on the CPU, control requests answered slowly)
world_reopen read the minecarts before the mineshaft's last one arrived (3
then 4 on every reopen), and the sculk walk polled the sensor with separate
requests that missed its 30-tick active window. The minecart list is read
once it stops changing; both clients sample the sensor every tick from one
ctl script, alongside the walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ludovic Marie
2026-10-27 04:08:32 +01:00
parent 8a2c7eee4a
commit 8b87d128e6
2 changed files with 28 additions and 7 deletions
+18 -5
View File
@@ -8,6 +8,7 @@ hosts: the guest always uses the server-player path.
import json
import math
import subprocess
import threading
import lab
import lan_e2e as lan
@@ -43,17 +44,29 @@ def check_walk(host, guest, out, role):
until(host, lambda: phase(host), lambda s: 'sculk_sensor_phase=inactive' in s)
player.wait(10)
assert 'sculk_sensor_phase=inactive' in phase(host), 'setup activated the sensor'
player.raw('key', 'w', 'down')
# The sensor stays active 30 ticks. Each client samples it every tick from inside one ctl script, so a
# runner that answers control requests slowly (frames drawn on the CPU) can't miss that window between
# separate requests; the other client samples alongside the walk.
probe = f'getblock {sensor[0]} {sensor[1]} {sensor[2]}'
other = guest if player is host else host
seen = {}
def watch(viewer, lines):
seen[viewer.name] = [r['block'] for r in viewer.script(lines, every=True) if 'block' in r]
watcher = threading.Thread(target=watch, args=(other, ['wait 1', probe] * 100))
watcher.start()
try:
player.wait(15)
watch(player, ['key w down', *['wait 1', probe] * 15, 'key w up', *['wait 1', probe] * 85])
finally:
player.raw('key', 'w', 'up')
watcher.join()
moved = {'client': math.dist(lan.pos_of(player), start),
'server': math.dist(lan.server_player(host, player)['position'], start)}
assert all(d > 1 for d in moved.values()), moved
for viewer in (host, guest):
until(viewer, lambda: phase(viewer), lambda s: 'sculk_sensor_phase=active' in s, ticks=85)
return {'moved': moved, 'phase': phase(host)}
active = {name: next((i for i, s in enumerate(states) if 'sculk_sensor_phase=active' in s), None)
for name, states in seen.items()}
assert len(active) == 2 and all(i is not None for i in active.values()), f'sensor never active: {seen}'
return {'moved': moved, 'first_active_sample': active}
finally:
player.raw('key', 'w', 'up')
host('command', command=f'fill {x - 2} {y} {z - 2} {x + 5} {y + 3} {z + 5} minecraft:air')
+10 -2
View File
@@ -68,8 +68,16 @@ def main():
return json.loads(result.stdout) if result.stdout.strip().startswith('{') else result.stdout
def carts():
found = ctl('status')['carts']
return sorted((c['kind'], *(round(v, 1) for v in c['position'])) for c in found)
"""The loaded minecarts once their list stops changing: on a loaded runner the mineshaft's chunks and their
entities keep arriving after the world counts as loaded, so one reading can be short of a cart."""
last = None
for _ in range(30):
found = sorted((c['kind'], *(round(v, 1) for v in c['position'])) for c in ctl('status')['carts'])
if found == last:
return found
last = found
ctl('wait', '40')
return last
ctl('launch', '--resources', str(RESOURCES), '--world', 'reopen', '--seed', '7', '--creative',
'--render-distance', '4', *extra)