From 1efc75e9b3916985e5ca05c811b4c0ae8dc64bf0 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Mon, 7 Sep 2026 13:38:41 +0200 Subject: [PATCH 1/6] CI: Forgejo-Actions-Workflow (Multi-Repo: server+common als Siblings, sb-common-Patch-Pfad fuer CI umgeschrieben) --- .forgejo/workflows/ci.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .forgejo/workflows/ci.yml diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..e91ef5b --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,25 @@ +name: server-ci +on: [push, pull_request] + +jobs: + build-test: + runs-on: self-hosted + steps: + - name: Checkout server + run: | + git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-server.git" server + cd server && git checkout "$GITHUB_SHA" + + - name: Checkout common (sibling) + run: git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-common.git" common + + - name: Patch sb-common path for CI + run: | + sed -i "s|/home/jan/Development/skara-brae/skara-brae-common/rust|$GITHUB_WORKSPACE/common/rust|g" server/Cargo.toml + grep -n "sb-common" server/Cargo.toml + + - name: Build + run: cargo build --workspace --manifest-path server/Cargo.toml + + - name: Test + run: cargo test --workspace --manifest-path server/Cargo.toml From a66d39207bc4d3fe15cd5ecb5efd300affce47b6 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Mon, 7 Sep 2026 14:34:49 +0200 Subject: [PATCH 2/6] ci: trigger test --- .forgejo/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index e91ef5b..95ceb7f 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -23,3 +23,5 @@ jobs: - name: Test run: cargo test --workspace --manifest-path server/Cargo.toml + +# trigger ci From 41a6e09aa80899a7c8fa79065524f813626019e6 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Mon, 7 Sep 2026 14:35:45 +0200 Subject: [PATCH 3/6] ci: trigger test 2 --- .forgejo/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 95ceb7f..7423fa0 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -25,3 +25,4 @@ jobs: run: cargo test --workspace --manifest-path server/Cargo.toml # trigger ci +# trigger ci 2 From 19e8727a7c6e36e6f5ef9356be2d18bf79ca9faa Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Mon, 7 Sep 2026 14:40:55 +0200 Subject: [PATCH 4/6] ci: trigger test 3 --- .forgejo/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 7423fa0..71d2ce7 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -26,3 +26,4 @@ jobs: # trigger ci # trigger ci 2 +# trigger ci 3 From 64e081aeb710b042f05aedae64cb9b10383387c0 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Mon, 7 Sep 2026 14:42:17 +0200 Subject: [PATCH 5/6] ci-fix: common unter Originalname clonen (include_str!-Pfade), sb-common-Absolutpfad in allen Cargo.toml umschreiben --- .forgejo/workflows/ci.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 71d2ce7..70d7332 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -10,20 +10,16 @@ jobs: git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-server.git" server cd server && git checkout "$GITHUB_SHA" - - name: Checkout common (sibling) - run: git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-common.git" common + - name: Checkout common (sibling, Originalname wegen include_str!-Pfaden) + run: git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-common.git" skara-brae-common - - name: Patch sb-common path for CI + - name: sb-common-Pfade fuer CI umschreiben (alle Cargo.toml) run: | - sed -i "s|/home/jan/Development/skara-brae/skara-brae-common/rust|$GITHUB_WORKSPACE/common/rust|g" server/Cargo.toml - grep -n "sb-common" server/Cargo.toml + grep -rl "/home/jan/Development/skara-brae/skara-brae-common/rust" server --include=Cargo.toml | xargs -r sed -i "s|/home/jan/Development/skara-brae/skara-brae-common/rust|$GITHUB_WORKSPACE/skara-brae-common/rust|g" + grep -rn "sb-common" server --include=Cargo.toml - name: Build run: cargo build --workspace --manifest-path server/Cargo.toml - name: Test run: cargo test --workspace --manifest-path server/Cargo.toml - -# trigger ci -# trigger ci 2 -# trigger ci 3 From 61fef279176b6c245d9d3fb202e5b499f0fba387 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Sat, 3 Oct 2026 09:56:41 +0200 Subject: [PATCH 6/6] WI-1: injectable RNG - request_rng() single construction point (StdRng::from_os_rng), thread rng through create_character and run_adventure, seeded determinism test (PSP1, issue #1) --- crates/sb-api/src/routes/mod.rs | 16 +++++++++++---- crates/sb-core/src/character.rs | 35 ++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/crates/sb-api/src/routes/mod.rs b/crates/sb-api/src/routes/mod.rs index edd8d65..343fc10 100644 --- a/crates/sb-api/src/routes/mod.rs +++ b/crates/sb-api/src/routes/mod.rs @@ -25,6 +25,11 @@ use sb_shared::{ use std::sync::Arc; use tokio::sync::RwLock; +fn request_rng() -> rand::rngs::StdRng { + use rand::SeedableRng; + rand::rngs::StdRng::from_os_rng() +} + pub(crate) struct AppState { users: RwLock>, items: Vec, @@ -285,7 +290,10 @@ async fn create_character( return Err(axum::http::StatusCode::BAD_REQUEST); } - let mut char = character::create_character(user_id, name, input.race, input.class); + let mut char = { + let mut rng = request_rng(); + character::create_character(&mut rng, user_id, name, input.race, input.class) + }; { let mut id = state.next_char_id.write().await; @@ -424,6 +432,8 @@ async fn run_adventure( .unwrap() .as_millis() as i64; + let mut rng = request_rng(); + let (char, enemy_def) = { let mut chars = state.characters.write().await; let char = chars.iter_mut().find(|c| c.id == char_id) @@ -439,7 +449,6 @@ async fn run_adventure( } char.nerve -= zone.nerve_cost; - let mut rng = rand::rng(); let enemy = domain::pick_enemy(&mut rng, &zone.encounters, &state.enemies); (char.clone(), enemy) }; @@ -494,14 +503,13 @@ async fn run_adventure( songs::BARD_SONGS.iter().find(|s| s.id == sid).map(|s| s.combat_effect) }), spell_effect.as_ref(), - &mut rand::rng(), + &mut rng, ); let (loot_names, updated_char) = { let mut chars = state.characters.write().await; let dropped_ids: Vec = if let Some(c) = chars.iter_mut().find(|c| c.id == char_id) { use rand::Rng; - let mut rng = rand::rng(); if log.winner == c.name { c.xp += log.xp_gained as i64; diff --git a/crates/sb-core/src/character.rs b/crates/sb-core/src/character.rs index 1c89d2d..927fe04 100644 --- a/crates/sb-core/src/character.rs +++ b/crates/sb-core/src/character.rs @@ -126,10 +126,15 @@ pub fn training_cost(stat: i64) -> i32 { (5.0_f64 * 1.3_f64.powi(scaled)) as i32 } -pub fn create_character(user_id: u32, name: String, race: Race, class: Class) -> Character { - let mut rng = rand::rng(); +pub fn create_character( + rng: &mut impl rand::Rng, + user_id: u32, + name: String, + race: Race, + class: Class, +) -> Character { let class_str = class.as_str().to_string(); - let stats = roll_stats(&mut rng, &race); + let stats = roll_stats(rng, &race); let max_hp = calc_max_hp(&class, &stats); let max_energy = calc_max_energy(&class); let max_nerve = calc_max_nerve(&class); @@ -412,4 +417,28 @@ mod tests { assert!(dwarf.strength > human.strength); assert!(dwarf.intelligence < human.intelligence); } + + #[test] + fn create_character_is_deterministic_under_seeded_rng() { + use rand::SeedableRng; + let first = create_character( + &mut rand::rngs::StdRng::seed_from_u64(42), + 7, + "Twin".into(), + Race::Human, + Class::Warrior, + ); + let second = create_character( + &mut rand::rngs::StdRng::seed_from_u64(42), + 7, + "Twin".into(), + Race::Human, + Class::Warrior, + ); + assert_eq!(format!("{:?}", first.stats), format!("{:?}", second.stats)); + assert_eq!(first.hp, second.hp); + assert_eq!(first.max_hp, second.max_hp); + assert_eq!(first.energy, second.energy); + assert_eq!(first.nerve, second.nerve); + } }