Game
Seeds: how the game rolls its diceSD
How the game rolls its dice. One seed per save decides everything left to chance, the same on every machine, so saves stay small, co-op needs nothing sent and bugs can be replayed.
One number grows a whole world. A new game picks a seed once and keeps it in the save; every "random" thing in that playthrough comes out of it: which clients call, what they say, the headlines, and soon the weather. The seed decides the world; the players decide the story.
The player's view
Two siblings, one world. You host, your sister joins. Her screen shows the same morning calls at the same minutes, the same headline, the same haggling answers, because both machines worked them out from the same number. Nothing but that number was sent when she joined.
A new game is a new world. Start another save and the first week is different: other clients call, on other days, with other quips. Load the same save twice and it plays out the same until you choose differently: take the Hendersons' job in one and not the other, and the two games split from there.
Characters keep their homes. Mrs. Henderson's garden, her pool and her hedges are the same in every save: she is a character with her own home, written by hand like the story, the prices and the tools.
The street agrees. The houses, trees and parked cars on your street are the same for everyone. Later, the cars driving by and the people walking past are in the same spot on every sibling's screen too.
Later, seeds you can share. You could type a seed on the new-game screen to replay a world a friend loved ("seed 1234 has an amazing first week"), or play today's daily challenge on the same gardens as everyone else.
A computer can't roll dice. It runs a formula that turns a starting number into numbers that look random; the same starting number always gives the same numbers, on every machine. That number is the seed. Hand-written content (story, characters, prices, tools, rules, hand-built places) is the same for everyone; things left to chance come from a seed. That one trick pays off four ways:
- Every save is differentNew calls, new events and new weather, without building anything by hand.
- Small savesStore one number and rebuild the rest when needed, instead of storing 40 gardens.
- Co-op for freeHost and guest compute the same thing from the same seed; nothing is sent.
- Bugs you can replay"seed 48213, day 9, the pool leaves are inside the shed" reopens the exact same situation; tests rely on it.
Ideas
SD1: the client's yes to an extra, the same for everyone
When a player offers a client an extra job (raking the leaves...), the client's yes or no used to be a
UnityEngine.Random.value roll on the machine that clicked, and the extra task was added there only: in co-op a guest
could get a "yes" the host never saw. Now the answer is a question to the seed, Upsells.Accepts:
SimRandom.Value(seed, day × 31 + client key, Salts.UpsellAccepts + task) against the accept chance (the client's
mood, regular or not). The answer is sent to everyone as GardenEventKind.ExtraOffered (the first one counts; late
joiners catch up), so every screen gets the same answer and the same extra task.
Needs: - · Done when: in a two-instance run, the guest offers an extra, and the host's screen shows the same answer and the same added task (mismatches=0).
SD2: named salts
There are two ways to roll, and only one is safe for anything the sim keeps:
- A dice sequence (
new System.Random(seed), used byGardenGenerator): rolls come in order (the house, then the roof, then the pool). Adding one roll in the middle shifts every roll after it, so changing the generator reshuffles gardens. - Asking a question (
SimRandom.Value(seed, a, salt), used by the company sim): each roll has its own label, the salt. Same question, same answer, whatever gets added later. Use this for anything new in the sim (golden rule 4).
Gardens are built in the dice-sequence style and rebuilt from scratch on every visit, so a generator change after release can move a regular client's pool between two visits. The company sim asks questions, so new features there don't disturb the old rolls; the weather does too (WE1).
The registry: Sim/Company/Salts.cs names every question the game asks SimRandom; a salt is used by exactly one
roll. Blocks for rolls in a loop are reserved with [SaltSpan(n)] (base + 0..n−1). Per-client questions (key + n,
the key being SimRandom.StringSeed of the mission id) live in Salts.Client; seedless client details in
Salts.ClientCard. Gameplay rolls kept their old values. The overlaps found were fixed, changing looks and text only:
grandma's calendar stains moved to 8000+ (they shared the deal of the week's roll), clients' text lines no longer use
the request uid as a salt, and a client's call chance and its arrival minute no longer share one roll.
Never change or reuse a salt once players have saves: retire old ones, add new ones.
Needs: - · Done when: SaltTests checks that no two salts share a value (spans included) and that every
SimRandom roll in Scripts/ names its salt.
SD3: fingerprint tests
The seed never changes; the code that reads it does. What an update does to an old save depends on whether the result is stored or recomputed:
- Stored in the save: safe. Requests already received, accepted jobs, mail, money, reviews: rolled once, written down, never re-rolled.
- New questions with new salts: safe. A new feature asks
(seed, day, 812), a salt nobody used; every old answer stays as it was (the gnome in "asking a question" mode). - Recomputed with changed code: changes. Gardens rebuilt each visit, future client calls, the weather rewrite: an old save gets the new result. A client's pool can move after a generator change; day 12's weather changes when WE1 ships.
Before release this doesn't matter (test saves can break). After release three habits keep players' saves stable:
never reuse a salt (SD2), store what the player must see unchanged, and keep fingerprint tests. Deliberate changes go
through SaveData.CurrentVersion migrations.
FingerprintTests (in Tests/EditMode/SeedTests.cs, real game data) builds every client's LawnPlan, every planned
street's lots, and a new game's first week on seed 48213 (requests, haggle answers, mail and voicemail timing, door
notes, deals, extras), and compares each with a stored fingerprint. A failure prints the new fingerprint and the dump to
the log: if the change is on purpose, paste the new value. Not covered: GardenGenerator's prop placement (scene
code).
Needs: SD2 · Done when: changing any roll behind a client's lawn plan, a street's lots or the first week on seed 48213 fails a fingerprint test that prints the new value.
SD4: a random seed for each new game
State.seed is an int, positive and odd, made at new game (Company.Day.cs, NormalizeV4) by SimRandom.NewSeed()
from a random Guid instead of the clock. That leaves about 1 billion possible worlds, which is plenty: two players only
have even odds of sharing a world after ~40,000 new games, and it would only mean the same calls and weather. A bigger
seed doesn't make the dice better; the mixing formula does (SimRandom.Hash already makes seed 1 and seed 2 come out
completely different).
Needs: - · Done when: new seeds are positive, odd and varied (SaltTests).
SD5: the shared street
A seed gives everyone the same start, not the same motion. Things that stand still (trees, houses, parked cars) match
forever. Things that move with each machine's Time.deltaTime drift apart: traffic and walkers are built from the
place's seed (Town.cs, Traffic.cs, Pedestrians.cs), but a guest who joined 40 s later, at another frame rate,
sees the red car somewhere else.
For each thing, ask what happens if two players see it differently:
- Nobody notices (clouds drifting, birds, engine sounds): leave it local.
- Players notice (traffic, walkers): seed + shared clock. Each car's route and start time come from
SimRandom(place seed, car slot), and its position is worked out from the synced network time: the same car in the same spot on every machine, nothing sent. Cloud drift can use the same trick. - Gameplay changes (a car that can hit you or block the van, a client walking up to talk, the mower): the host owns it and sends it (FishNet). It costs bandwidth, so keep it for what matters.
SD6: client strolls on the shared clock
Clients strolling round their gardens (ClientNpc, UnityEngine.Random) drift apart in co-op like the traffic.
Cosmetic, so they move to the shared clock with the street (SD5).
SD7: show and share the seed
Show the seed on the new-game screen and let players type one, to replay or share a good world ("seed 1234 has an amazing first week"). Cheap: one field. Short seeds are easier to read aloud and share, and a word ("grandma") can be hashed into a number.
SD8: a daily challenge
Everyone gets today's seed and plays the same gardens: a possible later mode.
SD9: save-seeded gardens for one-off clients
Mix the save seed into the job seeds of clients who aren't story characters (newspaper one-offs), if more variety between saves is wanted. Story characters keep their hand-typed seeds.
Tuning
| Name | Value | Idea | What it does / why |
|---|---|---|---|
| Seed range | positive odd 32-bit int, ~1 billion worlds | SD4 | even odds of two players sharing a world only after ~40,000 new games |
| Fingerprint seed | 48213 | SD3 | the first week the fingerprint tests replay |
| Upsell salt block | Salts.UpsellAccepts 9100, span 100 (+ task type) | SD1 | one question per extra task |
| Calendar stains | salts 8000+ | SD2 | moved off the deal of the week's roll |
Co-op
- State: the save seed
State.seedlives in the host's save; the host's seed is the one everybody uses (already so). - Ownership: anything rolled from the seed is computed on every machine; the host owns what changes gameplay (SD5's third case).
- Host-only: deciding answers that must be shared, like an accepted extra (SD1, sent as a garden event).
- Randomness: sim rolls come only from
SimRandomwith the seed from state and a named salt (SD2).UnityEngine.Randomis fine for effects nobody needs to agree on: splashes, cash pops, critters, birds, the engine pull-start (the local player's own action). - Channel: one number, once, when the guest joins; moving things that players notice use the synced clock (SD5).
- Late joiners: compute the same results from the seed and the synced clock; shared answers catch up from their events.
- Checks: two instances show the same calls, gardens and answers; guest logs show mismatches=0.
Tech
The seeds in the game
| Seed | Where | Decides |
|---|---|---|
Save seed State.seed | made at new game (Company.Day.cs, NormalizeV4; SD4) | new client requests, returning clients, mail vs voicemail and arrival time, spam, newspaper headlines, dad's chatter, haggling replies, the store's weekly pick (Company.Clients.cs, ClientTexts.cs, Company.Day.cs) |
Job seed MissionDefinition.seed | typed by hand per client in the game data (101, 202, 303...) | garden layout, hedges, trees, log pile, pool leaves, grime on patios, decks and driveways, the client's gender and quips; mixed with the day for fallen leaves and bare patches, so a return visit looks different (GardenGenerator.cs, MissionController.cs) |
Place seed site.seed | Town.cs | the street, neighbours' houses, trees, landscape, mountains, and the starting traffic and walkers |
- The job seed does not come from the save seed: Mrs. Henderson's garden is the same in every save, on purpose (clients are characters with their own homes). SD9 would change that for one-off clients only.
Calendar.WeatherOn(day)(inSim/Company/VanPacking.cs) reads only the day, not the seed: day 12 has the same weather in every save. WE1 replaces it with weather from the save seed and the hour.- Engine sounds aren't seeded at all.
The code
SimRandom(Sim/Company/CompanyRecords.cs):Hash(FNV-1a with a finaliser),Value,Range,Pick,NewSeed,StringSeed.Sim/Company/Salts.cs: every salt by name (SD2).Sim/Company/Upsells.cs: the extra's answer (SD1).- Tests:
Tests/EditMode/SeedTests.cs, withSaltTests(SD2, SD4) andFingerprintTests(SD3). - By hand: start two new games and compare the day-1 calls (they differ), then load the same save twice (they match).
- Save:
State.seedis the only field; deliberate changes to what old saves rebuild go throughSaveData.CurrentVersionmigrations.
Open questions
- Store or rebuild a regular client's garden? Gardens are rebuilt each visit with a dice sequence; if a regular client's garden must never change after release, save its layout instead.
GardenGenerator's prop placement isn't covered by the fingerprint tests (scene code): add a scene-level fingerprint, or accept that props can move?
Rejected
- A 64-bit seed: it only matters in games where players hunt rare worlds (Minecraft). Size changes how many worlds exist, not how random they feel.
- New sim rolls as a dice sequence: adding one roll reshuffles every roll after it. The sim asks questions (SD2).
- Story clients' gardens from the save seed: they're characters with their own homes, the same in every save.
- Sending every moving thing over the network: it costs bandwidth; only what changes gameplay is host-owned and sent (SD5).
- A seed from the clock: replaced by a random Guid (SD4).