Overview
Simple States generates switch-and-state logic puzzles in four modes. Classic: every switch toggles (XORs) a set of states — press the right set, order never matters; the deduction lives in the states that several switches share. Ordered: switches are gated behind board states, so they only fire in sequence — with decoys, and optional traps that turn a state back off when pressed (wrong tries cost work; every trapped board is verified deadlock-free). Lights Out: grids up to 56 cells where every cell is a light and a button; pressing flips it plus its orthogonal neighbours, every board solvable by construction — with pattern goals: all lit, blackout, or a painted target shape. Trilight: the mod-3 grid — cells cycle off → dim → bright, a press bumps a cell and its neighbours one step, and a cell can need two presses.
Every board is solved exactly — solution count, the shortest solution (the board's par), all solutions, switch roles (required / optional / dummy), rule diagnostics, and a 1–5★ difficulty — then stored as a plain ScriptableObject. Deterministic from a seed. Pure C# core with no package dependencies; Built-in, URP, and HDRP.
Requirements
- Unity 6000.3+ (developed on 6000.3.8f1).
- Core (generation / solving / analysis / storage): no package dependencies, any render pipeline.
- Demo & editor tooling: Input System and TextMeshPro (Unity prompts to import TMP essentials). The demo board is runtime primitives tinted via MaterialPropertyBlock — no material conversion on any pipeline.
- Core, Demo, and Editor are separate assembly definitions.
Quick start
No code: Window ▸ Living Failure ▸ Simple States ▸ Puzzle
Generator → pick a mode (Classic / Ordered / Lights Out / Trilight), set states/switches or the
grid, pick a preset, Generate → Save ▸ Current or
Save ▸ Pack. Open Scenes/DemoScene, assign packs on the
StatesDemo component, press Play. The demo's pack slots take packs,
not single-puzzle assets — a pack of one is fine.
using SimpleStates;
var settings = new GenerationSettings {
Mode = PuzzleMode.Classic,
StateCount = 6, SwitchCount = 6,
SolutionStyle = SolutionStyle.Single,
DummyPolicy = DummyPolicy.Allow,
MinSolutionSize = 2, MaxSolutionSize = 4,
Seed = "level-42",
};
StatesPuzzle puzzle = ClassicGenerator.GenerateVerified(settings);
// Ordered chains with traps (modes: Classic / Ordered / Lights Out / Trilight):
var ordered = new GenerationSettings {
Mode = PuzzleMode.Ordered, StateCount = 8, SwitchCount = 8,
SolutionStyle = SolutionStyle.Few, DummyPolicy = DummyPolicy.Require,
TrapPolicy = TrapPolicy.Require, MinSolutionSize = 4, MaxSolutionSize = 6,
MaxAttempts = 800,
};
StatesPuzzle chain = OrderedGenerator.GenerateVerified(ordered);
// Lights Out (a grid implies the mode; goals: AllLit / AllDark / Pattern):
var lights = new GenerationSettings {
GridWidth = 5, GridHeight = 5, MinSolutionSize = 6, MaxSolutionSize = 14,
GoalMode = LightsOutGoal.Pattern,
GoalPattern = 0b00100_01110_11111_01110_00100, // a diamond
};
StatesPuzzle grid = LightsOutGenerator.GenerateVerified(lights);
// Trilight (mod-3) - same recipe, three states:
StatesPuzzle tri = Mod3Generator.GenerateVerified(new GenerationSettings {
GridWidth = 5, GridHeight = 5, MinSolutionSize = 6, MaxSolutionSize = 14,
});
Generating
The Generator window shows settings on the left (they adapt to the mode) and the result set with its full analysis on the right: verdict, best solution, per-press path preview, readable rules, switch roles, all solutions, diagnostics, the rule matrix (or the board grid for Lights Out), and the generation report. Batches accumulate behind a progress bar; Only unique filters the browser; presets, Auto tune, and guardrail warnings cover the common recipes.
On grid boards the Board section is interactive: click cells
and they respond as they would in play, so you can watch a solution resolve instead of
reading a list of marked cells. A solid dot is a cell still to press, a hollow ring one
already pressed, and a badge (Trilight) the presses still needed. Cells are labelled by
column letter + row number (B1, C4), matching headers drawn on
the board. Where a board has several answers a picker steps through them, shortest first;
the status reads SOLVED whenever the board matches the goal, including by a route of your
own. Nothing here edits the asset.
Grid solution counts are fixed by the grid SIZE, not by the board — a 5×5 Lights Out always has 4 answers, a 4×4 always 16, most sizes exactly 1. The generator says which before you press Generate, and suggests sizes that give a unique board.
- Classic — solution style (Single / Few / Many), dummy policy, rule shape (states per switch, required overlap), degenerate-rule filters, exact shortest-solution target.
- Ordered — gated chains solved by BFS; decoys hide the chain; the trap policy adds decoys that turn states back off, verified deadlock-free (the goal stays reachable from every reachable state).
- Lights Out — the start is scrambled backwards from the solved board (always solvable); the GF(2) solver finds the true shortest, matched to the window or an exact target.
- Pattern goals (grid modes) — All Lit, All Dark (blackout), or a click-painted Pattern; any target is solvable by construction and stamped into the saved data.
- Trilight — three states over the same recipe; exact ℤ₃ math (counts are 3^nullity), scramble presses hit cells once or twice, solutions list a cell twice when it needs two presses.
Saved assets re-check with inspector Verify (single / all) and regenerate with Re-roll (current / every multiple-solution entry) — all undoable. Every board gets a challenge score mapped to 1–5★ (Lights Out is rated on press count).
Settings (GenerationSettings)
| Field | Range | Meaning |
|---|---|---|
Mode | Classic / Ordered | The puzzle type. A grid (below) implies Lights Out. |
StateCount / SwitchCount | 3–8 | States to open / switches to press. |
GridWidth / GridHeight | up to 8 per side (≤ 56 cells) | Grid size; both > 0 switches to a grid mode. |
CellStates | 2 / 3 | Grid flavor: binary Lights Out / mod-3 Trilight. |
GoalMode + GoalPattern | AllLit / AllDark / Pattern · bitmask | The grid target; Pattern = the painted bitmask. |
SolutionStyle | Single / Few / Many | How many valid solutions the board may have. |
DummyPolicy / TrapPolicy | None / Allow / Require | Decoys · decoys that turn a state back off (Ordered). |
MinSolutionSize / MaxSolutionSize | 1…switches / cells | Window for the shortest solution. |
MinStatesPerSwitch / MaxStatesPerSwitch | 1…states | Classic rule shape. |
RequireOverlap + MinOverlapStates | bool · 0…states | States shared by 2+ switches — the source of deduction. |
ForbidSingleStateSwitches / ForbidAllStateSwitches / ForbidDuplicateRules | bool | Degenerate-rule filters. |
AutoRuleShape + TargetShortestSize | bool · 0… | Pin the shortest solution to an exact size (0 = off). |
ScramblePresses | 0–56 | Lights Out scramble depth (0 = auto from the window). |
MaxAttempts | 1–5000 (window slider 50+) | Tries before keeping the best fallback (see the report). |
Seed | string | Empty = random; any string reproduces the board. |
Clamp() coerces every field into range. The report:
AttemptsUsed, RuleRejects, QualityRejects,
Matched.
API reference
| Type | Surface |
|---|---|
ClassicGenerator / OrderedGenerator / LightsOutGenerator / Mod3Generator | GenerateVerified(settings) → analyzed, rated StatesPuzzle; honest fallback via Report.Matched. |
ClassicSolver | Analyze(puzzle) — exhaustive ≤ 16 switches, auto-dispatch to GF(2) beyond. |
Gf2Solver | Analyze(puzzle) — Gaussian elimination; exact counts (2^nullity), full listing to 4,096 solutions. |
Mod3Solver | Analyze(puzzle) — the same over ℤ₃ for Trilight; counts are 3^nullity, twice-pressed cells listed twice. |
OrderedSolver | Analyze(puzzle) — BFS: shortest sequences + step breakdown; CountTraps(puzzle). |
StatesPuzzle | Mode, counts, ClassicRules (bitmask per switch — the incidence matrix), OrderedRules, grid, Start/Goal, persisted summary (Solvable/SolutionCount/ShortestSize/Difficulty/Unique), transient Analysis/Report, Settings, factories + label helpers. |
OrderedRule | Conditions (state must be off/on) + Effects (Effect.Toggle(d) / Effect.Set(d, on)). |
PuzzleAnalysis | Solvable, SolutionCount, ShortestSize, IsUnique, ShortestSolutions/AllSolutions, roles, Diagnostics, Steps (ordered, with blocked-press reasons), ChallengeScore, Difficulty, warnings, notes. |
StatesPuzzleAsset / StatesPuzzlePack | ScriptableObject storage (single / ordered set), both IPuzzleProvider; PuzzleAssets.CreatePuzzle/CreatePack factories. |
StatesGame (Demo) | LoadLevel, PressSwitch, ResetLevel, StartAutoSolve/CancelAutoSolve, live state, events (LevelLoaded/Reset/Solved, Pressed, PressBlocked). |
Reading a saved puzzle
StatesPuzzle p = pack.Get(0);
Debug.Log($"{p.Mode} · {p.StateCount} states · unique={p.Unique} · {p.Difficulty}★");
// Classic play is three lines of state:
long state = p.Start;
state ^= p.ClassicRules[2]; // press P3
bool won = state == p.Goal;
// Full picture on demand:
p.Analysis = p.IsMod3 ? Mod3Solver.Analyze(p)
: p.Mode == PuzzleMode.Ordered ? OrderedSolver.Analyze(p)
: ClassicSolver.Analyze(p);
Lights Out: cell (x, y) = index y * GridWidth + x,
used as both state bit and switch index.
The demo
Open Scenes/DemoScene, press Play. A Canvas landing menu picks one of the
four modes and endless-vs-pack; the HUD shows a goal-aware objective, live counter,
presses vs par, difficulty pips, and Reset / Solve / Menu, with a par
verdict on the Solved overlay. Click presses · R resets ·
F auto-solves step by step, exactly on par (input locks; R
cancels). Ordered boards glow the switches that would change something right now —
including trap recovery; pattern-goal boards dot their target cells on the board;
Trilight cells rise a physical step per state. Assign one pack per mode on
StatesDemo; skin via the renderer's prefab slots, palette, and layout
dials; SFX are synthesized (an AudioListener is required). One-click builders:
… ▸ Simple States ▸ Build Demo Scene (everything), plus standalone menu/HUD
builders under … ▸ Demo Setup.
Troubleshooting
- "Best-effort fallback" — nothing matched within Max attempts; raise it, widen the window, or loosen the strictest filter. The board is still analyzed honestly.
- Can't assign a saved puzzle in the demo — the slots take packs; use Save ▸ Pack (a pack of one is fine).
- Ordered too easy — enable traps (with dummies allowed); the glow marks actionable switches, including re-pressable trap recovery.
- F does nothing — already solved or already auto-solving (both intentional).
- "Malformed rule data" — incomplete/hand-edited asset; Re-roll or regenerate it.
- Huge grid-mode counts — counts are exactly 2^nullity (Trilight: 3^nullity); listing caps at 4,096 / 6,561 and says so. The shortest solution stays exact.
- Trilight lists a cell twice — intentional: that cell needs two presses (+1 mod 3 each).
- Grid caps at 56 cells — 64-bit masks; sliders clamp automatically.
- Missing text / TMP errors — import TMP Essentials (Examples & Extras not needed).
- Compile errors — the demo needs Input System + TextMeshPro; the core has no dependencies.
- No sound — add an AudioListener; SFX are synthesized, no clips to assign.
About
Simple States is made by Living Failure. Also available: Simple Connect (draw / pipe-rotate / network connection puzzles, square & hex) and Simple Hamiltonian (cover-all single-path puzzles) — the same verified, ScriptableObject-first workflow. Support: livingfailuregames@gmail.com — include the Unity version, and the seed + settings for board-specific issues. Distributed under the Unity Asset Store EULA.