npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cube-state-engine

v1.8.2

Published

An efficient representation in memory for tracking the Rubik's cube state on each movement.

Downloads

254

Readme

Cube State Engine

A tiny, dependency-free engine that tracks a Rubik's cube state, move by move.

npm dependencies license

Powers the solve tracking and analytics core of NexusTimer


What it does

Give it moves, and it tells you what the cube looks like and what just got solved.

import { CubeEngine } from "cube-state-engine";

const cube = new CubeEngine("R U' F R2 D"); // start from a scramble
cube.applyMoves("R U R' U'", { record: true });

cube.isSolved();   // false
cube.state();      // { UPPER: [[...]], LEFT: [[...]], ... }
cube.getMoves();   // "R U R' U'"

It handles the whole standard notation set (faces, wide moves, slices, rotations), works on 3x3 and 2x2, and ships with higher-level tools built on top: state predicates, a move simplifier, and a full solve analyzer that breaks a solve into CFOP or Roux stages.


Installation

pnpm install cube-state-engine

Ships CJS + ESM + TypeScript types. Works in Node and in the browser.


Zero dependencies

Not one runtime dependency. Nothing to audit, nothing to break, nothing bloating your bundle. The whole engine is plain JavaScript.


Core: CubeEngine

import { CubeEngine } from "cube-state-engine";

const cube = new CubeEngine();                    // solved 3x3
const scrambled = new CubeEngine("R U R' U'");    // start from a scramble
const pocket = new CubeEngine("", { size: 2 });   // 2x2

Applying moves

The friendliest way to drive the cube is a notation string:

cube.applyMoves("R U2 R' D' Rw M' x y2", { record: true });

Supported notation:

| Kind | Tokens | | --- | --- | | Faces | U D L R F B | | Wide | Uw Dw Lw Rw Fw | | Slices | M E S | | Rotations | x y z |

On record: applyMoves(seq) on its own does not record into history handy for setting up a position. Pass { record: true } when you want the moves logged. Unknown tokens are skipped silently.

Every move also has a direct method, all of which record by default:

cube.rotateU(true);   // U
cube.rotateR(false);  // R'
cube.rotateY(false);  // y'

| Method | Description | Returns | | --- | --- | --- | | constructor(scramble?, { size? }) | New cube, optionally scrambled (not recorded). size is 2 or 3 (default 3). | CubeEngine | | applyMoves(sequence, { record? }) | Apply a notation string. Not recorded unless record: true. | void | | state() | Current state as six face matrices. | object | | isSolved() | Whether every face is a single color. | boolean | | getMoves(asString?) | Move history — string (default) or array. | string \| string[] | | reset() | Back to solved, history cleared. | void | | rotateU/D/L/R/F/B(cw?) | Turn a face layer. | void | | rotateUw/Dw/Lw/Rw/Fw(cw?) | Turn two layers (wide). | void | | rotateM/E/S(cw?) | Turn a middle slice. | void | | rotateX/Y/Z(cw?) | Rotate the whole cube. | void |

On a 2x2, wide and slice moves are no-ops they have no inner layer to turn.

The state shape

state() returns six faces, each a size × size matrix of color letters (W O G R B Y):

{
  UPPER: [["W","W","W"], ["W","W","W"], ["W","W","W"]],
  LEFT:  [[...]], FRONT: [[...]], RIGHT: [[...]], BACK: [[...]], DOWN: [[...]]
}

Perfect for driving a 3D model map each sticker straight onto your mesh.


Predicates: "is this solved yet?"

Ready-made checks for the milestones people actually train. Every one accepts a CubeEngine, a state() object, or a flat sticker array pass whichever you already have.

import { isCrossComplete, isF2LComplete, isLastLayerOriented, matchesGoal } from "cube-state-engine";

isCrossComplete(cube);        // cross done (defaults to the DOWN color)
isF2LComplete(cube);          // cross + all four pairs
isLastLayerOriented(cube);    // OLL done on the U face
matchesGoal(cube, "oll+cp");  // OLL + corners permuted

matchesGoal is the flexible one great for building trainers:

matchesGoal(cube, "full");                        // fully solved
matchesGoal(cube, "cross");                       // cross only
matchesGoal(cube, { goal: "oll", face: "D" });    // OLL, but on a different face
matchesGoal(cube, (c) => c.isSolved());           // or your own predicate

Goal names: full / solved, oll, oll+cp, f2l, cross.

🧭 Every check compares stickers against the current center of their face, so it stays correct through x/y/z rotations, wide moves, and AUF exactly what real speedsolves are full of.


Move simplifier

The engine records U2 as two separate U turns. simplifyMoves joins them back up:

import { simplifyMoves } from "cube-state-engine";

simplifyMoves("U R R D R' B");      // "U R2 D R' B"
simplifyMoves(["y'", "y'", "L"]);   // ["y2", "L"]
simplifyMoves("F F'");              // "F F'"  ← unchanged, no cancellation

It only ever merges two identical quarter turns into their double. It will not cancel opposites or fold net rotations, so your move count stays honest. Accepts a string, a token array, or timed moves ([{ m, t }]) and returns the same shape.


Solve analyzer

Hand it a timed solve and it reconstructs the whole thing: the scramble (derived as the inverse of the solution), the method used, and when each stage finished.

import { analyzeSolution } from "cube-state-engine";

const result = analyzeSolution([
  { m: "U", t: 0 },
  { m: "R", t: 210 },
  // ... `t` is cumulative elapsed ms
]);
{
  method: "CFOP",
  solved: true,
  total: 14371,
  tps: 5.08,
  cross: { color: "W", at: 2340, duration: 2340, moveIndex: 14, move: "U'" },
  f2l: [
    { slot: "G-O", at: 3660, duration: 1320, moveIndex: 19, move: "F"  },
    { slot: "B-O", at: 5160, duration: 1500, moveIndex: 23, move: "B'" },
    { slot: "B-R", at: 8460, duration: 3300, moveIndex: 35, move: "B"  },
    { slot: "G-R", at: 9871, duration: 1411, moveIndex: 48, move: "F'" }
  ],
  oll: { at: 12540, duration: 2669, moveIndex: 63, move: "R'" },
  pll: { at: 14371, duration: 1831, moveIndex: 81, move: "D'" }
}
  • Detects CFOP and Roux automatically. Roux solves come back with firstBlock, secondBlock, cmll and lse instead (each block noting the side color it was built on). When neither staging fits, method is "unknown" and the timing fields are null.
  • allCrosses reports when the cross completed on every face color, not just the one you used.
  • unsupported lists any move tokens it couldn't parse, so bad timings never pass silently.

Also exported: invertSequence(tokens), which reverses and inverts a move list.


Advanced

For anyone building on the internals:

import { getMovePermutations, getCubeGeometry } from "cube-state-engine";

getMovePermutations(3);  // { U: { cw: [...], ccw: [...] }, ... }
getCubeGeometry(3);      // derived edges, corners, neighbors, opposites

The geometry is derived, not hardcoded a sticker is displaced by a face turn only if it physically belongs to that face, so grouping stickers by which turns move them reconstructs every edge and corner slot on its own.


License

MIT © Bryan Lundberg