Recently, I shared SpeedCube on Hacker News.
I expected a few technical comments and some feedback. Instead, my analytics showed around 15,000 views on the site after the post.
The traffic was exciting, but it also pushed me to explain more clearly what the project actually is — and which guarantees it does and does not make.
SpeedCube is an MIT-licensed, open-source platform for solving and practicing twisty puzzles.
You can try it here:
https://speedcube.com.br/en/solve/?utm_source=devto&utm_medium=article&utm_campaign=showdev
Source code:
Rubik's Cube Solver
Rust-first puzzle solver with a native HTTP API, React web UI, replay verification, and optional YOLO-based camera scanner.
The product goal is a web interface where a user can input a valid puzzle state and receive the shortest practical replay-verified solution found within explicit limits.
Current Status
-
crates/cube-engineowns state, moves, notation, validation, search, heuristics, pruning tables, solver strategies, and replay verification. -
crates/apiexposes the Rust engine through Axum HTTP endpoints. -
apps/webprovides puzzle-aware solve flows, visualization, playback, and locale resources. -
scanneris Python-only and YOLO-only for camera analysis, training helpers, and ONNX export. - AI routing is generated from canonical files under
ai/.
Project Docs
-
docs/project-plan.md: current technical direction, implementation rules, and puzzle boundaries. -
docs/runtime.md: local, Docker, production, tunnel, and training runtime commands. -
scanner/training/SCANNER_YOLO_RUNBOOK.md: scanner dataset, training, export, and artifact workflow. -
scanner/runtime/README.md: scanner runtime service details.
Prerequisites
- Node.js 24 and npm 10.9+…
What SpeedCube includes today
The current application includes:
- A stable 3×3 Rubik’s Cube solver
- An experimental 2×2 solver
- Notation and facelet input
- An optional camera-scanning workflow
- Interactive cube visualization and solution playback
- A speedcubing timer with event-specific scramble generation
- Algorithm reference pages
- Puzzle notation guides
- World-record and public WCA-data views
One important distinction is that appearing in the timer or puzzle catalog does not necessarily mean that a puzzle has a working solver.
At the moment, the 3×3 solver is the stable product path. The 2×2 solver is experimental. Other puzzles such as Pyraminx, Clock, Skewb, Square-1, and Megaminx are represented in parts of the platform, but they are not yet supported solver targets.
I would rather expose that distinction honestly than make the project appear more complete than it is.
A Rust-first architecture
The main architecture looks like this:
React + TypeScript web UI
↓
Axum HTTP API
↓
Rust cube engine
↓
Puzzle-specific state validation
↓
Bounded solver strategy
↓
Replay verification
↓
Visual solution playback
The Rust engine owns the parts that determine correctness:
- Puzzle state
- Moves and notation
- State validation
- Search algorithms
- Heuristics
- Pruning tables and pattern databases
- Solver strategies
- Replay verification
The API is responsible for HTTP contracts, request limits, puzzle routing, artifact loading, and response mapping.
The frontend collects input, sends solve requests, displays results, and plays the returned moves. It does not implement the actual solver logic.
This separation is useful because a visual interface should not silently become a second, inconsistent implementation of the puzzle rules.
The most important invariant: replay verification
Search algorithms can fail.
They can hit a depth limit, exhaust a node budget, depend on a missing pruning-table artifact, or produce an invalid candidate because of an implementation error.
For this project, returning an incorrect move sequence as a successful solution is worse than returning no solution.
The main invariant is therefore:
A candidate solution is not exposed as successful unless replaying every returned move from the requested state reaches the solved state.
Every successful solve is checked again by the Rust engine before the API returns it.
If replay fails, the result is treated as an error — not as a successful response with a warning.
That design also influences the UI. The frontend can display whether a result was replay verified, what strategy was used, and which limits were applied.
Solver strategies without false promises
The 3×3 engine currently exposes several computational strategies, including:
- Bounded IDA*
- A generated two-phase solver
- A quality-oriented two-phase variant
- An experimental multiprobe variant
- Bounded searches using pattern databases
- A portfolio that tries short-solution approaches before falling back to two-phase search
The default stable 3×3 path uses a generated two-phase strategy.
The experimental 2×2 implementation has puzzle-specific bounded IDA* and pattern-database-based strategies.
These strategies are not presented as a universal optimal solver.
The project does not promise that every solution will be mathematically optimal. It also does not claim that every scramble will be solved in 20 moves or fewer.
Instead, the goal is to find a practical, replay-verified solution within explicit depth, node, time, and artifact limits.
If a search cannot produce a verified result within those limits, the application should report that honestly.
Camera scanning is evidence, not truth
The optional scanner uses a separate pipeline:
Browser camera
↓
Rust API
↓
Python/FastAPI scanner service
↓
Optional YOLO/ONNX tile detector
↓
Reviewed sticker state
↓
Rust validation and solving
The scanner service can analyze a camera frame and produce tile evidence and confidence information.
However, the model output is not allowed to bypass puzzle validation.
The user can review the detected stickers, and the resulting state is then validated by the Rust engine before solving.
This matters because a visually plausible scan may still describe an impossible cube.
Lighting, reflections, occlusion, camera angle, and model mistakes can all produce incorrect evidence. The solver should not turn uncertain visual output into a confident but invalid solution.
The current runtime is intentionally focused: it uses an optional YOLO detector exported to ONNX rather than maintaining several competing detector and color-classifier fallbacks.
The frontend is more than a solve form
The web application is built with React, TypeScript, and Vite.
It contains puzzle-aware solve and scan flows, cube visualization, move playback, a timer, algorithm-reference pages, notation guides, and localized routes.
The visualization code lives in a separate private workspace package. It is not the source of truth for solving; it renders states and replays moves received from the validated product flow.
The timer has its own event catalog and scramble generation pipeline, covering several WCA events.
The algorithm pages currently include reference sets for puzzles such as 2×2, 3×3, and 4×4, including OLL, PLL, COLL, CLL, EG-1, and related subsets.
Those educational resources are separate from the computational strategies used by the Rust solver.
Why puzzle support is deliberately puzzle-specific
It is tempting to create one generic Puzzle, State, or Move abstraction and force every puzzle into it.
I decided against that direction.
Every supported puzzle needs to own its own:
- State representation
- Move model
- Notation parser
- Legal move generator
- Validator
- Solver strategy
- Replay verifier
- Heuristics and coordinates
- Artifact compatibility rules
Shared code is limited to genuinely puzzle-neutral concerns such as metadata, request budgets, result shapes, and frontend selection.
That creates more explicit code, but it also makes it harder to accidentally treat Square-1, Clock, Pyraminx, and a standard cube as if they shared the same physical rules.
Adding a puzzle to a catalog is easy. Supporting it correctly requires the complete path from input to validation, solving, replay, visualization, and tests.
What I learned while building it
Correctness needs an explicit owner
When puzzle rules are split between the frontend, backend, scanner, and visualizer, the implementations can disagree.
Making Rust responsible for validation, solving, and verification gives the application a clear correctness boundary.
Failure is a valid product result
A bounded solver should be able to say that it did not find a solution within the configured limits.
Returning an honest failure is better than hiding fallback behavior or implying guarantees the implementation does not provide.
Capability metadata matters
“Stable,” “experimental,” and “planned” are not cosmetic labels.
They prevent a catalog entry, timer scramble, or visualization from being mistaken for complete solver support.
Visual correctness is functional correctness
A replay animation is not merely decoration.
If the displayed moves do not match the state transitions used by the engine, users cannot trust the result. The renderer and engine therefore need consistent notation and state adapters.
Shipping creates better questions
Developing alone makes it easy to optimize for assumptions that real users do not share.
Publishing the project exposed questions about solution quality, limits, scanning reliability, mobile usability, notation, and puzzle support that are difficult to discover in isolation.
What is next
The main areas I want to continue improving are:
- Solution quality without weakening replay verification
- Scanner training, evaluation, and model reliability
- Better error messages for impossible or incorrectly scanned states
- Stabilizing the 2×2 product path
- Mobile usability
- Timer and training features
- Additional puzzles with complete puzzle-specific validation and tests
- More solver-quality reports using real and generated scrambles
I am particularly interested in feedback from people working with:
- Rust search algorithms and heuristics
- IDA* and pattern databases
- YOLO and ONNX deployment
- React visualization and animation
- Puzzle-state modeling
- Speedcubing tools
You can try SpeedCube here:
https://speedcube.com.br/en/solve/?utm_source=devto&utm_medium=article&utm_campaign=showdev
And the complete source code is available here:
https://github.com/williamisnotdefined/rubiks-cube-solver
Feedback, bug reports, and contributions are welcome.
Top comments (0)