---
# System prepended metadata

title: TinySea — Trophic Energy & Predator Outcomes

---

# **Question:**
How “energy” moves between trophic levels and, specifically, how **predators’ survival/reproduction** depend on the **amount of prey in the next lower tier**.

# TinySea — Trophic Energy & Predator Outcomes (Q3)
Purpose: Define trophic feeding/energy flow—demand → fedRate → proportional prey removal → FinalPerformance → reproduction & death across tiers.

---

**Scope:** survival/reproduction* is computed from parameters in [[`species.xlsx`.]](https://docs.google.com/spreadsheets/d/141_4gAArZyXMYOCcxK86yRJLOncS2UDW/edit?usp=drive_link&ouid=107445983843194401813&rtpof=true&sd=true)

* **Tiers:** `Tier 1` (basal, not predatory), `Tier 2` (eats Tier-1), `Tier 3` (eats Tier-2).
* **Per-species demand per turn:** `Demand_s = N_s × EatingAmount_s × Perf_s × Days`.
  * `Perf_s ∈ [0,1]` is the **thermal performance** from Q2.
* **Tier demand:** `DemandTier = Σ Demand_s` for all predators in that tier.
* **Available prey:** sum of population counts in the **next lower tier**.
* **Feeding outcome (shared across all predators in the tier):**
  `fedRate = min(1, AvailablePrey / DemandTier)` (if `DemandTier ≤ 0` → `fedRate = 1`; if `AvailablePrey = 0` → `fedRate = 0`).
* **Prey removal (consumption):** lower-tier population is reduced by the **amount eaten**, to each prey species.
* **FinalPerformance:** `FinalPerformance = Perf × fedRate`.
* **Deaths:** if `FinalPerformance < DeathThreshold`, losses = `max(MinimumDeaths, N × DeathRate × Days)`.
* **Reproduction:** if `FinalPerformance ≥ ReproThreshold` **and** `N ≥ 2`, births = `N × FinalPerformance × ReproductionMultiplier × Days`.
  Extra rule: **Tier-1** births are **×0.85** if **no Tier-2** species are present.
  (A global population cap may limit births if you apply one.)
* **Death causes labeling:** starvation when under-fed; otherwise cold/hot based on temperature side relative to optimum.
---

## 1) Inputs (per species from `species.xlsx`)

* **Tier** — trophic level (`1` basal, `2` mid, `3` top).
* **EatingAmount** — per-individual prey demand multiplier (per **day**).
* **ReproductionMultiplier** — scales births when reproducing.
* **DeathThreshold** — if final performance is **below** this, species **loses** individuals.
* **DeathRate** — fraction of population lost **per day** when dying.
* **MinimumDeaths** — floor on losses when dying.
* **ReproThreshold** — if final performance is **at/above** this, species **reproduces**.

**From Q2 (per species, this turn):**

* **Perf ∈ \[0,1]** — thermal performance at current water temperature.

**Runtime (per turn):**

* **N** — current population.
* **Days** — biology days per turn (e.g., 1 or 5).

---

## 2) Turn order (one full turn)

1. Compute **Perf** for every species (see Q2).
2. **Tier-2 feeding** (consumes Tier-1), then **Tier-3 feeding** (consumes Tier-2):
   compute **fedRate** and **remove prey**.
3. For **every species** (all tiers):
   `FinalPerformance = Perf × fedRate` → apply **reproduction** then **death** rules.

> **Tier-1** does **not** eat lower tiers → **`fedRate = 1`**.

---

## 3) Feeding & energy flow (predator tiers only)

Let predator tier be **T** (T=2 or 3), prey tier **T−1**.

### 3.1 Per-species demand (per turn)

```csharp
Demand_s = N_s × EatingAmount_s × Perf_s × Days
```

* Appetite scales with **Perf** (cold/hot → they eat less), and with **Days**.

### 3.2 Tier demand vs available prey

```csharp
DemandTier   = Σ Demand_s        // over predators at tier T
AvailablePrey = Σ N_p            // over prey at tier T−1
```

### 3.3 Shared fedRate for the predator tier

```csharp
if DemandTier ≤ 0       → fedRate = 1
else if AvailablePrey=0 → fedRate = 0
else                    → fedRate = min(1, AvailablePrey / DemandTier)
```

* The **same** `fedRate` applies to **all** predators in that tier this turn.

### 3.4 Prey removal (consumption) from tier T−1

Total removed:

* If `fedRate < 1`: remove **all** `AvailablePrey`.
* If `fedRate = 1`: remove **exactly** `DemandTier`.

Split removals **proportionally** to each prey species’ current share: ( artic, tropical, common )

```csharp
totalRemoved = (fedRate < 1) ? AvailablePrey : DemandTier
for each prey i:
    share_i  = N_p_i / (Σ N_p)
    remove_i = share_i × totalRemoved
    N_p_i    = max(0, N_p_i − remove_i)
```

*(Optional bookkeeping: count these as **Eaten** deaths for the prey tier.)*

**Edge cases**

* If `Σ N_p = 0`, no removal occurs (prey already empty), predators get `fedRate = 0`.
* Clamp populations at **≥ 0**; no negatives.

---

## 4) Final performance (the value used by outcomes)

```csharp
FinalPerformance = Perf × fedRate
```

* **Tier-1:** `fedRate = 1` ⇒ `FinalPerformance = Perf`.
* **Tier-2/3:** scarcity reduces `FinalPerformance` by the tier’s `fedRate`.

---

## 5) Reproduction (apply births first)

A species reproduces **only if**:

* `FinalPerformance ≥ ReproThreshold`, **and**
* `N ≥ 2` (needs at least two individuals).

Births per turn:

```csharp
Births = N × FinalPerformance × ReproductionMultiplier × Days
```
**Tier-1 special case:** if **no Tier-2 species exist** this turn, apply a **15% decrease**:
Note: Tier-1 has a special condition where if Tier-2 species does not exist, then decrease the birth rate by 15%. This is most likely done for balancing purposes.

Code : -> 131 in CharacterManager
```csharp
float reproReduction = foodChainLevel == 1 && !HasCreaturesOfTier(2) ? 0.85f : 1f;
```
```csharp
Births ← Births × 0.85
```

(Apply any global population cap before updating.)

```csharp
N ← N + Births
```

---

## 6) Death (apply after reproduction)

If `FinalPerformance < DeathThreshold`, the species **loses** individuals:

```csharp
Deaths = max(MinimumDeaths, N × DeathRate × Days)
N ← max(0, N − Deaths)
```

**Cause labeling (optional)**

* If low feeding drove the failure (very low `fedRate`) → **Starve**.
* Else classify by temperature side of optimum → **Cold** or **Hot**.

If `DeathThreshold ≤ FinalPerformance < ReproThreshold`, population is unchanged.

---

## 7) Worked example (one predator tier)

**Days = 5**.
**Prey (Tier-1):** X `N=80`, Y `N=40` ⇒ `AvailablePrey = 120`.
**Predators (Tier-2):**

* A: `N=40`, `EatingAmount=1.0`, `Perf=0.6` → `Demand_A = 40×1.0×0.6×5 = 120`
* B: `N=10`, `EatingAmount=1.2`, `Perf=0.5` → `Demand_B = 10×1.2×0.5×5 = 30`
  `DemandTier = 150`

`fedRate = min(1, 120/150) = 0.8` → A & B both use `0.8`.

**Prey removal:** `totalRemoved = 120` (scarce prey).
Shares: X `80/120=0.666…`, Y `40/120=0.333…` → remove X=80, Y=40.

**FinalPerformance:** `FP_A = 0.6×0.8 = 0.48`, `FP_B = 0.5×0.8 = 0.40`.
Each species then applies its own **ReproThreshold/DeathThreshold**, **ReproductionMultiplier/DeathRate**, and **MinimumDeaths** with `Days = 5`.

---

## 8) Standalone C# reference
The Standalone C# reference provides a minimal, engine-agnostic implementation of the TinySea temperature model so anyone can reproduce results outside the project (e.g., in tests, tools, or other languages).

```csharp
using System;
using System.Collections.Generic;

public static class TinySeaQ3
{
    // Per-species demand (per turn)
    public static double Demand(double N, double eatingAmount, double perf, double days)
    {
        return N * eatingAmount * perf * days;
    }

    // Shared fedRate for predator tier (2 or 3)
    public static double FedRate(double availablePrey, double demandTier)
    {
        if (demandTier <= 0.0) return 1.0;
        if (availablePrey <= 0.0) return 0.0;
        double r = availablePrey / demandTier;
        return r < 1.0 ? r : 1.0;
    }

    // Proportional prey removal from lower tier
    public static void RemovePreyProportionally(double totalRemoved, IList<double> preyPop)
    {
        if (totalRemoved <= 0.0 || preyPop == null || preyPop.Count == 0) return;
        double sum = 0.0; for (int i = 0; i < preyPop.Count; i++) sum += Math.Max(0.0, preyPop[i]);
        if (sum <= 0.0) return;

        if (totalRemoved >= sum) { for (int i = 0; i < preyPop.Count; i++) preyPop[i] = 0.0; return; }

        for (int i = 0; i < preyPop.Count; i++)
        {
            double take = (preyPop[i] / sum) * totalRemoved;
            preyPop[i] = Math.Max(0.0, preyPop[i] - take);
        }
    }

    // One species: apply reproduction then death for this turn
    public static void StepPopulation(ref double N, double days,
                                      double finalPerf,
                                      double deathThreshold, double deathRate, double minimumDeaths,
                                      double reproThreshold, double reproMultiplier,
                                      int tier, bool anyTier2Present,
                                      double? maxCap = null)
    {
        // Reproduction first
        if (finalPerf >= reproThreshold && N >= 2.0)
        {
            double births = N * finalPerf * reproMultiplier * days;
            if (tier == 1 && !anyTier2Present) births *= 0.85; // Tier-1: −15% if no Tier-2 present
            if (maxCap.HasValue) births = Math.Min(births, Math.Max(0.0, maxCap.Value - N));
            N += births;
        }

        // Death second
        if (finalPerf < deathThreshold)
        {
            double deaths = Math.Max(minimumDeaths, N * deathRate * days);
            N = Math.Max(0.0, N - deaths);
        }
    }
}
```

---

## 9) Quick checklist (per turn)

* Compute **Perf** (Q2).
* For Tier-2 then Tier-3: compute **DemandTier**, **AvailablePrey**, **fedRate**, and **remove prey** **proportionally**.
* For each species: compute `FinalPerformance = Perf × fedRate`, then apply **Reproduction** (if eligible) and **Death**.
* Log: `DemandTier`, `AvailablePrey`, `fedRate`, `totalRemoved`, per-species `Perf`, `FinalPerformance`, **Births/Deaths**, updated `N`.

```mermaid
flowchart TD
  A([Next Turn]) --> B["Advance temperature\n(set TempC)"]
  B --> C["Compute thermal performance"]
  C --> D{"Predation step? (tiers 2/3)"}
  D -- Yes --> E["Predation"]
  D -- No --> F["Skip predation"]
  E --> G["Finalize performance"]
  F --> G
  G --> H{"Death check"}
  H -- Yes --> I["Apply deaths"]
  H -- No --> J{"Reproduction check"}
  J -- Yes --> K["Apply births"]
  J -- No --> L([End of turn])
  I --> L
  K --> L
```




## 10) Relevant Unity Code :

<details>  
<summary>CharacterManager</summary>
    
### 10.1) CharacterManager:
Responsible for all individuals of that variant speices the game has a total of 27 CharacterManager one for each species variant.
* GetEatingRate() = eatingAmount * performanceRate (why Perf affects demand),
* ReproduceOrDie(...) (needs ≥2 to reproduce; uses Repro/Death thresholds, ReproductionMultiplier, DeathRate, MinimumDeaths; Tier-1 0.85 births if no Tier-2 present),
* GetFinalPerformance() (= perf * fedRate).    

```csharp
using UnityEngine;
using System.Collections.Generic;

public class CharacterManager : MonoBehaviour {

    //species stats (these change during gameplay)
	public float speciesAmount = 0;
	public float performanceRate = 1;
    public float fedRate = 1;
    public float cost = 100;
    public LevelManager.Variant variant;

    //object references
	public ThermalCurve thermalcurve;
	public PlayerManager player;
	/*
	 *  Species Properties
	 */
	public string uniqueName = "Nameless"; //a unique name for each species
    public float eatingAmount = 3; //the amount of fish I need to eat every day
    public int foodChainLevel = 1; //how high I am in the food chain (1 == bottom)
    public float reproductionMultiplier = .5f; //how many babies I have every day

    public float deathThreashold = .3f; //if performance gets too low, you start dying
    public float deathRate = .5f; //if I'm dying, population drops by this ratio every day
    public float minimumDeaths = 1; //if I'm dying, I will always lose at least this many fish!
    public float reproThreshold = .25f; // Only reproduce if above this threshold

    public int eatingStars = 5;
    public int reproductionStars = 5;
    public int deathThreasholdStars = 5;
    public int deathRateStars = 5;
    public int thermalBreadthStars = 5;
    public string description = "This is a fish";
    public string temperatureThresholdText;
    public string reproductionRateText;
    public Sprite icon;

    public enum DeathCause { Cold, Hot, Starve, Eaten, Sold};
    
    public Queue<DeathCause> deathList = new Queue<DeathCause>();

    public enum BirthCause { Reproduction, Bought};
    
    public Queue<BirthCause> birthList = new Queue<BirthCause>();

    private float lastTemp = 0;

    public void updatePerformance(float temperature)
    {
        lastTemp = temperature;
        performanceRate = thermalcurve.getCurve(temperature + 273);
    }
    
	public float GetEatingRate(){
		return eatingAmount * performanceRate;
	}

    public float getFinalPerformance()
    {
        return fedRate * performanceRate;
    }

    //fish reproduce or die based on performance.
    public void ReproduceOrDie(float days)
    {
        if (speciesAmount == 0)
        {
            return;
        }

        Debug.Log("reproducing or dying: " + speciesAmount + " of " + uniqueName);
        //if performance is too low, fish die
        if (getFinalPerformance() < deathThreashold)
        {
            float deaths = speciesAmount * deathRate * days;
            deaths = Mathf.Max(minimumDeaths, deaths);
            speciesAmount -= deaths;
            speciesAmount = Mathf.Max(0, speciesAmount);
            Debug.Log("Deaths: " + deaths);
            //figure out why we're dying (starve, too hot, or too cool)
            if (fedRate < deathThreashold)
            {
                Debug.Log("died from starvation");
                SessionRecorder.instance.WriteToSessionDataWithRound(",Death by Starve - " + GetSessionRecorderText() + "," + deaths.ToString());
                for (int i = 0; i < deaths; i++)
                {
                    deathList.Enqueue(DeathCause.Starve);
                }
            }
            else
            {
                if (lastTemp + 273 < thermalcurve.optimalTemp)
                {
                    Debug.Log("died from cold");
                    SessionRecorder.instance.WriteToSessionDataWithRound(",Death by Freeze - " + GetSessionRecorderText() + "," + deaths.ToString());
                    for (int i = 0; i < deaths; i++)
                    {
                        deathList.Enqueue(DeathCause.Cold);
                    }
                }
                else
                {
                    Debug.Log("died from heat");
                    SessionRecorder.instance.WriteToSessionDataWithRound(",Death by Heat - " + GetSessionRecorderText() + "," + deaths.ToString());
                    for (int i = 0; i < deaths; i++)
                    {
                        deathList.Enqueue(DeathCause.Hot);
                    }
                }
            }
        }
        else if (player.getTotalFishCount() >= player.maxFishes)
        {
            Debug.Log("Max fishes, not reproducing");
            return;
        }
        else if (getFinalPerformance() < reproThreshold)
        {
            Debug.Log("Not higher than the reproduction threshold");
            return;
        }
        else if (speciesAmount < 2)
        {
            Debug.Log("Need two fish to reproduce");
            return;
        }
        else 
        {
            float fishToMax = (player.maxFishes) - player.getTotalFishCount();
            float reproReduction = foodChainLevel == 1 && !HasCreaturesOfTier(2) ? 0.85f : 1f;
            float reproduced = speciesAmount * getFinalPerformance() * reproductionMultiplier * reproReduction * days;
            if (reproduced > fishToMax)
            {
                reproduced = fishToMax;
            }
            speciesAmount = speciesAmount + reproduced;
            Debug.Log("reproduced : " + reproduced);
            SessionRecorder.instance.WriteToSessionDataWithRound(",Reproduction - " + GetSessionRecorderText() + "," + reproduced.ToString());
            for (int i = 0; i < reproduced -.9f; i++)
            {
                birthList.Enqueue(BirthCause.Reproduction);
            }
        }

        if (speciesAmount < 1)
        {
            speciesAmount = 0;
            //clear out any straggler fish
            deathList.Enqueue(DeathCause.Starve);
            deathList.Enqueue(DeathCause.Starve);
            deathList.Enqueue(DeathCause.Starve);
            deathList.Enqueue(DeathCause.Starve);
        }
    }

    public string GetSessionRecorderText()
    {
        return "Tier " + foodChainLevel + " " + variant.ToString() + " " + GetGeneralistOrSpecialistType();
    }

    private string GetGeneralistOrSpecialistType()
    {
        string textToReturn = "ERR";
        switch (reproductionRateText)
        {
            case "Low":
                textToReturn = "Generalist";
                break;
            case "Average":
                textToReturn = "Average Species";
                break;
            case "High":
                textToReturn = "Specialist";
                break;
        }
        return textToReturn;
    }

    private bool HasCreaturesOfTier(int tier)
    {
        foreach (CharacterManager c in player.species)
        {
            return c.foodChainLevel == tier && c.speciesAmount > 0;
        }
        return false;
    }
}

``` 
</details>

            
<details>  
<summary>PlayerManager</summary> 
            
#### 10.2) PlayerManager: 

Player Manger has the logic for buying/selling logic and Predation locgic.
* Computes per-species demand (speciesAmount * GetEatingRate() * dayStep),
* Computes tier fedRate (min(1, AvailablePrey / ΣDemand)),
* Consumes prey proportionally (eatAtLevel(...)), and advances biology by dayStep.
            
```csharp
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Events;

public class PlayerManager : MonoBehaviour {

	/*
	 *  Player's species
     *  Make sure that these are in the same order as the SwimmingHolder's prefabs!
	 */
	public List<CharacterManager> species;
    public SwimmingHolder holder;

    //Highest level in the species list
    public int lowestLevel = 1;
    public int highestLevel = 3;
    public Temperature temperature;
	/*
	 *  Player's data
	 */

	public float days = 0;
	public float moneys = 0;
    public float dayStep = 1;
    public float dailyIncome = 100;
    public float netWorthIncomeRate = .2f;
    public float sellRate = .5f;
    public float maxFishes = 200;
    public float stepWaitTime = 1;
    public UnityEvent onNextTurn;
    //float stepTimer = 0;
    bool waitingForTemperature = false;
    bool waitingForSteps = false;
    enum FishSteps { predation, reproduce, capMax, finished};
    FishSteps currentStep = FishSteps.finished;
    int predationPhase = 2;
    int highestPredator = 3;
    [HideInInspector]
    public bool busy = false;

    //gameobjects
    public Text moneyText;

    public int tierScaling = 5;

    public RectTransform tier1EcoBar;
    public RectTransform tier2EcoBar;
    public RectTransform tier3EcoBar;

    private Vector3 tierProportions;

    public RectTransform tier1Glow;
    public RectTransform tier2Glow;
    public RectTransform tier3Glow;
    public RectTransform fullGlow;

    public ParticleSystem tooCoolPart;
    public ParticleSystem tooHotPart;
    public ParticleSystem eatenPart;
    public ParticleSystem starvedPart;
    public ParticleSystem reproducePart;

    /*
	 * Initialize with three species at each level
	 */
    private void Awake() 
    {
        species = new List<CharacterManager>(GetComponentsInChildren<CharacterManager>());
        foreach (CharacterManager c in species)
        {
            c.player = this;
            //Debug.Log(c.uniqueName);
        }
        holder = FindObjectOfType<SwimmingHolder>();
	}

    private void Start()
    {
        SessionTimer.ResetRoundTime();
    }

    public void nextTurn()
    {
        if(!busy && !holder.anyCreaturesBusy() && !LevelManager.isBusy)
        {
            RecordRoundData();
            SessionTimer.ResetRoundTime();

            waitingForTemperature = true;
            busy = true;
            temperature.updateTemperature();
            temperature.updateDay();
            onNextTurn.Invoke();
        }
    }

    public void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            nextTurn();
        }
        if (!temperature.animating && waitingForTemperature)
        {
            waitingForTemperature = false;
            currentStep = FishSteps.predation;
            predationPhase = 2;
            //stepTimer = 0;
            waitingForSteps = true;

            moneys += dailyIncome;
            foreach (CharacterManager c in species)
            {
                moneys += c.cost * c.speciesAmount * netWorthIncomeRate;
            }

        }
        if (waitingForSteps)
        {
            //Debug.Log(currentStep);
            if (!holder.anyCreaturesBusy())
            {
                switch (currentStep)
                {
                    case FishSteps.predation:
                        //calculate performance rates (based on temperature and food supply)
                        foreach (CharacterManager c in species)
                        {
                            c.updatePerformance(temperature.temperature);
                        }
                        //bigger creatures eat the smaller ones
                        Predation(dayStep, predationPhase);
                        predationPhase++;
                        if (predationPhase > highestPredator)
                        {
                            currentStep = FishSteps.reproduce;
                        }
                        break;
                    case FishSteps.reproduce:
                        //creatures reproduce or die depending on performance rate
                        foreach (CharacterManager c in species)
                        {
                            c.ReproduceOrDie(dayStep);
                        }
                        currentStep = FishSteps.capMax;
                        //stepTimer = stepWaitTime;
                        break;
                    case FishSteps.capMax:
                        capFishMax();
                        currentStep = FishSteps.finished;
                        break;
                    case FishSteps.finished:
                        busy = false;
                        waitingForSteps = false;
                        break;
                }
            }
        }
        
        /*
        if (Input.GetKeyDown("q"))
        {
            moneys += 999999;
        }
        */

        //set ecosystem pyramid.
        float totalFishes = getTotalFishCount();
        if (Mathf.FloorToInt(totalFishes) <= 0)
        {
            tier1EcoBar.localScale = new Vector3(0, 1, 1);
            tier2EcoBar.localScale = new Vector3(0, 1, 1);
            tier3EcoBar.localScale = new Vector3(0, 1, 1);
        }
        if (Mathf.FloorToInt(totalFishes) > 0)
        {
            float t1Proportion = Mathf.FloorToInt(getTotalAmountAtLevel(1)) / totalFishes;
            float t2Proportion = (Mathf.FloorToInt(getTotalAmountAtLevel(2)) * tierScaling) / totalFishes;
            float t3Proportion = (Mathf.FloorToInt(getTotalAmountAtLevel(3)) * tierScaling * tierScaling) / totalFishes;

            if (Mathf.Max(t1Proportion, t2Proportion, t3Proportion) > 0)
            {
                tierProportions = new Vector3(t1Proportion, t2Proportion, t3Proportion) /
                                Mathf.Max(t1Proportion, t2Proportion, t3Proportion);
            }
            else { tierProportions = new Vector3(0, 0, 0); }

            tier1EcoBar.localScale = new Vector3(tierProportions.x, 1, 1);
            tier2EcoBar.localScale = new Vector3(tierProportions.y, 1, 1);
            tier3EcoBar.localScale = new Vector3(tierProportions.z, 1, 1);

            if (tierProportions.x == 1 && tierProportions.y == 1 && tierProportions.z == 1)
            {
                tier1Glow.gameObject.SetActive(false);
                tier2Glow.gameObject.SetActive(false);
                tier3Glow.gameObject.SetActive(false);
                fullGlow.gameObject.SetActive(true);
            }
            else
            {
                fullGlow.gameObject.SetActive(false);
            }

            if (tierProportions.x == 1)
            {
                tier1Glow.gameObject.SetActive(true);
            }
            else
            {
                tier1Glow.gameObject.SetActive(false);
            }

            if (tierProportions.y == 1)
            {
                tier2Glow.gameObject.SetActive(true);
            }
            else
            {
                tier2Glow.gameObject.SetActive(false);
            }

            if (tierProportions.z == 1)
            {
                tier3Glow.gameObject.SetActive(true);
            }
            else
            {
                tier3Glow.gameObject.SetActive(false);
            }
        }

        moneyText.text = "$" + Mathf.Floor(moneys).ToString();
    }

    public void BuyCreatures(int index, float amount, bool money_access = true)
    {
        if(money_access)
            moneys = moneys - species[index].cost * amount;
        for (int i = 0; i < amount; i++)
        {
            species[index].birthList.Enqueue(CharacterManager.BirthCause.Bought);
        }
        species[index].speciesAmount += amount;
    }

    public void SellCreatures(int index, float amount)
    {
        for (int i = 0; i < amount; i++)
        {
            species[index].deathList.Enqueue(CharacterManager.DeathCause.Sold);
        }
        species[index].speciesAmount -= amount;
        moneys = moneys + species[index].cost * sellRate * amount;
    }

    private void capFishMax()
    {
        //fish out any excess fish
        float totalFish = getTotalFishCount();
        if (totalFish > maxFishes)
        {
            int fishToSell = Mathf.CeilToInt(totalFish - maxFishes);
            moneys += fishToSell * sellRate;
            for (int i = 0; i < fishToSell; i++)
            {
                //randomly pick a fish
                float fish = Random.Range(0, Mathf.FloorToInt(getTotalFishCount()));
                //step through character managers until we find the fish that we want to remove.
                foreach (CharacterManager c in species)
                {
                    if (fish <= c.speciesAmount)
                    {
                        c.speciesAmount--;
                        c.deathList.Enqueue(CharacterManager.DeathCause.Sold);
                        break;
                    }
                    fish -= c.speciesAmount;
                }
            }
        }
    }

    //returns the total amount of all fish
    public float getTotalFishCount()
    {
        float totalFish = 0;
        foreach (CharacterManager c in species)
        {
            totalFish += c.speciesAmount;
        }
        return totalFish;
    }

    public int GetTotalFishInt()
    {
        int totalFish = 0;
        foreach (CharacterManager c in species)
        {
            totalFish += (int)c.speciesAmount;
        }
        return totalFish;
    }

    //gets the total amount of all species for a given level
    public float getTotalAmountAtLevel(int level)
    {
        float amount = 0;
        foreach (CharacterManager c in species)
        {
            if (c.foodChainLevel == level)
            {
                amount += c.speciesAmount;
            }
        }
        return amount;
    }

    public int GetTotalFishAtLevelInt(int level)
    {
        int amount = 0;
        foreach (CharacterManager c in species)
        {
            if (c.foodChainLevel == level)
            {
                amount += (int)c.speciesAmount;
            }
        }
        return amount;
    }

    //returns a list of all the creatures for a given level
    private List<CharacterManager> getCharactersAtLevel(int level)
    {
        List<CharacterManager> list = new List<CharacterManager>();
        foreach (CharacterManager c in species)
        {
            if (c.foodChainLevel == level)
            {
                list.Add(c);
            }
        }
        return list;
    }

    //eats a certain amount of creatures sampled evenly within that level
    //negative things will happen if eatAmount is > the amount of creatures
    //^^^^^^^^ pun intended.
    private void eatAtLevel(int level, float eatAmount)
    {
        //make a list of all creatures in this level
        float totalAmount = getTotalAmountAtLevel(level);
        List<CharacterManager> eatenCreatures = getCharactersAtLevel(level);
        //eat them... EAT THEM!!! 
        if (totalAmount == 0)
            return;
        foreach (CharacterManager c in eatenCreatures)
        {
            float ratio = c.speciesAmount / totalAmount;
            float eatenFish = ratio * eatAmount;
            c.speciesAmount = c.speciesAmount - eatenFish;
            if (eatenFish > 0)
            {
                Debug.Log("Amount of " + c.uniqueName + " eaten: " + eatenFish.ToString());
                SessionRecorder.instance.WriteToSessionDataWithRound(",Death by Predator - " + c.GetSessionRecorderText() + "," + eatenFish.ToString());
            }
            for (int i = 0; i < eatenFish - .9f; i++)
            {
                c.deathList.Enqueue(CharacterManager.DeathCause.Eaten);
            }
        }
    }

    // Food obtain
    private void Predation(float dayStep, int tier)
    {
        //step through each species level, skipping the first one (they don't eat anything)
        List<CharacterManager> leveliCharacters = new List<CharacterManager>();
        float leveliAmount = 0;
        float foodRequestAmount = 0;
        foreach (CharacterManager c in species)
        {
            if (c.foodChainLevel == tier)
            {
                leveliCharacters.Add(c);
                leveliAmount += c.speciesAmount;
                foodRequestAmount += c.speciesAmount * c.GetEatingRate() * dayStep;
            }
        }
            
        //if we don't have enough food to go around
        if (foodRequestAmount > getTotalAmountAtLevel(tier - 1))
        {
            //set fed performance to the reduced amount. 
            float subOptimalFoodRate = getTotalAmountAtLevel(tier - 1) / foodRequestAmount;
            foreach (CharacterManager c in leveliCharacters)
            {
                c.fedRate = subOptimalFoodRate;
            }
            eatAtLevel(tier - 1, getTotalAmountAtLevel(tier - 1));
        }
        else
        {
            foreach (CharacterManager c in leveliCharacters)
            {
                c.fedRate = 1;
            }
            eatAtLevel(tier - 1, foodRequestAmount);
        }
    }
    
    public void RecordRoundData()
    {
        string linesToWrite = "Next Round Pressed,,,\n" +
                              ",,,Round Duration," + SessionTimer.FormatSessionRoundTime() + "\n" +
                              ",,,Money," + moneys.ToString() + "\n" +
                              ",,,Current Temperature," + temperature.temperature.ToString() + "\n" +
                              ",,,Forecast Low," + temperature.forecastLow.ToString() + "\n" +
                              ",,,Forecast High," + temperature.forecastHigh.ToString() + "\n" +
                              ",,,Ecosystem Pyramid by Percentage," + tierProportions.x.ToString() + "/" + tierProportions.y.ToString() + "/" + tierProportions.z.ToString() + "\n";
        foreach (CharacterManager cm in species)
        {
            if (cm.speciesAmount >= 1)
            {
                linesToWrite += ",,,Total " + cm.GetSessionRecorderText() + "," + cm.speciesAmount.ToString() + "\n";
            }
        }
        linesToWrite = linesToWrite.Substring(0, linesToWrite.Length - 1);
        SessionRecorder.instance.WriteToSessionDataWithRound(linesToWrite);
    }
}

```
</details>  