# Determinstic Seeding (Global Seed)

This page explains how to enable and use the **Global Seed** to get deterministic, debuggable spawn behavior in the Runtime Spawner. With a fixed seed and the same content, the spawner will produce the *same* spawn choices (per-entry RNG), positions, and group sizes every run- great for QA repros, speedruns, and perf testing.

***

### What the seed controls

* A single **Global Seed** drives a fast, reproducible[ **PCG32** RNG](https://www.pcg-random.org/index.html) for spawns.
* For each spawn request, the spawner derives a stable sub-stream using:
  * the **Global Seed**
  * the `SpawnEntry`’s **stable identity**
  * context hints (e.g., `SpawnContext.SourceName` such as a wave/table name, or region name)
* The derived RNG is used for:
  * **Placement sampling** (global/region/wave)
  * **Group size & grouping order** (leader + members)
  * **Member offsets** around the leader

> Result: given the same seed, scene, and timing, spawn locations and groups repeat exactly.

***

### Enabling determinism

#### In the Inspector

1. Select your **RuntimeSpawner**.
2. Enable **Use Deterministic Seed**.
3. Set **Global Seed** (any int).

#### From code (recommended for game bootstrap)

Call this **before** any spawns start (i.e., before `StartSpawners()` or before waves fire).

```csharp
using MegaCrush.Spawner;
using UnityEngine;

public class GameBootstrap : MonoBehaviour
{
    [SerializeField] private RuntimeSpawner spawner;
    [SerializeField] private int missionSeed = 123456; // set from save/CLI/dev console

    void Awake()
    {
        if (!spawner) spawner = FindAnyObjectByType<RuntimeSpawner>();

        // Locks the package into deterministic mode and broadcasts the change.
        spawner.SetGlobalSeed(missionSeed);

        // Optionally start the system afterward
        spawner.Init();
        spawner.StartSpawners();
    }
}
```

You can listen to `RuntimeSpawner.onSeedChanged` if you mirror the seed to UI/telemetry:

```csharp
private void OnEnable()  => RuntimeSpawner.onSeedChanged += OnSeedChanged;
private void OnDisable() => RuntimeSpawner.onSeedChanged -= OnSeedChanged;

private void OnSeedChanged(int seed)
{
    Debug.Log($"Spawner seed now {seed}");
    // Update HUD, write to a run log, etc.
}
```

***

### Picking good seeds

A few simple patterns:

#### 1) Per-run random → saved for repro

```csharp
int seed = System.Environment.TickCount;        // or RandomNumberGenerator for crypto
spawner.SetGlobalSeed(seed);
// Save 'seed' to your run history so QA can replay exactly the same mission.
```

#### 2) Campaign + node → stable across sessions

```csharp
int seed = Hash32(playerCampaignSeed, currentNodeId);
spawner.SetGlobalSeed(seed);

// Example non-cryptographic hash
static int Hash32(int a, int b)
{
    unchecked { uint h = 2166136261; h = (h ^ (uint)a) * 16777619; h = (h ^ (uint)b) * 16777619; return (int)h; }
}
```

#### 3) Command line / dev console

```
-game_seed=987654
```

Parse once, call `SetGlobalSeed(value)` in your bootstrap.

***

### Best practices

* **Call early.** Set the seed **before** any spawns can occur (including auto-start or `WaveTrigger.StartAutomatically`).
* **Use the same seed everywhere** (if your own systems also randomize). For true full-game determinism, route your other RNG-using systems off the same root seed.
* **Log the seed** in your run summary so QA can replay bugs easily.
* **Don’t mix UnityEngine.Random** into spawn decisions you expect to be deterministic. The spawner already supplies a per-request RNG internally; if you extend spawns, use that RNG rather than global state.

***

### Scope & limitations

Determinism covers:

* Entry placement sampling (global/region/wave)
* Group size and per-member offsets
* Spawner-internal randomization that uses the derived PCG32 stream

Not guaranteed deterministic:

* **Wave entry selection order** inside `WaveSpawnLoop` if you rely on `UnityEngine.Random` in your own code paths.
  * If you need fully deterministic wave composition, either pre-author explicit sequences, or adapt your selection to route through the same seed (e.g., add a small utility that uses `SpawnerSeedRouter` for wave picking).
* External nondeterminism: physics timing, asynchronous content loads, time-based triggers, or anything randomized outside the spawner using other RNGs.

> In short: the spawner side is deterministic; the rest of your game must also avoid nondeterministic influences if you want a perfectly repeatable run.

***

### Frequently asked

**Q: Can I change the seed mid-mission?**\
A: You can call `SetGlobalSeed(...)` at any time; it will affect subsequent spawn requests. For reproducible runs, set it once at mission start.

**Q: Does the seed affect waves?**\
A: Yes - spawn placement and group behavior within a wave are deterministic. If you also want deterministic *which* `SpawnEntry` a wave picks when randomizing, drive that selection from the same seed (see limitation above).

**Q: How does this interact with pooled objects?**\
A: Pooling is unaffected. The RNG decides *where/what/how many*; the pool just supplies instances.

**Q: Do I need to enable anything else?**\
A: No. Turn on **Use Deterministic Seed** and set a seed. Everything else is built in.

***

### Example: QA repro flow

1. On mission start, generate/log a seed:

   ```csharp
   int seed = System.Environment.TickCount;
   spawner.SetGlobalSeed(seed);
   Debug.Log($"RUN SEED: {seed}");
   ```
2. If a bug is found, re-launch with `-game_seed=<value>` or paste the seed in your dev console and rerun.
3. The same enemies, placements, and groupings will appear at the same times/places (subject to the limitations above).

***

### API recap

* **Inspector**
  * `Use Deterministic Seed` (bool)
  * `Global Seed` (int)
* **Runtime**
  * `void SetGlobalSeed(int seed)`
  * `int GlobalSeed { get; }`
  * `bool UseDeterminism { get; }`
  * `static event Action<int> onSeedChanged`

That’s it - flip the switch, set a seed, and enjoy reproducible spawns.
