<!-- Sludge balance model — SL-01 / DF-180 · design · 2026-06-16 -->

# Sludge — Design Bible & Balance Model

This formalises GDD §3–4 (`gdd.md`) into a tunable model and explains **why**
the numbers are what they are. The numbers themselves live in one machine-readable file the sim
reads — [`../sim/balance.json`](../sim/balance.json) — so nothing in this doc is the source of truth
for a value; it is the source of truth for the *reasoning*. When you change a number, change it there
and update the rationale here.

> **House rule (design discipline):** every number a designer would touch is data, no functions, no
> magic constants in the sim. If the sim (SL-03+) or the test harness (SL-21) needs a number that
> isn't in `balance.json`, that's a bug — add it to the file, don't hard-code it.

---

## 0. The whole model on one page

| Symbol | Meaning | Default | Knob in `balance.json` |
| --- | --- | --- | --- |
| `S₀` | organism's starting sludge value (its core tile) | 10 | `spread.organismStartS` |
| `F` | base friction per tile (flat map) | 1 | `spread.tileBaseF` |
| `S_min` | a tile carries sludge only while `S ≥ S_min` | 1 | `spread.cullThresholdS` |
| `+B` | child organism's boost to its tile's S | 5 | `child.boostS` |
| `N` | proto-organ tap cost | 25 | `protoOrgan.cost` |
| — | child placement cost | 40 | `child.cost` |
| — | acid glob cost | 30 | `acid.cost` |
| `m` | maintenance per unit S | 1.0 | `economy.maintenancePerS` |
| `y` | income per covered food tile | 6 | `economy.incomePerFoodTile` |

**The three equations that drive everything:**

1. **Spread:** `S(neighbour) = S(source) − F(neighbour)`, placed while `≥ S_min`. (GDD §3)
2. **Maintenance:** `cost = m · Σ S` over *every* sludge tile in the network (children included). (GDD §4/§6)
3. **Hunger:** `reserve += income − cost` each tick; `income = y · (#food tiles covered)`. Below zero → starve. (GDD §4)

Everything else (children, proto-organs, acid, withering, death) is a modifier on these three.

---

## 1. Spread — the geometry of a sludge

The rule is literal and deterministic: a tile takes the **highest S among its sludged orthogonal
neighbours, minus its own friction**, and keeps sludge while that result is `≥ S_min`.

On a **flat map** (`F = 1` everywhere) starting from `S₀ = 10`, S decays by 1 per tile of distance:

```
S(d) = S₀ − d·F = 10 − d        (d = grid distance from the core)
```

so the field reaches `d = 9` (where `S = 1`) and stops at `d = 10` (`S = 0 < S_min`).

| Quantity | Formula | Flat-map value |
| --- | --- | --- |
| Reach (radius) | `R = ⌊(S₀ − S_min)/F⌋` | **9 tiles** |
| Footprint (tiles) | `1 + 4·(1+…+R)` | **181 tiles** |
| Full maintenance | `Σ_{d=0}^{R} 4d·(S₀−d)` *(+ centre)* | **670** (see §3) |

### 1.1 A note on the GDD's "≈10 tiles in diameter"

The literal rule above gives a footprint **~19 tiles across**, not ~10. The GDD's "~10 tiles in
diameter" is an *illustration*, not a constraint — and it's a useful one, because it pins down the
lever:

> diameter `≈ 2·(S₀ − S_min)/F + 1`.

To land on the GDD's picture you'd run `S₀ = 5, F = 1` (diameter ≈ 9) **or** `S₀ = 10, F = 2`
(diameter ≈ 9). **We keep the GDD's hard numbers `S₀ = 10, F = 1` as canonical** and accept the
larger field on purpose: more rings of S means **finer-grained consolidation decisions** (§4) — a
diameter-9 blob only has ~5 rings to shed, a diameter-19 blob has 9. Designers who prefer the tighter
GDD footprint change `spread.organismStartS` to 5; the model is identical, just shorter-range.

Reference table for picking a footprint:

| `S₀` | `F` | Reach | Diameter | Tiles | Full maintenance |
| --- | --- | --- | --- | --- | --- |
| 10 | 1 | 9 | 19 | 181 | 670 |
| 10 | 2 | 4 | 9 | 41 | 170 |
| 5 | 1 | 4 | 9 | 41 | 95 |
| 14 | 1 | 13 | 27 | 365 | 1834 |

### 1.2 Friction terrain

A tile's `F` is its terrain friction, not always 1. Higher-friction tiles eat more S per step, so the
sludge **dies out sooner** through them — this is what carves the GDD's tendrils-through-channels
shape (a low-F channel lets S travel far; a high-F wall stops it). `F = ∞` (or any `F > S₀`) is a hard
wall. Concrete surface→F values are SL-02's job; this model only fixes the **base** `F` and the rule.

---

## 2. Children — projecting reach, at a price

A child adds `+B = 5` to **its** tile's S before the spread continues from there. Drop a child on a
tile that currently holds `S` and that tile becomes `S + 5`, re-thickening locally and extending the
tendril ~5 more tiles down-channel than it could reach unaided. This is the **expansion / offence**
tool: it's how you push a tendril through a long channel toward contested food or around an enemy.

The price is twofold and both halves are real decisions:

- **Up-front:** `child.cost = 40` food.
- **Ongoing:** the child *raises ΣS*, so it permanently raises maintenance by the S of the whole
  sub-field it now powers — typically far more than its own +5. A child dropped on the rim of a flat
  field (placing a fresh `S = 5` source) adds a ~41-tile, ~95-S lobe → **+95/tick maintenance** for
  one tap. "Is this child worth it?" = "will the food it reaches beat ~95/tick?" — a genuine curve,
  not a free upgrade.

Children are **reabsorbable** (`child.reabsorbYield = 0.8` → reclaim 80% of `child.cost` as food) and
**wither on death**: the sludge a dead child was powering loses its boost and recomputes — anything
that now falls below `S_min` dies (GDD §2.2). Killing a key child is therefore an encirclement attack.

---

## 3. Economy — income vs. maintenance

**Maintenance** is the cost of *being* a sludge: `cost = m · Σ S` over every tile (GDD §4 "total S
value of the organism and all children"; GDD §6 "larger networks are more expensive"). We read "the
organism and all children" as **the whole body** — every sludge tile's S, since the organism *is* its
sludge — because that is the only reading under which §6's "larger networks cost more" is true. With
`m = 1.0`, maintenance is exactly ΣS.

The flat-map full field costs **670/tick**. The instructive part is *where that cost sits*:

```
ring maintenance  C(d) = 4d · (S₀ − d)      (tiles in ring d × their S)
```

| Ring `d` | Tiles | S each | Ring cost `C(d)` |
| --- | --- | --- | --- |
| 0 (core) | 1 | 10 | 10 |
| 1 | 4 | 9 | 36 |
| 2 | 8 | 8 | 64 |
| 3 | 12 | 7 | 84 |
| 4 | 16 | 6 | 96 |
| 5 | 20 | 5 | **100** ← peak |
| 6 | 24 | 4 | 96 |
| 7 | 28 | 3 | 84 |
| 8 | 32 | 2 | 64 |
| 9 (frontier) | 36 | 1 | 36 |

`C(d)` peaks at `d = S₀/2` and the **outer half of the radius carries ~57% of all maintenance**
(rings 5–9 = 380 of 670) while covering the most *area* (the frontier). That single fact is what makes
both encirclement and consolidation real — see §4.

**Income** is `y · (food tiles covered)`; a proto-organ over food multiplies that tile's yield by
`protoOrgan.foodEatMultiplier = 3` (GDD §2.1 "consumes faster"). At `y = 6`, a bare 670-cost field
breaks even on **~112 covered food tiles**, or ~37 organ-boosted ones. These absolute numbers are a
**reference operating point**, not sacred — `y`, `m` and the costs co-scale; tune `y`/`m` together to
move the whole economy without changing any of the *shapes* below.

---

## 4. The two decisions this model has to make real

### 4.1 Encirclement — why expanding isn't always right

Add one frontier ring and you pay its maintenance **now**; you only get income **if** that ring lands
on food. Marginal cost of the next ring grows with circumference (`≈ 4d` more tiles), while the food
it captures depends entirely on the map. So there is always a **frontier where the next ring costs
more than it earns** — past it, expanding *toward food-poor ground* makes you weaker, not stronger.

An opponent weaponises this. Because maintenance is front-loaded into the outer rings (§3) and those
rings hang off **chokepoints**, a single acid glob or sludge-on-sludge cut at a narrow channel severs
everything downstream (GDD §5; mechanic = SL-04 withering). The victim instantly loses that whole
lobe's *income* but had been paying its *cost* — and if the lobe was their main food, the cut tips
`income < cost` and starts the starve. **Encirclement = make your rival's network cost more than it
earns, then cut the part that earns.**

The levers that keep this sharp: `acid.burnRadius`/`burnSPerTick` and the attack/defend rates
(`protoOrgan.attackRatePerTick`) set how cheaply a cut can be forced vs. defended; `tileBaseF` and a
channel's width set how exposed the chokepoints are (defence = widen the channel with proto-organ
smoothing or acid, so one glob can't sever it).

### 4.2 Consolidation — why retreating is a real strategy

When you're losing ground or just got cut off, the model rewards **deliberately shrinking**. Shed the
outer rings and you drop the *bulk* of your maintenance for the *least* income (the food-poor
frontier): consolidating a flat field from reach 9 → reach 5 cuts maintenance **670 → 390 (−42%)**
while only giving up the outermost, thinnest, usually-already-contested coverage. A smaller network
that stays above water beats a big one that starves (GDD §6).

Two ways it happens, both tunable:

- **Proactive (player):** reabsorb outer children/proto-organs (`reabsorbYield = 0.8`) to *bank*
  biomass as food — turning an over-extended network into a war chest before the rival can cut it.
- **Automatic (starvation):** see §5 — a starving organism sheds its own frontier to claw back toward
  `income ≥ cost`. Same curve, forced.

Because the canonical field has **9 rings**, consolidation is a graded dial (drop one ring, or five),
not a binary — which is exactly why §1.1 keeps `S₀ = 10`.

### 4.3 Consumption-linked acid potency (SL-38)

Acid **potency** scales *inversely* with the firing player's live **ΣS** — the exact maintenance load
the economy already computes every tick (§3), so this adds no new economic system. A small / contracted
organism lands **sharper** acid; a large / sprawling one lands **weaker** acid. The knob is *effect*,
not price: `acid.cost` stays flat (`30`); the firing player's ΣS multiplies `acid.burnSPerTick` for that
shot (`burnRadius` is left flat — one lever, kept readable). This makes **"contract to strike" a real
play**: let your outlying frontier wither (§4.2 consolidation) to drop ΣS into a higher-potency band,
then strike.

The curve is **banded**, not continuous, so players get readable breakpoints to aim for ("shed to the
next band and my acid jumps a tier") rather than an opaque slider. `acid.potencyBands` is an ordered
list of `{ maxSigmaS, burnMultiplier }`; the firing player's ΣS picks the **first** band whose
`maxSigmaS` is `null` (the catch-all) or `≥` that ΣS. The boundaries are anchored to the §3 **ΣS-by-reach**
of a flat field (cumulative `C(d)` from §3 — reach 4 ≈ 290, reach 6 ≈ 486, the full reach-9 field = **670**,
children add ≈ +95 ΣS each), so the **typical healthy field sits at 1.0×** — this is a *deviation* modifier
around normal play, not a universal buff or nerf:

| ΣS band | `maxSigmaS` | potency × | intent |
| --- | --- | --- | --- |
| very small (heavily contracted) | `290` | **1.5×** | the "coil and strike" reward (capped — see G4) |
| small | `490` | **1.25×** | a contracted field hits above its weight |
| mid (typical healthy field) | `850` | **1.0×** | baseline = pre-SL-38 behaviour; the full reach-9 field (670) + a child or two |
| large | `1200` | **0.8×** | sprawl tax on offence |
| very large (over-extended) | `null` | **0.6×** | the floor for a badly over-extended body |

Two **guardrails** are acceptance gates (Charlie's calls), demonstrated in the SL-21 harness
(`sludge/sim/sim.test.js` #42), not asserted from the curve on paper:

- **G3 — must not overcorrect ("winning is punished").** Anchoring **mid at 1.0×** is the mechanism: a
  normal-sized defender keeps **full** acid; only *genuine* over-extension (ΣS past 850 — well beyond the
  full single-organism field) drops to 0.8/0.6×. The harness pins the flat reach-9 field at exactly ΣS 670 →
  1.0×, so expanding sensibly to defend is never self-defeating. This is meant to **stack gently** with the
  economy's existing expansion brake (§4.1), not double it. *If the harness ever shows expansion becoming
  self-defeating, widen the mid band / soften the large-band penalties — do not ship a curve that violates
  this.*
- **G4 — turtling must not dominate.** Being small *already* costs you board, food capture and front
  (§1–4); the high-potency bands **compensate** a contracted player, they do not **reward** permanent
  turtling. The low-ΣS bonus is **capped at 1.5×** (tiny ≠ absurd acid), and the harness shows a sprawling
  firer is *weakened, not crippled* (it still removes real S). *If the harness ever shows "stay minimal and
  snipe" winning across representative ΣS bands, lower the small-band multipliers toward 1.0×.*

> **Gate status (first-pass bands, this build): G3 PASS, G4 PASS** — recorded against the harness #42
> assertions. The bands are tuned against real `balance.json` ΣS ranges; if a future balance edit moves
> those ranges, re-derive the boundaries (and re-record the gates) rather than keeping these constants.

---

## 5. Hunger, starvation & death

Each tick:

```
reserve += income − maintenance        (clamped to [0, hunger.reserveCap])
```

`hunger.startingReserve = 200` is the opening buffer; `reserveCap = 600` stops a runaway leader from
banking infinite food (≈ one ring-5's worth of slack). While the field earns its keep, reserve sits at
the cap and nothing bad happens.

When `income < maintenance` long enough to drive `reserve` to 0, the organism **starves**:

1. If `hunger.autoConsolidate` (default **true**): each starving tick it **withers its current
   outermost frontier** (the lowest-S connected tiles) — automatic §4.2 consolidation. This drops
   maintenance fastest-per-tile and often restores `income ≥ cost` on its own, stabilising the
   organism at a smaller sustainable size. This is the self-correcting curve that makes the game about
   *attrition*, not instant death.
2. If there's nothing left to shed and it's still starving, the **core erodes** by
   `hunger.coreErosionPerTick = 1` S/tick.
3. **Death** when the organism core `S ≤ hunger.deathAtOrganismS (0)`. The match ends when one
   organism dies (GDD §6).

So a neglected organism doesn't die the instant income dips — it shrinks, fights for a sustainable
equilibrium, and only dies when **even its smallest viable form can't be fed** (i.e. its rival has
genuinely encircled it off from food). That's the win condition the whole model exists to produce.

---

## 6. Actions reference (cost side)

All four player actions and their tunable knobs. Behaviours are GDD §2; the numbers are the starting
balance.

| Action | Gesture | Cost | Key knobs | Notes |
| --- | --- | --- | --- | --- |
| **Proto-organ** | tap | `N = 25` | `foodEatMultiplier 3`, `smoothRatePerTick 0.25`, `attackRatePerTick 2`, `neutraliseRatePerTick 2` | Context-sensitive (eat / smooth / attack-defend / neutralise, GDD §2.1). Reabsorb returns `0.8·N`. |
| **Child** | long-press | `40` | `boostS 5`, `reabsorbYield 0.8` | Extends reach (§2); raises ΣS; withers what it powered on death. |
| **Acid glob** | tap off-sludge | `30` | `smoothRatePerTick 2.0`, `burnRadius 2`, `burnSPerTick 4`, `potencyBands` | Opens map (friction→clear, faster than an organ) or burns a hole in enemy sludge → can sever (GDD §2.3). **Burn scales with the firer's ΣS** (`potencyBands`, §4.3) — cost stays flat. |

Smoothing rates are deliberately ordered **acid (2.0) ≫ proto-organ (0.25)**: acid is the fast,
expendable map-opener; the organ is the slow, permanent investment that also fights. That gap is a
tuning lever for "open the map" vs. "hold the line" pacing.

---

## 7. Tick & determinism

`tick.hz = 10` fixed steps/second; `tick.spreadStepsPerTick = 1` (the field advances one ring per
tick, so growth is *visible* and interruptible — you can cut a tendril mid-extension). The sim must be
a **fixed-step, seedable** loop: same seed + same inputs ⇒ identical run. This is non-negotiable —
it's what lets SL-14 net the game by lockstep and SL-21 test the balance by replay. Nothing in this
model uses wall-clock time or randomness.

---

## 8. What this hands to the next tasks

- **SL-02 (map/terrain):** owns concrete surface→`F` values and channel/chokepoint authoring. This doc
  fixes only base `F` and the spread rule; `F = ∞` is a wall, low-`F` is a channel.
- **SL-03 (sim core):** reads `balance.json`; implements §1 spread, §3 economy, §5 hunger exactly.
- **SL-04 (connectivity/withering):** §2 (child death) and §4.1 (cut-off) define when tiles wither.
- **SL-05 (economy):** §3/§5 are its spec.
- **SL-06/07/08 (organs/children/acid):** §6 is the cost/rate table.
- **SL-38 (consumption-linked acid potency):** §4.3 — acid burn scales inversely with the firer's ΣS
  via `acid.potencyBands`; boundaries are anchored to the §3 ΣS-by-reach (mid-field at 1.0×).
- **SL-21 (test harness):** assert the §1 footprint (181 tiles / 670 maintenance at defaults), the
  §3 break-even, the §5 starve→consolidate→death sequence, and the §4.3 acid-potency gates as
  regression checks.

### Open question (non-blocking)

The GDD's "≈10 tiles in diameter" (§1.1) is treated as illustrative; canonical stays `S₀ = 10, F = 1`
(diameter ≈ 19) for finer consolidation. If the designer wants the literal tighter footprint, set
`spread.organismStartS = 5` — flagged here rather than blocking, since the *rule* is unambiguous.
