From 2518376ee018eaeba6c50e56c096e40452b90305 Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Tue, 3 Nov 2026 03:03:22 +0100 Subject: [PATCH] Harnesses: retry rejected vanilla runs, observe boat dips at once, fixed piglin barter check - one_player: vanilla's run is judged by the harness's check before the comparison, and run again (up to 3 times) when it is no proof, so a vanilla bobber that missed its armor stand on a loaded runner no longer reads as a Horizon diff (fishing_hook_entities). Horizon's run is never retried. - boat_submerged: the short dip and the re-submersion are observed at the end of their own 20-tick step, not a step later; at ~40 ticks plus the client's syncs a loaded host pushed them past the 60 that eject. - command_exec piglin pickups: what an adult's barter throws comes from vanilla's unseeded level random and the piglin picks some of it back up; check that none of the gold lies on the ground instead of the item count. Co-Authored-By: Claude Opus 5.5 --- tools/command_exec_corpus.py | 6 +++++- tools/test_boat_submerged_parity.py | 14 ++++++++------ tools/test_locator_bar_parity.py | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/tools/command_exec_corpus.py b/tools/command_exec_corpus.py index 66fe356b6e..1eeb939e61 100644 --- a/tools/command_exec_corpus.py +++ b/tools/command_exec_corpus.py @@ -1670,7 +1670,11 @@ def piglin_pickups(): {'op': 'tick', 'n': 140}, cmd('/execute if items entity @e[tag=barter,limit=1] weapon.offhand *'), cmd('/execute if items entity @e[tag=barter,limit=1] weapon.mainhand minecraft:gold_ingot'), - cmd('/execute if entity @e[type=minecraft:item]'), + # A baby drops nothing. What an adult's barter throws comes from the level's random (vanilla's is + # unseeded), and the piglin picks some of it back up, so only the gold's fate is fixed: none of it lies + # on the ground. + cmd('/execute if entity @e[type=minecraft:item]' if baby + else '/execute if items entity @e[type=minecraft:item] contents minecraft:gold_ingot'), ] if baby: ops.append(cmd('/execute if data entity @e[tag=barter,limit=1] Inventory[{id:"minecraft:golden_sword"}]')) diff --git a/tools/test_boat_submerged_parity.py b/tools/test_boat_submerged_parity.py index 06d9e3c6ad..a7e0151e5b 100644 --- a/tools/test_boat_submerged_parity.py +++ b/tools/test_boat_submerged_parity.py @@ -16,10 +16,11 @@ EXPECTED = [] PROBES = [] -def probe(label, mounted, player='Alice'): +def probe(label, mounted, player='Alice', actions=()): + """A step running `actions`, at whose end `player` must be (or no longer be) a passenger.""" PROBES.append((label, player)) EXPECTED.append(f'{label} mounted={str(mounted).lower()}') - SCRIPT.append((label, [])) + SCRIPT.append((label, list(actions))) for kind, water in [('oak_boat', 'water'), ('birch_chest_boat', 'water'), @@ -38,11 +39,12 @@ for kind, water in [('oak_boat', 'water'), ('birch_chest_boat', 'water'), for n in range(3): SCRIPT.append((f'{case}-surface-wait-{n}', [])) probe(case + '-surface', True) - SCRIPT.append((case + '-short-dip', [f'cmd Alice fill -3 81 -3 3 84 3 minecraft:{water}'])) - probe(case + '-short', True) + # Each dip is one step (20 ticks, plus the ticks the client's syncs take) observed at its own end, far from + # the 60 that eject: observed a step later, a loaded machine's syncs pushed a short dip past 60 (2026-11-02). + probe(case + '-short', True, actions=[f'cmd Alice fill -3 81 -3 3 84 3 minecraft:{water}']) SCRIPT.append((case + '-surface-reset', ['cmd Alice fill -3 81 -3 3 84 3 minecraft:air'])) - SCRIPT.append((case + '-submerge', [f'cmd Alice fill -3 81 -3 3 84 3 minecraft:{water}'])) - probe(case + '-reset', True) + probe(case + '-reset', True, actions=[f'cmd Alice fill -3 81 -3 3 84 3 minecraft:{water}']) + # 20 + 40 + 20 ticks under water by the last observation: past 60 however fast the steps go. for n in range(2): SCRIPT.append((f'{case}-underwater-wait-{n}', [])) probe(case + '-ejected', False) diff --git a/tools/test_locator_bar_parity.py b/tools/test_locator_bar_parity.py index 05f4794617..14ed061160 100644 --- a/tools/test_locator_bar_parity.py +++ b/tools/test_locator_bar_parity.py @@ -270,6 +270,10 @@ def sides(name): return lock, build, vanilla_server, horizon_server, client +# How many times one_player runs vanilla's side before giving up on a run its `check` rejects. +VANILLA_ATTEMPTS = 3 + + def one_player(name, script, shown, check, scope, step_seconds=4.0): """A harness of one player, Alice: on each server she joins, then runs `script`, a list of (step, client actions) that each last `step_seconds` of the server's time (20 ticks a second). The lines `shown(transcript)` keeps must match vanilla's exactly, and @@ -308,7 +312,17 @@ def one_player(name, script, shown, check, scope, step_seconds=4.0): return out.read_text(encoding='utf-8').splitlines() world = generate_world(vanilla_server, build) - vanilla_all = run('vanilla', vanilla_server, world) + # Vanilla's run is the specification only when `check` accepts it. On a loaded machine a real-time step can + # miss its moment (a bobber that lands before the armor stand is there), so a run it rejects is made again, + # up to VANILLA_ATTEMPTS times, before Horizon's is compared with it; Horizon's run is never retried. + for attempt in range(1, VANILLA_ATTEMPTS + 1): + vanilla_all = run('vanilla', vanilla_server, world) + why = check(shown(vanilla_all), vanilla_all) + if why is None: + break + print(f'{name}: vanilla run {attempt} of {VANILLA_ATTEMPTS} is no proof ({why})', flush=True) + else: + raise AssertionError(f'{why}: the script did not run on vanilla in {VANILLA_ATTEMPTS} runs (see {build})') horizon_all = run('horizon', horizon_server, world) errors = [l for l in vanilla_all + horizon_all if ' error ' in f' {l} ' or 'disconnected' in l] vanilla, horizon = shown(vanilla_all), shown(horizon_all) @@ -317,8 +331,6 @@ def one_player(name, script, shown, check, scope, step_seconds=4.0): diff = '\n'.join(difflib.unified_diff(vanilla, horizon, 'vanilla', 'horizon', n=1, lineterm='')) raise AssertionError(f'{len(errors)} errors, transcripts differ (see {build}/vanilla.txt and horizon.txt):\n' + '\n'.join(errors[:10]) + '\n' + diff[:6000]) - if (why := check(vanilla, vanilla_all)) is not None: - raise AssertionError(f'{why}: the script did not run (see {build})') lab.write_json(build / 'report.json', dict( schema=1, minecraft=lock['minecraft']['version'], platform=platform.platform(), java=subprocess.check_output(['java', '--version'], text=True).splitlines()[0],