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>
This commit is contained in:
Steven Wroblewski
2026-10-01 18:29:32 +02:00
parent a0cb6b12b7
commit b7d5383650
2 changed files with 48 additions and 4 deletions
+7 -2
View File
@@ -19,16 +19,20 @@ use crate::world::TICKS_PER_SECOND;
/// Version of the messages. Raise it whenever a message changes.
pub const PROTOCOL_VERSION: u32 = 1;
/// Longest message a guest may send, in bytes. The longest one is a greeting
/// with a name of full length, which needs 65 bytes.
/// with a name of full length, which needs 65 bytes. Greetings of every
/// future version must fit as well: a longer one is rejected unread instead
/// of being recognised as another version.
pub const MAX_GUEST_MESSAGE_BYTES: usize = 128;
/// Ticks between two state checksums in the stream of ticks: one second.
pub const CHECK_INTERVAL_TICKS: u64 = TICKS_PER_SECOND as u64;
// Never changes, see the module documentation.
const TO_HOST_HELLO: u8 = 1;
const TO_HOST_ACT: u8 = 2;
const TO_HOST_DIVERGED: u8 = 3;
const TO_GUEST_WORLD: u8 = 1;
// Never changes, see the module documentation.
const TO_GUEST_REFUSED: u8 = 2;
const TO_GUEST_TICK: u8 = 3;
@@ -75,7 +79,8 @@ impl Action {
}
}
/// Why a host turned a guest away.
/// Why a host turned a guest away. The codes never change, because games of
/// other versions read them.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refusal {
/// The guest's game speaks another version of these messages.
+41 -2
View File
@@ -12,7 +12,7 @@ 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::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,
@@ -162,6 +162,45 @@ fn the_protocol_format_is_unchanged() {
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"
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"
);
}
}