From 9116946874b23d69fb7b445b9e7dc96484fdda33 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 7 Oct 2026 14:52:10 +0200 Subject: [PATCH] SimulationDice: fmix32 moves out of FireControl, with the structured per-zone dice stream Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/FireControl.cs | 20 ++--------- Assets/Scripts/ServerShared/SimulationDice.cs | 35 +++++++++++++++++++ .../ServerShared/SimulationDice.cs.meta | 11 ++++++ 3 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 Assets/Scripts/ServerShared/SimulationDice.cs create mode 100644 Assets/Scripts/ServerShared/SimulationDice.cs.meta diff --git a/Assets/Scripts/ServerShared/FireControl.cs b/Assets/Scripts/ServerShared/FireControl.cs index 04e960d33..80d8d4b76 100644 --- a/Assets/Scripts/ServerShared/FireControl.cs +++ b/Assets/Scripts/ServerShared/FireControl.cs @@ -667,22 +667,6 @@ public static class FireControl // so whether a gated shot draws is unobservable from anywhere. The claim this comment used to make is // gone with the stream it was about, and the test that asserted it became vacuous the same moment; what // replaced it pins the live rule instead (combat touches no shared stream, on any path). - // 9.1 (docs/fire-control-cut.md): fmix32, MurmurHash3's 32-bit finalizer -- xor-shift, multiply, - // xor-shift, multiply, xor-shift. Diffuses a structured seed (here, a zone seed XORed with a small, - // low-bits-only ShotId) across every bit before the first xorshift draw reads it, so two seeds that - // differ only in ShotId's low bits still land on uncorrelated first outputs. Caller-side mixing only - // -- CultMath.Random itself is unchanged (see Commit's own note on why that follow-up is a separate - // CultLib cut). - private static uint MixSeed(uint seed) - { - seed ^= seed >> 16; - seed *= 0x85ebca6bu; - seed ^= seed >> 13; - seed *= 0xc2b2ae35u; - seed ^= seed >> 16; - return seed; - } - // Cut 12.2 (docs/fire-control-cut.md, "Bearing timing"): steps 1-5. Everything the shooter decided is // frozen in `shot` (R10); everything the target is doing -- position (deviation) and now also facing // (bearing) -- is read live, here, at the commit tick. The Cut 7 guard extends to cover the new factors: @@ -722,11 +706,11 @@ public static class FireControl // round for NextFloat's first call, and one round does not diffuse a seed that differs from its // neighbours only in ShotId's low bits. Unmixed, every zone's first draw was a near-constant // function of ShotId (measured span ~0.125), making a shot a step function of p fixed at zone - // creation, not a roll (Soul C1). MixSeed is a standard fmix32 finalizer (MurmurHash3): this is + // creation, not a roll (Soul C1). SimulationDice.Mix is a standard fmix32 finalizer (MurmurHash3): this is // a caller's obligation, not a CultMath defect -- seeding xorshift with a structured value and // drawing once is the documented way to get a correlated first output, and a follow-up to give // CultMath.Random a mixed-seed entry point of its own is recorded for a separate CultLib cut. - var random = new Random(MixSeed((zone.CombatSeed * 2654435761u) ^ (uint) shot.ShotId | 1u)); + var random = new Random(SimulationDice.Mix((zone.CombatSeed * 2654435761u) ^ (uint) shot.ShotId | 1u)); var p = CommitProbability(shot, now, out var sil); var hit = p > 0f && random.NextFloat() < p; diff --git a/Assets/Scripts/ServerShared/SimulationDice.cs b/Assets/Scripts/ServerShared/SimulationDice.cs new file mode 100644 index 000000000..94d8f8946 --- /dev/null +++ b/Assets/Scripts/ServerShared/SimulationDice.cs @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using Random = CultMath.Random; + +// Structured simulation seeds: the one place a zone seed, a stream and an ordinal become a generator. Every +// draw is a pure function of its arguments, never of a shared stream, so one roll cannot perturb another. +public static class SimulationDice +{ + public const uint LootStream = 1; + public const uint MineStream = 2; + + // 9.1 (docs/fire-control-cut.md): fmix32, MurmurHash3's 32-bit finalizer -- xor-shift, multiply, + // xor-shift, multiply, xor-shift. Diffuses a structured seed (a zone seed XORed with a small, + // low-bits-only id) across every bit before the first xorshift draw reads it, so two seeds that differ + // only in their low bits still land on uncorrelated first outputs. Caller-side mixing only -- + // CultMath.Random itself is unchanged. FireControl.Commit's per-shot seed is built on this verbatim. + public static uint Mix(uint seed) + { + seed ^= seed >> 16; + seed *= 0x85ebca6bu; + seed ^= seed >> 13; + seed *= 0xc2b2ae35u; + seed ^= seed >> 16; + return seed; + } + + // The generator for the `ordinal`th roll of `stream` in the zone identified by `zoneSeed`. + public static Random For(uint zoneSeed, uint stream, uint ordinal) + { + var seed = Mix(Mix(zoneSeed ^ Mix(stream)) ^ ordinal); + return new Random(seed == 0 ? 1u : seed); + } +} diff --git a/Assets/Scripts/ServerShared/SimulationDice.cs.meta b/Assets/Scripts/ServerShared/SimulationDice.cs.meta new file mode 100644 index 000000000..c32cf0aba --- /dev/null +++ b/Assets/Scripts/ServerShared/SimulationDice.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da428be21afa46a991b55ab9b794d192 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: