Skip to main content

Crate banished_core

Crate banished_core 

Source
Expand description

§banished-core

The exact foundations that the rest of Un-Banished builds on. Each part is a port of an engine function of Banished 1.0.7, and gives the same bits as the original:

ModuleWhat it contains
crtThe C runtime’s single-precision maths: sinf, cosf, acosf, atan2f
mathVector helpers, headings, and the engine’s Transform (a quaternion and an offset)
randomThe game’s random number generator, a Mersenne Twister (MT19937)
noiseThe Perlin noise of the terrain generator
schedulerGame time (integer microseconds) and the entity manager’s update buckets
calendarThe game’s year counter
pickingRay–triangle intersection for selecting objects

The crate has no dependencies. It knows nothing about the game’s rules or its data files. banished-game re-exports every module, so banished_game::random and banished_core::random are the same module.

§Why it exists

Banished’s simulation depends on float rounding. For example, the map generator adds noise values and the result decides where a river runs. Rust’s f32::sin and the C runtime’s sinf can differ in the last bit, and that is enough to change a map. So simulation code in Un-Banished uses crt and math, never Rust’s own float functions.

§Use it

[dependencies]
banished-core = { path = "crates/core" }

§Random numbers

Random gives the same numbers as the game for the same seed. A new game, the map generator and the citizens’ names all start from one.

use banished_core::random::Random;

let mut rng = Random::new(1234);
let a = rng.rand();                // any u32
let b = rng.rand_below(10);        // 0 to 9
let c = rng.rand_range(-5, 5);     // -5 to 5, both included
let d = rng.rand_tol(3);           // -3 to 3
let e = rng.rand_float();          // 0 to 1 (1 not included)
let f = rng.rand_float_tol();      // -1 to 1 (-1 not included)

The generator’s state is the same as the engine object: an index and 624 words. A save stores it, and Random::from_parts(index, state) restores it.

To check that a port draws numbers in the same order as the original, record the draws with random::trace (see Emulate for exact numbers):

use banished_core::random::{trace, Random};

trace::start();
let mut rng = Random::new(7);
rng.rand_below(3);
let draws = trace::take();          // each draw, with its kind and the Rust caller
assert_eq!(draws.len(), 1);

§Exact float maths

use banished_core::{crt, math};

let s = crt::sinf(0.5);                    // the C runtime's sinf, bit for bit
let angle = crt::atan2f(1.0, 2.0);
let unit = math::normalize3([3.0, 0.0, 4.0]);
let heading = math::heading(0.0, 1.0);     // a 2D direction, in degrees

math::Transform is the engine’s transform: a rotation (quaternion) and an offset. Transform::of_model(yaw, position) builds the transform of a placed model, and apply, rotate and inverse use it.

§Perlin noise

use banished_core::noise::PerlinNoise;

// persistence, octaves, and the two seeds.
let noise = PerlinNoise::new(0.5, 4.0, 17, 31);
let value = noise.generate(12.5, 40.25);

§Game time

The engine counts time in integer microseconds (System::Time). Convert to and from seconds as the original does:

use banished_core::scheduler::{seconds, ticks};

let t = ticks(1.5);          // 1,500,000
let s = seconds(t);          // 1.5

scheduler::EntityScheduler reproduces how the game spreads entity updates over frames: five buckets, each with a quota of entities per frame (QUOTAS = [50, 15, 8, 5, 2]).

§The calendar

use banished_core::calendar::Calendar;
use banished_core::scheduler::ticks;

let mut calendar = Calendar::default();   // year 1
calendar.advance(ticks(3600.0));          // one year of game time
assert_eq!(calendar.year, 2);

§The API

The full API documentation is at https://tanepiper.github.io/unbanished/api/banished_core/index.html.

§Tests

The parity tests for this crate are in crates/game/tests/ (for example crt_math.rs, transform.rs, time_emu.rs and picking_emu.rs). They compare the ports with output recorded from the original functions in the emulator, and skip when that output is missing.

cargo test --release -p banished-core
cargo test --release -p banished-game --test crt_math

Modules§

calendar
The game’s calendar: the year, and the time since the year began.
crt
The float functions of the Microsoft C runtime, as the game calls them: sinf, cosf, acosf and atan2f.
math
Engine math helpers (Runtime-steam-x64.dll Math::Scalar, Math::Vector2/3) with the original’s precision: the C runtime’s sinf/cosf/atan2f (crate::crt), sqrtf (correctly rounded), and the operations in the original’s order.
noise
The engine’s Perlin noise, which the terrain generator uses.
picking
Picking: which triangle a ray hits, as the engine finds the object under the pointer.
random
The game’s random number generator: a Mersenne Twister (MT19937).
scheduler
Game time, and the entity manager’s update buckets.