A puzzle game where the puzzle piece is ladder logic. Program a Mitsubishi-style PLC on a grid editor, hit Run, and watch power flood the rung while a real machine moves in 3D beside it. Forty work orders across ten categories take you from a single contact driving a single coil to a PID loop holding a tank on setpoint and a gantry crane that has to wait out the swing of its own load.
Play the demo rung → · a real solver and grader running in the page, no install
Three columns, all live at once.
- Work order — the brief reads like a machine manual: equipment list, sequence of operation, interlocks, acceptance criteria. Terminal assignments light up as the sim runs, and progressive hints are there when you want them.
- Ladder editor — a grid of cells you fill with contacts, coils, timers, counters and function blocks. Series is AND, vertical links are OR. Keyboard-first (one letter per instruction), zoomable, with in-place editing of any address, preset or operand.
- Operator panel — push buttons, maintained switches, e-stops, lamps, motors and analog trends bound to real X/Y addresses. Hold digit keys 1–9 to press several buttons at once, which the two-hand safety press genuinely requires.
Press Run and the rung lights up cell by cell as power floods it from the left rail. Press Submit and the server replays your program through scripted test scenarios, with machine dynamics, and scores it.
Work order 02. The motor is running with nothing held down: the parallel Y0 contact under the start button is sealing the rung in, and you can see exactly which cells are carrying power.
Every puzzle family drives a machine visualization that is a diagnostic instrument rather than decoration: nothing animates on its own, and every transform is a pure function of the deterministic state the process model computed from dt. Four scenes are hero models authored in Blender and loaded as glTF; two are procedural, because their subject is a number moving and a shape that is that number reads better than geometry would.
Mitsubishi FX addressing: X inputs, Y outputs, M relays, T timers, C counters, and D data registers.
- Bit instructions — normally-open and normally-closed contacts, rising- and falling-edge contacts, OUT / SET / RST coils, timers (presets in 100 ms units) and counters.
- Word instructions —
compare(a conducting contact carrying= <> > < >= <=),MOV,ADD/SUB/MUL/DIV, and a realPIDblock with gain, integral and derivative times, its own sample period, an output clamp and conditional anti-windup integration. - Analog I/O — transmitters report raw counts (0..4000, the FX analog cards these puzzles model), never pre-scaled engineering units. Scaling them is the first analog puzzle's lesson, not something the plant does as a favor. Registers are 16-bit signed and saturate rather than wrap, and arithmetic evaluates at full precision before saturating on the store, so "divide before you multiply" is a lesson the puzzles teach.
The second genre drops ladder logic entirely: you wire the terminals of fixed components — a 3-phase supply, contactors, a thermal overload, pushbuttons, lamps and a motor — using IEC terminal numbering (K1.A1, F1.96). Two editable views of the same document:
Wires and terminals color by live net potential (L1 brown, L2 black, L3 grey, N blue, PE green-yellow). Merge two supply potentials onto one net and the breaker trips, exactly as it would in the panel.
Submitting runs every scenario the puzzle declares: a scripted input timeline with assertions, driven through the same simulation engine and the same process model the client just used.
- Sequencing puzzles are paced by the program driving them, so a step can run to a milestone (
until) rather than a fixed deadline. Grading pace instead of behavior is how you fail a correct program for being 200 ms slow. - Regulating puzzles get a different question: a step carries a setpoint, a band, a settle time and overshoot and steady-error caps, evaluated across the whole step. A loop that happens to be sitting on setpoint when the clock runs out has not been shown to work, and one that got there through a 40% overshoot would have put product on the floor.
- Scoring is 85 marks for scenarios passed plus 15 for performance, and the performance marks only land once everything passes. Sequencing puzzles spend them on cycle time against a declared par; regulating puzzles spend them on integral of absolute error. A correct but leisurely program is solved and unlocks what follows, and still has to be pipelined to reach 100.
- Replay — every failing scenario gets a ▶ button that re-runs it scan by scan, right in the editor, with a scrubber. Jump straight to the first failing scan and watch the rung that did it.
- Trace strip — a logic-analyzer view, one row per device or register, filled where the bit is high. It reads the live sim's rolling window or the replay's full trace, and word devices draw as strip charts, because a regulator cannot be judged from an instantaneous number.
| Category | What it teaches |
|---|---|
| Basics (3) | Contacts, coils and seal-in logic. |
| Timers & Counters (4) | On-delay, off-delay, oscillators and counting. |
| Stations (2) | Sequenced single-station machines: a conveyor index and a two-hand safety press. |
| Elevator (4) | Multi-floor dispatch, up/down latches with tie-break, and door interlocks enforced physically. |
| Control Cabinet (6) | Wire real 400 V starters terminal to terminal: DOL, two-station control, reversing, indication. |
| Packaging Machine (4) | Group boxes 2 → 4 → 16 with pushers, a flipping lift, a retaining bracket and an out-feed. |
| Pick & Place (4) | Index a robot arm between an infeed and a tray, one part at a time, without overfilling. |
| Drill Station (4) | Clamp, spin up, drill and sort mixed stock through one automatic station. |
| Process Control (5) | Scale a transmitter, build a P regulator by hand out of SUB/MUL/ADD, then let a PID block kill the offset. |
| Motion Control (4) | Speed references, drive ramp parameters, and the stopping distance a loaded carriage implies. |
Categories unlock sequentially — each one's first puzzle is always open, and the rest gate on the previous solve. Enforced on the API, not just hidden in the UI.
The architectural bet of the whole project: a single pure-TypeScript simulation engine in packages/shared runs on both the client and the server. The client runs it for live play; the server runs the identical code as the source of truth for scoring. There is no second implementation to drift.
packages/shared (no runtime deps)
├── ladder/ program model + address parsing
├── circuit/ cabinet components, net solver, wiring grader
├── sim/ rungSolver + SimEngine scan cycle
└── puzzle/ spec schema, process models, validator, grader
│ │
imported by │ │ imported by
▼ ▼
packages/client (Vite React) packages/server (Express + node:sqlite)
live sim, ladder editor, HMI authoritative grading, auth, persistence
They agree bit-for-bit because of two rules the codebase enforces rather than documents:
- The engine advances only by an explicit
dt, never wall-clock time.npm run lintbansDate,performance,Math.random,windowanddocumentinsidepackages/shared. If the engine could read the clock, client and server would stop agreeing. - The client's live scan
dtis the gradingdt(50 ms, one shared constant). Boolean puzzles tolerated a mismatch because every process model's timings were exact multiples of both; an integrator does not. Continuous plants integrate on a fixed 10 ms sub-step with a carried remainder on top of that, so a trajectory is identical at anydt.
rungSolver.ts treats a rung as a graph and floods power from the left rail using disjoint-set union over column-boundary nodes. scanCycle.ts evaluates rungs top to bottom and applies coils immediately, so a later rung sees an earlier rung's coil in the same scan, and snapshots the previous-bit image at the end of each scan, which is what makes edge contacts work.
git clone https://github.com/CuplexUser/AutomationSolver.git
cd AutomationSolver
npm install
npm run dev # server on :4000, client on :5173 (Vite proxies /api → :4000)Open http://localhost:5173, create an account, and start solving.
Google and GitHub sign-in are optional: copy packages/server/.env.example, set GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and/or GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET, and providers left blank are simply hidden on the sign-in page.
- Google callback:
http://localhost:4000/api/auth/google/callback - GitHub callback:
http://localhost:4000/api/auth/github/callback
With SMTP unconfigured, verification and password-reset emails are written to the server console instead of being sent.
npm install must work on a fresh machine with nothing but Node — no C++ toolchain, no node-gyp, no prebuild roulette. Nothing in this repo compiles native code, and the substitutions are deliberate:
| Instead of | This project uses |
|---|---|
| better-sqlite3 / sqlite3 | Node's builtin node:sqlite (DatabaseSync) |
| argon2 / bcrypt | node:crypto scrypt, stored as scrypt$salt$hash |
| connect-sqlite3 | a custom session store on node:sqlite |
packages/
shared/ ladder model, circuit solver, scan-cycle engine, puzzle specs,
process models, validator, grader (no runtime deps)
server/ Express + Passport, node:sqlite data layer, submit + grading API
client/ Vite React SPA: ladder editor, cabinet editor, live sim, 3D machines
docs/ FEATURE-MAP.md (what exists), ROADMAP.md (what's next)
site/ the GitHub Pages landing page, including a playable rung demo
The database stores puzzle references by slug only; puzzle content is never duplicated into it. A player can keep several named save slots per puzzle, and submitting saves into whichever one is active, so a submission never loses work.
npm test # shared engine (vitest) + server API (supertest)
npm run test:shared # just the simulation-engine unit tests
npm run typecheck # tsc --noEmit across all packages
npm run lint # oxlint repo-wide + ESLint react-hooks on the client
npm run test:e2e -w @automationsolver/client # Playwright: build and solve a puzzleThe engine tests cover rung power flow (series, parallel, NC, edge), timers, counters, register saturation and PID behavior. Two of them are the load-bearing ones: grade.test.ts and gradeCabinet.test.ts hold a canonical solution for every shipped puzzle, which is the guardrail against authoring a puzzle nobody can solve.
Add a PuzzleSpec under packages/shared/src/puzzle/content/, register it in content/index.ts, and add a canonical solution to grade.test.ts. A spec declares its I/O devices, optional working registers, allowed instructions, a process model (passthrough, or a stateful one like drill or tank) and graded scenarios.
Process models are small deterministic state machines that react to Y outputs and drive X inputs. They grow by feature detection off the puzzle's own device list, so one model serves a whole category and an early puzzle never fails an interlock it cannot see.
Write the briefing as an instruction manual rather than prose: a short lead paragraph, then ## Section blocks (Equipment, Sequence of operation, Interlocks and safety, Field notes, Acceptance).
docs/FEATURE-MAP.md— where every capability lives and why it is built that waydocs/ROADMAP.md— the phased plan for what comes next
See LICENSE.









