The capacity lie

Clarify's design document has a section titled "Bottle capacity: unequal -- key design decision, see Appendix B for measured evidence." The argument is precise and specific. Density-based settling makes separation free because light liquid always floats on heavy liquid and can always be scooped back. If every bottle has the same capacity, parking a color costs nothing. You pour the lightest layer into any bottle with space. The greedy strategy always works. Optimal moves stay at two to eight. Dead-end rates sit near zero. There are no real decisions.

The fix proposed in the design is to give each bottle its own capacity so that a color must go back into a bottle that can hold its full volume, which turns parking into a bin-packing problem. The design's Appendix B shows prototype solver data from a bounded IDA* search. Equal capacity: par median two to six, dead-end rate near zero percent. Unequal capacity [6,5,4,3]: par median six, max nine, dead-end rate twenty percent. Unequal capacity [7,6,6,5,4,3]: par median seven, max seven, dead-end rate fifty-four percent. The design concludes: unequal caps are the only lever that works. The difficulty target for the shipped game is par five to nine with dead-end rate fifteen to thirty-five percent.

The shipped generator tells a different story.

Par distribution across 399 daily Clarify levels. 4-bottle cap-6 levels: par 2-7 avg 4.18. 5-bottle cap-6: par 3-9 avg 5.50. 6-bottle cap-7: par 4-7 avg 5.67.
Par distribution across 399 daily levels. All bottles in a tier share one capacity. 4-bottle (cap 6): par 2-7, avg 4.18. 5-bottle (cap 6): par 3-9, avg 5.50. 6-bottle (cap 7): par 4-7, avg 5.67.

Open tools/gen_levels.js and read the TIERS array: Monday uses caps [6, 6, 6, 6], Tuesday uses caps [6, 6, 6, 6, 6], Saturday and Sunday use caps [7, 7, 7, 7, 7, 7], and every bottle in a tier has the same capacity.

The comment above the array says: "CAPS are now UNIFORM per tier... Uniform capacity keeps every board's tubes equal-height and every unit of liquid the same visual height, matching the shipped Clarify design."

The generator was changed. The design was not.

What the numbers show

The solver runs as part of the verification step. verify.js loads all 399 daily levels and proves each par with a bounded BFS. The results are deterministic and auditable.

Here is the actual distribution.

TierBottlesCapacityLevelsPar minPar maxPar avg
Mon4657274.18
Tue5657395.50
Wed4657274.18
Thu5657395.50
Fri5657395.50
Sat6757475.67
Sun6757475.67

The design wanted Monday at par three to five and it lands at average 4.18 with a floor of two, while Friday wanted par six to nine and it lands at average 5.50, and Saturday wanted par six to nine with dead-end rate twenty-five to forty percent but it lands at average 5.67 with a ceiling of seven, so the upper end of the design's difficulty band is barely touched.

Dead-end rate is not computed in the shipped generator because the field is set to zero with a comment that says simplified version does not calculate, but the design's Appendix B is explicit: with equal capacity, dead-end rate stays at zero to four percent, with unequal [6,5,4,3] it reaches twenty percent, and with [7,6,6,5,4,3] it hits fifty-four percent, so the shipped game almost certainly sits in the zero-to-four percent range.

Why it happened

The commit message is not in the repository but the comment in gen_levels.js gives the rationale: visual uniformity with equal-height tubes and equal visual height per liquid unit, and the layout system in layout.js has a constant CAP_HEIGHT_RATIO = 1.0 with a comment explaining that tube height is now independent of capacity, making the per-bottle height scaling a no-op so boards stay visually uniform whether or not capacities vary within a level.

The layout code supports unequal capacities, the renderer supports it, the core rules support it, the solver supports it, but only the generator was switched to uniform caps and the design document was left unchanged.

The design's Appendix B anticipated this when it says: "par is structurally capped... no matter how you add colors, capacity, or shuffle steps, par stays under nine... the difficulty target is par five to nine plus dead-end rate fifteen to thirty-five percent, not par twenty." The prototype data proved unequal caps could reach par nine, but the shipped uniform caps top out at par nine only in the five-bottle tier and only rarely, with one level out of 171 reaching par nine, while the six-bottle tier never exceeds par seven.

What this means for players

A Monday or Wednesday level averages four optimal pours when the design wanted three to five, and the difference is one pour which sounds small but in a two-to-five-minute daily puzzle one pour is the difference between scanning the board and seeing the answer versus pausing to think.

The design's dead-end rate target for Wednesday was fifteen to twenty-five percent which means roughly one in five first moves leads to a state that cannot be solved, a genuine decision point where the player must recognize the trap and hit undo, but at zero to four percent that moment almost never arrives.

The game still works because the density settling is still the hook, the tutorial teaches that heavy sinks under light with T3 at par four proven by exhaustive search, the daily levels are solvable, and the streak system works, but the depth that the design document argues for, the depth that justified the entire unequal-capacity architecture, is not in the shipped levels.

The fix is five lines

The TIERS array in gen_levels.js would need to change from [6, 6, 6, 6] to [6, 5, 5, 4] for Monday, from [6, 6, 6, 6, 6] to [6, 6, 5, 5, 4] for Tuesday, and so on, matching the caps column in the design's section 5.2 table, because the generator's canFitColors check already handles unequal caps, the scrambler already works with any capacity array, the solver already proves par for any capacity array, and the verifier already asserts level.caps.every(cap => Number.isInteger(cap) && cap > 0) with no uniformity requirement.

Regenerating the 399 levels would take a few minutes on a laptop and the daily snapshot in tools/daily-snapshot.json would need to be updated, then the deployment would ship new levels for every future date while past dates would keep their old levels because the daily mapping is frozen by the snapshot.

The design document would finally match the code, the par distribution would shift toward the target band, the dead-end rate would become measurable and tunable, and the key design decision would stop being a lie.

We have not made the change because the generator comment says matching the shipped Clarify design while the design document says something else, so one of them is wrong, but the numbers are in the repository and the solver proves them every time node tools/verify.js runs.