Files
Grovewright/core/tests/golden.rs
T
Steven Wroblewski b7d5383650 Gruß-Anfang und Ablehnung festschreiben
Ein Test hält fest, dass ein Gruß mit Kennung und Version beginnt und
eine Ablehnung aus Kennung und Grund besteht. Beides bleibt auch bei
einer neuen Protokollversion gleich, damit sich Spiele verschiedener
Versionen weiter sagen können, dass sie nicht zusammenpassen. Die
Meldung des festgeschriebenen Nachrichtenformats weist darauf hin.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 18:29:32 +02:00

207 lines
6.9 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, PROTOCOL_VERSION};
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. \
The start of a greeting and the refusal must never change, see \
the_greeting_start_and_the_refusal_never_change"
);
}
/// Covers the promise that games of every version can still tell each other
/// that they do not fit: a greeting starts with its tag and the version, and
/// a refusal is its tag and the code of its reason. Unlike every other part
/// of the messages, these stay the same when PROTOCOL_VERSION is raised.
#[test]
fn the_greeting_start_and_the_refusal_never_change() {
let hello = ToHost::Hello {
rules: 1,
key: [3; 16],
name: "Ada".to_owned(),
}
.encode();
let mut start = vec![1];
start.extend(PROTOCOL_VERSION.to_le_bytes());
assert_eq!(
hello[..start.len()],
start[..],
"a greeting starts with the tag 1 and the version; this never \
changes, not even with a new PROTOCOL_VERSION"
);
let refusals = [
(Refusal::OtherProtocol, 1),
(Refusal::OtherRules, 2),
(Refusal::WorldFull, 3),
(Refusal::TooManyPlayers, 4),
(Refusal::AlreadyPlaying, 5),
];
for (refusal, code) in refusals {
assert_eq!(
ToGuest::Refused(refusal).encode(),
vec![2, code],
"a refusal is the tag 2 and the code of its reason; this never \
changes, not even with a new PROTOCOL_VERSION"
);
}
}