//! 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> { 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" ); }