Genre expansion
Which genres ship next and why, revisited against what’s actually
landed. The original sequencing (masyu → nurikabe → nurimisaki →
heyawake → yajilin) was written when adding a genre meant ~7 files
across core/, cli/, and eframe/, and when the OR-Tools
encoder didn’t yet cover the rules that nurikabe-family genres
need. Both of those have changed. This chapter rewrites the plan
on top of the current infra.
What shipped vs. the plan
| Genre | Original plan | Actual |
|---|---|---|
| akari | done at plan time | done |
| slitherlink | done at plan time | done |
| sudoku | not on plan | shipped (variant-CtC market fit; jumped the queue) |
| takuzu | not on plan | shipped (worked example for the “smallest possible genre” path) |
| masyu | Phase 1 | not started |
| nurikabe | Phase 2 | not started |
| nurimisaki | Phase 3 | not started |
| heyawake | Phase 4 | not started |
| yajilin | Phase 5 | not started |
| hashiwokakero | deferred | now in scope (named in the business plan) |
Sudoku displaced masyu because the business plan
makes variant-Sudoku in the CtC orbit the beachhead audience.
Takuzu displaced nurikabe because shipping a third tiny genre
(single mod.rs + parse.rs + tests.rs, no pzv codec, no
scaffold passes) validated the universal-genre architecture and
gave the Adding a genre chapter a minimum-viable example to
point at.
What changed in the infra
Three concrete shifts since the original plan was written.
Frontend wiring is one line
Adding a genre to CLI + eframe + bench is now one entry in the
core::genre::GENRES slice, not three full files. The post-collapse
architecture in Adding a genre spells this out: all three
harnesses iterate the slice and dispatch through the runtime
&'static Genre handle; per-genre cli/src/<genre>.rs and
eframe/src/<genre>.rs no longer exist. The wiring cost per genre
dropped from “a day or two” to “an hour.”
OR-Tools already encodes the stubbed rules
core/src/algorithm/ortools/solver.rs::encode_constraint is
exhaustive on Rule and has arms for every variant currently
stubbed in eval.rs:
| Rule | eval.rs (FastSolver) | OR-Tools encoder |
|---|---|---|
Connected | Pending | implemented |
NoTwoByTwo | Pending | implemented |
ValueGroupedSize | Pending | implemented |
Polyomino | Pending | implemented |
BoundedSize | Pending | implemented |
Path { closed: false } | Pending | implemented |
Sum / Increasing / MinDifference | Pending | implemented |
This is the part that re-orders the roadmap. A genre whose rules
are stubbed in eval.rs is not blocked from shipping — OR-Tools
solves it correctly, the universal walker enumerates it, and
trial-1 carries forces in the absence of a fast eval arm. The
oracle works before the fast path does. Fast-path arms can land
later when bench numbers force them.
Universal walker, generator, and tests
walk_canonical removed per-genre enumerators (~200 LOC each).
scaffold_solve_derive covers most generation. Per-genre
dataset-smoke tests are copy-paste. The dataset infra
(puzzlekit-dataset repo, /private/tmp/penmark-data/) is in
place. The cost of “adding a genre that uses already-shipped
rules” is now mostly: write build_constraints, write the pzv
parser, write tests.
Recommended order
The new ordering optimizes for two things: cheapest first (stay in flow, ship visible progress), and then heaviest infra-leverage (the genre that promotes the most stubs to real fast-path eval). This is a different ordering than the original plan, which optimized for engine-reuse compounding alone.
Phase 1 — masyu (cheapest delta)
Still the right first ship. Slitherlink-minus-clues with two new
local rules (MasyuBlack, MasyuWhite). Reuses every existing
slitherlink primitive: Path { closed: true }, DegreeIn,
union-find, parse_url_frame, the pzv decode4Cell family.
Eval-only first cut for the two new rules; trial-1 carries
forces. OR-Tools needs new exhaustive arms for the two
variants, but they’re local and small.
Why this ordering still holds: it’s the genre with the smallest possible delta against shipped code, so it’s the ideal “warm up the new architecture” ship and proves the once-per-genre cost is actually as low as the Adding a genre chapter claims.
Ship target: a few days. Definition of done unchanged from the original plan — 50-puzzle masyu dataset smoke passes, ortools cross-check, eframe pane renders.
Phase 2 — hashiwokakero
New addition from the business plan. Hashi is named explicitly as “within reach on the same architecture.” Engine-wise it sits between slither (edges) and nurikabe (Connected). It needs:
- An edge-multiplicity rule — the existing edge substrate carries
binary
drawn/not drawn; Hashi needs{0, 1, 2}per edge slot. Either a newMark::Numericon edges (cleanest) or a newRule::EdgeMultiplicity { allowed: [0,1,2] }over edges (smaller diff but encodes a marker as a constraint). ExactCountper island over its incident edges (already shipped).Connected { mark: Bridge }over the bridge edge set (stubbed ineval.rs; needs a real arm — see below).- A planarity / non-crossing cut: bridges can’t cross. Lazy cut pattern, same shape as the slither loop closure.
The Hashi solver work doubles as the forcing function for
promoting Connected from stub to real. Once Connected has a
fast eval arm, nurikabe / nurimisaki / heyawake / yajilin all
benefit.
Why earlier than Phase 3+: the business plan explicitly calls Hashi out as part of the “Nikoli family within reach” pitch. CTC solves Hashi occasionally on YouTube. It’s a different shape of puzzle from the cell-coloring family, which broadens the engine demo without demanding the whole nurikabe-family stub-to-real push at once.
Ship target: ~2 weeks. Most of the time goes to the planarity cut and to making Connected fast enough for trial-1 to be useful.
Phase 3 — nurikabe (the high-leverage stub-to-real push)
This was Phase 2 in the original plan, and the underlying
argument hasn’t changed: implementing real fast-path eval for
Connected, NoTwoByTwo, and ValueGroupedSize unlocks four
later genres simultaneously. What’s changed is that Hashi
(Phase 2 above) already paid for Connected’s real arm, which
makes nurikabe’s incremental cost smaller than originally
estimated.
Three rules need real eval.rs arms after Hashi:
NoTwoByTwo { mark }— slow-path is trivial (walk every 2×2); fast-path is per-2×2 aggregate with force-when-three-and-one-undecided.ValueGroupedSize— designed for nurikabe; per-value-seed BFS over committed cells of that mark, asserting size == value.Connected(already done in Phase 2 if Hashi shipped first; otherwise here).
The pzv work is the standard decode4Cell family.
Ship target: ~3 weeks. Most of the time is the propagator work for the three rules.
Phase 4 — nurimisaki, heyawake (proves Phase 3 transports)
Both reuse Connected + NoTwoByTwo from Phase 3. Both ship
fast on top of that base.
- Nurimisaki adds one new local rule:
Cape { at, len }. LOS ray of lengthlen-1white, terminated by black/wall, other three neighbors black/wall. Eval-only first cut. - Heyawake adds room geometry + adjacency. No new
Rulevariants — encodes viaExactCountper numbered room +AtMost(1)per adjacent black pair + per-straight-runForbidden(ExactCount(black, k))for the room-boundary rule. Lots of constraints, no new engine work.
Land them in the same phase because nurimisaki proves Phase 3’s stub promotions transport, and heyawake stress-tests the constraint count on existing rules.
Ship target: ~2 weeks combined.
Phase 5 — yajilin (the prize)
Original Phase 5; still the hardest. Cell-only Model A
({Black, WhiteLoop, Numbered} per cell, loop recovered from
WhiteLoop set via Connected + DegreeIn(2)) reuses everything in
place by then. Model B (edges + cells coupled biconditional)
deferred until benches force it.
If Phase 3’s Connected arm proves not strong enough as a
“single closed loop” check on cells, add a cell-substrate
Path { closed: true } variant. The slither edge-based version
demonstrates the algorithm is tractable.
Ship target: ~3 weeks.
Phases 1-5 in roughly two months
Estimate is honest if focused: masyu (3-5 days) + hashi (2 weeks)
- nurikabe (3 weeks) + nurimisaki+heyawake (2 weeks) + yajilin
(3 weeks). About 10 weeks of focused work, less if some genres
ship faster than estimated, more if
Connected’s fast eval arm turns into a multi-week project on its own.
This is materially faster than the original plan’s pace because
of the architecture collapse: each genre is mostly one file in
core/ plus three Vec entries elsewhere, and OR-Tools solves
correctly today for every rule shape we care about. The gating
work is per-rule fast-path eval, not per-genre wiring.
Deferred (still out of scope)
- shakashaka — needs
Mark::Triangle(Empty, NW, NE, SW, SE)(5-cardinal mark; engine assumes Binary/Numeric) and aRegionIsRectanglegeometric rule with no analog. Revisit when there’s a concrete demand signal. - lits — tetromino placement is a different paradigm than
per-coord candidate sets. Would need
Rule::OneOfShapesand same-shape adjacency across rooms. Tractable but architecturally distinct. - dbchoco — region-pairing congruence; needs shape-comparison primitives.
- Crossword, mazes, hidden-information genres — covered in the Puzzle families chapter; sister-track work, not in-scope for the CSP roadmap.
Locked decisions (carry over)
- Trial-1 + OR-Tools oracle carries any genre as a “slow but correct” first cut. Fast-path eval lands when benches demand.
- New genres are one-file-in-
core/puzzles/<genre>/+ three Vec entries (CLI / eframe / bench). No per-genre frontend files. - pzv URL framing goes through
core/src/puzzles/pzv.rs::parse_url_frame. - Each genre lands with a 50-puzzle dataset smoke test against the puzzlekit / puzzlink corpus.
Why this matters for the business plan
The business plan makes engine breadth a long-term moat: “the home for all Nikoli-style puzzles” is the embed-pitch differentiator no competitor can offer. The Phase 1 beachhead is variant-Sudoku — done — but Phase 4 of the business roadmap (“multi-genre expansion and market growth”) is the phase that turns the engine into a real pitch. Shipping six more genres in the next ~10 weeks moves Phase 4 from “long-term moat” to “ready to demo when the marketplace ships in the business plan’s Phase 2.”
The cheap-then-leveraged ordering (masyu → hashi → nurikabe → nurimisaki+heyawake → yajilin) means the engine demo gets visibly broader every couple of weeks, which is the showing-up-in-CtC-orbit cadence the business plan’s go-to-market section depends on.