Appearance
Sum range
Every exercise generated from a recipe walks a running total from step to step. sumMax is the highest value that total may ever reach.
sumMax
text
0 <= every running sum <= recipe.sumMaxThe ceiling is inclusive: sumMax: 19 permits a running sum of exactly 19. The floor is a hard 0, and it is not a recipe field — a soroban has no negative state, so there is nothing to configure.
| Property | Value |
|---|---|
| Type | integer |
| Range | 1 … 1999 |
| Default on create | 99 |
| Applies to | every running sum, including the value each step lands on |
The upper bound of 1999 exists because sumMax + 1 sizes the generator's feasibility analysis arrays; it is a memory bound, not a pedagogical one.
sumMax also determines the generator's candidate operand alphabet: every power of ten up to sumMax. A recipe with sumMax: 99 can produce steps in the ones and the tens; sumMax: 9 can only produce steps in the ones. You do not configure that alphabet directly — you narrow it with rules, as Restricting places describes.
Why it is a recipe field, not a rule
It is fair to ask why sumMax is not simply a constraint rule such as current_sum <= 99.
The generator proves feasibility before it searches. That analysis is a dynamic program indexed by (layer, sum), and it allocates its working arrays before any rule is evaluated — it has to know the width of the sum axis up front. A rule cannot size an allocation; it can only narrow a domain that already exists.
So the division of labour is:
sumMaxestablishes the domain. Structural, authored on the recipe.- A rule referencing
current_sumnarrows that domain further. It can never widen it.
Writing current_sum <= 500 on a recipe with sumMax: 99 does nothing. Writing current_sum <= 19 on a recipe with sumMax: 99 genuinely tightens the ceiling — with one important caveat, below.
Confining sums to a narrower window
Suppose you want every running sum inside 0..19. The obvious move is to set sumMax: 19 and be done, and for most recipes that is exactly right.
But when you want a window narrower than the recipe's sumMax — or a floor above the structural 0 — you reach for a step-scope constraint rule. The naive version is not enough:
current_sum >= 0 AND current_sum <= 19This is incomplete. current_sum is prev_sum + current_value: it describes the state after the step is applied. It says nothing about where the exercise started.
current_sum alone does not bound the opening value
current_sum constrains transitions only. With just the current_sum bound above, exercises were measured opening on start values well outside the window — 16 of 40 generated exercises opened above 19 (on values such as 26 and 22), with the first step dropping into range from outside it. Every individual transition satisfied the rule; the opening was never a transition.
The fix is to bound the previous sum as well:
current_sum >= 0 AND current_sum <= 19 AND prev_sum <= 19prev_sum <= 19 is not redundant with current_sum <= 19. The two clauses close different holes:
| Clause | Rejects |
|---|---|
current_sum <= 19 | a step that would land above the window |
prev_sum <= 19 | a step taken from a position already above the window — including the very first step, whose prev_sum is the opening value |
With both clauses in place the same measurement produced 0 of 40 exercises opening outside 0..19.
This is the concrete technique for a recipe teaching carries into the tens, where sums must stay in 0..19 — a range no fixed digit-count setting could ever express. Either sumMax: 19, or a looser sumMax plus the rule above, works directly.
Prefer sumMax when the window starts at 0
If the window you want is 0..sumMax, set sumMax and skip the rule — the ceiling is already enforced structurally and prunes the search more cheaply. Reach for the constraint rule when you need a floor above 0, or a ceiling below the recipe's sumMax for a subset of situations.
Related opening-value techniques
A sum-range rule constrains transitions. If the opening value needs its own shape — a clean tens digit, a zero ones digit — guard on step_index:
IF step_index = 0 THEN digitAt(prev_sum, 1) = 0See Restricting places for the full treatment of opening-value pinning, and Variables for how prev_sum, current_value, and current_sum relate.
Sum rules and place channels
A rule attached to a place-channel (place: 1, 10, 100, 1000) cannot reference current_sum, and compilation rejects it if it does. Inside a channel, current_sum would mean "prev_sum plus that one channel's digit" — an intermediate state a composite step never actually passes through, because it commits every channel together.
Whole-step sum conditions belong on an ordinary global attachment (place: 0) with usage: constraint. Written that way, a sum rule works unchanged on recipes that produce composite steps. See Multi-place combo steps.
See also
- Recipe concepts — where
sumMaxsits among the other recipe fields. - Variables —
prev_sumvscurrent_sum. - Troubleshooting — when a tight
sumMaxmakes a recipe infeasible.