mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Respect the controlling passenger for remote boats
This commit is contained in:
1 parent
a589a3dd9b
commit
24224f4728
8 files changed
+381
-7
No files matched your search
@@ -71,6 +71,7 @@ mod boarding;
|
||||
mod boat_authority_fixture;
|
||||
mod boat_leads;
|
||||
mod boat_passengers;
|
||||
mod boat_rider_fixture;
|
||||
mod boats;
|
||||
mod books;
|
||||
mod bossbars;
|
||||
@@ -21760,6 +21761,10 @@ fn start_game<'a>(
|
||||
},
|
||||
);
|
||||
}
|
||||
if let Some(directory) = std::env::var_os("HORIZON_BOAT_RIDER_FIXTURE") {
|
||||
game.run_boat_rider_fixture(std::path::Path::new(&directory))?;
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(directory) = std::env::var_os("HORIZON_BOAT_AUTHORITY_FIXTURE") {
|
||||
game.run_boat_authority_fixture(std::path::Path::new(&directory))?;
|
||||
return Ok(None);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//! Headless passenger-order regression through the real remote client paths.
|
||||
use super::{Game, Keys, boats::Boat};
|
||||
use horizon_client::{Result, held::Source, motion::PlayerMotion};
|
||||
use horizon_core::geometry::Vec3;
|
||||
use horizon_protocol::{connection::Pipe, packets::play::Serverbound};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::path::Path;
|
||||
use winit::keyboard::KeyCode;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Case {
|
||||
name: String,
|
||||
first: Vec<bool>,
|
||||
velocity: [f64; 3],
|
||||
spin: f32,
|
||||
keys: [bool; 4],
|
||||
event: i8,
|
||||
}
|
||||
impl Game<'_> {
|
||||
pub(super) fn run_boat_rider_fixture(&mut self, directory: &Path) -> Result<()> {
|
||||
let cases: Vec<Case> =
|
||||
serde_json::from_str(&std::fs::read_to_string(directory.join("inputs.json"))?)?;
|
||||
let (client, peer) = Pipe::pair();
|
||||
self.connect_local(client, "BoatSeatFixture")?;
|
||||
self.remote.as_mut().unwrap().entity_id = 1;
|
||||
self.player = Some(PlayerMotion::new(self.motion, Vec3::new(0.0, 100.0, 0.0))?);
|
||||
|
||||
let mut output = Vec::new();
|
||||
for case in cases {
|
||||
self.boats.clear();
|
||||
let mut b = Boat::new(3, "oak".into(), Vec3::new(0.0, 100.0, 0.0), 0.0);
|
||||
let [x, y, z] = case.velocity;
|
||||
b.velocity = Vec3::new(x, y, z);
|
||||
b.delta_rotation = case.spin;
|
||||
self.keys.release_source(Source::Agent);
|
||||
for (key, held) in [KeyCode::KeyA, KeyCode::KeyD, KeyCode::KeyW, KeyCode::KeyS]
|
||||
.into_iter()
|
||||
.zip(case.keys)
|
||||
{
|
||||
if held {
|
||||
self.keys.press(key, Source::Agent);
|
||||
}
|
||||
}
|
||||
self.boats.push(b);
|
||||
let mut steps = Vec::new();
|
||||
for first in case.first {
|
||||
self.remote_set_passengers(3, if first { &[1, 2] } else { &[2, 1] });
|
||||
let mut accepts_server_moves = false;
|
||||
self.tick_remote_objects(
|
||||
|_, p| {
|
||||
accepts_server_moves = true;
|
||||
p
|
||||
},
|
||||
|_, yaw| yaw,
|
||||
|_| false,
|
||||
);
|
||||
self.remote_ride(
|
||||
Keys {
|
||||
left: case.keys[0],
|
||||
right: case.keys[1],
|
||||
forward: case.keys[2],
|
||||
backward: case.keys[3],
|
||||
..Keys::default()
|
||||
},
|
||||
7,
|
||||
)?;
|
||||
let packets = self.remote_vehicle_packets();
|
||||
if case.event != 0 {
|
||||
self.remote_boat_event(3, case.event);
|
||||
}
|
||||
let boat = &self.boats[0];
|
||||
steps.push(json!({
|
||||
"controlled": self.remote.as_ref().unwrap().first_passenger,
|
||||
"accepts_server_moves": accepts_server_moves,
|
||||
"hands_busy": self.hands_busy(),
|
||||
"moves": packets.iter().filter(|p| matches!(p, Serverbound::MoveVehicle(_))).count(),
|
||||
"paddles": packets.iter().filter(|p| matches!(p, Serverbound::PaddleBoat(_))).count(),
|
||||
"spin": format!("{:08x}", boat.delta_rotation.to_bits()),
|
||||
"velocity": boat.velocity.components().map(|v| format!("{:016x}", v.to_bits())),
|
||||
}));
|
||||
}
|
||||
output.push(json!({"name": case.name, "steps": steps}));
|
||||
}
|
||||
peer.shutdown();
|
||||
self.remote = None;
|
||||
std::fs::write(directory.join("rust.json"), serde_json::to_vec(&output)?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -908,7 +908,7 @@ impl Game<'_> {
|
||||
/// client moves only the boat its player drives.
|
||||
fn boat_can_simulate_movement(&self, id: u32) -> bool {
|
||||
if self.remote.is_some() {
|
||||
self.riding == Some(id)
|
||||
self.controls_boat(id)
|
||||
} else {
|
||||
!self.guest_vehicles().contains(&id)
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ impl Game<'_> {
|
||||
/// `LocalPlayer.isHandsBusy`: paddling a boat.
|
||||
pub(super) fn hands_busy(&self) -> bool {
|
||||
self.riding
|
||||
.is_some_and(|id| self.boats.iter().any(|b| b.id == id))
|
||||
.is_some_and(|id| self.controls_boat(id) && self.boats.iter().any(|b| b.id == id))
|
||||
&& [
|
||||
winit::keyboard::KeyCode::KeyW,
|
||||
winit::keyboard::KeyCode::KeyS,
|
||||
|
||||
@@ -332,7 +332,7 @@ impl Game<'_> {
|
||||
pub(super) fn turn_remote_object(&mut self, id: i32, yaw: Option<f32>, on_ground: bool) {
|
||||
let id = id as u32;
|
||||
// The boat the player drives turns here (isLocalInstanceAuthoritative).
|
||||
let driven = self.riding;
|
||||
let driven = self.riding.filter(|&id| self.controls_boat(id));
|
||||
if let Some(b) = self.boats.iter_mut().find(|b| b.id == id) {
|
||||
if Some(id) == driven {
|
||||
return;
|
||||
@@ -390,7 +390,7 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
// The boat the player drives moves here, not where the server says.
|
||||
let driven = self.riding;
|
||||
let driven = self.riding.filter(|&id| self.controls_boat(id));
|
||||
for b in &mut self.boats {
|
||||
if Some(b.id) == driven {
|
||||
continue;
|
||||
|
||||
@@ -243,6 +243,12 @@ impl Game<'_> {
|
||||
Some(position)
|
||||
}
|
||||
|
||||
/// AbstractBoat.getControllingPassenger: only the first player steers a boat.
|
||||
/// A non-remote world retains its existing local movement ownership.
|
||||
pub(super) fn controls_boat(&self, id: u32) -> bool {
|
||||
self.riding == Some(id) && self.remote.as_ref().is_none_or(|r| r.first_passenger)
|
||||
}
|
||||
|
||||
/// The local player's tick in its vehicle, in place of its own movement:
|
||||
/// false when it rides nothing.
|
||||
pub(super) fn remote_ride(&mut self, keys: Keys, sprint_window: i32) -> Result<bool> {
|
||||
@@ -276,8 +282,11 @@ impl Game<'_> {
|
||||
// It steers with the input `LocalPlayer.rideTick` latched on the
|
||||
// previous tick, so the paddle packet describes the motion the
|
||||
// server is sent; this tick's keys are latched after it ticked.
|
||||
let mut b = self.tick_boat(self.boats[i].clone(), true);
|
||||
b.input = [keys.left, keys.right, keys.forward, keys.backward];
|
||||
let mut b = self.boats[i].clone();
|
||||
if self.controls_boat(id) {
|
||||
b = self.tick_boat(b, true);
|
||||
b.input = [keys.left, keys.right, keys.forward, keys.backward];
|
||||
}
|
||||
// positionRider: the seat turns with the boat; clampRotation.
|
||||
self.yaw += b.delta_rotation;
|
||||
let delta = (self.yaw - b.yaw + 180.0).rem_euclid(360.0) - 180.0;
|
||||
@@ -338,6 +347,7 @@ impl Game<'_> {
|
||||
}
|
||||
let Some(b) = self
|
||||
.riding
|
||||
.filter(|&id| self.controls_boat(id))
|
||||
.and_then(|id| self.boats.iter().find(|b| b.id == id))
|
||||
else {
|
||||
return Vec::new();
|
||||
@@ -372,6 +382,7 @@ impl Game<'_> {
|
||||
}
|
||||
let Some(b) = self
|
||||
.riding
|
||||
.filter(|&id| self.controls_boat(id))
|
||||
.and_then(|id| self.boats.iter_mut().find(|b| b.id == id))
|
||||
else {
|
||||
return;
|
||||
@@ -403,7 +414,7 @@ impl Game<'_> {
|
||||
/// the synched bubble time.
|
||||
pub(super) fn tick_remote_boat_bubbles(&mut self) {
|
||||
for i in 0..self.boats.len() {
|
||||
let driven = self.riding == Some(self.boats[i].id);
|
||||
let driven = self.controls_boat(self.boats[i].id);
|
||||
if !driven {
|
||||
// `AbstractBoat.tick`: the non-authoritative copy carries no
|
||||
// cached motion into the next time this player drives it.
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package dev.horizonlab.parity;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.client.player.*;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.*;
|
||||
import net.minecraft.world.entity.vehicle.boat.Boat;
|
||||
import net.minecraft.world.entity.vehicle.boat.AbstractBoat;
|
||||
import net.minecraft.world.entity.player.Input;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.*;
|
||||
import net.minecraft.world.phys.*;
|
||||
|
||||
/** Actual boat authority, LocalPlayer rideTick and sendChanges; world lifecycle and packet sinks only are isolated. */
|
||||
public final class VanillaBoatRiderOracle {
|
||||
static Field field(Class<?> type, String name) throws Exception {
|
||||
Field f=type.getDeclaredField(name); f.setAccessible(true); return f;
|
||||
}
|
||||
static <T> T make(Class<T> type) throws Exception {
|
||||
return type.cast(VanillaVehicleContainerOracle.allocate.invoke(VanillaVehicleContainerOracle.unsafe,type));
|
||||
}
|
||||
static class Lvl extends ServerLevel {
|
||||
int next, paddles;
|
||||
Lvl(){super(null,null,null,null,null,null,false,0L,List.of(),false);}
|
||||
@Override public boolean isClientSide(){return true;}
|
||||
@Override public int getNextEntityId(){return next++;}
|
||||
@Override public void sendPacketToServer(Packet<?> p){if(p instanceof ServerboundPaddleBoatPacket) paddles++;}
|
||||
@Override public RegistryAccess registryAccess(){return ParityData.access;}
|
||||
@Override public BlockState getBlockState(BlockPos p){return Blocks.AIR.defaultBlockState();}
|
||||
@Override public FluidState getFluidState(BlockPos p){return Fluids.EMPTY.defaultFluidState();}
|
||||
@Override public List<Entity> getEntities(Entity e,AABB b,Predicate<? super Entity> p){return List.of();}
|
||||
}
|
||||
static class Vessel extends Boat {
|
||||
Vessel(Lvl l){super(EntityTypes.OAK_BOAT,l,()->Items.OAK_BOAT);}
|
||||
@Override public void baseTick(){}
|
||||
@Override public void move(MoverType t,Vec3 m){}
|
||||
@Override protected void applyEffectsFromBlocks(){}
|
||||
@Override protected void positionRider(Entity e, Entity.MoveFunction f){}
|
||||
@Override public float getGroundFriction(){return 0;}
|
||||
@Override public net.minecraft.world.scores.PlayerTeam getTeam(){return null;}
|
||||
}
|
||||
static class Rider extends LocalPlayer {
|
||||
Rider(){super(null,null,null,null,null,null,false,null,null);}
|
||||
@Override public void tick(){}
|
||||
@Override protected void updateBob(){}
|
||||
@Override public boolean isSprinting(){return false;}
|
||||
@Override public boolean onGround(){return false;}
|
||||
@Override protected boolean isControlledCamera(){return true;}
|
||||
}
|
||||
static class Sink extends ClientPacketListener {
|
||||
int moves;
|
||||
Sink(){super(null,null,null);}
|
||||
@Override public boolean hasClientLoaded(){return true;}
|
||||
@Override public void send(Packet<?> p){if(p instanceof ServerboundMoveVehiclePacket)moves++;}
|
||||
}
|
||||
public static void main(String[] args)throws Exception {
|
||||
ParityData.init(); VanillaVehicleContainerOracle.reflect();
|
||||
Lvl l=make(Lvl.class); field(Level.class,"registryAccess").set(l,ParityData.access);
|
||||
JsonArray out=new JsonArray();
|
||||
for(var element:JsonParser.parseString(Files.readString(Path.of(args[0]))).getAsJsonArray()){
|
||||
JsonObject c=element.getAsJsonObject();
|
||||
Rider p=make(Rider.class); Sink sink=make(Sink.class);
|
||||
field(Entity.class,"level").set(p,l);
|
||||
field(Entity.class,"position").set(p,Vec3.ZERO);
|
||||
field(AbstractClientPlayer.class,"clientAvatarState").set(p,new net.minecraft.client.entity.ClientAvatarState());
|
||||
field(LocalPlayer.class,"connection").set(p,sink);
|
||||
field(LocalPlayer.class,"lastSentInput").set(p,Input.EMPTY);
|
||||
ClientInput input=new ClientInput();
|
||||
field(LocalPlayer.class,"input").set(p,input);
|
||||
JsonArray keys=c.getAsJsonArray("keys");
|
||||
input.keyPresses=new Input(keys.get(2).getAsBoolean(),keys.get(3).getAsBoolean(),keys.get(0).getAsBoolean(),keys.get(1).getAsBoolean(),false,false,false);
|
||||
Entity other=c.get("mob").getAsBoolean()?make(net.minecraft.world.entity.monster.zombie.Zombie.class):make(RemotePlayer.class); p.setId(1); other.setId(2);
|
||||
Vessel boat=new Vessel(l); boat.setPos(0,100,0);
|
||||
field(Entity.class,"vehicle").set(p,boat);
|
||||
JsonArray vel=c.getAsJsonArray("velocity");
|
||||
boat.setDeltaMovement(new Vec3(vel.get(0).getAsDouble(),vel.get(1).getAsDouble(),vel.get(2).getAsDouble()));
|
||||
Field spin=field(AbstractBoat.class,"deltaRotation");spin.setFloat(boat,c.get("spin").getAsFloat());
|
||||
JsonArray steps=new JsonArray();
|
||||
for(var own:c.getAsJsonArray("first")){
|
||||
boolean first=own.getAsBoolean();
|
||||
field(Entity.class,"passengers").set(boat,first?ImmutableList.of(p,other):ImmutableList.of(other,p));
|
||||
sink.moves=0;l.paddles=0;
|
||||
boat.tick();p.rideTick();p.sendChanges();
|
||||
if(c.get("event").getAsInt()!=0)boat.handleEntityEvent(c.get("event").getAsByte());
|
||||
JsonObject s=new JsonObject();
|
||||
s.addProperty("controlled",p.getControlledVehicle()==boat);
|
||||
s.addProperty("accepts_server_moves",!boat.isLocalInstanceAuthoritative());
|
||||
s.addProperty("hands_busy",p.isHandsBusy());
|
||||
s.addProperty("moves",sink.moves);s.addProperty("paddles",l.paddles);
|
||||
s.addProperty("spin",String.format("%08x",Float.floatToRawIntBits(spin.getFloat(boat))));
|
||||
JsonArray v=new JsonArray();for(double n:new double[]{boat.getDeltaMovement().x,boat.getDeltaMovement().y,boat.getDeltaMovement().z})v.add(String.format("%016x",Double.doubleToRawLongBits(n)));
|
||||
s.add("velocity",v);steps.add(s);
|
||||
}
|
||||
JsonObject row=new JsonObject();row.add("name",c.get("name"));row.add("steps",steps);out.add(row);
|
||||
}
|
||||
Files.writeString(Path.of(args[1]),out.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare passenger authority through actual vanilla and Horizon client paths.
|
||||
|
||||
The Java oracle runs AbstractBoat.tick, LocalPlayer.rideTick and sendChanges with
|
||||
real first/second passenger identities. Only empty-world lifecycle, collision
|
||||
movement and packet transport are stand-ins. Horizon runs its remote passenger
|
||||
handler, object ticker, ride tick, outgoing vehicle packets and item-use gate in
|
||||
an isolated headless process with a memory connection. No desktop input is used.
|
||||
|
||||
The contract compares ownership, packet counts, interpolation dispatch, hands,
|
||||
angular/vertical motion and every non-authoritative velocity component exactly.
|
||||
Authoritative horizontal motion remains in the raw outputs as a diagnostic: the
|
||||
unchanged Rust sin/cos steering kernel differs from vanilla Mth lookup (observed
|
||||
maximum 2.164393663406372e-6 here). This is not a full trajectory comparison.
|
||||
"""
|
||||
import argparse
|
||||
import copy
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import struct
|
||||
import tempfile
|
||||
try:
|
||||
from . import lab, test_rust_parity as base, test_model_baking_parity as baking
|
||||
from .integration_env import run_game
|
||||
except ImportError:
|
||||
import lab
|
||||
import test_rust_parity as base
|
||||
import test_model_baking_parity as baking
|
||||
from integration_env import run_game
|
||||
|
||||
SOURCES = [
|
||||
'crates/horizon-client/src/game.rs',
|
||||
'crates/horizon-client/src/game/boat_rider_fixture.rs',
|
||||
'crates/horizon-client/src/game/remote_vehicles.rs',
|
||||
'crates/horizon-client/src/game/boats.rs',
|
||||
'crates/horizon-client/src/game/remote_objects.rs',
|
||||
'crates/horizon-client/src/game/hand.rs',
|
||||
'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaBoatRiderOracle.java',
|
||||
'mods/horizon/src/test/java/dev/horizonlab/parity/ParityData.java',
|
||||
'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaVehicleContainerOracle.java',
|
||||
'tools/test_boat_rider_parity.py',
|
||||
]
|
||||
|
||||
|
||||
def cases():
|
||||
out = []
|
||||
for keys in itertools.product((False, True), repeat=4):
|
||||
for velocity, spin in (([0., 0., 0.], 0.), ([.125, -.0625, -.25], .125)):
|
||||
for first in ([False], [True], [True, False, False, True], [False, True, False]):
|
||||
out.append(dict(name=f'rider-{len(out)}', keys=keys, velocity=velocity,
|
||||
spin=spin, first=first, mob=False, event=0))
|
||||
for keys in itertools.product((False, True), repeat=4):
|
||||
out.append(dict(name=f'mob-first-{len(out)}', keys=keys, velocity=[.125, -.0625, -.25],
|
||||
spin=.125, first=[False, False], mob=True, event=0))
|
||||
for event, first, mob in itertools.product((71, 72), ([False], [True], [True, False, True]), (False, True)):
|
||||
out.append(dict(name=f'bubble-{len(out)}', keys=[False, False, True, False],
|
||||
velocity=[.125, -.0625, -.25], spin=.125, first=first, mob=mob, event=event))
|
||||
return out
|
||||
|
||||
|
||||
|
||||
def contract(row):
|
||||
"""Project the same authority-based contract on either raw implementation."""
|
||||
result = copy.deepcopy(row)
|
||||
for step in result['steps']:
|
||||
if step['controlled']:
|
||||
step['velocity'] = [None, step['velocity'][1], None]
|
||||
return result
|
||||
|
||||
|
||||
def compare_outputs(inputs, native, rust):
|
||||
assert len(native) == len(rust) == len(inputs)
|
||||
errors = [(case['name'], n, r) for case, n, r in zip(inputs, native, rust)
|
||||
if contract(n) != contract(r)]
|
||||
different_cases = set()
|
||||
deltas = []
|
||||
for case, n, r in zip(inputs, native, rust):
|
||||
assert len(n['steps']) == len(r['steps']) == len(case['first'])
|
||||
for ns, rs in zip(n['steps'], r['steps']):
|
||||
if ns['controlled'] and rs['controlled']:
|
||||
for axis in (0, 2):
|
||||
a, b = ns['velocity'][axis], rs['velocity'][axis]
|
||||
if a != b:
|
||||
different_cases.add(case['name'])
|
||||
av = struct.unpack('>d', bytes.fromhex(a))[0]
|
||||
bv = struct.unpack('>d', bytes.fromhex(b))[0]
|
||||
deltas.append(abs(av - bv))
|
||||
return errors, dict(cases=len(different_cases), components=len(deltas),
|
||||
max_absolute_delta=max(deltas, default=0.0),
|
||||
compared=False, reason='unchanged authoritative sin/cos steering kernel')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--binary', type=Path, help='use an already built debug client')
|
||||
parser.add_argument('--resources', type=Path, default=lab.RESOURCES)
|
||||
parser.add_argument('--java-only', action='store_true')
|
||||
args = parser.parse_args()
|
||||
lock, jars, _ = base.verify_artifacts()
|
||||
work = lab.LOCAL / 'boat-rider-parity'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
report = work / 'report.json'
|
||||
report.unlink(missing_ok=True)
|
||||
hashes = {name: lab.digest(lab.ROOT / name, 'sha256') for name in SOURCES}
|
||||
inputs = cases()
|
||||
lab.write_json(work / 'inputs.json', inputs)
|
||||
classes = work / 'classes'
|
||||
classes.mkdir(exist_ok=True)
|
||||
sources = lab.ROOT / 'mods/horizon/src/test/java'
|
||||
package = sources / 'dev/horizonlab/parity'
|
||||
dependencies = os.pathsep.join(map(str, [jars['client'], *baking.client_libraries()]))
|
||||
|
||||
def run(command, name):
|
||||
with (work / f'{name}.log').open('w', encoding='utf-8') as log:
|
||||
result = subprocess.run(list(map(str, command)), cwd=lab.ROOT, stdout=log,
|
||||
stderr=subprocess.STDOUT, timeout=1800)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f'{name} failed: {(work / f"{name}.log").read_text(encoding="utf-8")[-4000:]}')
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-nowarn',
|
||||
'-sourcepath', sources, '-cp', dependencies, '-d', classes,
|
||||
*(package / f'{name}.java' for name in
|
||||
('ParityData', 'VanillaVehicleContainerOracle', 'VanillaBoatRiderOracle'))], 'compile-java')
|
||||
run(['java', '-cp', str(classes) + os.pathsep + dependencies,
|
||||
'dev.horizonlab.parity.VanillaBoatRiderOracle', work / 'inputs.json', work / 'vanilla.json'], 'vanilla')
|
||||
if args.java_only:
|
||||
print(f'actual Java passenger methods: {len(inputs)} cases')
|
||||
return
|
||||
binary = args.binary
|
||||
if binary is None:
|
||||
run(['cargo', 'build', '-p', 'horizon-client', '--bin', 'horizon-client'], 'compile-rust')
|
||||
binary = lab.cargo_target() / 'debug' / ('horizon-client.exe' if os.name == 'nt' else 'horizon-client')
|
||||
with tempfile.TemporaryDirectory(prefix='hz-boat-rider-') as scratch:
|
||||
with (work / 'rust.log').open('w', encoding='utf-8') as log:
|
||||
run_game([str(binary.resolve()), '--headless', '--resources', str(args.resources.resolve()),
|
||||
'--game-dir', scratch], cwd=scratch,
|
||||
env=lab.game_env(HORIZON_BOAT_RIDER_FIXTURE=str(work),
|
||||
HORIZON_API_DISCOVERY='http://127.0.0.1:9'),
|
||||
stdout=log, stderr=subprocess.STDOUT, check=True, timeout=180)
|
||||
native = json.loads((work / 'vanilla.json').read_text(encoding='utf-8'))
|
||||
rust = json.loads((work / 'rust.json').read_text(encoding='utf-8'))
|
||||
errors, horizontal = compare_outputs(inputs, native, rust)
|
||||
if errors:
|
||||
raise AssertionError(f'{len(errors)}/{len(inputs)} boat rider cases differ: {errors[:2]}')
|
||||
assert hashes == {name: lab.digest(lab.ROOT / name, 'sha256') for name in SOURCES}, 'sources changed during validation'
|
||||
ticks = sum(len(case['first']) for case in inputs)
|
||||
lab.write_json(report, dict(minecraft=lock['minecraft']['version'], cases=len(inputs), ticks=ticks,
|
||||
output_sha256=lab.digest(work / 'rust.json', 'sha256'),
|
||||
source_hashes=hashes, authoritative_horizontal_diagnostic=horizontal,
|
||||
scope=__doc__))
|
||||
print(f'boat rider: {len(inputs)}/{len(inputs)} authority cases and {ticks} tick states match vanilla; '
|
||||
f'authoritative horizontal diagnostic: {horizontal}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user