Hello, a few weeks ago a test clan walked into a tier 1 elite node that the offer card rated an even fight, and got erased to the last larva. The card was not lying about the budget. The director had spent exactly what the card promised, and then quietly handed the elite a 35 percent stat bonus on the house. This post is about the system that assembles every enemy squad in Mutarch, and about the rule that keeps it honest: the number the game shows you must be the number the director actually spent.
One point budget, spent like money
Mutarch’s expedition ladder has no top. The content tier is an unbounded integer, so
hand-authoring encounters means either a content treadmill I lose by definition (I am one
person) or fights that go trivial the moment the player outgrows the last thing I wrote. So
nobody authors fights. A director does, in src/game/combat/encounterDirector.ts, and it
works like a pit boss with a till.
Every offer on the expedition board freezes a requiredPower at spawn. Engage a node and
the director’s point budget is that frozen number times a node multiplier (NODE_MULT:
1.0 for a regular fight, 1.25 for an elite stand, 1.6 for a boss), swung by a variance roll of
plus or minus 10 percent (BUDGET_VARIANCE_PCT = 0.1). It then picks a composition idea and a
template (a ratio recipe like “tank plus ranged”), and fills slots with creature cards until
the money is gone. Each card’s price is its power score grown by the same per-level curve the
unit’s stats actually get, 1 + 0.12 × (level − 1), so a point spent is a point of fielded
threat at every level. There is deliberately no body cap; fight size is emergent from budget
divided by price.
Why variance at all? Because identical fights at the same tier read as stamped from a mold. 10 percent is wide enough that scouting both stands on a route is worth your time, and narrow enough that the power rating stays honest: the card’s number is the center of the spend, and the worst case is 10 percent hot.

Two guards backstop the degenerate end: a budget too small to afford anything still fields
one cheapest body (an enemy node with no enemies reads as a bug, not a gift), and a comp that
resolves to a single battlefield body appends the cheapest card anyway. Both overspend on
purpose and flag it (guardOverspend) so the sim harness can tell honesty from accident.
The elite that did not pay rent
Back to the tester’s wipe. On elite and boss nodes the director elevates one pick to a leader:
1.35 times stats (ELITE_STAT_MULT) plus 3 bonus levels (ELITE_LEVEL_BONUS), compounding
through the level curve. The original sin was sequencing. The fill loop charged the leader its
plain card price, and the elevation happened after the loop, so the entire premium landed on
the field as free threat on top of a fully spent budget. At tier 1, where one buffed body is
most of the fight, “free premium” translated to a stomp the card had no way to warn about.
The obvious fix, and the one I shipped first, was to make the elite pay after the fact: compose the squad, charge the real premium, then trim escorts cheapest-first until the spend fits the budget again. It even sounds principled. It was wrong in a way I only saw in the offline ladder sim: cheapest-first trimming kept eating the same victims, the cheap offensive chaff, while expensive support bodies survived the cut. Healer comps came out of the trimmer as a leader, a healer, and nothing that could kill anything. Those fights did not get harder, they got longer, stalling to the tick cap and resolving as draws, and the trim could eat the whole escort and stall composition outright. I had built a machine that pays its debts by selling its furniture.
The insight, embarrassingly late, is that you do not balance a budget by un-spending it. You balance it by not spending money you have already promised to someone else. The shipped director prices the premium into a reserve up front: before filling a single slot, it computes the worst-case premium (the priciest elite-capable body in the pool, priced at the top of the level jitter band) and carves that off the till. The template’s ratio recipe then fills a balanced comp into what is actually affordable, and the real premium is charged when the leader is elevated. Reserve is always at least the premium, so spend stays at or under budget, no trimming, ever. The tester’s even fight is now an even fight.

One dial: max(0.8, 0.25 + 0.25 × tier)
Where does requiredPower come from? One function, tierFactor in
src/game/balance/progression.ts, so simple I hesitate to graph it:
tierFactor(tier) = max(0.8, 0.25 + 0.25 × tier)
Enemy level is your squad’s average level times this factor. The point budget is your power anchor times this factor (times the node multiplier). One dial scales both axes of difficulty together, over every integer tier: 0.8 at tiers 1 and 2, 1.0 at tier 3, 1.25 at tier 4, 1.5 at tier 5, then a straight line, 2.75 at tier 10, 5.5 by tier 21. No lookup table to fall off, no clamp to hit. One dial means one place to be wrong, which is a feature: every balance bug in this system so far has been findable by staring at one line of arithmetic.

The floor is the dial’s one war story. The raw line gives tier 1 a factor of 0.5, and a fresh clan’s anchor of roughly 400 times 0.5 is a 200-point budget: exactly one full-price card, often one body. The opening fights of the entire game were 1v5s. Flooring tiers 1 and 2 at 0.8 puts about 320 points in the till, which reliably affords 2 or more cards and 4 or more bodies, and the openers read as packs again.
Divergent progression gets the same one-formula treatment. When your deepest clan fights at
frontier tier F, an offer at tier t below it softens: effective difficulty is
tierFactor(t) × max(0.5, 1 − 0.08 × (F − t)), 8 percent per tier of lag, floored at half
strength about 6 to 7 tiers back. I could have built a level-sync system, but the
synchronization would touch every stat read in combat, and future me would refuse to maintain
it. Instead outgrown tiers get progressively stompier by design, and the offer card labels
them “Farm” instead of pretending they are a challenge.

Texture comes from elsewhere. From tier 3 up, offers can roll affixes
(src/game/world/offerAffixes.ts): Frenzied enemies swing 20 percent faster, Veteran spawns
field 20 percent higher levels, Swarming widens the budget 15 percent but spends all of it on
the cheapest body available, each priced into the run’s reward multiplier. There were six;
Entrenched got cut when forced-single-affix measurement showed it spiking wipe rate by 24.6
percentage points, which is not texture, that is a wall. And because deep play needs a place,
not just a number, every 5-tier band past the authored arc (Border through Apex Verge) gets a
generated name, so tier 23 lives in the Rootmaw Descent rather than in “tier 23”.
The offer card is a contract
Offers freeze requiredPower at spawn and never re-anchor. A player who outgrows the board
quickly can therefore engage a stale offer whose budget buys a comically small fight. The
tempting fix, re-anchoring stale offers to your live power, breaks a promise: the offer card
already described the fight (“a spitter brood with carapace cover”, derived from the
composition), and a re-anchored director might compose entirely new species into it. You
would engage the spitter brood and meet something else.
So padding follows a description-honesty rule: an outgrown comp is topped up by duplicating
its own cards, round-robin, never new species. The fight fields more of exactly what it
promised. The padded demand is one shared formula, paddedRequiredPower: the larger of the
frozen budget and 0.6 times your live anchor times tierFactor, capped at 3 times frozen so a
pathologically stale offer cannot swell into a perf-hostile horde. The 0.6 floor sits
deliberately below 1; outgrown offers staying stompy is the farm content working, padding only
removes the degenerate one-card-versus-an-army case.
The gotcha that forced “one shared formula” into a single exported function: my first padding
pass computed the pad target inside the director, while the offer card’s power rating still
banded against the frozen number. The board said trivial, the fight padded up to substantial,
and the card was lying in the safe-looking direction, which is the worst direction. Now
offerPeakDemand on the card calls the exact same paddedRequiredPower the director spends
through, so the UI cannot drift from the sim without a compile error.
Side note: you might ask whether plus or minus 10 percent and live-anchor padding add up to hidden rubber banding. The variance is rolled as the first draw off the encounter’s seeded rng (the forked-stream setup from #3), so a given node always rolls the same budget, the pre-fight preview already includes it, and it never reacts to how you are doing. The padding is on the card before you click. Rubber banding is the game adjusting to you behind your back; this is the game telling you what it spent.
As always, let me know what you think.