Design

16. Balancing

Every formula, every tunable constant, and the pacing model.

Every formula and tunable constant is here, with the checks that hold balance in place.

Source of truth. The build is normative: every number below is checked against the catalog in packages/simulation/src/catalog at the build this page ships with. Live tuning names the knobs and why they sit where they do, and What the checks cover says what each automated check asserts. Numbers measured from the build are in Measurements.

Number ranges#

Upload Labs players lose interest when numbers become meaningless (Influences). Quorum keeps every number in a real range.

NumberRange at startRange at the end of the first companyHard limit
Users15050 millionAbout 6 billion (online population)
Demand3 rps1 million rpsUsers × R_user
Latency30–500 ms30–500 ms
Cash$300Trillions of game dollars
ValuationAbout $1 billion at the IPOAbout $100 billion at 6 billion users

Cash, revenue, and Burn are game dollars. Valuation, payout, and Legacy use real dollars, converted by the valuation scale.

Formatting: SI suffixes only (k, M, B, T). No letter suffixes such as "aa".

Constants#

ConstantSymbolValueWhere used
Step length100 msSimulation
Calendar rate1 sim s = 1 calendar minSimulation
Requests per userR_user0.02 rpsDemand
Diurnal amplitudeA0.45Demand
Copy price growth1.25×Prices
Resize price growth2× per size stepVertical and horizontal
Sell refund50%Components
Insight yield1 × √(sampled rps), per Component type and kindObservability
Default sample rate10%Observability
Default retention15 min; pool cap at least 100Observability
Study duration20 s (Garage) to 60 min (Public)Progression
Spike speed2×Team
Growth rategSee User growth
Churn ratec0.004/sUser growth
Declined-round growth0.7 × gFunding
Abroad seed shareseed_share0.05Going global
Fickle sharef0.5Users and growth
Steady churns0.2 × cUsers and growth
Border mixingm0.1Users and growth
Mirror timemirror_time30 calendar minutesMirror a region
Staged sync waitstage_wait60 calendar minutesGitOps
GitOps pollsync_poll3 calendar minutes (Argo CD's default)GitOps
Plan refreshrefresh_time0.5 calendar minutes per NodeIaC tools
IaC tool switchiac_switch1,440 calendar minutesIaC tools
Valuation scale1.5 × 10⁻⁴Valuation scale
Coordination penaltyk0.08Team
Dev Hours per day8 per engineer, 6.4 founderTeam
Adoption timeτ7 calendar daysAdoption
Lift spreadσ1.0Measured lift
Upkeep ceiling0.4 × S / (S + 2,000), max 0.6Upkeep
Unhappy mood0.35, one Mail per slump; content again at 0.7Burnout
Page mood−0.08 per pageBurnout
Freeze mooda calendar week of a spent budget takes all of itBurnout
Pay-gap mooda third of a point a calendar day at a 50% gapBurnout
Time off3 calendar daysBurnout
Block reminder1 calendar day after LaterBlocked
Block hire threshold8 engineers on one teamBlocked
Market gain cap+100% of the Era marketSizes
Link timeout30 sSimulation
Drawer sizemax($25, 60 s of revenue)Economy
Offline cap8 h; 12, 18, 24 h with Night shiftSimulation
Offline churn cap5%Simulation
SLA credit10% of subscription revenue while the trailing week misses the signed targetIncidents
On-call ack5 sim s after the pageIncidents
CDC change rate20,000 changes/s (05)CDC Connector
Sidecar overhead5% of a member's CPU and Burn (05)Service Mesh
Sidecar hop1 ms per hop (05)Service Mesh
Mesh Insight+20% on watched traffic (05)Service Mesh
Mesh Platform tax0.5 engineers per meshTeam
Rollout stepsoff, 5%, 25%, 50%, 100% (05)Feature Flags
Certificate life90 calendar days; 45 from Series C (05)Secrets and Certificates
Certificate renewaltwo thirds of the lifeSecrets and Certificates
L4 multiplier10× the capacity, 0.2 ms, 0.6× the BurnLoad Balancer
Gateway burst200 requests on top of the limitAPI Gateway
ESI page gain+20 points of page hit ratio (10)CDN
Bot managementfalse positives × 0.5WAF
Schedule lead120 calendar minutes before the peakAutoscaler
ARM price, speed0.8×, and 0.95× CPUApp Server
Quantization valueINT8 0.95, INT4 0.85; speed 2× and 3×GPU Node
Batch gain, TTFTbatch 8: 1.6×, +150 ms; batch 32: 2.2×, +500 msGPU Node
Bulkhead wait1,000 ms per callLinks
Hedge load+10% calls on the LinkLinks
Serialization CPU0.02 reference ms per backend callPerformance
Dual-write stale0.5% of search answers, from Series BKeeping copies in sync
Materialized view0.5× scan CPU, 5% stale answersData Warehouse
Chaos experimentevery 120 s, 10 s long, 20% of the target's calls fail, 1 ◇ Failure a secondReliability

The rollout steps and the certificate's two lifetimes are 05's own. A mesh takes a Cluster's half an engineer of Platform Dev Hours. The pages that own each mechanic give the rate it runs at.

Four rows describe the Features lifecycle: adoption time, lift spread, the upkeep ceiling, and the market gain cap. Adoption time is exact arithmetic, stepped a Step at a time, so the 63% after a week is the continuous curve's figure rather than a closed form. The market gain cap holds the Features' market gains together at +100% of the Era's market.

Prices#

place price(component, k)=base price(component)×1.25k−1k = copy number\begin{aligned} \text{place price} \left(\text{component} ,\ k\right) &= \text{base price} \left(\text{component}\right) \times 1.25^{k-1} \\ &\quad \text{k = copy number} \end{aligned}

The 1.25 growth on copies is a soft push toward bigger instances and toward better design over more copies. It is stylized, because real clouds charge the same for the hundredth VM as the first. The justification is operational overhead, and the card says so. Selling refunds half of what the last copy cost, at the copy markup.

Cost index#

Request values stay fixed. Prices rise with the Era. Every price in this document is a Garage price, and the Era's cost index multiplies it: placement, Burn, egress, and third-party fees. A separate salary index multiplies engineer salary. Without them, a vCPU costs about 0.1% of the value of the requests it serves, and Burn never matters.

EraCost indexSalary indexvCPU ($/s)Engineer salary ($/s)
Garage110.050.40
Pre-seed440.201.60
Seed880.403.20
Series A12120.604.80
Series B16160.806.40
Series C20201.008.00
Pre-IPO24241.209.60
Public24241.209.60

Garage and Pre-seed sit at the floor because there the smallest box, not the traffic, sets the cost. From Seed the index climbs by 4 points an Era, so infrastructure and payroll grow with the company while request values stay put. Salary follows the index, and the language factor multiplies on top of it: PHP 0.85, Node.js and C# 1.0, Python and Java 1.05, Ruby 1.1, Go 1.15, Elixir 1.2, Rust and Zig 1.25, Haskell 1.4 (Runtimes). Live tuning records how far these values sit from the design's.

A Balanced design should land at a margin of 25% to 45%, which puts the Rule of 40 checklist item within reach but not free. pacing.test.ts records the margin at Seed and Series A entry.

Valuation scale#

Game revenue is stylized: 50 million users at 0.02 rps send 1 million rps, and the average request is worth about $1 to $3. A year of that revenue is about $500 billion. A real company of that size reports an ARR near $77 million. The valuation scale, 1.5 × 10⁻⁴, converts a year of game revenue into that real ARR. At the IPO, a Rule of 40 multiple of 13 gives a Valuation of about $1 billion. At 6 billion users the Valuation is about $100 billion.

Vertical and horizontal#

A size step doubles vCPU, RAM, and Burn. Price is linear in resources, as in real clouds, so price per vCPU does not decide between one big box and many small ones. These tradeoffs do:

One big boxMany small boxes
No load balancer, no network hopRedundancy: one failure loses a fraction
One runtime base memory instead of manyFine-grained scaling: add 10%, not 100%
Runtimes that cannot use more cores waste the size (Node.js on one loop, a pool at one worker)Needs a load balancer and more connections to the database
Resize means a restartRolling changes
Hard cap at metal (96 vCPU)Each copy costs 25% more to place

Flat Components have size steps of their own, and each step doubles both capacity and Burn: Payments 6, Analytics 8, Load Balancer 6, Connection Pooler 4, Cache 8, Search Engine 8, Queue 6, WAF 6, Payment Provider 8, GPU Node 8, LLM API 8, Wide-column Store 6, Distributed SQL 6, Document Store 8, Event Stream 6, Realtime Hub 8, and Vector Index 6.

User growth#

growth=g(Era)×Users×(1−Users/Market)×h(A)\text{growth} = g \left(\text{Era}\right) \times \text{Users} \times \left(1 - \text{Users} / \text{Market}\right) \times h \left(A\right)
Erag (per sim second)Time to the milestone at Apdex 1.0 with no capacity limit
Garage0.004012 minutes, from 150 to 1,000
Pre-seed0.002221 minutes, from 1,000 to 5,000
Seed0.001628 minutes, from 5,000 to 25,000
Series A0.000956 minutes, from 25,000 to 150,000
Series B0.00071 hour 12 minutes, from 150,000 to 1 million
Series C0.00051 hour 56 minutes, from 1 million to 10 million
Pre-IPO0.00032 hours 30 minutes, from 10 million to 50 million
Public0.0002

The times solve the logistic equation from the Era's start to its milestone with the market sizes in Progression: t = ln(((M − U₀) / U₀) × (U₁ / (M − U₁))) / g. The sum is about 7 hours 36 minutes.

From Series C the formula runs per region (Simulation). With seed_share at 0.05, a Company that reaches Series C with 1,000,000 users starts Europe at 37,500, Asia-Pacific at 25,000, and South America at 12,500. Far demand is then about 7% of the total, not 60%, so a single-region Design meets the round trip as a warning, not as a collapse. North America's market alone is 5.6 million, so the 10 million Milestone needs users abroad. The table above assumes one region and the Era's full market.

Churn is steep on purpose, and much steeper than in real life. Brutlag (2009) found that 400 ms of extra latency cost Google 0.59% of searches over six weeks; the game compresses weeks into minutes. Fickle users carry the steepness: at the Series C target of 0.92, an Apdex of 0.91 loses them at about 3% per calendar hour. Steady users leave at a fifth of that. Three calendar days at 0.909 take a region of 1,000,000 users, half of them steady, to about 360,000, not 93,000 as with one rate. The Churn breakdown shows the rate.

Word of mouth crosses borders at m = 0.1. With 1,000,000 users at home, a region abroad grows as if it had 100,000 users of its own on top of its seed, so it fills fast once its users are served well, and it recovers after it lost its users.

Brutlag also found that searches stayed low for a while after the delay was removed. The game does not model that lag: users respond to the last calendar hour only, so a fix shows its effect within the hour. A lag would hide the feedback the Churn breakdown exists to give.

These are lower bounds. Capacity, Cash, and research make a real player slower, while Features raise g and the market, which makes growth faster still.

Growth alone sets the length of the first two Eras. At g × 1 the Garage takes 12 sim minutes to go from 150 to 1,000 users and Pre-seed 21 more to 5,000, so Seed arrives at about 33 sim minutes whatever the Design does. At the Company pace, where g is doubled, the same growth takes half as long.

Pacing model#

The pacing model is a set of scripted players that run against the real simulation in tests, so a balance change shows up as a test failure rather than in a playtest report. Every bot leaves the Team's autopilot on, and every bot but Greedy hires while payroll, the new hire included, stays under 25% of revenue, up to 4 engineers.

BotStrategyDesign limitCheck
BalancedRelieves the bottleneck Node, researches by a fixed priority list, builds each Era's contentInside the targetsSeeds 1–5: Seed reached on every seed at 26.8–35.7 sim minutes, Pre-seed within 20 min, positive Garage and Pre-seed margins
GreedyBigger boxes and the cheapest research; never hiresNot beat a target by more than 40%Seeds 1–3: Seed no faster than 27 minutes
StubbornNever changes the starting StackReach IPO within 1.6× the target timeSeeds 1–3: Seed within 1.6× Balanced on the same seed
PolyglotMigrates to every language it researchesNot beat Balanced by more than 30%Seeds 1–3: Seed no faster than 0.7× Balanced
DeclinerPlays Balanced and declines every funding roundLegacy per hour not beat Balanced by more than 15%Seeds 1–3: reaches Seed with Equity at 100%
IdlePlays Balanced, but logs in every 2 hours, spends everything, and leavesReach Pre-seed within 24 hours of game timebots.test.ts (SLOW_TESTS=1)
MarginPlays like Balanced and records margin at each Era milestoneMargin in 25% to 45% from Seed onpacing.test.ts records the margin at Seed and Series A entry

A balance change passes when the bots meet their limits on seeds 1–5: Balanced runs on all five seeds, the others on seeds 1–3 (pacing.test.ts and bots.test.ts, both under SLOW_TESTS=1).

Dominance#

Pillar 2 says no option strictly dominates another. dominance.test.ts runs in every pnpm test and in CI. Within each language, every runtime must beat each of its siblings by at least 10% on one reference score: throughput on the real Ledger of a Pre-seed Design, RAM per process, realtime connections per GB, startup, cold speed, stall-free CPU work, GC pause, leak-proof processes, a self-sized pool, or one of the six axes of its profile. Across languages, BEAM holds the most realtime connections per GB, beating Go and Kestrel. The option sets are the 28 runtimes, each language's frameworks, the instance families (Burstable, General, Compute, Memory), and the PHP-FPM pool modes. Axum and Actix are within about 5% of each other on CPU and velocity, and ARM runs at 0.8× the price and 0.95× the CPU, so a CPU-millisecond on ARM costs 0.84× General's. The fix for a dominated option is to change the option, not to tweak a number.

Runtime balance targets#

balance.test.ts asserts these comparisons on the real Ledger of a Pre-seed Design:

CheckTarget
Java cold against warm throughput30% at start, 100% after 30 s
Rust against Go, server cost for the same load25% to 40% lower
Rust against Go, Dev Hours for the same feature set50% to 70% higher
Elixir against Node.js, rt connections per GB3× to 5×
Rails against Go, Dev Hours for the same feature set35% to 50% lower

Under a PHP-FPM Design with OPcache, Node.js cluster mode answers read at 1.5× to 2.5× the throughput per dollar, and 3× to 6× with a database at 100 ms; a single Node.js process with 5% login adds 40 to 120 ms to p99.

Prestige curve#

legacy=floor⁡(payout/$1,000,000)\text{legacy} = \operatorname{floor}\left(\sqrt{\text{payout} / \$1{,}000{,}000}\right)
CompanyExpected payoutLegacyCumulativeShop progress (1,563 ★)
1$500 million22221%
2$1.5 billion38604%
3$5 billion701308%
5$20 billion14137124%
8$50 billion22396762%
9$80 billion2821,24980%
12$200 billion4472,442the whole shop, and half again

The twelve perks of the Legacy shop cost 1,563 ★ in all. The cumulative column follows payouts of $500 million, $1.5, $5, $10, $20, $30, $40, $50, $80, $120, $160, and $200 billion for companies 1 to 12, so the shop is complete around the tenth company.

Payout is Equity × Valuation × the Exit's multiplier, so the rounds set it (Economy): 10% at 1,000 users, 19% at 5,000 and 25,000, 13% at 150,000, 12% at 1,000,000 and 10,000,000, and 15% sold in the IPO offering.

Live tuning#

The pacing knobs live in catalog/tuning.ts, and the cost and salary indexes in each Era (EraDef.costIndex and EraDef.salaryIndex). Two tunings read them:

  • A Company plays at LIVE_TUNING: growth × 2, an incident every 200 sim seconds, and the first 90 seconds after the Pre-seed raise.

  • A Challenge and a Sandbox replay at CHALLENGE_TUNING: growth × 1, an incident every 720 seconds, and the first after 600. The pacing bots use it too, so a run shows a whole curve inside a test.

Both share the calendar pace, so a calendar day lasts 60 sim seconds. The tuning parameter of step, growthOf, directIncidents, raise, and decline selects one.

KnobChallenge paceCompany paceWhy they differConstant
Growth paceg × 1g × 2A session of 15 to 30 minutes needs the faster rate; a replay keeps the slower one so a whole curve fits a testCHALLENGE_TUNING, LIVE_TUNING (PLAYTEST_PACE)
Incident gap720 sim s mean (12 minutes)200 sim s meanA replay spreads incidents across the curve; a session has to meet severalINCIDENT_GAP
First incident600 sim s after the Pre-seed raise90 sim s after the Pre-seed raiseA session meets an incident early; a replay does not need toFIRST_INCIDENT_AFTER
Calendar pace60 sim s a day60 sim s a dayOne value for both: a hire and a deploy have to fit inside a sessionCALENDAR_PACE

The cost index, the salary index, and the speeds are one value each, set per Era or per perk rather than per tuning:

KnobValueWhyConstant
Cost index1 / 4 / 8 / 12 / 16 / 20 / 24 / 24Rises with the Era so Burn stays a real share of revenue at every size of companyEraDef.costIndex
Salary indexfollows the cost index: 1 / 4 / 8 / 12 / 16 / 20 / 24 / 24Payroll has to stay a real share of Burn at the prices the player paysEraDef.salaryIndex
Company speeds0.5×, 1×, 3×, 10×; 20× and 40× with Time dilationA slower speed for watching a Design settle, faster ones for the late ErasSPEEDS, DILATION_SPEEDS

What the checks cover#

  • pacing.test.ts, bots.test.ts and legacy-pacing.test.ts play the bots against the real Simulation; the slower ones run under SLOW_TESTS=1 and CI does not set it.

  • balance.test.ts asserts the runtime targets above and runs in pnpm test.

  • digests.test.ts and breadth.test.ts assert determinism, finite numbers and save round trips across the whole catalog.

  • The bridge and the shutdown fire at the numbers in Economy; no bot plays a Company into a shortfall.