Aetheria/Assets/Scripts/ServerShared/SimulationDice.cs
soul cc0c164574 determinism-ledger: every ItemManager states its seed; scatter on the zone's dice
- ItemManager(uint seed, ...) refuses seed 0 and seeds Random from it.
- RunStart.ItemSeed(galaxy, step) is public (formula unchanged), refuses a
  zero result; named steps ArenaStep, StageStep, ContinueStep and NoRunSeed.
- Continue seeds the item stream from the saved run's seed (ContinueStep);
  a scene with no galaxy uses NoRunSeed.
- Zone's unpack scatter draws SimulationDice.For(CombatSeed, ScatterStream,
  1-based pack ordinal), never the item stream.
- Loadout's scratch manager and AetherDb's catalog command take fixed seeds;
  every test and Editor construction gains 1u (mechanical rewrite).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:01:41 +02:00

43 lines
2.1 KiB
C#

/* 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;
// A pilot's own decisions, ordinal Agent.Id: its preferred engagement range and its patrol circuit. AI behaviour
// never rides ItemManager.Random, so minting items cannot change how a pilot fights.
public const uint CombatSampleStream = 3;
public const uint PatrolStream = 4;
// Generation placement of an unpacked ship packed at the origin, ordinal its 1-based index in the pack, on the zone's
// seed (CombatSeed): the run seed feeds drops and mines only (ruling run-seed-drops-only), and placement never rides
// ItemManager.Random.
public const uint ScatterStream = 5;
// 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);
}
}