Every goal threshold I picked has a bot sitting on both sides of it

Same Seed shows you three season goals at the start of each seed. Picking those thresholds was the part of the project I was least excited about, and it is the part I would redo first if the file came back to me in a different shape. Here is the table that decided most of the shipped goals, and the two that the table cut.

Season goal thresholds across 400 seeds: casual bot, greedy bot, and variety bot reach rates. Shipped goals span 8-100% (variety bot) and 0-15% (casual bot). Cut goals like no-wilt and burst>=2 span 68-100% casual and 83-100% greedy, so neither endpoint separates.
Season goal thresholds, 400 seeds, ruleset 2. Blue = shipped goals (variety bot clears 8-100%, casual bot clears 0-15%). Tan = cut because the casual bot already clears them. Moss = shipped but the greedy bot fails them. Gray = too hard for the variety bot.

Why the threshold, not the goal, is the decision

A season goal in Same Seed is a target the player can either clear or not, over the course of twelve days and 36 actions. The game does not pay anything for clearing one; it exists to give the player something to aim at on days where the board has no single obvious answer. The variety bonus is the score; the goals are the signposts.

A signpost that every player passes on the way somewhere else is just decoration. A signpost that no player reaches is just a wall. The threshold, not the label, is the design decision: grow three different kinds of flower can be a beginner tutorial at threshold 3 and an endgame achievement at threshold 6, and the label does not change.

The probe: three bots, 400 seeds

So I built the thing that would answer it. The file is tools/goal-probe.js. It plays three bots over 400 different seeds (which is the same as 400 different board deals and 400 different weather sequences, because every piece of the puzzle is a pure function of the seed string):

Each candidate goal is tested against each bot and each seed, producing a reach rate: the fraction of seeds where that bot cleared that goal. The probe prints a verdict for each candidate: "good gap" means the casual bot stays under 35% and the variety bot sits above 70%, so the threshold has separated the two kinds of player. "Too easy" means the casual bot already clears it. "Too hard" means the variety bot still does not.

The table that decided most of it

The shipped goal set, goalsFor(), rolls three goals per seed from this pool:

GoalCasualGreedyVarietyVerdict
variety ≥ 31%100%100%shipped
variety ≥ 40%40%100%shipped
variety ≥ 50%2%100%shipped
variety = 60%0%97%shipped
actions ≥ 301%50%70%shipped
burst ≥ 3 (3 in one day)10%95%83%shipped
on-soil ≥ 411%100%100%shipped
on-soil ≥ 52%100%100%shipped
on-soil ≥ 60%100%99%shipped
no wilt72%91%88%cut, too easy
burst ≥ 268%100%100%cut, weak separation

Read the table top to bottom for a moment and the shape of the problem is visible. The "variety ≥ 6" row: the variety bot clears it 97% of the time, the casual bot 0%, and the greedy bot 0%. That is the cleanest teaching row in the file. The player who has played a few sessions and noticed that "one more kind of flower" competes with "one more expensive flower" clears it almost always, and the two players who have not learned that yet never do.

"No wilt" is the opposite problem. The casual bot clears it 72% of the time without trying. Wilted plants cost the casual bot about 0.57 plants per season on average (270 across 400 seeds), and that is not enough to drop it below 72%. A goal that the day-one player clears three out of four times is a goal the player never reads twice, which is the definition of decoration.

Where the numbers surprised me

Two rows in that table I did not see coming, and I want to name them because they changed how I think about what a "good" threshold looks like.

First: the greedy bot fails "variety ≥ 5" almost always (2%). The greedy bot is a competent player by my definition. It plants on matching soil, it picks the highest-value flower it can still finish, it rescues blooming flowers before they fade. And it still plays monocultures. It just does not look at the variety ladder, so it never deliberately plants a second species even when the ladder says it would pay for itself. The variety bot exists to answer "but what if the player noticed?" and it clears 5 and 6 species 100% of the time. That 100% is the row I trust most in the whole file, and it is also the row that says "the variety ladder does the work, not the bot." The goal is doing what it is supposed to do: making the player look at the ladder.

Second: the casual bot clears "burst ≥ 2" 68% of the time. I had assumed that cutting two flowers on the same day was a small but real planning decision. The probe says the casual bot does it by accident, because a daisy (3 days) and a tulip (4 days) planted on days 1 and 2 both bloom by day 4 or 5 and both get cut on the same rescue pass. That is not a goal; it is a description of what the bot does anyway. Cut it.

What the shipped mix looks like

goalsFor() rolls one variety goal (weights 40 / 35 / 20 / 5 for thresholds 3 / 4 / 5 / 6), one soil goal (50 / 35 / 15 for 4 / 5 / 6), and one craft goal (50 / 50 between "use 30 of 36 actions" and "burst ≥ 3"), and shuffles the three. Over 400 seeds the actual distribution of the goal mix is variety3 14%, variety4 12%, variety5 6%, variety6 1%, soil4 15%, soil5 12%, soil6 6%, busy30 18%, burst3 15%.

The full three-goal set, measured as "the player cleared all three," lands at 4% for the casual bot and 91% for the variety bot. That is a 23x gap on the compound event, which is bigger than any single row in the table. That is also why the goals are not paid. A reward that separates 4% from 91% is a reward that tells you exactly how much the player has learned, which is useful information for the player and a free signal for anyone reading a replay.

What this method is and is not

The probe does not tell you whether the goals are fun. It tells you whether they separate. A goal can separate perfectly and still be boring, and a goal can be genuinely fun and still not separate (a fun goal that both bots clear 90% of the time is a comfort goal, not a teaching one, and I would rather have the teaching one). The 400-seed number is a proxy for "a player who has played a few sessions" rather than an expert, and if a real expert reads two harvests ahead in a way the variety bot does not, the "variety = 6" row underestimates how much of a stretch it is for a player who has not figured it out yet.

What the method does buy is a file I can rerun. The next time I retune the variety ladder or add a seventh species, the same probe tells me which of the nine thresholds in core.js just moved past the "good gap" bar, and I do not have to sit and play the game for an hour to find out. The threshold table is now a test fixture with a known answer rather than a design memo with an opinion.

The one line of code that matters

The whole goal system is a pure function of the seed, and it never touches score(). That last clause is the one I want to underline, because it is the thing that lets the next developer retune the thresholds without a RULESET bump, and without breaking any stored replay. The goals are a client-side layer over the same state, and the score server does not see them. If the goals were paid, every threshold change would silently revalue every saved season, and the probe would be measuring a number that the leaderboard had already baked in. The fact that they are not paid is what lets the thresholds be a design decision rather than a migration.

The file is in tools/goal-probe.js and the shipped goals are in core.js under the GOALS array. Rerun the probe any time you change the ladder, and the table will tell you which rows just stopped separating.

← Back to the blog