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.
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):
- Casual bot. Plants whatever looks ready to cut, waters only what is visibly parched, no end-of-season planning. It stands in for a player on day one.
- Greedy bot. Chases the highest-value flower that fits the remaining days, always on matching soil when possible, but does not look at the variety ladder.
- Variety bot. Same greedy logic but prefers species it has not yet harvested, which is what a player who has actually played a few sessions and noticed the bonus starts to do.
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:
| Goal | Casual | Greedy | Variety | Verdict |
|---|---|---|---|---|
| variety ≥ 3 | 1% | 100% | 100% | shipped |
| variety ≥ 4 | 0% | 40% | 100% | shipped |
| variety ≥ 5 | 0% | 2% | 100% | shipped |
| variety = 6 | 0% | 0% | 97% | shipped |
| actions ≥ 30 | 1% | 50% | 70% | shipped |
| burst ≥ 3 (3 in one day) | 10% | 95% | 83% | shipped |
| on-soil ≥ 4 | 11% | 100% | 100% | shipped |
| on-soil ≥ 5 | 2% | 100% | 100% | shipped |
| on-soil ≥ 6 | 0% | 100% | 99% | shipped |
| no wilt | 72% | 91% | 88% | cut, too easy |
| burst ≥ 2 | 68% | 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.