M1a: deterministischer Rust-Kern #3
@@ -0,0 +1,147 @@
|
||||
//! Pinned values for everything that must not change unnoticed.
|
||||
//!
|
||||
//! Every other test compares 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::command::Command;
|
||||
use grovewright_core::hash::fnv1a64;
|
||||
use grovewright_core::map::Pos;
|
||||
use grovewright_core::mapgen::outcrop_origin;
|
||||
use grovewright_core::player::PlayerId;
|
||||
use grovewright_core::replay::Recording;
|
||||
use grovewright_core::world::World;
|
||||
|
||||
const SEED: u64 = 2026;
|
||||
|
||||
/// The commands of a session of 60 ticks. It is built from literal values
|
||||
/// and the outcrop origin of the map, not from constants such as
|
||||
/// `DIG_COOLDOWN_TICKS`, so that changing a rule changes the result and not
|
||||
/// the script.
|
||||
///
|
||||
/// Never change or extend this script. To pin more, add a new script and a
|
||||
/// new pinned value instead.
|
||||
fn frozen_script(world: &World) -> Vec<Vec<Command>> {
|
||||
let origin = outcrop_origin(world.map());
|
||||
let right_of_origin = Pos::new(origin.x + 1, origin.y);
|
||||
// Pairs of (tick, command). Commands of the same tick are applied in the
|
||||
// order listed here.
|
||||
let scripted = [
|
||||
// Two players join in the same tick.
|
||||
(
|
||||
0,
|
||||
Command::Join {
|
||||
key: [1; 16],
|
||||
name: "Ada".to_owned(),
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
Command::Join {
|
||||
key: [2; 16],
|
||||
name: "Grace".to_owned(),
|
||||
},
|
||||
),
|
||||
// Player 0 hits the outcrop origin three times, five ticks apart.
|
||||
// The third hit breaks the rock and gives a stone.
|
||||
(
|
||||
2,
|
||||
Command::Dig {
|
||||
player: PlayerId(0),
|
||||
pos: origin,
|
||||
},
|
||||
),
|
||||
(
|
||||
7,
|
||||
Command::Dig {
|
||||
player: PlayerId(0),
|
||||
pos: origin,
|
||||
},
|
||||
),
|
||||
(
|
||||
12,
|
||||
Command::Dig {
|
||||
player: PlayerId(0),
|
||||
pos: origin,
|
||||
},
|
||||
),
|
||||
// Player 1 hits the tile to the right of the origin once.
|
||||
(
|
||||
20,
|
||||
Command::Dig {
|
||||
player: PlayerId(1),
|
||||
pos: right_of_origin,
|
||||
},
|
||||
),
|
||||
// Player 1 leaves and returns with a name of 40 bytes, which the
|
||||
// world shortens to 32.
|
||||
(
|
||||
30,
|
||||
Command::Leave {
|
||||
player: PlayerId(1),
|
||||
},
|
||||
),
|
||||
(
|
||||
40,
|
||||
Command::Join {
|
||||
key: [2; 16],
|
||||
name: "ä".repeat(20),
|
||||
},
|
||||
),
|
||||
];
|
||||
let mut ticks = vec![Vec::new(); 60];
|
||||
for (tick, command) in scripted {
|
||||
ticks[tick].push(command);
|
||||
}
|
||||
ticks
|
||||
}
|
||||
|
||||
/// Covers the map generator, the map encoding (terrain numbers, byte order of
|
||||
/// width and height) and the snapshot header.
|
||||
#[test]
|
||||
fn the_starting_snapshot_is_unchanged() {
|
||||
let actual = fnv1a64(&World::new(SEED).to_snapshot());
|
||||
assert_eq!(
|
||||
actual, 0x1b78_d996_1586_fbf1,
|
||||
"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() {
|
||||
let mut world = World::new(SEED);
|
||||
for commands in frozen_script(&world) {
|
||||
world.step(&commands);
|
||||
}
|
||||
assert_eq!(
|
||||
world.state_hash(),
|
||||
0xc304_43df_fc74_dcfc,
|
||||
"the rules or the snapshot format changed; old recordings no longer \
|
||||
verify; if saves are affected, raise SNAPSHOT_VERSION in \
|
||||
core/src/snapshot.rs; 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(SEED);
|
||||
let mut recording = Recording::start(&world);
|
||||
for commands in frozen_script(&world) {
|
||||
recording.push_tick(&commands);
|
||||
world.step(&commands);
|
||||
}
|
||||
assert_eq!(
|
||||
fnv1a64(&recording.to_bytes(&world)),
|
||||
0x2312_5a7d_d8ef_5ae0,
|
||||
"the recording or command format changed; raise RECORDING_VERSION in \
|
||||
core/src/replay.rs; then update this value"
|
||||
);
|
||||
}
|
||||
+25
-1
@@ -2,7 +2,9 @@ use grovewright_core::command::Command;
|
||||
use grovewright_core::inventory::ITEM_STONE;
|
||||
use grovewright_core::map::{Pos, Terrain};
|
||||
use grovewright_core::mapgen::{outcrop_origin, spawn_pos};
|
||||
use grovewright_core::player::{PlayerId, PlayerKey, MAX_KNOWN_PLAYERS, MAX_ONLINE_PLAYERS};
|
||||
use grovewright_core::player::{
|
||||
PlayerId, PlayerKey, MAX_KNOWN_PLAYERS, MAX_NAME_BYTES, MAX_ONLINE_PLAYERS,
|
||||
};
|
||||
use grovewright_core::world::{Event, World, DIG_COOLDOWN_TICKS, TICKS_PER_SECOND};
|
||||
|
||||
fn key(n: u8) -> PlayerKey {
|
||||
@@ -112,6 +114,28 @@ fn a_returning_player_can_change_their_name() {
|
||||
assert_eq!(world.player(id).unwrap().name, "Neuer Name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlong_names_are_stored_shortened_on_both_join_paths() {
|
||||
let mut world = World::new(1);
|
||||
world.step(&[Command::Join {
|
||||
key: key(1),
|
||||
name: "ä".repeat(20),
|
||||
}]);
|
||||
assert_eq!(world.player(PlayerId(0)).unwrap().name, "ä".repeat(16));
|
||||
|
||||
world.step(&[Command::Leave {
|
||||
player: PlayerId(0),
|
||||
}]);
|
||||
world.step(&[Command::Join {
|
||||
key: key(1),
|
||||
name: "x".repeat(MAX_NAME_BYTES + 10),
|
||||
}]);
|
||||
assert_eq!(
|
||||
world.player(PlayerId(0)).unwrap().name,
|
||||
"x".repeat(MAX_NAME_BYTES)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fifth_online_player_is_refused() {
|
||||
let mut world = World::new(1);
|
||||
|
||||
Reference in New Issue
Block a user