a0cb6b12b7
Gäste grüßen mit Protokollversion, Regel-Kennung, Schlüssel und Name und schicken danach nur Aktionen ohne Spielernummer. Der Host schickt seine Welt, Ablehnungen mit Grund und jeden Takt mit seinen Befehlen, jede Sekunde mit einer Prüfsumme. Ein Gruß einer anderen Version bleibt erkennbar, und das Format ist in den festgeschriebenen Werten verankert. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
168 lines
5.5 KiB
Rust
168 lines
5.5 KiB
Rust
//! Pinned values for everything that must not change unnoticed.
|
|
//!
|
|
//! Most other tests compare two computations inside the same build, so a
|
|
//! change that affects both sides alike stays green. This file compares
|
|
//! against values recorded once. If one of them fails, rules or encodings
|
|
//! changed, on purpose or by accident. If saves or recordings are affected,
|
|
//! raise the version constant named in the failure message, then update the
|
|
//! value here deliberately.
|
|
|
|
use grovewright_core::codec::Writer;
|
|
use grovewright_core::command::Command;
|
|
use grovewright_core::hash::fnv1a64;
|
|
use grovewright_core::map::{Map, Pos, Terrain, Tile};
|
|
use grovewright_core::player::PlayerId;
|
|
use grovewright_core::protocol::{Action, Refusal, ToGuest, ToHost};
|
|
use grovewright_core::replay::Recording;
|
|
use grovewright_core::rules::{
|
|
end_hash, frozen_offline_script, frozen_script, rules_id, RULES_SEED,
|
|
};
|
|
use grovewright_core::world::World;
|
|
|
|
/// Covers the map generator, the map encoding and the snapshot header and
|
|
/// checksum.
|
|
#[test]
|
|
fn the_starting_snapshot_is_unchanged() {
|
|
let actual = fnv1a64(&World::new(RULES_SEED).to_snapshot());
|
|
assert_eq!(
|
|
actual, 0x6812_937b_f2b2_19ae,
|
|
"the starting world or the snapshot format changed; if saves are \
|
|
affected, raise SNAPSHOT_VERSION in core/src/snapshot.rs; then \
|
|
update this value"
|
|
);
|
|
}
|
|
|
|
/// Covers the rules of joining, leaving and digging, the shortening of names
|
|
/// and the encoding of players.
|
|
#[test]
|
|
fn the_frozen_session_ends_in_the_same_state() {
|
|
assert_eq!(
|
|
end_hash(frozen_script),
|
|
0xc304_43df_fc74_dcfc,
|
|
"the rules or the snapshot format changed, and with them the rules \
|
|
id; if saves are affected, raise SNAPSHOT_VERSION in \
|
|
core/src/snapshot.rs; then update this value"
|
|
);
|
|
}
|
|
|
|
/// Covers the encoding of a player who is offline.
|
|
#[test]
|
|
fn the_frozen_offline_session_ends_in_the_same_state() {
|
|
assert_eq!(
|
|
end_hash(frozen_offline_script),
|
|
0x904f_10e1_7c52_8be4,
|
|
"the rules or the snapshot format changed, and with them the rules \
|
|
id; if saves are affected, raise SNAPSHOT_VERSION in \
|
|
core/src/snapshot.rs; then update this value"
|
|
);
|
|
}
|
|
|
|
/// Covers how the identity of the rules is combined from the end states of
|
|
/// the frozen scripts.
|
|
#[test]
|
|
fn the_rules_id_is_unchanged() {
|
|
assert_eq!(
|
|
rules_id(),
|
|
0x04f1_1b1e_16c5_feff,
|
|
"the rules id changed: builds with other rules can no longer play \
|
|
together, and older recordings report other rules; if the tests \
|
|
above fail as well, deal with those first; then update this value"
|
|
);
|
|
}
|
|
|
|
/// Covers the recording header and layout and the wire format of commands.
|
|
#[test]
|
|
fn the_recording_format_is_unchanged() {
|
|
let mut world = World::new(RULES_SEED);
|
|
let mut recording = Recording::start(&world);
|
|
for commands in frozen_script(&world) {
|
|
recording.step(&mut world, &commands);
|
|
}
|
|
assert_eq!(
|
|
fnv1a64(&recording.to_bytes(&world)),
|
|
0xf203_b229_8dad_7c53,
|
|
"the recording or command format changed; if other tests in this \
|
|
file fail as well, deal with those first, because a recording \
|
|
contains a snapshot and an end state; otherwise raise \
|
|
RECORDING_VERSION in core/src/replay.rs; then update this value"
|
|
);
|
|
}
|
|
|
|
/// Covers the byte layout of a map: width before height, little endian,
|
|
/// terrain numbers and damage. The map is not square, so a swap of width
|
|
/// and height shows.
|
|
#[test]
|
|
fn the_map_encoding_is_unchanged() {
|
|
let mut map = Map::filled(3, 2, Terrain::Ground);
|
|
map.set(Pos::new(1, 0), Tile::new(Terrain::Bedrock));
|
|
map.set(Pos::new(0, 1), Tile::new(Terrain::HardRock));
|
|
map.set(
|
|
Pos::new(2, 1),
|
|
Tile {
|
|
terrain: Terrain::SoftRock,
|
|
damage: 1,
|
|
},
|
|
);
|
|
let mut w = Writer::new();
|
|
map.encode(&mut w);
|
|
assert_eq!(
|
|
w.into_bytes(),
|
|
vec![
|
|
3, 0, 0, 0, // width
|
|
2, 0, 0, 0, // height
|
|
0, 0, 3, 0, 0, 0, // row 0: ground, bedrock, ground
|
|
2, 0, 0, 0, 1, 1, // row 1: hard rock, ground, soft rock hit once
|
|
],
|
|
"the map encoding changed; raise SNAPSHOT_VERSION in \
|
|
core/src/snapshot.rs; then update these bytes"
|
|
);
|
|
}
|
|
|
|
/// Covers every message between host and guests, built from literal values.
|
|
#[test]
|
|
fn the_protocol_format_is_unchanged() {
|
|
let to_host = [
|
|
ToHost::Hello {
|
|
rules: 0x0102_0304_0506_0708,
|
|
key: [3; 16],
|
|
name: "Ada".to_owned(),
|
|
},
|
|
ToHost::Act(Action::Dig {
|
|
pos: Pos::new(-2, 9),
|
|
}),
|
|
ToHost::Diverged { tick: 40, hash: 77 },
|
|
];
|
|
let to_guest = [
|
|
ToGuest::World {
|
|
snapshot: vec![5, 6, 7],
|
|
},
|
|
ToGuest::Refused(Refusal::WorldFull),
|
|
ToGuest::Tick {
|
|
tick: 12,
|
|
commands: vec![Command::Dig {
|
|
player: PlayerId(1),
|
|
pos: Pos::new(3, 4),
|
|
}],
|
|
check: Some(99),
|
|
},
|
|
ToGuest::Tick {
|
|
tick: 13,
|
|
commands: vec![],
|
|
check: None,
|
|
},
|
|
];
|
|
let mut bytes = Vec::new();
|
|
for message in &to_host {
|
|
bytes.extend(message.encode());
|
|
}
|
|
for message in &to_guest {
|
|
bytes.extend(message.encode());
|
|
}
|
|
assert_eq!(
|
|
fnv1a64(&bytes),
|
|
0xc263_69c0_f892_247b,
|
|
"the messages between host and guests changed; raise \
|
|
PROTOCOL_VERSION in core/src/protocol.rs; then update this value"
|
|
);
|
|
}
|