Gerüst für den Rust-Kern anlegen

Workspace, leeres Crate grovewright-core und ein Wächter-Test, der den
Quelltext des Kerns auf Verstöße gegen die Gleichschritt-Regeln prüft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Steven Wroblewski
2026-09-30 20:08:01 +02:00
parent fc909f963d
commit 477b9a41dd
8 changed files with 171 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "grovewright-core"
version = "0.1.0"
edition = "2021"
rust-version = "1.94"
publish = false
description = "Deterministic simulation core of Grovewright"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
float_arithmetic = "deny"
+7
View File
@@ -0,0 +1,7 @@
//! Deterministic core of Grovewright.
//!
//! Everything that belongs to the shared world lives here. The core never
//! touches the engine, the clock, files or the network, and it calculates
//! only with whole numbers. Given the same commands it produces the same
//! world on every machine. That is what lets every player simulate the whole
//! world and exchange nothing but commands.
+78
View File
@@ -0,0 +1,78 @@
//! Guards the lockstep rules: the core must not use anything whose result
//! can differ between machines or between runs.
use std::fs;
use std::path::{Path, PathBuf};
/// Text that must not appear anywhere in the source of the core, and why.
const FORBIDDEN: &[(&str, &str)] = &[
("f32", "floating point results can differ between machines"),
("f64", "floating point results can differ between machines"),
(
"HashMap",
"iteration order differs between runs; use BTreeMap or Vec",
),
(
"HashSet",
"iteration order differs between runs; use BTreeSet or Vec",
),
("std::time", "the core must not read the clock"),
("Instant", "the core must not read the clock"),
("SystemTime", "the core must not read the clock"),
("std::thread", "the core must stay single-threaded"),
("std::env", "the core must not depend on the environment"),
(
"std::fs",
"the core must not touch files; the shell does that",
),
(
"std::net",
"the core must not open connections; the shell does that",
),
("unsafe", "the core forbids unsafe code"),
];
fn rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
for entry in fs::read_dir(dir).expect("source directory is readable") {
let path = entry.expect("directory entry is readable").path();
if path.is_dir() {
rust_files(&path, out);
} else if path.extension().is_some_and(|ext| ext == "rs") {
out.push(path);
}
}
}
#[test]
fn core_source_uses_no_forbidden_apis() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
rust_files(&src, &mut files);
files.sort();
assert!(
!files.is_empty(),
"no source files found in {}",
src.display()
);
let mut problems = Vec::new();
for file in &files {
let text = fs::read_to_string(file).expect("source file is readable");
for (number, line) in text.lines().enumerate() {
for (needle, reason) in FORBIDDEN {
if line.contains(needle) {
problems.push(format!(
"{}:{}: `{needle}`: {reason}",
file.display(),
number + 1
));
}
}
}
}
assert!(
problems.is_empty(),
"forbidden APIs in the core:\n{}",
problems.join("\n")
);
}