When we started treating solvers as infrastructure for Blupoli Puzzles, it was easy to imagine that the catalogue might eventually converge on one universal algorithm. The opposite happened. The more puzzle families we implemented, the more clearly each mechanic demanded its own representation.
Dominosa and Stitches can both be discussed in matching terms, but their constraints differ. Slant mixes local clue counts with a global no-cycle rule. Aquarium models horizontal water levels. Str8ts reasons about consecutive intervals. Kropki treats every boundary between neighbouring cells as a relation. The useful abstraction was not one algorithm. It was a shared contract for proving that generated content deserves to be published.
The shared part is the contract, not the algorithm
Every engine needs to answer concrete questions. Is this state legal? Does a solution exist? Is it unique when the product promises uniqueness? Can the case be reproduced from a seed or serialized state? Can the generator reject a candidate that does not satisfy the same rules the player sees?
Those questions give Blupoli a common integration surface even when the implementation underneath uses very different mathematics.
Dominosa naturally becomes exact cover
Every cell belongs to exactly one domino and every permitted value pair must be used exactly once. Candidate domino placements therefore cover two kinds of requirement at the same time: the cells they occupy and the domino identity they consume.
That model fits exact cover much more naturally than pretending Dominosa is simply another grid-filling puzzle.
Stitches uses matching, but not the same model
Stitches also connects regions, yet the meaning of the connection is different. The engine has to respect which regions may connect, how many stitches they require and which pairings remain available after each decision.
Sharing search utilities can be useful. Sharing a false domain model just because both puzzles involve pairs would make the code less clear.
Slant combines local arithmetic with a global graph rule
Each cell chooses one of two diagonals and clues around vertices impose local counts. A board can satisfy all of those local numbers and still be invalid because the diagonals form a cycle.
The engine therefore needs both local propagation and global connectivity. Slant is a good reminder that the least visible rule can be the one that determines the architecture.
Aquarium should reason in water levels, not isolated cells
Row and column clues matter, but the cells of one aquarium are constrained by a shared water level. Representing every cell as an unrelated boolean wastes that structure.
Modelling possible fill heights by region removes impossible states early and matches the unit a human player actually reasons about.
Str8ts compartments are consecutive intervals
Str8ts keeps row and column uniqueness but adds the defining rule that values in each white compartment form a consecutive set, in any order.
That makes interval reasoning central. Candidate values are useful only if they can belong to at least one feasible consecutive range for the compartment.
Kropki stores meaning on boundaries too
White and black dots encode relationships between orthogonal neighbours, and under the full negative rule the absence of a dot can constrain a pair as well. The board is not only a collection of cell values; its edges carry information.
A good candidate model therefore reacts to row, column and neighbour constraints whenever a value is placed.
Uniqueness means continuing after the first solution
For generated puzzles, one solution only proves that the candidate is solvable. If uniqueness is required, the engine must keep searching. The second solution is the one that disqualifies the board.
That is why many counters stop at two. Zero, one and more-than-one are the only outcomes the publication gate needs.
The first optimization is usually the model
Before choosing a heuristic, it is worth asking what one decision in the solver actually represents. In Aquarium, a useful variable may be the possible water level of a whole region rather than the state of one cell. In Dominosa it may be a domino placement. In Slant it is a diagonal together with the connectivity consequences that choice creates. A good representation removes impossible states before search begins. A weak one forces the solver to rediscover the same domain structure repeatedly.
This is why “use backtracking” is not a meaningful architecture by itself. Backtracking describes a search technique. The important design work happens earlier, when the engine decides what constitutes state, what can be propagated and which contradictions can be detected without branching.
Propagation should earn the right to branch
A practical solver tries to exhaust deterministic consequences before opening alternatives. Removing impossible candidates can force a number. Fixing one region level can force cells in several rows. Choosing one pairing can eliminate others. Rejecting a diagonal because it would close a cycle can immediately constrain neighbouring cells.
The less work search has to do, the more predictable the engine becomes. Propagation also gives us information that raw runtime does not: how much of a puzzle was resolved through direct logical consequences and how much required speculative branching.
One solution proves solvability, not publication readiness
If a puzzle promises a unique solution, the first solution is only the first half of the proof. It tells us the candidate can be completed. The second solution is the one that disqualifies it.
That is why solution counting often stops at two. Zero means impossible, one means unique and two means “more than one” for the product decision. Enumerating every remaining solution would spend more time without changing the outcome. The same distinction between creating and verifying a board is explored in Generating is not solving.
Counting to two turns a solver into a production gate
This small cutoff changes the role of the algorithm. A solver stops being only a developer tool or a hidden answer generator. It becomes infrastructure that can run inside content generation, CI, benchmark tools or internal editors and answer a very practical question: does this candidate satisfy the contract the product advertises?
That makes logical guarantees affordable enough to check repeatedly. We are not trying to prove every interesting property of the search tree. We are collecting exactly the evidence needed to accept or reject content.
Reproducibility matters as much as randomness
A generator that produces varied boards but cannot recreate a failure is difficult to maintain. When a player or test finds a broken candidate, “try another random board” is not debugging. We need a seed, case identifier or serialized state that reconstructs the exact puzzle.
Reproducible inputs also make optimization measurable. If a candidate-ordering change improves one class of puzzles and regresses another, the comparison should use the same set of boards. Otherwise normal random variance can masquerade as a performance improvement.
A seed is useful, but durable state is stronger
A seed does not necessarily preserve the same board forever. If generation logic, random-number consumption or ordering changes, yesterday's seed may produce a different candidate. Keeping a serialized representation of important regression cases protects them from that evolution.
The same representation can support saved games, internal tools and bug reports. A puzzle engine is easier to test when its logical state has a stable form outside the DOM, outside animation state and outside one browser session.
The generator should not be the only witness for its own correctness
If the code that creates a puzzle is also the only code that certifies it, one wrong assumption can be repeated at both stages. We do not always need two independent complete solvers, but we do need some independent pressure on the important rules.
Hand-built adversarial fixtures, rule invariants, a separate solution counter or property-based tests can provide that second point of view. The goal is not duplicate implementation for its own sake; it is preventing the system from proving itself correct using the exact assumptions that may be wrong.
Property-based tests explore beyond the examples we thought of
Fixtures are essential because they preserve known edge cases. They are also limited by imagination. Property-based tests can produce many generated states and ask durable questions: does every returned solution satisfy all rules? Does every candidate marked unique actually have one solution? Does serialize-and-load preserve meaning?
When one of those generated cases fails, the exact state can be retained as a named regression. Random exploration discovers the surprise; a deterministic fixture makes sure we never forget it.
Metamorphic tests are useful when exact outputs are expensive
Some transformations should preserve a puzzle property even when we do not know the full expected output in advance. A legal symmetry, a consistent renaming of symbols, or a serialize-and-load round trip should not turn a valid solution into an invalid one.
These relationships give us another form of evidence without writing a second solver or maintaining a giant table of expected answers. They are particularly valuable in generators because the possible input space is much larger than the collection of examples a human can curate manually.
Adversarial boards should become permanent citizens of the suite
A solver test collection should not contain only elegant published boards. It needs near-valid states, contradictions that appear late, multiple-solution candidates, boundary sizes and cases that deliberately stress the most expensive global rule.
Every production bug can become executable documentation of one engine boundary. Over time, the suite turns into a history of what the solver has learned not to break, which is much more useful than a folder of happy-path demos.
Difficulty cannot be normalized by one formula
There is no meaningful universal equation that proves a hard Aquarium is equivalent to a hard Kropki. Their useful signals differ. One engine may care about interval narrowing, another about candidate density, another about search depth, forced chains or the interaction between local and global constraints.
Blupoli can still expose a shared difficulty vocabulary to players, while every game calibrates that vocabulary with evidence appropriate to its mechanic. The common part is the UX contract; the underlying metric remains game-specific.
Fewer clues are not automatically harder
Removing a clue can increase difficulty, but it can also introduce ambiguity or accidentally expose a simpler forced path. Difficulty emerges from the structure of constraints rather than from raw clue count.
An instrumented solver can record propagation depth, branch points and technique usage. None of those measures is a perfect model of human effort, but together they offer a stronger basis than “this one has three fewer clues” or “this one took longer on my laptop.”
Benchmarks need stable case sets
An optimization can look excellent on one board and perform worse on another family of states. Stable seeds, representative sizes and known pathological fixtures make comparisons meaningful.
For an interactive puzzle site, high percentiles matter as much as averages. A solver that usually finishes in milliseconds but occasionally freezes the main thread for several seconds can still create a poor experience. Benchmarking has to represent the tail, not merely the best story we can tell about the mean.
Memory belongs in the performance budget too
A solver can prune aggressively and still be expensive if each recursive branch copies large structures. Compact state, controlled mutation and rollback can matter as much as candidate ordering.
Different games may choose different memory strategies. That is another reason not to force one internal solver framework. The shared platform should expose measurement, cancellation and diagnostics while allowing each engine to use the representation its constraints reward.
Long searches need cancellation
In the browser, verification should stop when the result is no longer useful. If the player changes difficulty, starts another game or navigates away, continuing an expensive search wastes CPU and can race with newer state.
Cancellation sounds like a product concern rather than puzzle theory, but it is part of what makes an algorithm safe to integrate into a responsive application. A correct answer that arrives after the relevant game has disappeared is not useful work.
Web Workers protect responsiveness, not logical complexity
Moving computation away from the main thread can keep input and rendering responsive, but it does not make a weak algorithm efficient. Workers introduce their own contracts: state must be serializable, stale requests need cancellation, and a late response must never overwrite the state of a newer game.
Parallelism should therefore be a measured response to a real bottleneck. Strong propagation and a good model often provide more value than adding concurrency around a search space that is unnecessarily large.
Memoization is only safe when state equivalence is precise
Transposition tables can remove repeated work, but the cache key has to represent every rule-relevant dimension: board size, variant, optional rule set and logical state. Two boards that look the same on screen may represent different problems if their contracts differ.
Caching therefore raises the modelling bar. We can only reuse an answer safely when we know exactly why two states are equivalent.
The solver should not know how the board is rendered
Domain logic tied to CSS classes, DOM coordinates or animation state is difficult to test and difficult to reuse. The solver should receive a puzzle model and return domain information.
This boundary is part of the Blupoli Puzzles engine architecture: rules and state live on one side; shell, interaction and presentation live on the other. Different algorithms can still share that clean separation.
The UI should not secretly become a second rules engine
The opposite failure is just as dangerous. If rendering code independently decides whether a move is valid, the application now has two versions of the rules. They will eventually disagree.
The interface should ask the domain or render state that has already been validated. That way an interaction redesign does not change game meaning, and tests or future clients can rely on the same rule contract.
Validator and solver agreement should be tested explicitly
It is not enough to assume that the completion check and the search engine mean the same thing by “valid.” They may use different representations or be optimized for different tasks. Complete, nearly complete and invalid fixtures should pass through both paths.
If the UI celebrates a board that the solver later rejects, the problem is not merely cosmetic. It is a domain contradiction that should fail before publication.
Solving and explaining are different jobs
A backtracking engine can reach the correct answer through a branch that would make a terrible hint. A helpful tutoring layer needs a comprehensible deduction, a reason and controlled disclosure.
“We have a solver” does not mean “we have a tutor.” The two systems can share candidate sets, rule checks and traces, but the pedagogical layer needs its own policy about what is useful to reveal.
A hint can be correct and still be a bad hint
Giving away a whole value may be logically sound but pedagogically excessive. A hint can also rely on guessing even when the product wants to teach deduction. That means hint generation needs constraints around technique, depth and amount of information.
The solver provides evidence. The product decides which part of that evidence helps the player continue without replacing the act of solving.
Explanation-ready traces should use domain concepts
If we want richer hints later, events such as “candidate removed because of neighbour ratio,” “interval excluded” or “cycle would be created” are more useful than an opaque recursion step number.
These traces do not have to become a permanent production log. They can remain development instrumentation until a user-facing feature needs them. The important part is keeping enough semantic meaning that explanation does not depend on reverse-engineering a backtracking implementation.
Serialization migrations need domain tests, not only schema tests
A migration is not complete because the new JSON parses. The migrated puzzle should preserve its clues, variant, progress and solution properties.
For a unique puzzle, verifying uniqueness before and after migration can provide stronger evidence than validating fields alone. For an in-progress game, the legal actions and player state should remain equivalent. The domain has to survive the migration, not just the structure.
Persisted cases may need rule provenance
A saved game or regression fixture can represent an older variant contract. If rules evolve, enough context should remain to explain why a historical state behaves differently.
This does not require versioning every internal refactor. It means durable artifacts should carry the pieces of provenance that affect their interpretation, so an intentional engine change is not mistaken for corruption.
Determinism is a debugging tool, not an enemy of variety
Players can receive varied boards while development still has a deterministic mode. Freezing candidate ordering and randomness for a given seed makes performance profiles and logic failures repeatable.
Randomness is a content tool. Reproducibility is an engineering tool. Treating them as separate layers lets us keep both.
Tail latency deserves explicit gates
A solver that averages fifty milliseconds but occasionally takes eight seconds is a poor interactive generator. High-percentile measurements and named worst cases should therefore be part of performance review.
If a candidate cannot be verified within the product's budget, the safe response is not to skip uniqueness. We can reject that candidate, change generation strategy or process it elsewhere. Performance pressure should not silently weaken logical guarantees.
Every fixed bug should become retained knowledge
A correction becomes much more valuable when the failing state and the violated property remain in the test suite. The repository then remembers why the code changed, not only what changed.
Over time, those regressions form an executable history of each engine's boundaries. That memory gives us confidence to optimize aggressively because the tricky cases discovered in the past continue to defend themselves.
Shared observability is more useful than forced mathematical uniformity
A common layer can provide timeouts, counters, benchmark hooks, logging, cancellation and seed conventions without dictating the search representation. These are responsibilities that genuinely repeat across engines.
Sharing the tools that let us understand Dominosa, Slant and Aquarium is more valuable than forcing those games to pretend they solve the same kind of variable.
Abstractions should be extracted from repetition we can prove
When the same responsibility appears in three or four real engines, we have evidence that it belongs in shared infrastructure. Before then, a universal framework may encode repetition we only imagined.
This approach can feel slower at the beginning, but it produces a smaller and more durable platform. The shared layer grows from actual pressure rather than predictions about every puzzle we might build someday.
The catalogue itself is stress-testing our abstractions
Moving between exact cover, graph connectivity, matching, intervals and neighbour relations gives the architecture repeated chances to reveal assumptions that were too narrow.
That is one reason the broader catalogue is valuable beyond content volume. It forces the platform to prove that its shared contracts are genuinely generic while still leaving the mathematics where it belongs: inside the specific engine.
The universal solver we need is really a discipline of evidence
After Dominosa, Stitches, Slant, Aquarium, Str8ts and Kropki, the conclusion is not that we should search for one algorithm to unify them. The useful common ground is stricter and simpler: explicit models, propagation, reproducibility, uniqueness where required, measurable performance, adversarial testing and retained regressions.
That contract lets Blupoli Puzzles keep expanding into very different mechanics without treating mathematical diversity as an excuse for inconsistent quality. It also connects naturally to the broader challenge of scaling from one puzzle to a platform: the platform is coherent because the standards around each engine are shared, not because every engine is secretly the same.