From bbb8543ea76081ec273a996e2b469779fa1942a1 Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 17:41:20 +0000 Subject: [PATCH 1/6] fix(orbit64): copy V2 cubes with bounded work and plain numbers The snapshot of a cube read its array lengths as given, so an array with a hostile length (a Proxy or array-like reporting 1e9 entries) cost seconds or exhausted memory before validation could reject it. A length that was an object with a valueOf, or a bigint or symbol, was coerced more than once or threw. Entries and the frame were copied as they were, so an object with a valueOf passed validation and then answered differently when it was written. The length is now read once, only a non-negative integer counts, and at most 65 entries are copied: no array of a cube has more than 42 entries, so a longer one is no state either way and the reason is unchanged. Only a primitive number is kept as a number; anything else becomes undefined and fails validation without any caller code running on it. Co-Authored-By: Claude Sonnet 5.5 --- src/cube/notation/Orbit64V2.res | 31 +++++++++-- test/cube/notation/orbit64V2.test.ts | 78 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/cube/notation/Orbit64V2.res b/src/cube/notation/Orbit64V2.res index e16d9d4..d168d6f 100644 --- a/src/cube/notation/Orbit64V2.res +++ b/src/cube/notation/Orbit64V2.res @@ -392,10 +392,31 @@ let unrankState = (n, model, pose, value) => { let notAV2Token = "not a known V2 state token" +// No array of a cube has more than 42 entries (the center orbits of a 15x15x15). +// A longer one is read only up to this many entries plus one: it is no state +// either way, and a hostile length costs no time or memory. +let maxEntries = 64 + // A copy of the arrays and numbers of a cube, read once and by index, so that // holes become undefined (and fail validation) and a getter cannot answer -// differently between validation and ranking. -let readArray = values => Array.fromInitializer(~length=Array.length(values), i => values->get(i)) +// differently between validation and ranking. The length is read once and only +// a non-negative integer counts: anything else (an object with a valueOf, NaN, +// no length at all) reads as one undefined entry, which fits no layout. Only a +// primitive number is kept as a number: any other entry (an object whose +// valueOf answers differently each time, a Number object, a bigint) becomes +// undefined, so the copy is plain data and no caller code runs on it. +let readEntries = (values, entry) => { + let length = Array.length(values) + // isInt alone would convert a bigint or symbol (and throw), so look at the type first. + let size = + Type.typeof(length) == #number && isInt(length) && length >= 0 + ? Math.Int.min(length, maxEntries + 1) + : 1 + Array.fromInitializer(~length=size, i => entry(values->get(i))) +} + +let primitive = (x: int): int => Type.typeof(x) == #number ? x : Obj.magic(undefined) +let readArray = values => readEntries(values, primitive) let snapshot = (cube: cube): cube => { let {cornerPieces, cornerTwists, midgePieces, midgeFlips, wings, centers, rotations, frame} = cube @@ -404,10 +425,10 @@ let snapshot = (cube: cube): cube => { cornerTwists: readArray(cornerTwists), midgePieces: readArray(midgePieces), midgeFlips: readArray(midgeFlips), - wings: readArray(wings)->Array.map(readArray), - centers: readArray(centers)->Array.map(readArray), + wings: readEntries(wings, readArray), + centers: readEntries(centers, readArray), rotations: readArray(rotations), - frame, + frame: primitive(frame), } } diff --git a/test/cube/notation/orbit64V2.test.ts b/test/cube/notation/orbit64V2.test.ts index 11b0d45..db9a774 100644 --- a/test/cube/notation/orbit64V2.test.ts +++ b/test/cube/notation/orbit64V2.test.ts @@ -730,4 +730,82 @@ describe('Orbit64 V2 hardening', () => { reads = 0 expect(encodeOrbit64V2State(2, arrays as Orbit64V2Cube).TAG).toBe('Ok') }) + + it('keeps only primitive numbers and runs no caller code on them', () => { + const solved = orbit64V2Solved(3, 'Color') + const ran: string[] = [] + const trick = { + valueOf() { + ran.push('valueOf') + return 0 + }, + toString() { + ran.push('toString') + return '0' + }, + } + for (const bad of [trick, new Number(0), 0n, '0', null, Number.NaN, [0]]) { + const twists = [...solved.cornerTwists] + twists[0] = bad as number + const reason = (cube: unknown) => { + const result = encodeOrbit64V2State(3, cube as Orbit64V2Cube) + return result.TAG === 'Error' ? result._0 : 'accepted' + } + expect(reason({ ...solved, cornerTwists: twists }), typeof bad).toBe( + 'corner twists must be 8 values in 0..2', + ) + expect(reason({ ...solved, frame: bad }), typeof bad).toBe( + 'frame must be 0..23 on odd framed cubes and 0 otherwise', + ) + } + expect(ran).toEqual([]) + }) + + it('does bounded work for a hostile length and reads it once', () => { + const solved = orbit64V2Solved(3, 'Color') + const started = Date.now() + for (const length of [1e6, 2 ** 31, 2 ** 53, Number.MAX_VALUE, 65, 1e9]) { + const huge = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + return key === 'length' + ? length + : (Reflect.get(target, key, receiver) as unknown) + }, + }) + const result = encodeOrbit64V2State(3, { ...solved, cornerPieces: huge }) + expect(result, String(length)).toEqual({ + TAG: 'Error', + _0: 'corner pieces are not a permutation of 8', + }) + } + // A length that is no non-negative integer is not an array's length. + for (const length of ['8', 8n, Number.NaN, -1, 2.5, Symbol('8'), {}]) { + const odd = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + return key === 'length' + ? length + : (Reflect.get(target, key, receiver) as unknown) + }, + }) + expect( + encodeOrbit64V2State(3, { ...solved, cornerPieces: odd }), + String(typeof length), + ).toEqual({ + TAG: 'Error', + _0: 'corner pieces are not a permutation of 8', + }) + } + let reads = 0 + const shrinking = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + if (key === 'length') return ++reads === 1 ? 8 : 3 + return Reflect.get(target, key, receiver) as unknown + }, + }) + expect( + encodeOrbit64V2State(3, { ...solved, cornerPieces: shrinking }), + ).toEqual(encodeOrbit64V2State(3, solved)) + expect(reads).toBe(1) + expect(Date.now() - started).toBeLessThan(2000) + }) }) From 7dfca64be1278da01124ca05459c7919942f4d1f Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 17:41:20 +0000 Subject: [PATCH 2/6] feat(orbit64): read and write the V2 picture model in the facelet bridge The picture model writes one base64url symbol per facelet, the sticker's index within its class (corner 3p+j, midge and wing 2p+j, center orbit p, fixed center 4p+r), so a picture string is as long as a color string. The bridge now accepts the picture model for n = 3..15, canonical and framed, through the existing functions: toFacelets, fromFacelets, encodeFacelets and decodeFacelets. The color API is unchanged except that a picture type is no longer an error; the 2x2x2 has no picture entry and says so. Reading validates in the order of FORMAT-V2.md, with the reference's reasons: symbols before the count, then per class (corner, midge, wings outermost first, centers in orbit order, fixed centers) a range error before a repeat, then each slot's pieces, then the pose, then the laws. A fixed center's rotation is carried through whole-cube turns by the same geometry that turns the stickers. The string is now walked once and only its first 6 n^2 values are kept, so a huge string costs time but no memory, for both models; the first bad character is still reported before the length. Co-Authored-By: Claude Sonnet 5.5 --- src/cube/notation/Orbit64V2Facelets.res | 385 ++++++++++++++++--- src/cube/notation/Orbit64V2Facelets.resi | 20 +- src/cube/notation/orbit64V2Facelets.ts | 7 +- test/cube/notation/orbit64V2Facelets.test.ts | 67 ++-- 4 files changed, 376 insertions(+), 103 deletions(-) diff --git a/src/cube/notation/Orbit64V2Facelets.res b/src/cube/notation/Orbit64V2Facelets.res index 6dff081..0c41e03 100644 --- a/src/cube/notation/Orbit64V2Facelets.res +++ b/src/cube/notation/Orbit64V2Facelets.res @@ -1,11 +1,16 @@ // The facelet bridge of Orbit64 V2: a cube state to its stickers and back, for -// n x n x n cubes, n = 2..15, in the color model. -// Format: FORMAT-V2.md of flix-orbit64@d701008 (Apache-2.0); reference +// n x n x n cubes, n = 2..15, in the color model, and for n = 3..15 in the +// picture model. +// Format: FORMAT-V2.md of flix-orbit64@a046e34 (Apache-2.0); reference // implementation: src/Orbit64/V2/Facelets.flix there. // -// A facelet string writes the 6 n^2 stickers as the letters U R F D L B, one -// per facelet (index `face * n^2 + row * n + col`), six face fields in that -// order separated by one space. Reading ignores spaces. It does not reuse +// A facelet string writes the 6 n^2 stickers one per facelet (index +// `face * n^2 + row * n + col`), six face fields in the order U R F D L B +// separated by one space. The color model writes the letters U R F D L B. The +// picture model, where every sticker is distinct, writes one base64url symbol +// per facelet: the sticker's index within its class (corner `3 p + j`, midge +// and wing `2 p + j`, center orbit `p`, fixed center `4 p + r`, "Picture +// facelets" in FORMAT-V2.md). Reading ignores spaces. It does not reuse // `NotationOutput`, which also trims and upper-cases, accepts any white space // and answers with a warning instead of a reason. // @@ -316,15 +321,85 @@ let unrotate = (n, turn, fs) => fs->get(faceletAt(n, forward(turn, p), forward(turn, q))) }) +// -------------------------------------------------------- fixed center rotations + +let cross = ((ax, ay, az): vec, (bx, by, bz): vec): vec => ( + ay * bz - az * by, + az * bx - ax * bz, + ax * by - ay * bx, +) + +// The reference direction of a face: toward row 0, so toward B on U and F on D. +let reference = (face): vec => + switch face { + | 0 => (0, 0, -1) + | 3 => (0, 0, 1) + | _ => (0, 1, 0) + } + +// A direction on a face turned k clockwise quarter turns, seen from outside. +let rec spin = (normal: vec, direction: vec, k) => + k <= 0 ? direction : spin(normal, cross(direction, normal), k - 1) + +// The clockwise quarter turns from the reference direction of `face` to `direction`. +let quarterTurns = (face, direction: vec) => + range(4) + ->Array.find(k => spin(normalOf(face), reference(face), k) == direction) + ->Option.getOr(0) + +// Where the fixed center of `face` with rotation r lands under `move`, and its new rotation. +let carryFixed = (move: vec => vec, face, r) => { + let own = spin(normalOf(face), reference(face), r) + let landed = faceOfNormal(move(normalOf(face))) + (landed, quarterTurns(landed, move(own))) +} + +// `permuted` is `before` with its facelets moved; carries the fixed centers along. +let turnFixed = (n, move, before, permuted) => { + let out = Array.copy(permuted) + let fixed = fixedFacelets(n) + range(6)->Array.forEach(face => { + let value = before->get(fixed->get(face)) + let (landed, r) = carryFixed(move, face, mod(value, 4)) + out->Array.setUnsafe(fixed->get(landed), 4 * (value / 4) + r) + }) + out +} + +// The picture of a cube turned by `turn`: stickers and fixed center rotations. +let rotatePicture = (n, turn, vs) => turnFixed(n, v => forward(turn, v), vs, rotate(n, turn, vs)) + +// The picture of a cube turned back by `turn`. +let unrotatePicture = (n, turn, vs) => + turnFixed(n, v => backward(turn, v), vs, unrotate(n, turn, vs)) + // ---------------------------------------------------------------- cube to stickers -let writeOriented = (n, sticker, table, pieces, twists) => +let symbols = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + +// The character of a sticker value: a letter in the color model, a base64url +// symbol in the picture model. +let symbolOf = (model: Orbit64V2.model, value) => + model == Picture ? symbols->String.charAt(value) : letters->get(value) + +// What sticker k of the piece in slot `piece` of `table` shows: in the color +// model the face color of its home facelet, in the picture model its index +// `m piece + k` within its class. +let stickerValue = (n, model: Orbit64V2.model, table, piece, k) => + model == Picture + ? Array.length(table->get(piece)) * piece + k + : faceOf(n, table->get(piece)->get(k)) + +let writeOriented = (n, model, sticker, table, pieces, twists) => table->Array.forEachWithIndex((slotFacelets, slot) => { let m = Array.length(slotFacelets) - let home = table->get(pieces->get(slot)) + let piece = pieces->get(slot) let twist = twists->get(slot) for k in 0 to m - 1 { - sticker->Array.setUnsafe(slotFacelets->get(k), faceOf(n, home->get(mod(k - twist + m, m)))) + sticker->Array.setUnsafe( + slotFacelets->get(k), + stickerValue(n, model, table, piece, mod(k - twist + m, m)), + ) } }) @@ -333,16 +408,22 @@ let writeCenters = (sticker, table: array, colors: array) => sticker->Array.setUnsafe(facelet, colors->get(slot)) ) +// A fixed center shows its face color, or in the picture model its piece and rotation. +let fixedValue = (model: Orbit64V2.model, cube: Orbit64V2.cube, face) => + model == Picture ? 4 * face + cube.rotations->get(face) : face + // The stickers of a canonical-pose cube; fixed centers are home. -let paint = (n, t, cube: Orbit64V2.cube) => { +let paint = (n, model, t, cube: Orbit64V2.cube) => { let sticker = Array.make(~length=6 * n * n, 0) - t.fixed->Array.forEachWithIndex((facelet, face) => sticker->Array.setUnsafe(facelet, face)) - writeOriented(n, sticker, t.corners, cube.cornerPieces, cube.cornerTwists) + t.fixed->Array.forEachWithIndex((facelet, face) => + sticker->Array.setUnsafe(facelet, fixedValue(model, cube, face)) + ) + writeOriented(n, model, sticker, t.corners, cube.cornerPieces, cube.cornerTwists) if odd(n) { - writeOriented(n, sticker, t.midges, cube.midgePieces, cube.midgeFlips) + writeOriented(n, model, sticker, t.midges, cube.midgePieces, cube.midgeFlips) } t.wings->Array.forEachWithIndex((table, i) => - writeOriented(n, sticker, table, cube.wings->get(i), Array.make(~length=24, 0)) + writeOriented(n, model, sticker, table, cube.wings->get(i), Array.make(~length=24, 0)) ) t.centers->Array.forEachWithIndex((table, i) => writeCenters(sticker, table, cube.centers->get(i)) @@ -350,15 +431,15 @@ let paint = (n, t, cube: Orbit64V2.cube) => { sticker } -// Why a type has no facelet letters, if it has none. +// Why a type has no facelet string, if it has none. let typeFault = (n, model: Orbit64V2.model, pose: Orbit64V2.pose) => if !isInt(n) { Some("the cube size is not an integer in 2..15") } else if !validSize(n) { Some(`no V2 type for the ${Int.toString(n)}x${Int.toString(n)}x${Int.toString(n)}`) - } else if model == Picture { - Some("the picture model has no letters") - } else if model != Color { + } else if model == Picture && n == 2 { + Some("the 2x2x2 has no picture entry: its picture model is the color model") + } else if model != Color && model != Picture { Some("unknown sticker model") } else if pose != Canonical && pose != Framed { Some("unknown pose") @@ -366,10 +447,10 @@ let typeFault = (n, model: Orbit64V2.model, pose: Orbit64V2.pose) => None } -let spell = (n, fs) => +let spell = (n, model, fs) => range(6) ->Array.map(face => - range(n * n)->Array.map(i => letters->get(fs->get(face * n * n + i)))->Array.join("") + range(n * n)->Array.map(i => symbolOf(model, fs->get(face * n * n + i)))->Array.join("") ) ->Array.join(" ") @@ -383,8 +464,15 @@ let toFacelets = (n, model, pose, cube) => switch Orbit64V2.faultOf(n, model, pose, cube) { | Some(why) => Error(why) | None => - let fs = paint(n, tables(n), cube) - Ok(spell(n, odd(n) ? rotate(n, turnOf(cube.frame), fs) : fs)) + let fs = paint(n, model, tables(n), cube) + let turn = turnOf(cube.frame) + Ok( + spell( + n, + model, + !odd(n) ? fs : model == Picture ? rotatePicture(n, turn, fs) : rotate(n, turn, fs), + ), + ) } } } catch { @@ -393,14 +481,32 @@ let toFacelets = (n, model, pose, cube) => // ---------------------------------------------------------------- stickers to cube -@val external codePoints: string => array = "Array.from" +// The face index of a facelet letter, by code point; -1 for any other. +let colorIndex = code => + switch code { + | 85 => 0 + | 82 => 1 + | 70 => 2 + | 68 => 3 + | 76 => 4 + | 66 => 5 + | _ => -1 + } -// The face index of a facelet letter (a whole code point is quoted, where the -// reference quotes a UTF-16 unit); a tab or newline is a bad letter like any other. -let letterIndex = letter => - switch faceIndex(letter) { - | -1 => Error(`facelet '${letter}' is not one of U R F D L B`) - | index => Ok(index) +// The value of a base64url symbol, by code point; -1 for any other. +let symbolIndex = code => + if code >= 65 && code <= 90 { + code - 65 + } else if code >= 97 && code <= 122 { + code - 71 + } else if code >= 48 && code <= 57 { + code + 4 + } else if code == 45 { + 62 + } else if code == 95 { + 63 + } else { + -1 } let rec traverse = (items, f, index, out) => @@ -417,23 +523,45 @@ let rec traverse = (items, f, index, out) => let traverseAll = (items, f) => traverse(items, f, 0, []) -// The stickers of a facelet string as face indices, or why it is no string of n^2 per face. -let parse = (n, text) => - codePoints(text) - ->Array.filter(c => c != " ") - ->traverseAll((letter, _) => letterIndex(letter)) - ->Result.flatMap(indices => { - let length = Array.length(indices) - if length != 6 * n * n { - Error( - `expected ${Int.toString(6 * n * n)} facelets for the ${Int.toString(n)}x${Int.toString( - n, - )}x${Int.toString(n)}, got ${Int.toString(length)}`, - ) - } else { - Ok(indices) +// The values of a facelet string, spaces ignored, or why it is no string of n^2 +// per face. A whole code point is quoted where the reference quotes a UTF-16 +// unit; a tab or newline is a bad character like any other. The string is +// walked once and only the first 6 n^2 values are kept, so a huge string costs +// time but no memory: its first bad character is reported before its length. +let parse = (n, text, indexOf, bad) => { + let expected = 6 * n * n + let values = [] + let count = ref(0) + let at = ref(0) + let fault = ref(None) + let size = String.length(text) + while Option.isNone(fault.contents) && at.contents < size { + let code = String.codePointAt(text, at.contents)->Option.getOr(32) + // A code point outside the BMP is no symbol, so the walk ends there. + at := at.contents + 1 + if code != 32 { + switch indexOf(code) { + | -1 => fault := Some(bad(String.fromCodePoint(code))) + | index => + if count.contents < expected { + values->Array.push(index) + } + count := count.contents + 1 + } } - }) + } + switch fault.contents { + | Some(why) => Error(why) + | None => + count.contents == expected + ? Ok(values) + : Error( + `expected ${Int.toString(expected)} facelets for the ${Int.toString(n)}x${Int.toString( + n, + )}x${Int.toString(n)}, got ${Int.toString(count.contents)}`, + ) + } +} let checkCounts = (n, fs) => switch range(6)->Array.find(f => fs->Array.filter(x => x == f)->Array.length != n * n) { @@ -464,21 +592,24 @@ let readPiece = (n, table, seen) => { ->Option.flatMap(found => found) } -let noPiece = (what, slot, seen) => { - let shown = seen->Array.map(f => letters->get(f))->Array.join("") +let noPiece = (model, what, slot, seen) => { + let shown = seen->Array.map(v => symbolOf(model, v))->Array.join("") `${what} slot ${Int.toString(slot)} shows ${shown}, which is no ${what} of this cube` } +let pieces = found => found->Array.map(((p, _)) => p) +let twists = found => found->Array.map(((_, o)) => o) + let readOriented = (n, what, table, fs) => range(Array.length(table)) ->traverseAll((slot, _) => { let seen = seenAt(table, slot, fs) switch readPiece(n, table, seen) { | Some(found) => Ok(found) - | None => Error(noPiece(what, slot, seen)) + | None => Error(noPiece(Color, what, slot, seen)) } }) - ->Result.map(found => (found->Array.map(((p, _)) => p), found->Array.map(((_, o)) => o))) + ->Result.map(found => (pieces(found), twists(found))) // A wing carries no twist: its two stickers sit in the order of its home slot or not at all. let readWingsStrict = (n, table, fs) => { @@ -487,7 +618,7 @@ let readWingsStrict = (n, table, fs) => { range(Array.length(table))->traverseAll((slot, _) => { let seen = seenAt(table, slot, fs) switch homes->Array.findIndex(home => home == seen) { - | -1 => Error(noPiece("wing", slot, seen)) + | -1 => Error(noPiece(Color, "wing", slot, seen)) | p => Ok(p) } }) @@ -514,20 +645,26 @@ let readCube = (n, t, frame, fs): result => ) ) -// Rotates an odd cube so that its fixed centers are home; the frame is the one they show. -let normalizeOdd = (n, t, fs) => { - let shown = t.fixed->Array.map(at => fs->get(at)) +// The frame whose faces, in slot order, are `shown`. +let frameShown = shown => switch range(24)->Array.find(r => facesOfFrame(r) == shown) { - | Some(r) => Ok((r, unrotate(n, turnOf(r), fs))) + | Some(r) => Ok(r) | None => Error("the six fixed centers are not a right-handed whole-cube frame") } -} -// Rotates an even cube so that its DBL corner is in slot DBL with twist 0. -let normalizeEven = (n, t, fs) => +// Rotates an odd cube so that its fixed centers are home; the frame is the one they show. +let normalizeOdd = (n, t, fs) => + frameShown(t.fixed->Array.map(at => fs->get(at)))->Result.map(r => ( + r, + unrotate(n, turnOf(r), fs), + )) + +// Rotates an even cube so that its DBL corner is in slot DBL with twist 0; +// `atHome` says whether a rotated string has it there. +let normalizeEven = (n, fs, atHome) => switch range(24)->Array.findMap(r => { let rotated = unrotate(n, turnOf(r), fs) - readPiece(n, t.corners, seenAt(t.corners, 6, rotated)) == Some((6, 0)) ? Some(rotated) : None + atHome(rotated) ? Some(rotated) : None }) { | Some(rotated) => Ok(rotated) | None => Error("no whole-cube rotation puts the DBL corner home") @@ -538,24 +675,148 @@ let posedStickers = (n, pose: Orbit64V2.pose, t, fs) => if odd(n) { normalizeOdd(n, t, fs)->Result.map(((r, posed)) => (pose == Framed ? r : 0, posed)) } else if pose == Canonical { - normalizeEven(n, t, fs)->Result.map(posed => (0, posed)) + normalizeEven(n, fs, rotated => + readPiece(n, t.corners, seenAt(t.corners, 6, rotated)) == Some((6, 0)) + )->Result.map(posed => (0, posed)) } else { Ok((0, fs)) } -let readStickers = (n, model, pose, text) => { +let readColorStickers = (n, pose, text) => { let t = tables(n) - parse(n, text) + parse(n, text, colorIndex, letter => `facelet '${letter}' is not one of U R F D L B`) ->Result.flatMap(fs => checkCounts(n, fs)->Result.map(() => fs)) ->Result.flatMap(fs => posedStickers(n, pose, t, fs)) ->Result.flatMap(((frame, posed)) => readCube(n, t, frame, posed)) - ->Result.flatMap(cube => +} + +// ------------------------------------------------------------ picture to cube + +// The stickers of one piece type: where they sit, how many values they can +// show, and `per`, how many values name the same piece. +type group = {what: string, facelets: array, limit: int, per: int} + +// Every sticker class of a cube, in the order the reference checks them: a +// sticker never leaves its class. +let groups = t => + [ + [{what: "corner", facelets: Array.flat(t.corners), limit: 24, per: 1}], + Array.length(t.midges) == 0 + ? [] + : [{what: "midge", facelets: Array.flat(t.midges), limit: 24, per: 1}], + t.wings->Array.map(table => {what: "wing", facelets: Array.flat(table), limit: 48, per: 1}), + t.centers->Array.map(table => {what: "center", facelets: table, limit: 24, per: 1}), + Array.length(t.fixed) == 0 + ? [] + : [{what: "fixed center", facelets: t.fixed, limit: 24, per: 4}], + ]->Array.flat + +// A sticker on a facelet of the wrong class, or a piece shown twice; a range +// error comes before a repeat. +let checkGroup = (vs, g) => + switch g.facelets->Array.find(at => vs->get(at) >= g.limit) { + | Some(at) => + Error( + `facelet ${Int.toString(at)} shows '${symbolOf( + Picture, + vs->get(at), + )}', which is no ${g.what} sticker`, + ) + | None => + let values = g.facelets->Array.map(at => vs->get(at)) + let uses = Array.make(~length=g.limit, 0) + values->Array.forEach(v => uses->Array.setUnsafe(v / g.per, uses->get(v / g.per) + 1)) + switch values->Array.find(v => uses->get(v / g.per) > 1) { + | Some(v) => Error(`${g.what} sticker '${symbolOf(Picture, v)}' occurs more than once`) + | None => Ok() + } + } + +// The piece and twist spelled by the m symbols of a slot: `m p + (k - o) mod m`. +let readPictureSlot = seen => { + let m = Array.length(seen) + let first = seen->get(0) + let p = first / m + let o = mod(m - mod(first, m), m) + range(m)->Array.every(k => seen->get(k) == m * p + mod(k - o + m, m)) ? Some((p, o)) : None +} + +// The pieces and twists of a table of slots; a `strict` slot admits no twist. +let readPictureOriented = (what, table, strict, vs) => + range(Array.length(table)) + ->traverseAll((slot, _) => { + let seen = seenAt(table, slot, vs) + switch readPictureSlot(seen) { + | Some((p, o)) if !strict || o == 0 => Ok((p, o)) + | _ => Error(noPiece(Picture, what, slot, seen)) + } + }) + ->Result.map(found => (pieces(found), twists(found))) + +// The coordinates of a canonical-pose picture; the frame is `frame`. +let readPictureCube = (n, t, frame, vs): result => + readPictureOriented("corner", t.corners, false, vs)->Result.flatMap(((cp, co)) => + (odd(n) ? readPictureOriented("midge", t.midges, false, vs) : Ok(([], [])))->Result.flatMap((( + mp, + mo, + )) => + t.wings + ->traverseAll( + (table, _) => readPictureOriented("wing", table, true, vs)->Result.map(((p, _)) => p), + ) + ->Result.map( + (wings): Orbit64V2.cube => { + cornerPieces: cp, + cornerTwists: co, + midgePieces: mp, + midgeFlips: mo, + wings, + centers: t.centers->Array.map(table => table->Array.map(at => vs->get(at))), + rotations: t.fixed->Array.map(at => mod(vs->get(at), 4)), + frame, + }, + ) + ) + ) + +// The frame to record and the symbols in the pose the type reads them in. +let posedPicture = (n, pose: Orbit64V2.pose, t, vs) => + if odd(n) { + frameShown(t.fixed->Array.map(at => vs->get(at) / 4))->Result.map(r => ( + pose == Framed ? r : 0, + unrotatePicture(n, turnOf(r), vs), + )) + } else if pose == Canonical { + normalizeEven(n, vs, rotated => + readPictureSlot(seenAt(t.corners, 6, rotated)) == Some((6, 0)) + )->Result.map(posed => (0, posed)) + } else { + Ok((0, vs)) + } + +// Every sticker is of its class and every slot spells one piece, in any pose. +let checkPieces = (n, t, vs) => + groups(t) + ->traverseAll((g, _) => checkGroup(vs, g)) + ->Result.flatMap(_ => readPictureCube(n, t, 0, vs)) + +let readPictureStickers = (n, pose, text) => { + let t = tables(n) + parse(n, text, symbolIndex, letter => `facelet '${letter}' is not a base64url symbol`) + ->Result.flatMap(vs => checkPieces(n, t, vs)->Result.map(_ => vs)) + ->Result.flatMap(vs => posedPicture(n, pose, t, vs)) + ->Result.flatMap(((frame, posed)) => readPictureCube(n, t, frame, posed)) +} + +let readStickers = (n, model: Orbit64V2.model, pose, text) => + ( + model == Picture ? readPictureStickers(n, pose, text) : readColorStickers(n, pose, text) + )->Result.flatMap(cube => switch Orbit64V2.faultOf(n, model, pose, cube) { | Some(why) => Error(why) | None => Ok(cube) } ) -} @genType let fromFacelets = (n, model, pose, facelets) => diff --git a/src/cube/notation/Orbit64V2Facelets.resi b/src/cube/notation/Orbit64V2Facelets.resi index af06c85..fca36a4 100644 --- a/src/cube/notation/Orbit64V2Facelets.resi +++ b/src/cube/notation/Orbit64V2Facelets.resi @@ -23,18 +23,22 @@ type slots = { // The facelet string of a cube state of the type (n, model, pose): six face // fields in the order U R F D L B, a space between fields. A canonical cube is // written in its reference pose; a framed cube of an odd size is that picture -// rotated by its frame. Only the color model has letters: a picture type is an -// Error, as is a cube that is no state of the type. +// rotated by its frame. The color model writes the letters U R F D L B; the +// picture model (n = 3..15) writes one base64url symbol per facelet, the +// sticker's index within its class, with the rotation of a fixed center in the +// symbol of an odd cube. A cube that is no state of the type is an Error. @genType let toFacelets: (int, Orbit64V2.model, Orbit64V2.pose, Orbit64V2.cube) => result // The cube state a facelet string shows, in the pose of the type. Spaces are -// ignored; every other character must be one of U R F D L B. A canonical read -// turns the cube so that its reference is home first (the fixed centers of an -// odd cube, the DBL corner of an even one) and so accepts any whole-cube -// orientation. A framed read of an odd cube keeps the orientation as -// `frame`; of an even cube, in its corners. Anything that is no reachable cube -// is an Error with its reason, never an exception. +// ignored; every other character must be one of U R F D L B (color model) or a +// base64url symbol (picture model). A canonical read turns the cube so that its +// reference is home first (the fixed centers of an odd cube, the DBL corner of +// an even one) and so accepts any whole-cube orientation. A framed read of an +// odd cube keeps the orientation as `frame`; of an even cube, in its corners. +// Anything that is no reachable cube is an Error with its reason, never an +// exception. The checks run in the order of FORMAT-V2.md, so the reason is the +// reference's. @genType let fromFacelets: (int, Orbit64V2.model, Orbit64V2.pose, string) => result diff --git a/src/cube/notation/orbit64V2Facelets.ts b/src/cube/notation/orbit64V2Facelets.ts index b81871a..f6c5850 100644 --- a/src/cube/notation/orbit64V2Facelets.ts +++ b/src/cube/notation/orbit64V2Facelets.ts @@ -1,7 +1,8 @@ // Orbit64 V2 facelet bridge for 2x2-15x15 cubes: typed entry point for the -// ReScript module (Orbit64V2Facelets.res). The color model only; a picture -// type is an error. The generated functions are used as they are. -// Format: FORMAT-V2.md of flix-orbit64@d701008 (Apache-2.0). +// ReScript module (Orbit64V2Facelets.res): the color model for 2x2x2-15x15x15 +// and the picture model for 3x3x3-15x15x15. The generated functions are used +// as they are. +// Format: FORMAT-V2.md of flix-orbit64@a046e34 (Apache-2.0). export type { decodedFacelets as Orbit64V2DecodedFacelets, slots as Orbit64V2Slots, diff --git a/test/cube/notation/orbit64V2Facelets.test.ts b/test/cube/notation/orbit64V2Facelets.test.ts index 721cdd0..d53ed17 100644 --- a/test/cube/notation/orbit64V2Facelets.test.ts +++ b/test/cube/notation/orbit64V2Facelets.test.ts @@ -3,6 +3,7 @@ import { decodeOrbit64V2State, encodeOrbit64V2State, type Orbit64V2Cube, + type Orbit64V2Model, type Orbit64V2Pose, orbit64V2Entries, orbit64V2Entry, @@ -367,6 +368,8 @@ describe('Orbit64 V2 facelet reference rows', () => { }) const sizeFault = 'the cube size is not an integer in 2..15' +const noPicture2 = + 'the 2x2x2 has no picture entry: its picture model is the color model' describe('Orbit64 V2 facelet bridge rejects an invalid pose', () => { const solved3 = solvedFacelets(3) @@ -433,8 +436,16 @@ describe('Orbit64 V2 facelet bridge rejects an invalid pose', () => { expect(error(orbit64V2FromFacelets(16, 'Color', bogus, solved3))).toBe( 'no V2 type for the 16x16x16', ) + expect(error(orbit64V2FromFacelets(2, 'Picture', bogus, solved3))).toBe( + noPicture2, + ) + expect( + error( + orbit64V2FromFacelets(3, 'Bogus' as Orbit64V2Model, bogus, solved3), + ), + ).toBe('unknown sticker model') expect(error(orbit64V2FromFacelets(3, 'Picture', bogus, solved3))).toBe( - 'the picture model has no letters', + 'unknown pose', ) }) }) @@ -665,9 +676,9 @@ describe('Orbit64 V2 facelet bridge rejects invalid input', () => { ) }) - it('rejects types without letters', () => { - expect(error(orbit64V2FromFacelets(3, 'Picture', 'Framed', solved3))).toBe( - 'the picture model has no letters', + it('rejects types without a facelet string', () => { + expect(error(orbit64V2FromFacelets(2, 'Picture', 'Framed', solved3))).toBe( + noPicture2, ) for (const n of [16, 1, 0, -3]) { expect( @@ -685,27 +696,22 @@ describe('Orbit64 V2 facelet bridge rejects invalid input', () => { 'no V2 type for the 16x16x16', ) const cube = orbit64V2Solved(3, 'Picture') - expect(error(orbit64V2ToFacelets(3, 'Picture', 'Framed', cube))).toBe( - 'the picture model has no letters', - ) + expect( + error( + orbit64V2ToFacelets( + 2, + 'Picture', + 'Framed', + orbit64V2Solved(2, 'Color'), + ), + ), + ).toBe(noPicture2) expect(error(orbit64V2ToFacelets(16, 'Color', 'Framed', cube))).toBe( 'no V2 type for the 16x16x16', ) }) it('says why an entry or token has no facelets', () => { - const picture = orbit64V2Entries.filter((e) => e.model === 'Picture') - expect(picture.length).toBeGreaterThan(0) - for (const e of picture) { - expect( - error(encodeOrbit64V2Facelets(e.id, solvedFacelets(e.n))), - `${e.id}`, - ).toBe('the picture model has no letters') - expect( - error(decodeOrbit64V2Facelets(tokenOfSolved(e))), - `token of ${e.id}`, - ).toBe('the picture model has no letters') - } expect(error(encodeOrbit64V2Facelets(54, solved3))).toBe( 'unknown registry entry 54', ) @@ -812,6 +818,18 @@ describe('Orbit64 V2 facelet bridge rejects invalid input', () => { expect(read(huge)).toBe('expected 54 facelets for the 3x3x3, got 100000') }) + it('walks a huge string once and reports its first bad letter before its length', () => { + const started = Date.now() + const huge = 'U'.repeat(3_000_000) + expect(read(huge)).toBe('expected 54 facelets for the 3x3x3, got 3000000') + expect(read(' '.repeat(3_000_000))).toBe( + 'expected 54 facelets for the 3x3x3, got 0', + ) + expect(read(`${huge}x`)).toBe("facelet 'x' is not one of U R F D L B") + expect(read(`${solved3}${' '.repeat(2_000_000)}`)).toBe('accepted') + expect(Date.now() - started).toBeLessThan(5000) + }) + it('names an unknown entry without echoing caller text or running caller code', () => { const notInteger = 'the registry entry is not an integer' const ran: string[] = [] @@ -858,14 +876,3 @@ describe('Orbit64 V2 facelet bridge rejects invalid input', () => { ) }) }) - -// The token of the solved state of an entry: its offset written at its length. -function tokenOfSolved(e: { offset: bigint; length: number }): string { - const alphabet = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' - let token = '' - for (let v = e.offset, i = 0; i < e.length; i++, v /= 64n) { - token = alphabet[Number(v % 64n)] + token - } - return token -} From a5fcbd31b1b59a33dd07d60cae9d43a65ab9cea9 Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 17:41:20 +0000 Subject: [PATCH 3/6] test(orbit64): add the V2 picture facelet fixture and extend its generator The generator now also copies the 174 picture rows of TestV2FaceletPictureData.flix (generated in flix-orbit64 by facelet_picture_vectors.jl) into orbit64V2FaceletPictures.json. It fails on dropped, duplicated and unparsed rows, on a picture entry other than the two of a size, on a character outside the base64url alphabet and on tokens of unequal length, and pins the 174 rows and the move sequences per size. Both files regenerate byte for byte; the color file changes only its source revision. Co-Authored-By: Claude Sonnet 5.5 --- scripts/orbit64V2FaceletFixture.jl | 100 +- .../notation/orbit64V2FaceletPictures.json | 1226 +++++++++++++++++ test/cube/notation/orbit64V2Facelets.json | 2 +- 3 files changed, 1318 insertions(+), 10 deletions(-) create mode 100644 test/cube/notation/orbit64V2FaceletPictures.json diff --git a/scripts/orbit64V2FaceletFixture.jl b/scripts/orbit64V2FaceletFixture.jl index 1035a2c..356f92c 100644 --- a/scripts/orbit64V2FaceletFixture.jl +++ b/scripts/orbit64V2FaceletFixture.jl @@ -1,15 +1,20 @@ #!/usr/bin/env julia # -# Writes test/cube/notation/orbit64V2Facelets.json from the Orbit64 V2 +# Writes test/cube/notation/orbit64V2Facelets.json and +# test/cube/notation/orbit64V2FaceletPictures.json from the Orbit64 V2 # reference repository (flix-orbit64, Apache-2.0). # # julia scripts/orbit64V2FaceletFixture.jl # -# It copies the slot tables of FORMAT-V2-SLOTS.md (n = 2..15) and the rows of -# test/TestV2FaceletData.flix, which tools/format-v2/facelet_vectors.jl -# generates there. Nothing is computed here. The row count comes from the -# input. Needs only Julia's standard library; the output is deterministic, and -# written the way `biome format` leaves it, so regenerating it changes nothing. +# The first copies the slot tables of FORMAT-V2-SLOTS.md (n = 2..15) and the +# color rows of test/TestV2FaceletData.flix, which +# tools/format-v2/facelet_vectors.jl generates there. The second copies the +# picture rows of test/TestV2FaceletPictureData.flix, which +# tools/format-v2/facelet_picture_vectors.jl generates there (n = 3..15). Nothing +# is computed here. Both inputs are checked for dropped, duplicated and +# unparsed rows, and their row and move sequence counts are pinned below. Needs +# only Julia's standard library; the output is deterministic, and written the +# way `biome format` leaves it, so regenerating it changes nothing. root = length(ARGS) == 1 ? ARGS[1] : error("usage: julia scripts/orbit64V2FaceletFixture.jl ") read_text(path) = read(joinpath(root, path), String) @@ -135,6 +140,69 @@ length(rows) == EXPECTED_ROWS || error("TestV2FaceletData.flix: $(length(rows)) sequences_per_size = Dict(n => count(k -> k[1] == n, keys(rows_by_sequence)) for n in keys(ids_by_size)) sequences_per_size == EXPECTED_SEQUENCES || error("TestV2FaceletData.flix: move sequences per size $(sort(collect(sequences_per_size))), expected $(sort(collect(EXPECTED_SEQUENCES)))") +# ---- test/TestV2FaceletPictureData.flix ----------------------------------------- +# (4, "1R", "GGCEAQDCN ORBJFBWXL ...", +# "Jx4QRAuFTAAAE", +# ""), +# A row is three lines. The empty move text is the solved cube. + +const BASE64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" +picture_pattern = r"^\s*\((\d+), \"([^\"]*)\", \"([^\"]*)\",\n\s*\"([^\"]*)\",\n\s*\"([^\"]*)\"\),?[ \t]*$"m +picture_text = read_text("test/TestV2FaceletPictureData.flix") +picture_tuple_lines = length(collect(eachmatch(r"^\s*\(\d+,"m, picture_text))) +picture_rows = [Row(parse(Int, m.captures[1]), m.captures[2], m.captures[3], m.captures[4], m.captures[5]) + for m in eachmatch(picture_pattern, picture_text)] +isempty(picture_rows) && error("TestV2FaceletPictureData.flix: no rows") +length(picture_rows) == picture_tuple_lines || + error("TestV2FaceletPictureData.flix: $picture_tuple_lines tuple lines, $(length(picture_rows)) parsed as rows") +# The same structure as the color rows: a picture entry per pose, each move +# sequence of a size once for both. The picture entry ids of the registry are +# 4n - 8 (canonical) and 4n - 7 (framed) for n = 3..15; every size has exactly them. +picture_sequences = Dict{Tuple{Int,String},Vector{Int}}() +picture_ids = Dict{Int,Set{Int}}() +for r in picture_rows + cells = length(replace(r.facelets, " " => "")) + n = round(Int, sqrt(cells / 6)) + 6n^2 == cells || error("picture row of entry $(r.id): $cells facelets are no cube") + all(c -> c == ' ' || occursin(c, BASE64URL), r.facelets * r.canonical) || + error("picture row of entry $(r.id): a character outside the base64url alphabet") + all(c -> occursin(c, BASE64URL), r.token) && !isempty(r.token) || + error("picture row of entry $(r.id): token \"$(r.token)\" is not base64url") + r.canonical == "" || length(replace(r.canonical, " " => "")) == cells || + error("picture row of entry $(r.id): canonical facelets of another length") + r.id in (4n - 8, 4n - 7) || error("picture row of entry $(r.id) at size $n, expected $(4n - 8) or $(4n - 7)") + push!(get!(picture_sequences, (n, r.moves), Int[]), r.id) + push!(get!(picture_ids, n, Set{Int}()), r.id) +end +# A token has the length of its entry, so the rows of one entry agree on it. +token_lengths = Dict{Int,Set{Int}}() +for r in picture_rows + push!(get!(token_lengths, r.id, Set{Int}()), length(r.token)) +end +for (id, lengths) in token_lengths + length(lengths) == 1 || error("picture rows of entry $id have tokens of lengths $(sort(collect(lengths)))") +end +sort(collect(keys(picture_ids))) == collect(3:15) || + error("picture rows for sizes $(sort(collect(keys(picture_ids)))), expected 3:15") +for (n, ids) in picture_ids + ids == Set([4n - 8, 4n - 7]) || error("$(n)x$(n)x$(n): picture rows of entries $(sort(collect(ids))), expected $(4n - 8) and $(4n - 7)") +end +for ((n, moves), ids) in picture_sequences + sort(ids) == [4n - 8, 4n - 7] || + error("$(n)x$(n)x$(n): picture moves \"$moves\" have rows for entries $(sort(ids)), expected $(4n - 8) and $(4n - 7) once each") +end +# Pinned like the color counts: dropping both rows of a move sequence leaves the +# structure above intact. Change them together with the input. +const EXPECTED_PICTURE_SEQUENCES = Dict(3 => 41, 4 => 4, 5 => 5, 6 => 4, 7 => 5, 8 => 3, + 9 => 4, 10 => 3, 11 => 4, 12 => 3, 13 => 4, 14 => 3, 15 => 4) +const EXPECTED_PICTURE_ROWS = 2 * sum(values(EXPECTED_PICTURE_SEQUENCES)) +EXPECTED_PICTURE_ROWS == 174 || error("the pinned picture counts add up to $EXPECTED_PICTURE_ROWS rows, expected 174") +length(picture_rows) == EXPECTED_PICTURE_ROWS || + error("TestV2FaceletPictureData.flix: $(length(picture_rows)) rows, expected $EXPECTED_PICTURE_ROWS") +picture_per_size = Dict(n => count(k -> k[1] == n, keys(picture_sequences)) for n in keys(picture_ids)) +picture_per_size == EXPECTED_PICTURE_SEQUENCES || + error("TestV2FaceletPictureData.flix: move sequences per size $(sort(collect(picture_per_size))), expected $(sort(collect(EXPECTED_PICTURE_SEQUENCES)))") + # ---- JSON ---------------------------------------------------------------------- function quoted(text::AbstractString) @@ -196,6 +264,20 @@ $row_text } """ -target = joinpath(@__DIR__, "..", "test", "cube", "notation", "orbit64V2Facelets.json") -write(target, json) -println("wrote ", length(rows), " rows and the slot tables of ", length(slots), " sizes") +picture_json = """ +{ + "source": $(quoted("$source_name: test/TestV2FaceletPictureData.flix (generated there by tools/format-v2/facelet_picture_vectors.jl)")), + "generatedBy": "scripts/orbit64V2FaceletFixture.jl. Do not edit.", + "rowText": "id: registry entry; moves: layer turns (depth, face, suffix) and whole-cube turns x y z from the solved cube, empty for the solved cube; facelets: what the entry writes for the cube, one base64url symbol per facelet; token: its V2 token; canonical: the reference pose of that cube when the entry's facelets differ from it, else empty.", + "rowCount": $(length(picture_rows)), + "rows": [ +$(join([row_object(r) for r in picture_rows], ",\n")) + ] +} +""" + +notation = joinpath(@__DIR__, "..", "test", "cube", "notation") +write(joinpath(notation, "orbit64V2Facelets.json"), json) +write(joinpath(notation, "orbit64V2FaceletPictures.json"), picture_json) +println("wrote ", length(rows), " color rows and the slot tables of ", length(slots), " sizes, and ", + length(picture_rows), " picture rows") diff --git a/test/cube/notation/orbit64V2FaceletPictures.json b/test/cube/notation/orbit64V2FaceletPictures.json new file mode 100644 index 0000000..20de398 --- /dev/null +++ b/test/cube/notation/orbit64V2FaceletPictures.json @@ -0,0 +1,1226 @@ +{ + "source": "flix-orbit64@a046e34adcdc212a69ce3dcc24b2e47b7fabfbe5: test/TestV2FaceletPictureData.flix (generated there by tools/format-v2/facelet_picture_vectors.jl)", + "generatedBy": "scripts/orbit64V2FaceletFixture.jl. Do not edit.", + "rowText": "id: registry entry; moves: layer turns (depth, face, suffix) and whole-cube turns x y z from the solved cube, empty for the solved cube; facelets: what the entry writes for the cube, one base64url symbol per facelet; token: its V2 token; canonical: the reference pose of that cube when the entry's facelets differ from it, else empty.", + "rowCount": 174, + "rows": [ + { + "id": 4, + "moves": "", + "facelets": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT", + "token": "AAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 5, + "moves": "", + "facelets": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT", + "token": "AAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 4, + "moves": "1R", + "facelets": "GGCEAQDCN ORBJFBWXL EDMSIIRLV PKXMMWSOK HFFVQTUNQ AHIAUUJPT", + "token": "Jx4QRAuFTAAAE", + "canonical": "" + }, + { + "id": 5, + "moves": "1R", + "facelets": "GGCEAQDCN ORBJFBWXL EDMSIIRLV PKXMMWSOK HFFVQTUNQ AHIAUUJPT", + "token": "DqtGGYRR_IAABg", + "canonical": "" + }, + { + "id": 4, + "moves": "1R2", + "facelets": "GGMEAIDCV WJOXGRLBB EDXSIWRLK PKJMMASOA HFFVQTUNQ NHIQUUCPT", + "token": "Q1IYQnp3CgAAI", + "canonical": "" + }, + { + "id": 5, + "moves": "1R2", + "facelets": "GGMEAIDCV WJOXGRLBB EDXSIWRLK PKJMMASOA HFFVQTUNQ NHIQUUCPT", + "token": "GT7JGO3so8AADA", + "canonical": "" + }, + { + "id": 4, + "moves": "1R'", + "facelets": "GGXEAWDCK LXWBHJBRO EDJSIARLA PKCMMQSON HFFVQTUNQ VHIIUUMPT", + "token": "HeJAwOp9GQAAM", + "canonical": "" + }, + { + "id": 5, + "moves": "1R'", + "facelets": "GGXEAWDCK LXWBHJBRO EDJSIARLA PKCMMQSON HFFVQTUNQ VHIIUUMPT", + "token": "CzTYSFfu6WAAEg", + "canonical": "" + }, + { + "id": 4, + "moves": "2R", + "facelets": "GDJEIADLA BBLREXOJW EKCSMQRON PPMMWISHV HFFVQTUNQ KCIWCUXGT", + "token": "H-QQCnux5AAEM", + "canonical": "TGXUAWICK LXWBHJBRO GDJEIADLA EKCSMQRON UVHNRFQTF VHSIUMMPP" + }, + { + "id": 5, + "moves": "2R", + "facelets": "GDJEIADLA BBLREXOJW EKCSMQRON PPMMWISHV HFFVQTUNQ KCIWCUXGT", + "token": "C_WGA-5itYABkk", + "canonical": "" + }, + { + "id": 4, + "moves": "2R2", + "facelets": "GKJEMADOA BBLREXOJW EPCSWQRHN PGMMAISCV HFFVQTUNQ KLIWKUXDT", + "token": "SwdFiuohcwAII", + "canonical": "PGMMAISCV WJOXGRLBB TDXUIWILK GKJEMADOA QNUTSVFFH NHRQUSCPE" + }, + { + "id": 5, + "moves": "2R2", + "facelets": "GKJEMADOA BBLREXOJW EPCSWQRHN PGMMAISCV HFFVQTUNQ KLIWKUXDT", + "token": "HCK6FBfMiyADDI", + "canonical": "" + }, + { + "id": 4, + "moves": "2R'", + "facelets": "GPJEWADHA BBLREXOJW EGCSAQRCN PDMMIISLV HFFVQTUNQ KOIWOUXKT", + "token": "KyPLr8mUxAAME", + "canonical": "EGCSAQRCN ORBJFBWXL PDMMIISLV TKXUMWIOK FTQFTNHVU AHDAUEJPG" + }, + { + "id": 5, + "moves": "2R'", + "facelets": "GPJEWADHA BBLREXOJW EGCSAQRCN PDMMIISLV HFFVQTUNQ KOIWOUXKT", + "token": "EC1sYeuXyYAEhs", + "canonical": "" + }, + { + "id": 4, + "moves": "1U", + "facelets": "DEGCBGAAJ KHIREXOJW BBLSIQRLN PKMMMISOV EDCVQTUNQ HFFWUUXPT", + "token": "HCLn0WY_gAAAB", + "canonical": "" + }, + { + "id": 5, + "moves": "1U", + "facelets": "DEGCBGAAJ KHIREXOJW BBLSIQRLN PKMMMISOV EDCVQTUNQ HFFWUUXPT", + "token": "Co0W7oZX0AAAAY", + "canonical": "" + }, + { + "id": 4, + "moves": "1U2", + "facelets": "ACDACEJGG HFFREXOJW KHISIQRLN PKMMMISOV BBLVQTUNQ EDCWUUXPT", + "token": "FW_tkzp0AAAAC", + "canonical": "" + }, + { + "id": 5, + "moves": "1U2", + "facelets": "ACDACEJGG HFFREXOJW KHISIQRLN PKMMMISOV BBLVQTUNQ EDCWUUXPT", + "token": "CAn5FzXrgAAAAw", + "canonical": "" + }, + { + "id": 4, + "moves": "1U'", + "facelets": "JAAGDCGED EDCREXOJW HFFSIQRLN PKMMMISOV KHIVQTUNQ BBLWUUXPT", + "token": "CvEhQyGuwAAAD", + "canonical": "" + }, + { + "id": 5, + "moves": "1U'", + "facelets": "JAAGDCGED EDCREXOJW HFFSIQRLN PKMMMISOV KHIVQTUNQ BBLWUUXPT", + "token": "BBpseSyhiAAABI", + "canonical": "" + }, + { + "id": 4, + "moves": "2U", + "facelets": "GGJEAADCA BBLWUUOJW EDCREXRLN PKMMMISOV HFFSIQUNQ KHIVQTXPT", + "token": "CvVq2Qt0PAABD", + "canonical": "JAAGDCGED EDCREXRLN HFFSIQUNQ SMPONKVIM KHIVQTXPT BBLWUUOJW" + }, + { + "id": 5, + "moves": "2U", + "facelets": "GGJEAADCA BBLWUUOJW EDCREXRLN PKMMMISOV HFFSIQUNQ KHIVQTXPT", + "token": "BBwIEWRLloAAZJ", + "canonical": "" + }, + { + "id": 4, + "moves": "2U2", + "facelets": "GGJEAADCA BBLVQTOJW EDCWUURLN PKMMMISOV HFFREXUNQ KHISIQXPT", + "token": "FXeM1qEqgAACC", + "canonical": "ACDACEJGG HFFREXUNQ KHISIQXPT VOSIOMMKP BBLVQTOJW EDCWUURLN" + }, + { + "id": 5, + "moves": "2U2", + "facelets": "GGJEAADCA BBLVQTOJW EDCWUURLN PKMMMISOV HFFREXUNQ KHISIQXPT", + "token": "CAzU0Hxv8AAAwy", + "canonical": "" + }, + { + "id": 4, + "moves": "2U'", + "facelets": "GGJEAADCA BBLSIQOJW EDCVQTRLN PKMMMISOV HFFWUUUNQ KHIREXXPT", + "token": "HCt6_TnRCAADB", + "canonical": "DEGCBGAAJ KHIREXXPT BBLSIQOJW MIVKPOPMS EDCVQTRLN HFFWUUUNQ" + }, + { + "id": 5, + "moves": "2U'", + "facelets": "GGJEAADCA BBLSIQOJW EDCVQTRLN PKMMMISOV HFFWUUUNQ KHIREXXPT", + "token": "CpBOHvWuYwABIb", + "canonical": "" + }, + { + "id": 4, + "moves": "1F", + "facelets": "GGJEAAQTF DBLCEXAJW RSELJDNQC ORBMMISOV HFPVQKUNM KHIWUUXPT", + "token": "DwGjrF5niGRAQ", + "canonical": "" + }, + { + "id": 5, + "moves": "1F", + "facelets": "GGJEAAQTF DBLCEXAJW RSELJDNQC ORBMMISOV HFPVQKUNM KHIWUUXPT", + "token": "BaCdYKNm0yWYGA", + "canonical": "" + }, + { + "id": 4, + "moves": "1F2", + "facelets": "GGJEAAMKP QBLTEXFJW NLRQKSCDE ACDMMISOV HFOVQRUNB KHIWUUXPT", + "token": "NMzhh3p3WwAAg", + "canonical": "" + }, + { + "id": 5, + "moves": "1F2", + "facelets": "GGJEAAMKP QBLTEXFJW NLRQKSCDE ACDMMISOV HFOVQRUNB KHIWUUXPT", + "token": "E8zUks3swiAAMA", + "canonical": "" + }, + { + "id": 4, + "moves": "1F'", + "facelets": "GGJEAABRO MBLKEXPJW CQNDLLESR FTQMMISOV HFAVQCUND KHIWUUXPT", + "token": "JctrzzVUC2RAw", + "canonical": "" + }, + { + "id": 5, + "moves": "1F'", + "facelets": "GGJEAABRO MBLKEXPJW CQNDLLESR FTQMMISOV HFAVQCUND KHIWUUXPT", + "token": "DixIbbP_hEWYSA", + "canonical": "" + }, + { + "id": 4, + "moves": "2F", + "facelets": "GGJNRFDCA BELRBXOAW EDCSIQRLN PKMJFBSOV HMFVNTUIQ KHIWUUXPT", + "token": "JioTgiAg4vVAw", + "canonical": "LXWEAABRO MBVKEOPJS CQNDLLESR FTQMMIHVU JFAGQCGND XWKPVHTUI" + }, + { + "id": 5, + "moves": "2F", + "facelets": "GGJNRFDCA BELRBXOAW EDCSIQRLN PKMJFBSOV HMFVNTUIQ KHIWUUXPT", + "token": "Dk_HUMwMVRv4SQ", + "canonical": "" + }, + { + "id": 4, + "moves": "2F2", + "facelets": "GGJIOMDCA BNLRSXOFW EDCSIQRLN PKMACESOV HJFVGTUBQ KHIWUUXPT", + "token": "NY-4smrHrwAQg", + "canonical": "VOSEAAMKP QBUTEVFJH NLRQKSCDE ACDMMIJGG WFOXQRLNB TPXUWWIHK" + }, + { + "id": 5, + "moves": "2F2", + "facelets": "GGJIOMDCA BNLRSXOFW EDCSIQRLN PKMACESOV HJFVGTUBQ KHIWUUXPT", + "token": "FBXlQugK4aAGMK", + "canonical": "" + }, + { + "id": 4, + "moves": "2F'", + "facelets": "GGJBHJDCA BILRPXOMW EDCSIQRLN PKMFTNSOV HAFVDTUEQ KHIWUUXPT", + "token": "D67lxls3nfVQQ", + "canonical": "UVHEAAQTF DBGCEGAJJ RSELJDNQC ORBMMIWXL SFPOQKVNM IUTHXPKWX" + }, + { + "id": 5, + "moves": "2F'", + "facelets": "GGJBHJDCA BILRPXOMW EDCSIQRLN PKMFTNSOV HAFVDTUEQ KHIWUUXPT", + "token": "BeGWKmI02zv-GU", + "canonical": "" + }, + { + "id": 4, + "moves": "1L", + "facelets": "TGJUAAICA BBLREXOJW GDCEIQDLN EKMSMIROV UVHNRFQTF KHSWUMXPP", + "token": "Ako4SeAlDgAEA", + "canonical": "" + }, + { + "id": 5, + "moves": "1L", + "facelets": "TGJUAAICA BBLREXOJW GDCEIQDLN EKMSMIROV UVHNRFQTF KHSWUMXPP", + "token": "ANvVG7QN5UABgA", + "canonical": "" + }, + { + "id": 4, + "moves": "1L2", + "facelets": "PGJMAASCA BBLREXOJW TDCUIQILN GKMEMIDOV QNUTSVFFH KHRWUSXPE", + "token": "B7UtSHJ5AQAIA", + "canonical": "" + }, + { + "id": 5, + "moves": "1L2", + "facelets": "PGJMAASCA BBLREXOJW TDCUIQILN GKMEMIDOV QNUTSVFFH KHRWUSXPE", + "token": "AuPw-yrtYGADAA", + "canonical": "" + }, + { + "id": 4, + "moves": "1L'", + "facelets": "EGJSAARCA BBLREXOJW PDCMIQSLN TKMUMIIOV FTQFTNHVU KHDWUEXPG", + "token": "BWt-12HUTwAMA", + "canonical": "" + }, + { + "id": 5, + "moves": "1L'", + "facelets": "EGJSAARCA BBLREXOJW PDCMIQSLN TKMUMIIOV FTQFTNHVU KHDWUEXPG", + "token": "AghPkMSvnaAEgA", + "canonical": "" + }, + { + "id": 4, + "moves": "1D", + "facelets": "GGJEAADCA BBLREXRLN EDCSIQUNQ SMPONKVIM HFFVQTXPT KHIWUUOJW", + "token": "AARJlenFfAABA", + "canonical": "" + }, + { + "id": 5, + "moves": "1D", + "facelets": "GGJEAADCA BBLREXRLN EDCSIQUNQ SMPONKVIM HFFVQTXPT KHIWUUOJW", + "token": "AAGbmDeqDoAAYA", + "canonical": "" + }, + { + "id": 4, + "moves": "1D2", + "facelets": "GGJEAADCA BBLREXUNQ EDCSIQXPT VOSIOMMKP HFFVQTOJW KHIWUURLN", + "token": "AAefQ2a2gAACA", + "canonical": "" + }, + { + "id": 5, + "moves": "1D2", + "facelets": "GGJEAADCA BBLREXUNQ EDCSIQXPT VOSIOMMKP HFFVQTOJW KHIWUURLN", + "token": "AALbuUaEcAAAwA", + "canonical": "" + }, + { + "id": 4, + "moves": "1D'", + "facelets": "GGJEAADCA BBLREXXPT EDCSIQOJW MIVKPOPMS HFFVQTRLN KHIWUUUNQ", + "token": "AAiTK9ORiAADA", + "canonical": "" + }, + { + "id": 5, + "moves": "1D'", + "facelets": "GGJEAADCA BBLREXXPT EDCSIQOJW MIVKPOPMS HFFVQTRLN KHIWUUUNQ", + "token": "AAM3MG9WkwABIA", + "canonical": "" + }, + { + "id": 4, + "moves": "1B", + "facelets": "LXWEAADCA BBVREOOJS EDCSIQRLN PKMMMIHVU JFFGQTGNQ XWKPVHTUI", + "token": "AGsKgHHftJEAA", + "canonical": "" + }, + { + "id": 5, + "moves": "1B", + "facelets": "LXWEAADCA BBVREOOJS EDCSIQRLN PKMMMIHVU JFFGQTGNQ XWKPVHTUI", + "token": "ACgj8Cqz47ZgAA", + "canonical": "" + }, + { + "id": 4, + "moves": "1B2", + "facelets": "VOSEAADCA BBUREVOJH EDCSIQRLN PKMMMIJGG WFFXQTLNQ TPXUWWIHK", + "token": "AVOpK46AfAAQA", + "canonical": "" + }, + { + "id": 5, + "moves": "1B2", + "facelets": "VOSEAADCA BBUREVOJH EDCSIQRLN PKMMMIJGG WFFXQTLNQ TPXUWWIHK", + "token": "AH9fcFVwLoAGAA", + "canonical": "" + }, + { + "id": 4, + "moves": "1B'", + "facelets": "UVHEAADCA BBGREGOJJ EDCSIQRLN PKMMMIWXL SFFOQTVNQ IUTHXPKWX", + "token": "AOlITMVK9JEQA", + "canonical": "" + }, + { + "id": 5, + "moves": "1B'", + "facelets": "UVHEAADCA BBGREGOJJ EDCSIQRLN PKMMMIWXL SFFOQTVNQ IUTHXPKWX", + "token": "AFd7HMn8G7ZmAA", + "canonical": "" + }, + { + "id": 4, + "moves": "x", + "facelets": "EDCSIQRLN ORBJFBWXL PKMMMISOV TPXUWWIHK FTQFTNHVU ACDACEJGG", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "x", + "facelets": "EDCSIQRLN ORBJFBWXL PKMMMISOV TPXUWWIHK FTQFTNHVU ACDACEJGG", + "token": "AAAAAAAAAAAAAE", + "canonical": "" + }, + { + "id": 4, + "moves": "x2", + "facelets": "PKMMMISOV WJOXGRLBB TPXUWWIHK GGJEAADCA QNUTSVFFH NLRQKSCDE", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "x2", + "facelets": "PKMMMISOV WJOXGRLBB TPXUWWIHK GGJEAADCA QNUTSVFFH NLRQKSCDE", + "token": "AAAAAAAAAAAAAI", + "canonical": "" + }, + { + "id": 4, + "moves": "x'", + "facelets": "TPXUWWIHK LXWBHJBRO GGJEAADCA EDCSIQRLN UVHNRFQTF VOSIOMMKP", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "x'", + "facelets": "TPXUWWIHK LXWBHJBRO GGJEAADCA EDCSIQRLN UVHNRFQTF VOSIOMMKP", + "token": "AAAAAAAAAAAAAM", + "canonical": "" + }, + { + "id": 4, + "moves": "y", + "facelets": "DEGCBGAAJ KHIWUUXPT BBLREXOJW MIVKPOPMS EDCSIQRLN HFFVQTUNQ", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "y", + "facelets": "DEGCBGAAJ KHIWUUXPT BBLREXOJW MIVKPOPMS EDCSIQRLN HFFVQTUNQ", + "token": "AAAAAAAAAAAAAB", + "canonical": "" + }, + { + "id": 4, + "moves": "y2", + "facelets": "ACDACEJGG HFFVQTUNQ KHIWUUXPT VOSIOMMKP BBLREXOJW EDCSIQRLN", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "y2", + "facelets": "ACDACEJGG HFFVQTUNQ KHIWUUXPT VOSIOMMKP BBLREXOJW EDCSIQRLN", + "token": "AAAAAAAAAAAAAC", + "canonical": "" + }, + { + "id": 4, + "moves": "y'", + "facelets": "JAAGDCGED EDCSIQRLN HFFVQTUNQ SMPONKVIM KHIWUUXPT BBLREXOJW", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "y'", + "facelets": "JAAGDCGED EDCSIQRLN HFFVQTUNQ SMPONKVIM KHIWUUXPT BBLREXOJW", + "token": "AAAAAAAAAAAAAD", + "canonical": "" + }, + { + "id": 4, + "moves": "z", + "facelets": "UVHNRFQTF DEGCBGAAJ RSELJDNQC ORBJFBWXL SMPONKVIM IUTHXPKWX", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "z", + "facelets": "UVHNRFQTF DEGCBGAAJ RSELJDNQC ORBJFBWXL SMPONKVIM IUTHXPKWX", + "token": "AAAAAAAAAAAAAQ", + "canonical": "" + }, + { + "id": 4, + "moves": "z2", + "facelets": "VOSIOMMKP QNUTSVFFH NLRQKSCDE ACDACEJGG WJOXGRLBB TPXUWWIHK", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "z2", + "facelets": "VOSIOMMKP QNUTSVFFH NLRQKSCDE ACDACEJGG WJOXGRLBB TPXUWWIHK", + "token": "AAAAAAAAAAAAAK", + "canonical": "" + }, + { + "id": 4, + "moves": "z'", + "facelets": "LXWBHJBRO MIVKPOPMS CQNDLLESR FTQFTNHVU JAAGDCGED XWKPVHTUI", + "token": "AAAAAAAAAAAAA", + "canonical": "GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT" + }, + { + "id": 5, + "moves": "z'", + "facelets": "LXWBHJBRO MIVKPOPMS CQNDLLESR FTQFTNHVU JAAGDCGED XWKPVHTUI", + "token": "AAAAAAAAAAAAAU", + "canonical": "" + }, + { + "id": 4, + "moves": "2R 2U 2F' 2R2 x 2U'", + "facelets": "EGCRSXRCN OWBFBNWUL PDMAKESLV TKXTEVIOK FQQJNBHSU AHDMWIJPG", + "token": "RyRm0NOasvVUc", + "canonical": "BNLWAUOFW XVKKHOTTI MEVDJLPAS QBUQMSFJH CXNGRCERR JMAPXHGID" + }, + { + "id": 5, + "moves": "2R 2U 2F' 2R2 x 2U'", + "facelets": "EGCRSXRCN OWBFBNWUL PDMAKESLV TKXTEVIOK FQQJNBHSU AHDMWIJPG", + "token": "Gq2mjk9aAxv_qw", + "canonical": "" + }, + { + "id": 4, + "moves": "2F 2U' 1L2 1R z' 2D 3B 1L 1R2 3B2 1D' 2F2", + "facelets": "RESUWDLHV WCUTBWIPN JGXKRSCRH AQGMKOFBM PVKJMLENB TFQXEIOAD", + "token": "KYDBZAHszr7CV", + "canonical": "WCUTBWIPN OXTAFFDIQ GOMQJBAMF BNELOJKVP XSHGQRJKC SDVEVHRUL" + }, + { + "id": 5, + "moves": "2F 2U' 1L2 1R z' 2D 3B 1L 1R2 3B2 1D' 2F2", + "facelets": "RESUWDLHV WCUTBWIPN JGXKRSCRH AQGMKOFBM PVKJMLENB TFQXEIOAD", + "token": "D5BIhYC4zYeI4H", + "canonical": "" + }, + { + "id": 4, + "moves": "1L2 3R' 3D2 1L 2R2 2B 2L' 2B' 1L 1D 2F' 1L2", + "facelets": "JPRVWGVDE FHQRCXCLN WCDMTQSBB TAATJKIIM KUXFNNHSU POLWFEOJG", + "token": "JZxueKMEIovrK", + "canonical": "FHQRCXCLN OWPJGOGEL AKMAIITTI USHNPFXUK DQBCSBWMS RGEPVDJVV" + }, + { + "id": 5, + "moves": "1L2 3R' 3D2 1L 2R2 2B 2L' 2B' 1L 1D 2F' 1L2", + "facelets": "JPRVWGVDE FHQRCXCLN WCDMTQSBB TAATJKIIM KUXFNNHSU POLWFEOJG", + "token": "DhqpbT0hjPR4L_", + "canonical": "" + }, + { + "id": 4, + "moves": "1B 2D2 3U 3L 3B' 2L2 1L' 2L' 3B 3R 3B' 3L'", + "facelets": "ENCXKDJRW XCBGQTTUI KQVKAHPOS QPUEWVNAH FWLIGLMFR AMDSNJGBO", + "token": "JXtMtJODffKVG", + "canonical": "SOPHCKVQK LLRWFFFIM WRJDIXCNE AMDSNJGBO TGXURCITB QPUEWVNAH" + }, + { + "id": 5, + "moves": "1B 2D2 3U 3L 3B' 2L2 1L' 2L' 3B 3R 3B' 3L'", + "facelets": "ENCXKDJRW XCBGQTTUI KQVKAHPOS QPUEWVNAH FWLIGLMFR AMDSNJGBO", + "token": "Dg48w7dRTzr36W", + "canonical": "" + }, + { + "id": 8, + "moves": "", + "facelets": "GApJhABCECDJDRGA BIDLTEFKMGHtObOW EQHCjIJSUKLNRZWN PYXMnMNacOPPSveV HgFFrQRikSTVUdmQ KoBILUVqsWXlXfuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 9, + "moves": "", + "facelets": "GApJhABCECDJDRGA BIDLTEFKMGHtObOW EQHCjIJSUKLNRZWN PYXMnMNacOPPSveV HgFFrQRikSTVUdmQ KoBILUVqsWXlXfuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 8, + "moves": "2R 1L 2D' 1U2 2L2 1U' 2L 1U' z 2U2 1B' 1L2", + "facelets": "OoRRbKSkOPQHJNgH IGlQXVREYGOdArCU KMhGLJLWmHNZXtSC VsTBVBEqiXFDFvIT MaPLfWUKAAInEjUW PQpNFCTecMDBSJuD", + "token": "OMDui3NJOCa5sbW06K0Tr5YWtLEK5o", + "canonical": "CStXZNHmWLJLGhMK LKnWPUIUaWAjMfAE HgNJHQPOkSKbRRoO PQpNFCTecMDBSJuD AYXIrGVGCORlUdEQ VsTBVBEqiXFDFvIT" + }, + { + "id": 9, + "moves": "2R 1L 2D' 1U2 2L2 1U' 2L 1U' z 2U2 1B' 1L2", + "facelets": "OoRRbKSkOPQHJNgH IGlQXVREYGOdArCU KMhGLJLWmHNZXtSC VsTBVBEqiXFDFvIT MaPLfWUKAAInEjUW PQpNFCTecMDBSJuD", + "token": "DPqJr710GDPn1dpr4bBKscqOmMDLRFy", + "canonical": "" + }, + { + "id": 8, + "moves": "3L2 3F' y2 4B' 3U' 1F2 z' 2L 1L2 1R2 1L' 3B2", + "facelets": "CSBJfWVcmUGlEjGO MkdLpFSKYCQtPRgW FiHNFRJoCNLZSNWR TMXQbXIQqOKhIvUV AenDJPMEOHBDHraU KATBLAEIsTDPXVuG", + "token": "M6zMFxJfChMPR418wEpn4f46E3etH8", + "canonical": "GuVXPDTsIEALBTAK LKtWdSQgkFCRMpYP CSBJfWVcmUGlEjGO FiHNFRJoCNLZSNWR HOJArHPeaBMnUDED VUvIhKOqQIXbQXMT" + }, + { + "id": 9, + "moves": "3L2 3F' y2 4B' 3U' 1F2 z' 2L 1L2 1R2 1L' 3B2", + "facelets": "CSBJfWVcmUGlEjGO MkdLpFSKYCQtPRgW FiHNFRJoCNLZSNWR TMXQbXIQqOKhIvUV AenDJPMEOHBDHraU KATBLAEIsTDPXVuG", + "token": "FvoyyHSIB4I-XnOd0nV48vfyL6SY4sV", + "canonical": "" + }, + { + "id": 8, + "moves": "2B2 4B 1L2 3L 2B2 4R2 4B2 1U 2U y2 3L' 2L2", + "facelets": "JUdFfPSiALXNGTuQ RMjEZDIQkFQXUDgM HSvPrJWYsRMlXFoT VEpSnBOCaKEhBPIO KeBILAVqGGTtAbmW DcVLRCNKWUHHNJOC", + "token": "N0jDYzrRnib5Z8nFEB6U3csMM6-ICC", + "canonical": "QuTGNXLAiSPfFdUJ KeBILAVqGGTtAbmW DcVLRCNKWUHHNJOC OIPBhEKaCOBnSpEV RMjEZDIQkFQXUDgM HSvPrJWYsRMlXFoT" + }, + { + "id": 9, + "moves": "2B2 4B 1L2 3L 2B2 4R2 4B2 1U 2U y2 3L' 2L2", + "facelets": "JUdFfPSiALXNGTuQ RMjEZDIQkFQXUDgM HSvPrJWYsRMlXFoT VEpSnBOCaKEhBPIO KeBILAVqGGTtAbmW DcVLRCNKWUHHNJOC", + "token": "HZQcECtpnWMa5gef13xTd3Q_XitVaMR", + "canonical": "" + }, + { + "id": 12, + "moves": "", + "facelets": "GAGpJhAABCEBACAECDDJDRCGA BIBDLTEEFKRFEGXMGHHtObJOW EQDHCjIIJSSJIKQUKLLNRZLWN PYKXMnMMNaMNMOIcOPPPSvOeV HgFFFrQQRiVRQSTkSTTVUdNmQ KoHBILUUVqWVUWUsWXXlXfPuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 13, + "moves": "", + "facelets": "GAGpJhAABCEBACAECDDJDRCGA BIBDLTEEFKRFEGXMGHHtObJOW EQDHCjIIJSSJIKQUKLLNRZLWN PYKXMnMMNaMNMOIcOPPPSvOeV HgFFFrQQRiVRQSTkSTTVUdNmQ KoHBILUUVqWVUWUsWXXlXfPuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 12, + "moves": "3R 3U 3F' 3R2 x 3U'", + "facelets": "EQGHCjIAJSRFSGXUKDLNRZCWN OMWTBbGVEIFQBTNOHWFDWtUKL PYDXMnMINaACKBEcOLPPSvLeV TuKfXlXMWsTSERVqVPULIBOoK FiQVQFRKTmJHNEBgQJSdHrSkU AGHRDJDUCEMNWOICBXAhJpPAG", + "token": "md-_e1cSvp82hBDj6fvTZ8fFDHbaxh-jXKpkx2P5Foy3lXMRclUc", + "canonical": "BINDLTETFKWVAWUMGQHtObFOW XsVLKfWRUoKMHPOuXSVBTlTqI MaEPVXNBPeDIJLLYMCOvPnAcS QmBdUVTESkQKMJSiRHQrFFJgH CSXNNHJGLWGARDCQIFKZEjRUR JCMJApBNDGPXXUHAAOCRGhIED" + }, + { + "id": 13, + "moves": "3R 3U 3F' 3R2 x 3U'", + "facelets": "EQGHCjIAJSRFSGXUKDLNRZCWN OMWTBbGVEIFQBTNOHWFDWtUKL PYDXMnMINaACKBEcOLPPSvLeV TuKfXlXMWsTSERVqVPULIBOoK FiQVQFRKTmJHNEBgQJSdHrSkU AGHRDJDUCEMNWOICBXAhJpPAG", + "token": "ObPnzkCnB3u0cYZVd75vRurp5KySCkvdQr_lysV9aHTE2Asmit_qw", + "canonical": "" + }, + { + "id": 12, + "moves": "4U' 5D' 4L' 5U' x' z' 3B2 3R2 2B z 3F2 5F'", + "facelets": "NeDZRNLQKUUWWAWqVUBLFFFCH IKXVQBFGPmPXRTNoEFSdKTRkU DEEDGRCBJAKMPPOGDCTpAJAWJ BIBXLjIINSSJIOQsWEXlXhHuT OMVrEbARQQJHHLLOHSRHWtTiC PYCfMnMDGaMNCVIcOKUPSvGgV", + "token": "ojUDeQqTJPt_s3EJ3zNghkyH4xYU4gHwO4rOXBztFbsDt-_aZwxi", + "canonical": "PYCfMnMDGaMNCVIcOKUPSvGgV WOJbOtHHAMTSERViRLQrCHLQE TuHhXlXEWsQOKJSSNIIjLXBIB JWAJApTCDGOPNMKAJBCRGDEED QmNdUVPTSkXGQFRKFXETIBPoK NeDZRNLQKUUWWAWqVUBLFFFCH" + }, + { + "id": 13, + "moves": "4U' 5D' 4L' 5U' x' z' 3B2 3R2 2B z 3F2 5F'", + "facelets": "NeDZRNLQKUUWWAWqVUBLFFFCH IKXVQBFGPmPXRTNoEFSdKTRkU DEEDGRCBJAKMPPOGDCTpAJAWJ BIBXLjIINSSJIOQsWEXlXhHuT OMVrEbARQQJHHLLOHSRHWtTiC PYCfMnMDGaMNCVIcOKUPSvGgV", + "token": "PNPhTWP3Ld5P40pjs7NEMlyy9ShH1MC6FlQNYorY6CYhZPnx5qSk-", + "canonical": "" + }, + { + "id": 12, + "moves": "x2 5B2 1F 2L' 3R' 1L2 3F2 5F2 5B 3F' 1R' 3D2", + "facelets": "AGUZThWINuNTRQPEXLPfJpGAX VeOvSbGFEIRRWSLOHGFDHrWQP KoHBWLUUVaJNAKQkABCPEjSUG FiTVInIVBqBEHHXcKWDRMXDsR BgMFLTQJRKEXIOIMSCTlOdAmD UYVHCJMAJSKMPPFCODLNQtCWN", + "token": "HJ-FSj8_vkz7PCR490S9U30zBaZOIpMdcmm1qayQ02XH_CTPzF-_", + "canonical": "WaQPGBVKCUHUDBSoUNAjKLJkE FiTVInIVBqBEHHXcKWDRMXDsR LKIlDFROTmMJLCAgQXSdBTEMO CSFNNHJPLWVAODCYMMOtUJKCQ XAGpJfPLXEPQTTNuNIWhTZUGA HORbVrHRGeWGXFOQFSEvPDLIS" + }, + { + "id": 13, + "moves": "x2 5B2 1F 2L' 3R' 1L2 3F2 5F2 5B 3F' 1R' 3D2", + "facelets": "AGUZThWINuNTRQPEXLPfJpGAX VeOvSbGFEIRRWSLOHGFDHrWQP KoHBWLUUVaJNAKQkABCPEjSUG FiTVInIVBqBEHHXcKWDRMXDsR BgMFLTQJRKEXIOIMSCTlOdAmD UYVHCJMAJSKMPPFCODLNQtCWN", + "token": "CrvR-9e351zeNo2tXLnG_07zIh5dTPcrCuekH6C2T0Yq_o3N7KPn5", + "canonical": "" + }, + { + "id": 12, + "moves": "4D' 4D2 4L' z 4F2 2B' y' 5U' 2D 5D2 3U 2L", + "facelets": "QiNhUVNTAkTXSAVqJURLFnFCH IKUlTZKJQQCDBGGoSVWfVdIaM DmEDGvTNMYLSJQDGVCBpKFWsX LEXtWPUPOcBLHHJIDFGbBTRMO RUSrEBFWXuORNMKWLKHHAJAOJ SgMjPRCBIAHEXIPePOEXNNQSC", + "token": "lo-zLqPc9HlBRwqfXopDuF94cGNBPZI3I4U8buP3456163aevCkI", + "canonical": "TQGfMlQGWaUJAVIKKDSdIZCoV WcJbOtOHGMXPGFREULDTLPBIB GYDpXDMQBsENICWmTSVFDvLGK EuKHJrXMHOSWMKAUFRLJRBOWA UkVLHhAARCNTRUFiNXJnQVTqF NeHRSNPECgQOUBMSEIIjCXPAP" + }, + { + "id": 13, + "moves": "4D' 4D2 4L' z 4F2 2B' y' 5U' 2D 5D2 3U 2L", + "facelets": "QiNhUVNTAkTXSAVqJURLFnFCH IKUlTZKJQQCDBGGoSVWfVdIaM DmEDGvTNMYLSJQDGVCBpKFWsX LEXtWPUPOcBLHHJIDFGbBTRMO RUSrEBFWXuORNMKWLKHHAJAOJ SgMjPRCBIAHEXIPePOEXNNQSC", + "token": "OHXjMX1y2614eqP7w3PZZSPNKiU4dxbUrVH2qZV89VuEOEx7ho9jQ", + "canonical": "" + }, + { + "id": 16, + "moves": "", + "facelets": "GAAppJhAAABChBABBCECCDCJECDDDJDRRGGA BIIDDLTEEEFKTFEFFKMGGHGtMGHHHtObbOOW EQQHHCjIIIJSjJIJJSUKKLKNUKLLLNRZZWWN PYYXXMnMMMNanNMNNacOOPOPcOPPPPSvveeV HggFFFrQQQRirRQRRikSSTSVkSTTTVUddmmQ KooBBILUUUVqLVUVVqsWWXWlsWXXXlXffuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 17, + "moves": "", + "facelets": "GAAppJhAAABChBABBCECCDCJECDDDJDRRGGA BIIDDLTEEEFKTFEFFKMGGHGtMGHHHtObbOOW EQQHHCjIIIJSjJIJJSUKKLKNUKLLLNRZZWWN PYYXXMnMMMNanNMNNacOOPOPcOPPPPSvveeV HggFFFrQQQRirRQRRikSSTSVkSTTTVUddmmQ KooBBILUUUVqLVUVVqsWWXWlsWXXXlXffuuT", + "token": "BBkX7xzp6e9rQ-NQaRoflLByq8hiTigeorqhz2bf7c5vruX0gVARZz_Fh7A0zAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 16, + "moves": "5F2 6B2 5R x2 3D' 2B' 5L' 4B2 2D2 1U2 6L' x'", + "facelets": "NGWZRSLGUUCCrGQRNCSOJIOnSBIIAFXpfuAA BEmDDUtQQHXitRUGMiUKSLMNMDQHHrLddIaQ VoevBChUAAVshKABRsaWNMWVmWSCRNDfRGuK EeQHvJlPXXOclBXWBcKSFECJUTLLMbIXBoYP OKqTTWJSDDJgFTVTTgOEHKEbONVFFnHVLkkF TQYXHMjIGNEIjJDCJEMVOPFPqKPPLPRZpAWG", + "token": "ANjtbuQ97CLfuJp18fcvogzvZEEs_wIT4XMf5VGIwodSk9g723wQSa634Tr3Rq211oEWQl7GTe76qeOXTMD", + "canonical": "PYoBXIbMLLTUJCEFSKcBWXBlcOXXPlJvHQeE FkkLVHnFFVNObEKHEOgTTVTFgJDDSJWTTqKO KuGRfDNRCSWmVWMNWasRBAKhsVAAUhCBveoV AAufpXFAIIBSnOIJOSCNRQGrCCUUGLSRZWGN QaIddLrHHQDMNMLSKUiMGURtiXHQQtUDDmEB GWApZRPLPPKqPFPOVMEJCDJjIENGIjMHXYQT" + }, + { + "id": 17, + "moves": "5F2 6B2 5R x2 3D' 2B' 5L' 4B2 2D2 1U2 6L' x'", + "facelets": "NGWZRSLGUUCCrGQRNCSOJIOnSBIIAFXpfuAA BEmDDUtQQHXitRUGMiUKSLMNMDQHHrLddIaQ VoevBChUAAVshKABRsaWNMWVmWSCRNDfRGuK EeQHvJlPXXOclBXWBcKSFECJUTLLMbIXBoYP OKqTTWJSDDJgFTVTTgOEHKEbONVFFnHVLkkF TQYXHMjIGNEIjJDCJEMVOPFPqKPPLPRZpAWG", + "token": "D8dc5wtyIP4YG7TDTjOVI7-SHFtQlpZ_rhHUajR44gRMuK4pv4Iquklstxhx3FBlDCqy81ITAuQc9rD-oYr", + "canonical": "" + }, + { + "id": 16, + "moves": "1F2 2B 1L2 6F' 2L' y2 6F' 4U 4R 1F 5F2 3D'", + "facelets": "UksrrHDVMNNaJCWCJcCBRAKnOHWFLnMvBeeV WmmdbGtTTTGSNSTSXGYGGDPvWRQQERLFFgID NuAffXNXRXWsVMNPSMKIIKFZKFACCXCBRooK AAQppJdSKRUEPOHOUEaNLMUhgJDWDJQVliiF SCIDPOHBEEPMXLEFPUSVQUVLYAHHOLPhbOcB IqqtlTTQOGMGHIJBLWuAXVBTQIJDKZEjjkUR", + "token": "AkncktfYkfWgyd7qPZGRJnWhZ9IrfEPSu7ie2rY2fs49aa4vm1IO6UchB10CpLL3DNsifLji0IwIOlT4ayC", + "canonical": "GSGvRDbGXPEIdTSDQgmTTGQFmTSGRFWtNYWL JEEhJFpUUUDipROMWiQKHLDlASONJVAdPagQ XsMZXKfWSFCofXPKCoARNIARuXMIFBNNVKKC OMULLBPPPVOcDEFUHOIEEQHbCBLVAhSHXSYP HacnnVrNJKLerNCAFesMWRWBkVCBHvUDJCOM EQuHTIjIAIQqjJXJOqkDVBGtUKBLMlRZTWGT" + }, + { + "id": 17, + "moves": "1F2 2B 1L2 6F' 2L' y2 6F' 4U 4R 1F 5F2 3D'", + "facelets": "UksrrHDVMNNaJCWCJcCBRAKnOHWFLnMvBeeV WmmdbGtTTTGSNSTSXGYGGDPvWRQQERLFFgID NuAffXNXRXWsVMNPSMKIIKFZKFACCXCBRooK AAQppJdSKRUEPOHOUEaNLMUhgJDWDJQVliiF SCIDPOHBEEPMXLEFPUSVQUVLYAHHOLPhbOcB IqqtlTTQOGMGHIJBLWuAXVBTQIJDKZEjjkUR", + "token": "Ysu0ppaL7DsqIYA5GCyxitNI51uVa8yNHVjasbpWnYw-8zelz9iAGbuty0xodv2O1z13Y7fMlzxc3hcIzhT", + "canonical": "" + }, + { + "id": 16, + "moves": "6B2 5B2 6F' 1L' 3F' 5R' 6L' 6B 6B' 6B 1R 4L2", + "facelets": "NqKdtUNVADBQNUASBQIUEPHHSUMPMXFFBkOH IWGRRTvHHNRivGHMRieFFNSVeFENTVVDDamQ DEAlPGZKKWIuZLKXIuWLLWIfKABWNfAJnscX BImtdWBLDACCTKTBSCoJOGOboJPMPnKHrOkP OMMJTEbGGCEYFQRDTYgQQCTXgQRCSLJppSAC SscLrMjWEVOajXJUOEUXIVFhUXJVDhRlPqGL", + "token": "AAPHIzTFBSuFEcYT2n1ajzsTtAzIu9gISNi4-lr8HdWYj_isEHb9XMpkvomJnfzmh1JyiOQ8U49-pWLpupk", + "canonical": "GuuffXPIIINclWXWWsAKKLBnEKLLAJDZZWKA BImtdWBLDACCTKTBSCoJOGOboJPMPnKHrOkP EYYXLCTETTSAJCDCCSMGRQRpMGQQQpObFggJ MaEhhLrOOFDGLVUVVqcEJIJPsWXXXlSjjUUR HOkBFFXMPMUSHHPEUIQBSAUNQBDAVNUtdKqN VeevvIDFFGHWDEFHHGaNNMNRmTSRRRQVViiT" + }, + { + "id": 17, + "moves": "6B2 5B2 6F' 1L' 3F' 5R' 6L' 6B 6B' 6B 1R 4L2", + "facelets": "NqKdtUNVADBQNUASBQIUEPHHSUMPMXFFBkOH IWGRRTvHHNRivGHMRieFFNSVeFENTVVDDamQ DEAlPGZKKWIuZLKXIuWLLWIfKABWNfAJnscX BImtdWBLDACCTKTBSCoJOGOboJPMPnKHrOkP OMMJTEbGGCEYFQRDTYgQQCTXgQRCSLJppSAC SscLrMjWEVOajXJUOEUXIVFhUXJVDhRlPqGL", + "token": "Hz_0KHAyIW7oASw5JwhescxNBviIy2dBSymj_A6QKOYXic_NP_lpnYZkT8y98KMcFVh3xDV6dJ2kqS8WjMY", + "canonical": "" + }, + { + "id": 20, + "moves": "", + "facelets": "GAAGppJhAAAABChBAABBCEBBACCAECCDDCJECDDDDJDRRCGGA BIIBDDLTEEEEFKTFEEFFKRFFEGGXMGGHHGtMGHHHHtObbJOOW EQQDHHCjIIIIJSjJIIJJSSJJIKKQUKKLLKNUKLLLLNRZZLWWN PYYKXXMnMMMMNanNMMNNaMNNMOOIcOOPPOPcOPPPPPSvvOeeV HggFFFFrQQQQRirRQQRRiVRRQSSTkSSTTSVkSTTTTVUddNmmQ KooHBBILUUUUVqLVUUVVqWVVUWWUsWWXXWlsWXXXXlXffPuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 21, + "moves": "", + "facelets": "GAAGppJhAAAABChBAABBCEBBACCAECCDDCJECDDDDJDRRCGGA BIIBDDLTEEEEFKTFEEFFKRFFEGGXMGGHHGtMGHHHHtObbJOOW EQQDHHCjIIIIJSjJIIJJSSJJIKKQUKKLLKNUKLLLLNRZZLWWN PYYKXXMnMMMMNanNMMNNaMNNMOOIcOOPPOPcOPPPPPSvvOeeV HggFFFFrQQQQRirRQQRRiVRRQSSTkSSTTSVkSTTTTVUddNmmQ KooHBBILUUUUVqLVUUVVqWVVUWWUsWWXXWlsWXXXXlXffPuuT", + "token": "BcdCm01Mn-9HbtXCU2Z7Qz8qT8ghZEZ4jpymPYKIG2-rEmpf9Q3b-GBs9Sky-MoqD5ugsVhj4KmiRlg753dwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 20, + "moves": "4R 4U 4F' 4R2 x 4U'", + "facelets": "EQQGHHCjIIAIJSjJIAJJSRFFSGGXUKKDLKNUKLDLLNRZZCWWN OMMWTTBbGGVFEIbHGVEEIFQQBTTNOHHWFEDOHGWFFDWttUKKL PYYDXXMnMMIMNanNMINNaACCKBBEcOOLPOPcOPLPPPSvvLeeV TuuKffXlXXMXWslWXMWWsTSSERRVqVVPUVLqVUPUULIBBOooK FiiQVVQFRRKSTmFQRKTTmJHHNEEBgQQJSTdgQRJSSdHrrSkkU AGGHRRDJDDUDCEJCDUCCEMNNWOOICBBXABhCBAXAAhJppPAAG", + "token": "BXqa18ry49nTHMfYEcWPY51CVPnQxDlQy14dql0SkUdWDiLKI1gm6uMJ5HxH8uIq33G4CVotK4aSGriHGJGg9B9LT1i9b_bYM_MEW7x_XfesO3lXMRclUc", + "canonical": "BIINDDLTEETEFKTFETFFKWVVAWWUMGGQHGtMGHQHHtObbFOOW XssVLLKfWWRVUofXWRUUoKMMHPPOuXXSVUBuXWSVVBTllTqqI MaaEPPVXNNBOPeXMNBPPeDIIJLLLYMMCOPvYMNCOOvPnnAccS QmmBddUVTTETSkVSTESSkQKKMJJSiRRHQRriRQHQQrFFFJggH CSSXNNNHJJGKLWHIJGLLWGAARDDCQIIFKLZQIJFKKZEjjRUUR JCCMJJApBBNCDGpABNDDGPXXXUUHAAAOCDRAABOCCRGhhIEED" + }, + { + "id": 21, + "moves": "4R 4U 4F' 4R2 x 4U'", + "facelets": "EQQGHHCjIIAIJSjJIAJJSRFFSGGXUKKDLKNUKLDLLNRZZCWWN OMMWTTBbGGVFEIbHGVEEIFQQBTTNOHHWFEDOHGWFFDWttUKKL PYYDXXMnMMIMNanNMINNaACCKBBEcOOLPOPcOPLPPPSvvLeeV TuuKffXlXXMXWslWXMWWsTSSERRVqVVPUVLqVUPUULIBBOooK FiiQVVQFRRKSTmFQRKTTmJHHNEEBgQQJSTdgQRJSSdHrrSkkU AGGHRRDJDDUDCEJCDUCCEMNNWOOICBBXABhCBAXAAhJppPAAG", + "token": "iUXG1lQR_FsSIZID_evsmf1iRzOzyaYLn29uNjxFuh-8Za1TRVGB_alaYM_xvf4vAkThkcyf9UdUx6TmNR6G4u8PcFHCfyRE3shomavwzzglk2Asmit_qw", + "canonical": "" + }, + { + "id": 20, + "moves": "7B2 1L2 6D' 1F' 7R' 2D2 2B2 7R' 2L2 6U' 7B2 6R2", + "facelets": "UiAGppJHPPPPOWhBAABBCEBBACCAECCDDCJECDDDDJHLrVkAA BIIBDXLZUUUUGSTFEEFQKRFFEGQXMGGHHQtOSXXXXhObbJOMW IKqUlBClRLLLHYHEJKLVSDEILLWQQEIJKWNQWJJKJPEVjSUuN FUiTVvMnMMMMNanNMMNNaMNNMOOIcOOPPOPsBAAAANQrvOeeV SGgFFFGDVFGGKkXHQQRRGKHRQSSCYHSTTSRcEKKJFRPtdNmmD KooHBjTTTTTTICLVUUVIWWVVUWILsWWXXIZgQRRSLdXffPuqR", + "token": "AC0nr_P456K8y37WYau48ArB3y8bukECL1-IGoXuES6BkLdKfJJT2gURE4_fpIVJc_aJBeAHzrli9lmzjTr3F_kivDiXoAdKlUH7nDWY1jAsO6VYhd8AAw", + "canonical": "" + }, + { + "id": 21, + "moves": "7B2 1L2 6D' 1F' 7R' 2D2 2B2 7R' 2L2 6U' 7B2 6R2", + "facelets": "UiAGppJHPPPPOWhBAABBCEBBACCAECCDDCJECDDDDJHLrVkAA BIIBDXLZUUUUGSTFEEFQKRFFEGQXMGGHHQtOSXXXXhObbJOMW IKqUlBClRLLLHYHEJKLVSDEILLWQQEIJKWNQWJJKJPEVjSUuN FUiTVvMnMMMMNanNMMNNaMNNMOOIcOOPPOPsBAAAANQrvOeeV SGgFFFGDVFGGKkXHQQRRGKHRQSSCYHSTTSRcEKKJFRPtdNmmD KooHBjTTTTTTICLVUUVIWWVVUWILsWWXXIZgQRRSLdXffPuqR", + "token": "CgL7GiyiVzD6grnbe3_RxEFXPDK62l6s_5FouhDZt8vQo5lbosW4aNoGyqYqZUkM7rh5PlkfQgrpXsERJP6aP1tBpU43AK79_i-WpQZUFIQll4BMjOgASA", + "canonical": "" + }, + { + "id": 20, + "moves": "6U' 3R 7B' 5D2 1R' 2L z2 6F y 3U' 7U' 6R'", + "facelets": "EoqDXNCTFNOOLeTEWVULoREMPPLOMWMNOLvkEVTPKVORnMcGS TUuPpfBtMHHAGafSTTSUkUNWWCVIQOIJKTPqOUUDUPPFFFgcH MQmNdHUXNXXXPsJHNOPCeKMSSRPVKMFEEPRYSQQQTrJnjSUAR KmiTVBQLXTDDVEZMLDCIEWSKCBWEsFJAAWhgRFRSWdXjHGAuG FSSQNlNJJJKKCWbIGFQEIAIILLBLCVVUDBLCIJJKAZWhtXYKL AMWCrpDbBGFRQIlNXXBBaJAHFQCBOAHGRCDOHGGRDDIvBHGiV", + "token": "BXFOss_0fYgLaA7sJwHHvh0LwsggUf15KtjA-ARWYwNWFjAd_ZHPFsc2TU_a6DFPPF_Jia9SEC-sSIwGBTrvZ7Hv4RczWPRvO2Gp9myplgWmG-TpVRXvgd", + "canonical": "QEEEhdGBVIWWWuVDCBASATDDBARGiTLKJFHmXMSFRjKLZWsgX ViGHBvIDDRGGHODCRGHAOBCQHHAJaBBXXNlIQRFGBbDprCWMA LKYXthWZAKJJICLBDUVVCLBLJIIAIEQFGIbWCKKJJJNlNQSSF OkMRTTEREWEEFonVMMWNqMTNMVODcPOPUOXGKLLLLNSVvOoeC RAUSjnJrTQQQSYRPEEFMKVPRQSMKeCPONHJsPXXXNXUHdNmQM HcgFFFPPUDUUOqPTKJIOQIVCUWNUkUSTTSfaGAHHMtBfpPuUT" + }, + { + "id": 21, + "moves": "6U' 3R 7B' 5D2 1R' 2L z2 6F y 3U' 7U' 6R'", + "facelets": "EoqDXNCTFNOOLeTEWVULoREMPPLOMWMNOLvkEVTPKVORnMcGS TUuPpfBtMHHAGafSTTSUkUNWWCVIQOIJKTPqOUUDUPPFFFgcH MQmNdHUXNXXXPsJHNOPCeKMSSRPVKMFEEPRYSQQQTrJnjSUAR KmiTVBQLXTDDVEZMLDCIEWSKCBWEsFJAAWhgRFRSWdXjHGAuG FSSQNlNJJJKKCWbIGFQEIAIILLBLCVVUDBLCIJJKAZWhtXYKL AMWCrpDbBGFRQIlNXXBBaJAHFQCBOAHGRCDOHGGRDDIvBHGiV", + "token": "iGajXsw4ZLBZMDvl-5E1FfhEkospFAnUku69feqhY7-9Ju0vurlGGw2ENKW4vWmXuJaFmcgVZSHJE3jMZP3huK59GizQVuptkSfvGi_mEIeSnXXf-g50LD", + "canonical": "" + }, + { + "id": 20, + "moves": "2L' 4R2 5F 7L 2U 7R' 4B2 y2 1R 3B' 5L' 6B'", + "facelets": "KGGOBZSLDDPQKcTFEEVUKSWNAWOMsQQRXTnMGHHBCtXppKuQP QsmNLdUVBTTCSkVATTDSkXMMGPFRiXRQCRrCIIXKMjFnFFOgH VooLvRRrQVQPRUDVULHCUIVVKCBWaWWIPBjaWXIMADMffDYAE NeeCZBDFJRLSTmNEFGKHEUJBMJCEcIOFIOPSNMAFXhCXXGQuG LKSTtNWPUNSUPqhJNSBPCBEATHDJIEMDGLbIEWRGVbBTdVMEO TYAPHHJlFAGGHOlNSXJJgQNOWKKAqOAULKJiODULLJIvRHWWA", + "token": "BEwlqjDKQ8vbo7U02wpWDmR7_oGW8eb_OLv1EOvT0Mn_vXE1pn2ncvCo-sK3uJWB972Bf7zxxAn5HUkHKha9pZah9_Wm2usZnO79sp6mGYUZb2Vx95u_co", + "canonical": "" + }, + { + "id": 21, + "moves": "2L' 4R2 5F 7L 2U 7R' 4B2 y2 1R 3B' 5L' 6B'", + "facelets": "KGGOBZSLDDPQKcTFEEVUKSWNAWOMsQQRXTnMGHHBCtXppKuQP QsmNLdUVBTTCSkVATTDSkXMMGPFRiXRQCRrCIIXKMjFnFFOgH VooLvRRrQVQPRUDVULHCUIVVKCBWaWWIPBjaWXIMADMffDYAE NeeCZBDFJRLSTmNEFGKHEUJBMJCEcIOFIOPSNMAFXhCXXGQuG LKSTtNWPUNSUPqhJNSBPCBEATHDJIEMDGLbIEWRGVbBTdVMEO TYAPHHJlFAGGHOlNSXJJgQNOWKKAqOAULKJiODULLJIvRHWWA", + "token": "bOrKj-BC-wvex9K23F6MnKrKK-5IEe5l4Duf055jrl-k1QdnkNWOvvBEd2psRs5ZSV_Eqw8OQZj9BTDn2Zk3hh8vPwekhgpmtmfIvt-SZHpicYKvNpnyvA", + "canonical": "" + }, + { + "id": 24, + "moves": "", + "facelets": "GAAApppJhAAAAABChBAAABBChBBABBBCECCCDCCJECCDDDCJECDDDDDJDRRRGGGA BIIIDDDLTEEEEEFKTFEEEFFKTFFEFFFKMGGGHGGtMGGHHHGtMGHHHHHtObbbOOOW EQQQHHHCjIIIIIJSjJIIIJJSjJJIJJJSUKKKLKKNUKKLLLKNUKLLLLLNRZZZWWWN PYYYXXXMnMMMMMNanNMMMNNanNNMNNNacOOOPOOPcOOPPPOPcOPPPPPPSvvveeeV HgggFFFFrQQQQQRirRQQQRRirRRQRRRikSSSTSSVkSSTTTSVkSTTTTTVUdddmmmQ KoooBBBILUUUUUVqLVUUUVVqLVVUVVVqsWWWXWWlsWWXXXWlsWXXXXXlXfffuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 25, + "moves": "", + "facelets": "GAAApppJhAAAAABChBAAABBChBBABBBCECCCDCCJECCDDDCJECDDDDDJDRRRGGGA BIIIDDDLTEEEEEFKTFEEEFFKTFFEFFFKMGGGHGGtMGGHHHGtMGHHHHHtObbbOOOW EQQQHHHCjIIIIIJSjJIIIJJSjJJIJJJSUKKKLKKNUKKLLLKNUKLLLLLNRZZZWWWN PYYYXXXMnMMMMMNanNMMMNNanNNMNNNacOOOPOOPcOOPPPOPcOPPPPPPSvvveeeV HgggFFFFrQQQQQRirRQQQRRirRRQRRRikSSSTSSVkSSTTTSVkSTTTTTVUdddmmmQ KoooBBBILUUUUUVqLVUUUVVqLVVUVVVqsWWWXWWlsWWXXXWlsWXXXXXlXfffuuuT", + "token": "CZXrUweYrfuFzoMgLm3FgWX21ndvrdRmX-81IopLVHRophR8zFO6PrBokyYZCMXhkhthrXQbnBeGJVZWGwiDJt4n7lYMZsTIKzF4LhCeCqqrzp-ntYKjjGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 24, + "moves": "5D' 5U 5D 3F' 3U' 8B 4L 4U", + "facelets": "GAAupppJhAAXAABChBAXABBChBBTBBBCECCVDCCJIEEEWGHbECDUDDDJBTjBsMMO MIaIDDDLXENEEEFKXJIIIJJSLVVUOVVqYWMWXWWlYGMHHHGtYGNHHHHtPbnbOOOW CSiAtNNNHJRAGKLWHIDAGLLWXFMEFFFKQIQCGLLZQIQFGKLZQIRDGKKZEjrTMUUR FiqSNVVQnMMJMMNaFQRJKTTmnNNJNNNacOOIPOOPcOOJPPOPcOPJPPPPSvvjeeeV HgggFJFArQQQQCRGLVUUUVVGHIQAHLLWUKKKLDKRkSSTTCSRkSTTTCTRUdddmEmD KooovBBILUUUPUVqTFNEPFFKrRRQRDRGkSSSMSSVsWWXRXWlsWXXMXXlXfffiuuT", + "token": "BYoYXS2twskYquTA5aUbbQzro7ywfhQw4bbYc-bwyUVBI0-tM0tqiQFjxbcuFuNqX3Sr4rgjYm3EwwdOwamcId6qDd8xCoUxsRw0lIVO-xy1JUScepm6u-YkwEv_65LSPMqNvUXn3cNE57o-EjkyMiHsA9tlKZLPE", + "canonical": "" + }, + { + "id": 25, + "moves": "5D' 5U 5D 3F' 3U' 8B 4L 4U", + "facelets": "GAAupppJhAAXAABChBAXABBChBBTBBBCECCVDCCJIEEEWGHbECDUDDDJBTjBsMMO MIaIDDDLXENEEEFKXJIIIJJSLVVUOVVqYWMWXWWlYGMHHHGtYGNHHHHtPbnbOOOW CSiAtNNNHJRAGKLWHIDAGLLWXFMEFFFKQIQCGLLZQIQFGKLZQIRDGKKZEjrTMUUR FiqSNVVQnMMJMMNaFQRJKTTmnNNJNNNacOOIPOOPcOOJPPOPcOPJPPPPSvvjeeeV HgggFJFArQQQQCRGLVUUUVVGHIQAHLLWUKKKLDKRkSSTTCSRkSTTTCTRUdddmEmD KooovBBILUUUPUVqTFNEPFFKrRRQRDRGkSSSMSSVsWWXRXWlsWXXMXXlXfffiuuT", + "token": "fXhTPfxdnPdGcrk4NRqxJU0Dxj_rJkvhsX2It6iZyok1aQuJQTUOY-fXHpdlejAfBzuci09jOVEKsmpGGV__Wb033Fl56oVdqPOJV7TpcBx-EGwk0O7pH2okwEv_65LSPMqNvUXn3cNE57o-EjkyMiHsA9tlKZLPE", + "canonical": "" + }, + { + "id": 24, + "moves": "2D 3B2 8R' 6F2 4D y2 8R z2 x y'", + "facelets": "MaaaPlPVfGWGVVUofHWGVUUofHXGUUUouHXHVUUBuHXTVVUBuHWGVVVBPnntcCcS TAAApppXtAAAAABMhBAAABBslBBABBBMqCCCDCCLqCCDDDCLqCDDDDDLIRRRGGGK QmmsdDdUlXXWXEWsdXSWRQQgVSSWSFSkiRRXQGRriRRWQHRriRQWQLQrFFFngOgH DEEmhPhGZSKXJJIQZTKSJIIQZTLTIIIQWTLRJIIHWTLQJJIHWTKQJJJHAJJJCUCJ NeeevvvRVPPPPPOkNOPPPOOcFOOPOOOUSNNNMNNjSNNMMMNjSNMMMMMjCXXXYYYE WOkObbbONLSLLLKUtGFHHGGMNKKLKKKEKFTFEFFTKFTEEEFTKFSEEEETLDVDIIIB", + "token": "AAAADqyqkVL_YR1ZSbXQq7noF034ArcHdJUe5PpU75P4nEMXntKKCa9qCWm6CMoxq2RnDEB7m8bqKzb0HosS_vYkBWoqhFVeryVHs-Pfjl_e-tL_0WhnIXLDPM1tcQbenEtW3DvAPnbTgxw-Gpv109fsHzYG-WLU7", + "canonical": "GQQQHHHJhIIIIIJCPJIIIJJUhJJIJJJCmXSTRQQJEKKLLLKJESTTTTTJDZZZWWWA BIIIDVDLTEEEESFKTFEEETFKTFFEFTFKEKKKLKKNMGGHHFGtUKLLLSLNObbbOkOW EYYYXXXCjMMMMMNSjNMMMNNSjNNMNNNSUOOOPOOFcOOPPPONkOPPPPPVRvvveeeN PuuufffMnHHHHHGanWXXXWWatGTHGGGacVVVUVVPCVVUUUVlcVUUUUUPSBBBoooV HgOgnFFFrQLQWQRirRHQWRRirRGQXRRikSFSWSSVgQQRWSXdsWEXWXXlUdDdsmmQ KGGGRRRILDDDDDCqLCDDDCCqLCCDCCCqMBBBABBlsBBAAABhMBAAAAAtXpppAAAT" + }, + { + "id": 25, + "moves": "2D 3B2 8R' 6F2 4D y2 8R z2 x y'", + "facelets": "MaaaPlPVfGWGVVUofHWGVUUofHXGUUUouHXHVUUBuHXTVVUBuHWGVVVBPnntcCcS TAAApppXtAAAAABMhBAAABBslBBABBBMqCCCDCCLqCCDDDCLqCDDDDDLIRRRGGGK QmmsdDdUlXXWXEWsdXSWRQQgVSSWSFSkiRRXQGRriRRWQHRriRQWQLQrFFFngOgH DEEmhPhGZSKXJJIQZTKSJIIQZTLTIIIQWTLRJIIHWTLQJJIHWTKQJJJHAJJJCUCJ NeeevvvRVPPPPPOkNOPPPOOcFOOPOOOUSNNNMNNjSNNMMMNjSNMMMMMjCXXXYYYE WOkObbbONLSLLLKUtGFHHGGMNKKLKKKEKFTFEFFTKFTEEEFTKFSEEEETLDVDIIIB", + "token": "zgioRDkZZThEnrraF_hSVjGGI0FLyjZuo-Kg3Kd5rUFVCsBxHasl-O9WOLC4MsFdFv8OAiEpBSCm0rlDoHh-hV7qXLyffzecLqPmvnuOLWIpilAN18crNme4gJsaA9QtWWQX7XfV_9QhOPCLNdPoccmOOvTlXs5t_", + "canonical": "" + }, + { + "id": 28, + "moves": "", + "facelets": "GAAAGpppJhAAAAAABChBAAAABBChBBAABBBCEBBBACCCAECCCDDCCJECCDDDDCJECDDDDDDJDRRRCGGGA BIIIBDDDLTEEEEEEFKTFEEEEFFKTFFEEFFFKRFFFEGGGXMGGGHHGGtMGGHHHHGtMGHHHHHHtObbbJOOOW EQQQDHHHCjIIIIIIJSjJIIIIJJSjJJIIJJJSSJJJIKKKQUKKKLLKKNUKKLLLLKNUKLLLLLLNRZZZLWWWN PYYYKXXXMnMMMMMMNanNMMMMNNanNNMMNNNaMNNNMOOOIcOOOPPOOPcOOPPPPOPcOPPPPPPPSvvvOeeeV HgggFFFFFrQQQQQQRirRQQQQRRirRRQQRRRiVRRRQSSSTkSSSTTSSVkSSTTTTSVkSTTTTTTVUdddNmmmQ KoooHBBBILUUUUUUVqLVUUUUVVqLVVUUVVVqWVVVUWWWUsWWWXXWWlsWWXXXXWlsWXXXXXXlXfffPuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 29, + "moves": "", + "facelets": "GAAAGpppJhAAAAAABChBAAAABBChBBAABBBCEBBBACCCAECCCDDCCJECCDDDDCJECDDDDDDJDRRRCGGGA BIIIBDDDLTEEEEEEFKTFEEEEFFKTFFEEFFFKRFFFEGGGXMGGGHHGGtMGGHHHHGtMGHHHHHHtObbbJOOOW EQQQDHHHCjIIIIIIJSjJIIIIJJSjJJIIJJJSSJJJIKKKQUKKKLLKKNUKKLLLLKNUKLLLLLLNRZZZLWWWN PYYYKXXXMnMMMMMMNanNMMMMNNanNNMMNNNaMNNNMOOOIcOOOPPOOPcOOPPPPOPcOPPPPPPPSvvvOeeeV HgggFFFFFrQQQQQQRirRQQQQRRirRRQQRRRiVRRRQSSSTkSSSTTSSVkSSTTTTSVkSTTTTTTVUdddNmmmQ KoooHBBBILUUUUUUVqLVUUUUVVqLVVUUVVVqWVVVUWWWUsWWWXXWWlsWWXXXXWlsWXXXXXXlXfffPuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 28, + "moves": "5R 5U 5F' 5R2 x 5U'", + "facelets": "EQQQGHHHCjIIIAIIJSjJIIAIJJSjJJIAJJJSRFFFSGGGXUKKKDLKKNUKKLDLLKNUKLLDLLLNRZZZCWWWN OMMMWTTTBbGGGVFFEIbHGGVFEEIbHHGVEEEIFQQQBTTTNOHHHWFEEDOHHGWFFEDOHGGWFFFDWtttUKKKL PYYYDXXXMnMMMIMMNanNMMIMNNanNNMINNNaACCCKBBBEcOOOLPOOPcOOPLPPOPcOPPLPPPPSvvvLeeeV TuuuKfffXlXXXMXXWslWXXMXWWslWWXMWWWsTSSSERRRVqVVVPUVVLqVVUPUUVLqVUUPUUULIBBBOoooK FiiiQVVVQFRRRKSSTmFQRRKSTTmFQQRKTTTmJHHHNEEEBgQQQJSTTdgQQRJSSTdgQRRJSSSdHrrrSkkkU AGGGHRRRDJDDDUDDCEJCDDUDCCEJCCDUCCCEMNNNWOOOICBBBXABBhCBBAXAABhCBAAXAAAhJpppPAAAG", + "token": "DSeTfUZRvxjmJqEGM67EBpL7C_w_2ecJxeEAlBEO8oL_DdHYxc45rdl1ZTYIs3zwMmD9MzOCklUPTdFGljJ5E1nlz3hmBG3sXRmgA8OVv2ZXu5yhobUfO2mNZ36DDQnENX3YZ-7qJ47dYH8mRYINyCqc3ZKSgn8LpQsE1RTWd5FLDdc_bkkl4ZWtM3YM_MEW7x_XfesO3lXMRclUc", + "canonical": "BIIINDDDLTEEETEEFKTFEETEFFKTFFETFFFKWVVVAWWWUMGGGQHGGtMGGHQHHGtMGHHQHHHtObbbFOOOW XsssVLLLKfWWWRVVUofXWWRVUUofXXWRUUUoKMMMHPPPOuXXXSVUUBuXXWSVVUBuXWWSVVVBTlllTqqqI MaaaEPPPVXNNNBOOPeXMNNBOPPeXMMNBPPPeDIIIJLLLLYMMMCOPPvYMMNCOOPvYMNNCOOOvPnnnAcccS QmmmBdddUVTTTETTSkVSTTETSSkVSSTESSSkQKKKMJJJSiRRRHQRRriRRQHQQRriRQQHQQQrFFFFJgggH CSSSXNNNNHJJJGKKLWHIJJGKLLWHIIJGLLLWGAAARDDDCQIIIFKLLZQIIJFKKLZQIJJFKKKZEjjjRUUUR JCCCMJJJApBBBNCCDGpABBNCDDGpAABNDDDGPXXXXUUUHAAAAOCDDRAAABOCCDRAABBOCCCRGhhhIEEED" + }, + { + "id": 29, + "moves": "5R 5U 5F' 5R2 x 5U'", + "facelets": "EQQQGHHHCjIIIAIIJSjJIIAIJJSjJJIAJJJSRFFFSGGGXUKKKDLKKNUKKLDLLKNUKLLDLLLNRZZZCWWWN OMMMWTTTBbGGGVFFEIbHGGVFEEIbHHGVEEEIFQQQBTTTNOHHHWFEEDOHHGWFFEDOHGGWFFFDWtttUKKKL PYYYDXXXMnMMMIMMNanNMMIMNNanNNMINNNaACCCKBBBEcOOOLPOOPcOOPLPPOPcOPPLPPPPSvvvLeeeV TuuuKfffXlXXXMXXWslWXXMXWWslWWXMWWWsTSSSERRRVqVVVPUVVLqVVUPUUVLqVUUPUUULIBBBOoooK FiiiQVVVQFRRRKSSTmFQRRKSTTmFQQRKTTTmJHHHNEEEBgQQQJSTTdgQQRJSSTdgQRRJSSSdHrrrSkkkU AGGGHRRRDJDDDUDDCEJCDDUDCCEJCCDUCCCEMNNNWOOOICBBBXABBhCBBAXAABhCBAAXAAAhJpppPAAAG", + "token": "BO7XTvpep6lWTnxiU2GJgnceJH6X8bajqjRgN4ZlmvEfpS6xSi1VoTGMBfRDQ07aEuRe8zNQ9t_lvS56eFLtZ0G2Lc0mQak4oumcAWlYJ8Zg5lq8nKPrtkeVBs9xJOOplA8xJvmXztWTBC-uWhDFKw_60xb28O-kXeQhz-fQbNZ8JTC3yVtuNJgg80xE3shomavwzzglk2Asmit_qw", + "canonical": "" + }, + { + "id": 28, + "moves": "7R' 6L2 x 6F' 3R2 2F 3D' 7L", + "facelets": "EQAQDfXHCjIAIIXMJSjJAIIXQJSTFFEEFNFKSJBJIVOKQUKCKLUOKNUKCLLUPKNkSTTTTPTVRZRZLoeWN OUMMRLTTBbKGGFVFEIbDGGFVEEIbLHGFLEEIJLHHFUEEBOUHHGVEEDCBNMAAXBhOLGGGVFFDWNttXqKKL PYQYKpfXMnMIMMAXNanNIMMAWNanNJMMBWNaMNJNMCVOIcOKOPDROPOUHGGOUEDcOLPPDTPPSvZvOGoeV TuYuPHpfXtHHHHHAGMlWMXXIBWslWNXXJBWsUWNWWKCVWiRRRQQCRrqVOUULFVLqVPUULDULIBvBHWGoK FiiSTVVlQFRRJSSSXmFQRJSSTMmFQQWSTTXmFQQITTTXNgQQIRSTIdcOKPPDDOPgQRJRSSWdHrrjVkksU AGWeCRBRDJDLPDDUCEJCLPDDVCEJCKPDCVCEACKOCBWBECBFNAAWBhgQJBRSSIdCBHMAAXAhJpHXGAuAG", + "token": "AAAAAAAAAAAAAAAAOzD2ecZbCNFP4StSNL1ZQjgJlwRI11Vgx19oA-kVLNNSypTJOUzG6RvKvxQhQVfb66DP7HdGn-wspmpZPY0N03mJRkjeodVoUSHygeP7gW2GjM3881xoo0nG1Z5MpYajmclEJKDxhPouP28iMDkXqcrHv-9vijWdm30a3n2V9Yx4hFaPH2IH54JwHh1P_9xgA", + "canonical": "GAuAGXHpJhAXAAMHBCdISSRBJQghBWAANFBCEBWBAOKCAECVCDPKCJECVDDPLCJECUDDPLDJDRBRCeWGA BIIIBDhDLTEEEEEBFKTFEEEEXFKLVVLUVAVqRFFFEGAGXMGGGHHMGtMGGHHHNGtUKDLLUBLNObbbJOCOW EQAQDfXHCjIAIIXMJSjJAIIXQJSTFFEEFNFKSJBJIVOKQUKCKLUOKNUKCLLUPKNkSTTTTPTVRZRZLoeWN PYQYKpfXMnMIMMAXNanNIMMAWNanNJMMBWNaMNJNMCVOIcOKOPDROPOUHGGOUEDcOLPPDTPPSvZvOGoeV HgcgFFFFFrQOQQQQRirRKQQQRRijJPIIWJJSVRPRQSSSTkSDSTTSSVkSDTTTTSVsWOIXXMXlUdPdNmmmQ KoGWHBvBILUDLUUPVqLVFLUUOVqrRCQQRRRiWVCKUWNWUsWBJXXNWlsWBIXXMWlMGAHHHHHtXfpHPuYuT" + }, + { + "id": 29, + "moves": "7R' 6L2 x 6F' 3R2 2F 3D' 7L", + "facelets": "EQAQDfXHCjIAIIXMJSjJAIIXQJSTFFEEFNFKSJBJIVOKQUKCKLUOKNUKCLLUPKNkSTTTTPTVRZRZLoeWN OUMMRLTTBbKGGFVFEIbDGGFVEEIbLHGFLEEIJLHHFUEEBOUHHGVEEDCBNMAAXBhOLGGGVFFDWNttXqKKL PYQYKpfXMnMIMMAXNanNIMMAWNanNJMMBWNaMNJNMCVOIcOKOPDROPOUHGGOUEDcOLPPDTPPSvZvOGoeV TuYuPHpfXtHHHHHAGMlWMXXIBWslWNXXJBWsUWNWWKCVWiRRRQQCRrqVOUULFVLqVPUULDULIBvBHWGoK FiiSTVVlQFRRJSSSXmFQRJSSTMmFQQWSTTXmFQQITTTXNgQQIRSTIdcOKPPDDOPgQRJRSSWdHrrjVkksU AGWeCRBRDJDLPDDUCEJCLPDDVCEJCKPDCVCEACKOCBWBECBFNAAWBhgQJBRSSIdCBHMAAXAhJpHXGAuAG", + "token": "AAAAAAAAAAAAAAAAFjJcbapiI0599HA-08cBeNUDmKGbUMAESsPHAXdn8M8_C_fLdXzKl2psB6eMeIDyeFxN-Ky6e_iQvmfhdxTlL02TeltTfLAHHmy68LV-UIkSdM0-20KnPTuqkBtcvhJ9Wat5jbxakd3RV8ms0hVo36wK5_nJ09QbGk7qE28YPBStMaB1q8TC9tDqC0r9__KkAE", + "canonical": "" + }, + { + "id": 28, + "moves": "8B' 2L 1F' y2 6U' 2L 7B' 7L x y'", + "facelets": "IqqqUtllTvOOONGNGYTSCCBBFAABUUVWHXXuHUUUXHXXPoUUUVGXXfoUUVVGWXfMRCCCBBBpKLLLWYssX VoeeOvBZSPUPPPPUKcdTSWRRVQgPVOPPOVKcIVOOONWJMaDNNMMGJnaWNMMMXJnIWFFFGXIbQfVVTiuiF LKKKXZttWDFFFGLGHOPVPPPPOKcDEEFGKHHOBEEEHJHHJIEEEFIHHbIEEFFIGHbECDDDIDDJMXXXKQYQP NWWWLRZRRVENNOOOPeNKLLLSKCUNKKLLTKCUQKKKKTJBSSJJJISJBjiGMNNOQPvSJIIISIAjCHHHDkQAE GuSAGppNJhXJAAAALCDEJFGLHHOhWIAABBLCEWIBACCLAEVSCDDCMJEVIDDDDLJmVBSSRRTFBTjTRMMUO UkksVrrrHdSSWRRRQghWAAAABLCdTTWRQQQgNTTXRQQQFmTTXSRQQFmTTWSRRQFaNMNMMMMnDBRlCGGGA", + "token": "Bv0KRYe-VBae437_SKMDwGAO9TQ67hoIKNIOB-4lp8q7OI59OgwHPmFjUMnpVabgtYr-qRBDmPH5pCDv_mbPqLtZxD06kxXW285rKyawHJPikBBQjCf8IyaRYX_Po9HBt2Gauge1-E4-0PGqwIogTfzBFCfZKc4nWqKcbQixS99yAh4TRrIID94saHZl_Lby0oDyTCw5qvqkQ2YAw", + "canonical": "GuSAGppNJhXJAAAALCDEJFGLHHOhWIAABBLCEWIBACCLAEVSCDDCMJEVIDDDDLJmVBSSRRTFBTjTRMMUO MEIIBDPDLXCEEEEVFKXDEEEEPFKXDFEEFPFKKDFFEGPGXQIIIJKPLZYDGHHHOGtQDHHHHKHtPJbbJOcOW CSiSQNNVNHJGJKKKEWHIMJKKLNWHINJKLLNWDINILLLOLkSOSTTSORQIQJJKKOZAAPBBCCPREjvjSUUeR FiuiTVVfQbIXGFFFWInJXMMMNWanJGMMNNDaMJWNMOOVIcKVOPPOVPgQVRRWSTdcKUPPPPUPSZBvOeeoV HgCgFFFnArQLQQQQMGrRBQQQRMGrRAQQRRMGVRARQSSMCsWAWXXWNlkSATTTTMRkSWTTTTNBUdhdNmmaD KMooHBTvILRUUUUSOqLCUUUUCOqLCVUUVCOqWCVVUWBNUYBGGHHBGtsBWXXXFNlsBXXXXAGlXpffPuAYT" + }, + { + "id": 29, + "moves": "8B' 2L 1F' y2 6U' 2L 7B' 7L x y'", + "facelets": "IqqqUtllTvOOONGNGYTSCCBBFAABUUVWHXXuHUUUXHXXPoUUUVGXXfoUUVVGWXfMRCCCBBBpKLLLWYssX VoeeOvBZSPUPPPPUKcdTSWRRVQgPVOPPOVKcIVOOONWJMaDNNMMGJnaWNMMMXJnIWFFFGXIbQfVVTiuiF LKKKXZttWDFFFGLGHOPVPPPPOKcDEEFGKHHOBEEEHJHHJIEEEFIHHbIEEFFIGHbECDDDIDDJMXXXKQYQP NWWWLRZRRVENNOOOPeNKLLLSKCUNKKLLTKCUQKKKKTJBSSJJJISJBjiGMNNOQPvSJIIISIAjCHHHDkQAE GuSAGppNJhXJAAAALCDEJFGLHHOhWIAABBLCEWIBACCLAEVSCDDCMJEVIDDDDLJmVBSSRRTFBTjTRMMUO UkksVrrrHdSSWRRRQghWAAAABLCdTTWRQQQgNTTXRQQQFmTTXSRQQFmTTWSRRQFaNMNMMMMnDBRlCGGGA", + "token": "Ap7j2hLnX4h7lU-fuz0haCQFm_OWGUnDD07FQvlOHuwGNTVu9cSCt2SFPku3gB6URBQff2YZWVq9nYxZ_2aN30ZBqZb19ygwkm1oMC6CCrd09gYeNI7-jS52hI_t3W6opMSaBcLkPR1XjlqgCDPMHT7IZ47xb61Owfz6qONCfHPKwMtHOoLDBfNQpyxmPsSbDvBa3JCVoB39mUZASN", + "canonical": "" + }, + { + "id": 32, + "moves": "", + "facelets": "GAAAAppppJhAAAAAAABChBAAAAABBChBBAAABBBChBBBABBBBCECCCCDCCCJECCCDDDCCJECCDDDDDCJECDDDDDDDJDRRRRGGGGA BIIIIDDDDLTEEEEEEEFKTFEEEEEFFKTFFEEEFFFKTFFFEFFFFKMGGGGHGGGtMGGGHHHGGtMGGHHHHHGtMGHHHHHHHtObbbbOOOOW EQQQQHHHHCjIIIIIIIJSjJIIIIIJJSjJJIIIJJJSjJJJIJJJJSUKKKKLKKKNUKKKLLLKKNUKKLLLLLKNUKLLLLLLLNRZZZZWWWWN PYYYYXXXXMnMMMMMMMNanNMMMMMNNanNNMMMNNNanNNNMNNNNacOOOOPOOOPcOOOPPPOOPcOOPPPPPOPcOPPPPPPPPSvvvveeeeV HggggFFFFFrQQQQQQQRirRQQQQQRRirRRQQQRRRirRRRQRRRRikSSSSTSSSVkSSSTTTSSVkSSTTTTTSVkSTTTTTTTVUddddmmmmQ KooooBBBBILUUUUUUUVqLVUUUUUVVqLVVUUUVVVqLVVVUVVVVqsWWWWXWWWlsWWWXXXWWlsWWXXXXXWlsWXXXXXXXlXffffuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 33, + "moves": "", + "facelets": "GAAAAppppJhAAAAAAABChBAAAAABBChBBAAABBBChBBBABBBBCECCCCDCCCJECCCDDDCCJECCDDDDDCJECDDDDDDDJDRRRRGGGGA BIIIIDDDDLTEEEEEEEFKTFEEEEEFFKTFFEEEFFFKTFFFEFFFFKMGGGGHGGGtMGGGHHHGGtMGGHHHHHGtMGHHHHHHHtObbbbOOOOW EQQQQHHHHCjIIIIIIIJSjJIIIIIJJSjJJIIIJJJSjJJJIJJJJSUKKKKLKKKNUKKKLLLKKNUKKLLLLLKNUKLLLLLLLNRZZZZWWWWN PYYYYXXXXMnMMMMMMMNanNMMMMMNNanNNMMMNNNanNNNMNNNNacOOOOPOOOPcOOOPPPOOPcOOPPPPPOPcOPPPPPPPPSvvvveeeeV HggggFFFFFrQQQQQQQRirRQQQQQRRirRRQQQRRRirRRRQRRRRikSSSSTSSSVkSSSTTTSSVkSSTTTTTSVkSTTTTTTTVUddddmmmmQ KooooBBBBILUUUUUUUVqLVUUUUUVVqLVVUUUVVVqLVVVUVVVVqsWWWWXWWWlsWWWXXXWWlsWWXXXXXWlsWXXXXXXXlXffffuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 32, + "moves": "4F' 10F2 x' 3R2 10F2 y' 7R 4D", + "facelets": "RqqKqllllTZVVSVWWWXuZIJSJKKLLWZUUSVWXXXuZUUTVXXXXuWUUTUWXXXfWUUCVWWXXfuUUSVWWWXfWUVSVWWWWfNLLVLssssX VeeeevvXvShAAAAAAMBChBAAAAANBChBBAAABTBChBBBABBNBCECCCCDCOCJOCCLDDDCCJECCDDDDPCJECDDDDDPDJDRRRRGGAGA OKKUKttttWbSSKSRRRQgbTSKSRRQQgnTTKSRQQQgbTTLSQQQQgOTTLTRQQQFcOOOPPPHOPOTTWSRRRQFOTSKSRRRRFHVVNViiiiF IUUMUjjjjEBKKGKJJJIQHLKGKJJIIQBLLMKJIIIQBLLHKIIIIQoLLHLJIIIHoLLHKJJIIHoXXGWVVVUBoLKGKJJJJHKNNrNSSSSC PYYYYXXvXMnMMMMMMANanNMMMMMBNaFQQRRSTBTmnNNNMNNBNacOOOOPOCOPaNNNMMVNNdcOOPPPPDOPcOPPPPPDPPJppppAAGAG UkkkkrLrrQDFFFFGVGHmDEFFFGVHHmDEEFFGUHHEDEEEFHUHHmIEEEEGUHHdIEEEFGGOHbIEEFFGJGHdIEFFFGVGGdBTTTTMqMML", + "token": "CEcJrHyBnYTvrlXFtst0FSHKib5RKfLkK8nkFODyDK0eIP6sfvHC16woWzz_fhenVfHN2msCcPGedRdyo-9ZeAY_6UXDISqFeMAm7X6MKzrx9nld6OBINAViKHdUv5ydobhOsig0LGgtJcZYC5fy66fa-XqjjQx_bQD6dpvg1rBgkqmILBc1YZiLSyG1bc6hOcJvdd1ZypkkxG4tQy7ZrRRjLB4-h2D7djFn25gILLKN_TJ0guMzJfr4cO-S6h-6_zM2WYNXU", + "canonical": "PYYYYXXvXMnMMMMMMANanNMMMMMBNaFQQRRSTBTmnNNNMNNBNacOOOOPOCOPaNNNMMVNNdcOOPPPPDOPcOPPPPPDPPJppppAAGAG HOOcObnbbOVTTOTTTTSKVSTOTTTSSKNKWOLLKKKUVSSPTSSSSKiRRPRQRRRtiRRPQQQRRtiRRHQQQQRtiRQOQQQQQtFFFPFggggW KooooBBHBINLXLLLLLKUNKXLLLLKKUrGGHHHMGGMNKWKLKKKKUSJVJJIJJJjSJVJIIIJJjSJVIIIIIJjSJUIIIIIIjCHBHHQQQQE AGAGGRRRRDJDPDDDDDCEJCPDDDDCCEJCCDDDLCCOJCOCDCCCCECBNBBABBBhCBTBAAABBhCBNAAAAABhCBMAAAAAAhSvXvveeeeV QmmEmdbddLrHHHHHHHGMrGHHHHOGGMLVVUUUGJVqrGGGHGGGGMkFFFFEFFFTkFFFEEEFFTkFFEEEEEFTkFEEEEEEETUDDDDIIIIB NWuWWZZZZRLUUUUUUIVqLVUUUUUJVqVSSCTTSSSKLVVVUVVJVqsWWWWXWKWlsWWWXXXKWlsWWXXXXLWlsWXXXXXLXlXffffuuWuT" + }, + { + "id": 33, + "moves": "4F' 10F2 x' 3R2 10F2 y' 7R 4D", + "facelets": "RqqKqllllTZVVSVWWWXuZIJSJKKLLWZUUSVWXXXuZUUTVXXXXuWUUTUWXXXfWUUCVWWXXfuUUSVWWWXfWUVSVWWWWfNLLVLssssX VeeeevvXvShAAAAAAMBChBAAAAANBChBBAAABTBChBBBABBNBCECCCCDCOCJOCCLDDDCCJECCDDDDPCJECDDDDDPDJDRRRRGGAGA OKKUKttttWbSSKSRRRQgbTSKSRRQQgnTTKSRQQQgbTTLSQQQQgOTTLTRQQQFcOOOPPPHOPOTTWSRRRQFOTSKSRRRRFHVVNViiiiF IUUMUjjjjEBKKGKJJJIQHLKGKJJIIQBLLMKJIIIQBLLHKIIIIQoLLHLJIIIHoLLHKJJIIHoXXGWVVVUBoLKGKJJJJHKNNrNSSSSC PYYYYXXvXMnMMMMMMANanNMMMMMBNaFQQRRSTBTmnNNNMNNBNacOOOOPOCOPaNNNMMVNNdcOOPPPPDOPcOPPPPPDPPJppppAAGAG UkkkkrLrrQDFFFFGVGHmDEFFFGVHHmDEEFFGUHHEDEEEFHUHHmIEEEEGUHHdIEEEFGGOHbIEEFFGJGHdIEFFFGVGGdBTTTTMqMML", + "token": "CINJzNTlQLvWikGiP_GtWMm1DqYGJdP8Yd3Q3R5uLPHi02kAcdx1KqVeWpOtKmrxcjMC8F28ef7HY83x2xHhYl9qSdarbUqVqo49DLFuR5qPG5WISRSGQayGF4VWkm9uAxgr6InBLL5jR5pKDvu_377xFWf3t3KpBujUXbIOBw_Y1fAcip-05c1BKeOm1U4K0RxopVPw8_TvCmDP8LLpklXOMxM2t0YscbTqVRXwsnGtKpq_BbACGAm8KgVwxsok7NwF91mqvl", + "canonical": "" + }, + { + "id": 32, + "moves": "3U2 9F' 7R z2 5F2 2U 7L 4R2 x y'", + "facelets": "RkUUUjjLjEZAKKKJJVIQZTKKKJJUIQZLLKNJIUIQZTLLKIIUIQWELLLJIUIHWTLLKJJUIHWTLKKJJVIHWTKKKJJVJHNVNNNSSqSC AGGGGRRRRDJDDDDDDDCEJCDDDDDCCEJCCDDDCCCEnNNWMNCNNaCBBBBABBBhCBBBAAABBhCBBAAAAABhOHGGGFHRFDJppppAAAAG OUMMMTTrTBbKGGGFFREIbLGGGFFQEIbRHGGFEQEIFLEQRTTTTmOLHHHFEQEDOLHHGFFQEDOLHGGFFREDcLPPPPPPPPWNtttKKiKL XMsssLLjLKfGWWWVVJUofHWWWVVIUofHXWWVUIUofQXXWUUIUouHXXXVUIUBuXXXCVVIUBuHXWWVVJUBuOWWWVVJVBTtlllqqSqI PYYYYXXXXMnMMMMMMMNanNMMMMMNNanNNMMMNNNaJCCNDCKCCEcOOOOPOOOPcOOOPPPOOPcOOPPPPPOPgQFTRSSSSdSvvvveeeeV FiKiiVVVlQFRFRRSSSXmFQFRRSSTXmFQERRSTTFmbHHHGEEQXIgQEQQSTTXdgQEQRSSTXdgQERRSSSXdCBAAAAAAWhHrTrrkkksU", + "token": "AAAAAS6MNJXz6eUVbO7qZC-6Y9A0QC6Q-ifAKxU2NIU3c0W1hgcQDS_RIqFoBSy_DuXsLTPfVdAGZjxP24StYsNETxfnacoSxKuEp7-NV5-8Xd2Z-YfSHymbrpTGQqs49jd1U3LfHNJ7E4UVj1myTjQYVmE_NIOcN1f5JhIDy2vTOZqI7qMMMfIT9z_xQ7xlxLzkjpi0BA28LDxfdcoxkAh_AZXoPAtYxcmDaqkLw4HoaosGZW0bSrBzkt_h_Xv4t5_wwrVbU", + "canonical": "GAAAAppppJDFRHFGGGHOhBAAAAABBChBBAAABBBChBBBABBBBCaNNCNMWNNnECCCDDDCCJECCDDDDDCJECDDDDDDDJDRRRRGGGGA BIIImDDDPLTEEETEEEPKrRQQTQQRPiTFFETEFFPKTFFFTFFFPKMGGGRHGGPtMGGGQHHGPtMGGHEHHHPtUKLRLLLLLNObbbFOOOcW EQQQQHHHHCjIIIIIIIJSLVUUUUUVVqjJJIIIJJJSjJJJIJJJJSUKKNKLKKKNUKKKLLLKKNUKKLLLLLKNkATLTETTTVRZZZZWWWWN PYYYYXXXXMnMMMMMMMNanNMMMMMNNanNNMMMNNNaJCCNDCKCCEcOOOOPOOOPcOOOPPPOOPcOOPPPPPOPgQFTRSSSSdSvvvveeeeV HCgggbFFFFrBQQQHQQRiTAEEEHEFFKrARQQHRRRirARRQGRRRikASSSESSSVkASSTETSSVkASTTQTTSVsWXXXXFXXlUhdddImmmQ KooooBBBBILUUUUUUUVqjJIIIIIJJSLVVUUUVVVqLVVVUVVVVqsWWWWXCWWlsWWWXXXWWlsWWXXXXXWlMGHHQHXHOtXffffuuuuT" + }, + { + "id": 33, + "moves": "3U2 9F' 7R z2 5F2 2U 7L 4R2 x y'", + "facelets": "RkUUUjjLjEZAKKKJJVIQZTKKKJJUIQZLLKNJIUIQZTLLKIIUIQWELLLJIUIHWTLLKJJUIHWTLKKJJVIHWTKKKJJVJHNVNNNSSqSC AGGGGRRRRDJDDDDDDDCEJCDDDDDCCEJCCDDDCCCEnNNWMNCNNaCBBBBABBBhCBBBAAABBhCBBAAAAABhOHGGGFHRFDJppppAAAAG OUMMMTTrTBbKGGGFFREIbLGGGFFQEIbRHGGFEQEIFLEQRTTTTmOLHHHFEQEDOLHHGFFQEDOLHGGFFREDcLPPPPPPPPWNtttKKiKL XMsssLLjLKfGWWWVVJUofHWWWVVIUofHXWWVUIUofQXXWUUIUouHXXXVUIUBuXXXCVVIUBuHXWWVVJUBuOWWWVVJVBTtlllqqSqI PYYYYXXXXMnMMMMMMMNanNMMMMMNNanNNMMMNNNaJCCNDCKCCEcOOOOPOOOPcOOOPPPOOPcOOPPPPPOPgQFTRSSSSdSvvvveeeeV FiKiiVVVlQFRFRRSSSXmFQFRRSSTXmFQERRSTTFmbHHHGEEQXIgQEQQSTTXdgQEQRSSTXdgQERRSSSXdCBAAAAAAWhHrTrrkkksU", + "token": "AI5oU7vYsxr6BFkycP7sEMugjoqPXTZTtiVD40nHnNIj13ml0W7ZH6_GCWpVeS-EmKbhXQ6p7ITYRY7an7q4LtAQYHLlpbvukh2Y4vZDyHGmPQvCe28IEi1WPRVyds5y-XInyclk4MJ_F4aTnRzPq_KyIucSz1BkXF9FnR-pLKxl1lBhi6lyDaWhkB40mizeOx6SRcIQXZB34SLd6AxGrG8xhLRnB9rxF1w3hIGg8M04jZsVR7qrFxiG3GmQ6rftglsXcsmVwr", + "canonical": "" + }, + { + "id": 36, + "moves": "", + "facelets": "GAAAAGppppJhAAAAAAAABChBAAAAAABBChBBAAAABBBChBBBAABBBBCEBBBBACCCCAECCCCDDCCCJECCCDDDDCCJECCDDDDDDCJECDDDDDDDDJDRRRRCGGGGA BIIIIBDDDDLTEEEEEEEEFKTFEEEEEEFFKTFFEEEEFFFKTFFFEEFFFFKRFFFFEGGGGXMGGGGHHGGGtMGGGHHHHGGtMGGHHHHHHGtMGHHHHHHHHtObbbbJOOOOW EQQQQDHHHHCjIIIIIIIIJSjJIIIIIIJJSjJJIIIIJJJSjJJJIIJJJJSSJJJJIKKKKQUKKKKLLKKKNUKKKLLLLKKNUKKLLLLLLKNUKLLLLLLLLNRZZZZLWWWWN PYYYYKXXXXMnMMMMMMMMNanNMMMMMMNNanNNMMMMNNNanNNNMMNNNNaMNNNNMOOOOIcOOOOPPOOOPcOOOPPPPOOPcOOPPPPPPOPcOPPPPPPPPPSvvvvOeeeeV HggggFFFFFFrQQQQQQQQRirRQQQQQQRRirRRQQQQRRRirRRRQQRRRRiVRRRRQSSSSTkSSSSTTSSSVkSSSTTTTSSVkSSTTTTTTSVkSTTTTTTTTVUddddNmmmmQ KooooHBBBBILUUUUUUUUVqLVUUUUUUVVqLVVUUUUVVVqLVVVUUVVVVqWVVVVUWWWWUsWWWWXXWWWlsWWWXXXXWWlsWWXXXXXXWlsWXXXXXXXXlXffffPuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 37, + "moves": "", + "facelets": "GAAAAGppppJhAAAAAAAABChBAAAAAABBChBBAAAABBBChBBBAABBBBCEBBBBACCCCAECCCCDDCCCJECCCDDDDCCJECCDDDDDDCJECDDDDDDDDJDRRRRCGGGGA BIIIIBDDDDLTEEEEEEEEFKTFEEEEEEFFKTFFEEEEFFFKTFFFEEFFFFKRFFFFEGGGGXMGGGGHHGGGtMGGGHHHHGGtMGGHHHHHHGtMGHHHHHHHHtObbbbJOOOOW EQQQQDHHHHCjIIIIIIIIJSjJIIIIIIJJSjJJIIIIJJJSjJJJIIJJJJSSJJJJIKKKKQUKKKKLLKKKNUKKKLLLLKKNUKKLLLLLLKNUKLLLLLLLLNRZZZZLWWWWN PYYYYKXXXXMnMMMMMMMMNanNMMMMMMNNanNNMMMMNNNanNNNMMNNNNaMNNNNMOOOOIcOOOOPPOOOPcOOOPPPPOOPcOOPPPPPPOPcOPPPPPPPPPSvvvvOeeeeV HggggFFFFFFrQQQQQQQQRirRQQQQQQRRirRRQQQQRRRirRRRQQRRRRiVRRRRQSSSSTkSSSSTTSSSVkSSSTTTTSSVkSSTTTTTTSVkSTTTTTTTTVUddddNmmmmQ KooooHBBBBILUUUUUUUUVqLVUUUUUUVVqLVVUUUUVVVqLVVVUUVVVVqWVVVVUWWWWUsWWWWXXWWWlsWWWXXXXWWlsWWXXXXXXWlsWXXXXXXXXlXffffPuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 36, + "moves": "6R 6U 6F' 6R2 x 6U'", + "facelets": "EQQQQGHHHHCjIIIIAIIIJSjJIIIAIIJJSjJJIIAIJJJSjJJJIAJJJJSRFFFFSGGGGXUKKKKDLKKKNUKKKLDLLKKNUKKLLDLLLKNUKLLLDLLLLNRZZZZCWWWWN OMMMMWTTTTBbGGGGVFFFEIbHGGGVFFEEIbHHGGVFEEEIbHHHGVEEEEIFQQQQBTTTTNOHHHHWFEEEDOHHHGWFFEEDOHHGGWFFFEDOHGGGWFFFFDWttttUKKKKL PYYYYDXXXXMnMMMMIMMMNanNMMMIMMNNanNNMMIMNNNanNNNMINNNNaACCCCKBBBBEcOOOOLPOOOPcOOOPLPPOOPcOOPPLPPPOPcOPPPLPPPPPSvvvvLeeeeV TuuuuKffffXlXXXXMXXXWslWXXXMXXWWslWWXXMXWWWslWWWXMWWWWsTSSSSERRRRVqVVVVPUVVVLqVVVUPUUVVLqVVUUPUUUVLqVUUUPUUUULIBBBBOooooK FiiiiQVVVVQFRRRRKSSSTmFQRRRKSSTTmFQQRRKSTTTmFQQQRKTTTTmJHHHHNEEEEBgQQQQJSTTTdgQQQRJSSTTdgQQRRJSSSTdgQRRRJSSSSdHrrrrSkkkkU AGGGGHRRRRDJDDDDUDDDCEJCDDDUDDCCEJCCDDUDCCCEJCCCDUCCCCEMNNNNWOOOOICBBBBXABBBhCBBBAXAABBhCBBAAXAAABhCBAAAXAAAAhJppppPAAAAG", + "token": "IUaqNDEppX5G9ZVMb1UkL6eT9bjqmbZsZ5l5xxG_nOMuozqt20L3jQr2GlUmCEkncC4Y91jj2tOQ1Io0N3K_8mTpNzEapB8QvjykS33ZZOmY3JFGBToFM_3C2InO0iGHc6QLTuAfMxFKPjWPGYujbx0voxLvbpUTYCcSsRNSulJ5OtX_jNgpF9tMMNvCEDegYRAGUE2vKwZ8-4b3Euy-SLb3Ensgo-vFFcKaiS4Musgz7xmXVEzO2ZGbXublNBdhpN-R7BmHDa0Cco8BCmBp3oyTpRRTWd5FLDdc_bkkl4ZWtM3YM_MEW7x_XfesO3lXMRclUc", + "canonical": "BIIIINDDDDLTEEEETEEEFKTFEEETEEFFKTFFEETEFFFKTFFFETFFFFKWVVVVAWWWWUMGGGGQHGGGtMGGGHQHHGGtMGGHHQHHHGtMGHHHQHHHHtObbbbFOOOOW XssssVLLLLKfWWWWRVVVUofXWWWRVVUUofXXWWRVUUUofXXXWRUUUUoKMMMMHPPPPOuXXXXSVUUUBuXXXWSVVUUBuXXWWSVVVUBuXWWWSVVVVBTllllTqqqqI MaaaaEPPPPVXNNNNBOOOPeXMNNNBOOPPeXMMNNBOPPPeXMMMNBPPPPeDIIIIJLLLLLYMMMMCOPPPvYMMMNCOOPPvYMMNNCOOOPvYMNNNCOOOOvPnnnnAccccS QmmmmBddddUVTTTTETTTSkVSTTTETTSSkVSSTTETSSSkVSSSTESSSSkQKKKKMJJJJSiRRRRHQRRRriRRRQHQQRRriRRQQHQQQRriRQQQHQQQQrFFFFFJggggH CSSSSXNNNNNHJJJJGKKKLWHIJJJGKKLLWHIIJJGKLLLWHIIIJGLLLLWGAAAARDDDDCQIIIIFKLLLZQIIIJFKKLLZQIIJJFKKKLZQIJJJFKKKKZEjjjjRUUUUR JCCCCMJJJJApBBBBNCCCDGpABBBNCCDDGpAABBNCDDDGpAAABNDDDDGPXXXXXUUUUHAAAAAOCDDDRAAAABOCCDDRAAABBOCCCDRAABBBOCCCCRGhhhhIEEEED" + }, + { + "id": 37, + "moves": "6R 6U 6F' 6R2 x 6U'", + "facelets": "EQQQQGHHHHCjIIIIAIIIJSjJIIIAIIJJSjJJIIAIJJJSjJJJIAJJJJSRFFFFSGGGGXUKKKKDLKKKNUKKKLDLLKKNUKKLLDLLLKNUKLLLDLLLLNRZZZZCWWWWN OMMMMWTTTTBbGGGGVFFFEIbHGGGVFFEEIbHHGGVFEEEIbHHHGVEEEEIFQQQQBTTTTNOHHHHWFEEEDOHHHGWFFEEDOHHGGWFFFEDOHGGGWFFFFDWttttUKKKKL PYYYYDXXXXMnMMMMIMMMNanNMMMIMMNNanNNMMIMNNNanNNNMINNNNaACCCCKBBBBEcOOOOLPOOOPcOOOPLPPOOPcOOPPLPPPOPcOPPPLPPPPPSvvvvLeeeeV TuuuuKffffXlXXXXMXXXWslWXXXMXXWWslWWXXMXWWWslWWWXMWWWWsTSSSSERRRRVqVVVVPUVVVLqVVVUPUUVVLqVVUUPUUUVLqVUUUPUUUULIBBBBOooooK FiiiiQVVVVQFRRRRKSSSTmFQRRRKSSTTmFQQRRKSTTTmFQQQRKTTTTmJHHHHNEEEEBgQQQQJSTTTdgQQQRJSSTTdgQQRRJSSSTdgQRRRJSSSSdHrrrrSkkkkU AGGGGHRRRRDJDDDDUDDDCEJCDDDUDDCCEJCCDDUDCCCEJCCCDUCCCCEMNNNNWOOOOICBBBBXABBBhCBBBAXAABBhCBBAAXAAABhCBAAAXAAAAhJppppPAAAAG", + "token": "DHp_05Jvng9anBf8qb_tkd7XfCVX-aRoptmNqqan2tUxfTYBMjkc1OQcSd_uQxtuyhFJXMFVcg9WT7PTlMsH-uXXdLJp_YumR1a9nE8xhdeZUrZ6QfXB838pETOtjsySy12EPZQLsyZ711QVqZRdSarx3ScZyXfnRA6nAmc_Bd7tdhA_1NEPaPI8klJoxhTcJGYCXh0hsCJu3lKcpxjHW0Scpu4sPXhp6Cj583FExgsTeamYv5zNkZaaQ5aV84jEndPWuImSpSDg6vWgY-Qns3S3XeefQbNZ8JTC3yVtuNJgg80xE3shomavwzzglk2Asmit_qw", + "canonical": "" + }, + { + "id": 36, + "moves": "10R' 1U 9D 9L' 6B 5U2 5D 2R", + "facelets": "DEEEEEhhDDGBVVVVWWWIJuRDCCCBBBRRARDDCCBBAJJARDDDCBAAJOAHEIEEHHHHKJGDDDDCBAKSpGDDDCCBBKKpGDDCCCBBLKpGDCCCCBBLLpAJJJJACCWWJ KooooIBBBvITEEEEOEEEFKjBIIIVIIJJSTFFEEOEFFFKrRRRQARRRRiRFFFFPGGGGXUCKKKLLKOKNMGGGHNHHGGtMGGHHNHHHGtMGHHHJHHHHtObbbbMOOOOW BIIIIBDDXXLjAIIIIIIMNSrRQQQQQQNNijBJIIIIJNNSLVAVUUVVVNqSBJJJIKKOTQkSSSSDTSSOVUCKKLLLLOONUCKLLLLLPONUCLLLLLLPPNRRZZZLWWeeN PQYYYKXXffMnIMMMMMMXWanJMMMMMMWWanJNMMMMNWWanJNNMMNNWGaDQUQQTTTTVNcKOOOPPOVBPcKOOPPPPVVPcKOPPPPPEFPcKPPPPPPUUPSZvvvOeeggV EAQQQGHHHHCrQQQQAQQQRiLVUUUKUUVOqrRRQQAQRRRiTFFFEOFFFFKVRRRRASSSSTsWAWWXXWWNlkSSSTDTTSSVkSSTTDTTTSVkSTTTDTTTTVUddddCmmmmQ HCCggFFFFFFLBBUUUUUUOqTABEEEEEFFKLAAUUUUVVOqjAJJIIJJNJSWHAVVUWWWNUMAGGGNHGGGtsAAWXXXXWNlsAAXXXXXXNlsXWXXXXXXMlXhhffPuuuYT", + "token": "Au7jVvk2xV3Ga-I6oWh_9pU4_brUrFeD0omIV_l5c0RFryo54z7RGDoA8wAa5xDDrn6b5PLZjjfqSQ_7OvAn0XPY_r4Q9AfJ0pmsCW44Vbdhv6N97zKZe2y0J9MPWzFgRGvFlJzBLkDj53EI4niNi7cmG8IcIUX-vYSYsarsGHLF2Eb0RbqpCVaP9-N1Fd_sKvLbJAXTZjGP539UncQNMWOrANs_DQL-WNL4lep4co0i_3jL-ni5hjr98nVkKC98UAF-76CC-BI4ctynYzYtbNR9r6TlLckEcIglUZjI0PLOKPn7a4kFjy_cXrTrmxTbimd6wR", + "canonical": "UkkksVTrLrEdSSSWRFRVQAdTSSARFRUQQdTTSWRFQUQQdTTTWREQUQQCDDDXBOAKAGmTTTXSFQUQHmTTTWSFRUQHmTTSWSFRVQHmTSSNSFRORHQVVVlTKiqiC AGGGGHRRRBDJDDDDEDDDVEJCDDDIDDCVEJCCDDEDCCVEJCCCDECCCVEACCCCEBBBWECBBBBHABBWhCBBBAHAABWhWLLKKHJJRIDWLKKSKOJRJDJppppJAAAuG RUUUkSLjrjBRCCCSBVBRAIZLKKSJAJQIIZLLKSJVIQIIZLLLSJUIQIILLLLDJUIQIBWLLLTKVIQIDWLLLSKVJQIDePPOSOVNNMXePOOOTNNNNXNNNNVQqSiSL OMMMURrTjTKbGGGCFRFBEobHGGKFRFIEobHHGKFREIEobHHHKFQEIEoMJNNLMAOVOIOHHHLGREIEBOHHHKGRFIEBOHHGOGRFJEBOHGGKGRFJFvWtttNXiKSKI SccccDnnnnPZKKKKQJJJIQvPOOOUNNMMYvPPOOQNMMMYvPPPOQMMMMYOPPPPQMMMMKePPPPTNMMMXePPPOTNNMMXgUEVVTWWWXfgUFVBVGWWWfVPPPPNaaaaM FqKqSUtlllTFOFOJNGNNMYFUFVNWGWXXuFUEVJWGXXXuFUEUJWHXXXuFUEUIXNXXXPgUEUIVGXXXfgUEUJVGWXXfCBBAJAGAAWhCBAAAHAAAXhHLTLjWMsssX" + }, + { + "id": 37, + "moves": "10R' 1U 9D 9L' 6B 5U2 5D 2R", + "facelets": "DEEEEEhhDDGBVVVVWWWIJuRDCCCBBBRRARDDCCBBAJJARDDDCBAAJOAHEIEEHHHHKJGDDDDCBAKSpGDDDCCBBKKpGDDCCCBBLKpGDCCCCBBLLpAJJJJACCWWJ KooooIBBBvITEEEEOEEEFKjBIIIVIIJJSTFFEEOEFFFKrRRRQARRRRiRFFFFPGGGGXUCKKKLLKOKNMGGGHNHHGGtMGGHHNHHHGtMGHHHJHHHHtObbbbMOOOOW BIIIIBDDXXLjAIIIIIIMNSrRQQQQQQNNijBJIIIIJNNSLVAVUUVVVNqSBJJJIKKOTQkSSSSDTSSOVUCKKLLLLOONUCKLLLLLPONUCLLLLLLPPNRRZZZLWWeeN PQYYYKXXffMnIMMMMMMXWanJMMMMMMWWanJNMMMMNWWanJNNMMNNWGaDQUQQTTTTVNcKOOOPPOVBPcKOOPPPPVVPcKOPPPPPEFPcKPPPPPPUUPSZvvvOeeggV EAQQQGHHHHCrQQQQAQQQRiLVUUUKUUVOqrRRQQAQRRRiTFFFEOFFFFKVRRRRASSSSTsWAWWXXWWNlkSSSTDTTSSVkSSTTDTTTSVkSTTTDTTTTVUddddCmmmmQ HCCggFFFFFFLBBUUUUUUOqTABEEEEEFFKLAAUUUUVVOqjAJJIIJJNJSWHAVVUWWWNUMAGGGNHGGGtsAAWXXXXWNlsAAXXXXXXNlsXWXXXXXXMlXhhffPuuuYT", + "token": "ARmVQJ10igMqaHTV_Icv_Hf1XyYPwKDRbvOTIP2NizmaIa_VtTeOaRXAWyAKFqZJYW96ddsRlVT322X-NhoO7otxX4dGW4Lrrvmgg4lVICTEp91PObL5jkjDju8lwjKEGahqF7rIcVhVdspjVO01FGSuSmjKjHo_hxG5QqAYiSsKMRqbmiX_Y4B1_PVL6DP4kBsSLYIvRlKV9s-_uymE8oVgIFI3pOEfYU8dODftKvTtH81Mfe1FklYfOuwFjxHOngCPmdwxHQbVKxK-xTRRCM-vId3V8SthqjMN_plLTlsNT12-SFNiFbHyo4PYWifSU-bOCGs", + "canonical": "" + }, + { + "id": 36, + "moves": "9D 5D' 7D2 5D' 1U 5D 8D' 7U x y'", + "facelets": "LSiKqQlNNNNDJRFVKWKKLWDIRFVKWKLLWDIQFVKWLLLWDIQEVKXLLLWBIQEULXLLLLIIQEUJWLLLZIIQEVJWKLLZIIQFVJWKKLZIIRFVJWKKKZBjrTLSsUUUR PYYYYKXXXXMnMMMMMMMMNanNMMMMMMNNanNNMMMMNNNanNNNMMNNNNaMNNNNMOOOOIcOOOOPPOOOPcOOOPPPPOOPcOOPPPPPPOPcOPPPPPPPPPSvvvvOeeeeV CiqSKTtVVVQHRVJFSGSSTmHQVJFSGSTTmHQUJFSGTTTmHQUIFSHTTTmDQUIETHTTTNQQUIERGTTTdQQUIFRGSTTdQQUJFRGSSTdQQVJFRGSSSdErLjTVMkkkU FqKiSUNlllTFVFRJWKWWXuFUFRJWKWXXuFUERJWKXXXuFUEQJWLXXXuFUEQIXLXXXPgUEQIVKXXXfgUEQJVKWXXfgUERJVKWWXfgUFRJVKWWWfHLTrjWUsssX JCCCCAJJJJApBBBBCCCCDGpABBBCCCDDGpAABBCCDDDGpAAABCDDDDGGAAAADDDDDCAAAAABCDDDRAAAABBCCDDRAAABBBCCCDRAABBBBCCCCRGhhhhEEEEED OMMMkRrLjTKbGGGSFRVJEobHGGSFRVIEobHHGSFRUIEobHHHSFQUIEoJHHHTFQUIEHOHHHTGRUIEBOHHHSGRVIEBOHHGSGRVJEBOHGGSGRVJFBWtttVXiqSKI", + "token": "DSkV44_U6AStbW20FZctjpZcKX8ckiCwQPnhoGaSTbaazOn84-K6bqVUv7vzusBBPPPQnbz_wg2NRseCPwNellE8Zjcr7FDroW42_6tqQZWIO6BDY9p0Lrw4srGcxB-5x5areyUC_CyZyDdM5HzNVwRnUZBDx1leeXiFM0qRN63wj49ANm-P7xCho-xuGzG-znwfPhBKa-YSGpn6FIsMrDM5VdXq2hvnYfbSXqLyVh8KSFxbMnCCfDacal7CTiRLlGj7wunztTCKCj4HsCHMeEaTLPwl1j6wMov3c91VQW0OA90sxfBoCNlnPwxDM6zzyDB2gB", + "canonical": "DEEEEEhhhhGRCCCCBBBBAARDCCCBBBAAARDDCCBBAAAARDDDCBAAAAACDDDDBAAAAGGDDDDCBAAApGDDDCCBBAApGDDCCCBBBApGDCCCCBBBBpAJJJJACCCCJ KooooHBBBBITEEEEEEEEFKjJIIIIIIJJSLVVUUUUVVVqrRRRQQRRRRiRFFFFEGGGGXkSSSSTTSSSVMGGGHHHHGGtMGGHHHHHHGtMGHHHHHHHHtObbbbJOOOOW BIIIIBDDDDLjIIIIIIIIJSrRQQQQQQRRiTFFEEEEFFFKLVVVUUVVVVqSJJJJIKKKKQsWWWWXXWWWlUKKKLLLLKKNUKKLLLLLLKNUKLLLLLLLLNRZZZZLWWWWN PYYYYKXXXXMnMMMMMMMMNanNMMMMMMNNanNNMMMMNNNanNNNMMNNNNaMNNNNMOOOOIcOOOOPPOOOPcOOOPPPPOOPcOOPPPPPPOPcOPPPPPPPPPSvvvvOeeeeV EQQQQDHHHHCrQQQQQQQQRiLVUUUUUUVVqjJJIIIIJJJSTFFFEEFFFFKVRRRRQSSSSTMGGGGHHGGGtkSSSTTTTSSVkSSTTTTTTSVkSTTTTTTTTVUddddNmmmmQ HggggFFFFFFLUUUUUUUUVqTFEEEEEEFFKrRRQQQQRRRijJJJIIJJJJSWVVVVUWWWWUUKKKKLLKKKNsWWWXXXXWWlsWWXXXXXXWlsWXXXXXXXXlXffffPuuuuT" + }, + { + "id": 37, + "moves": "9D 5D' 7D2 5D' 1U 5D 8D' 7U x y'", + "facelets": "LSiKqQlNNNNDJRFVKWKKLWDIRFVKWKLLWDIQFVKWLLLWDIQEVKXLLLWBIQEULXLLLLIIQEUJWLLLZIIQEVJWKLLZIIQFVJWKKLZIIRFVJWKKKZBjrTLSsUUUR PYYYYKXXXXMnMMMMMMMMNanNMMMMMMNNanNNMMMMNNNanNNNMMNNNNaMNNNNMOOOOIcOOOOPPOOOPcOOOPPPPOOPcOOPPPPPPOPcOPPPPPPPPPSvvvvOeeeeV CiqSKTtVVVQHRVJFSGSSTmHQVJFSGSTTmHQUJFSGTTTmHQUIFSHTTTmDQUIETHTTTNQQUIERGTTTdQQUIFRGSTTdQQUJFRGSSTdQQVJFRGSSSdErLjTVMkkkU FqKiSUNlllTFVFRJWKWWXuFUFRJWKWXXuFUERJWKXXXuFUEQJWLXXXuFUEQIXLXXXPgUEQIVKXXXfgUEQJVKWXXfgUERJVKWWXfgUFRJVKWWWfHLTrjWUsssX JCCCCAJJJJApBBBBCCCCDGpABBBCCCDDGpAABBCCDDDGpAAABCDDDDGGAAAADDDDDCAAAAABCDDDRAAAABBCCDDRAAABBBCCCDRAABBBBCCCCRGhhhhEEEEED OMMMkRrLjTKbGGGSFRVJEobHGGSFRVIEobHHGSFRUIEobHHHSFQUIEoJHHHTFQUIEHOHHHTGRUIEBOHHHSGRVIEBOHHGSGRVJEBOHGGSGRVJFBWtttVXiqSKI", + "token": "BO9oNVXv1wHBCQkjiBixFXhij4-qtsxCGF20nCZ23SR6DNe-1XUF6X3_x-Z7ZggYdttuOybf6MUU-orQ16FDeF52plSweJ5YXIlUn-BH2JgTFlwZRXHrkYaVQwKayYvlqtiATi3hHpC5qxS81a7NAKGmvpYZasGDbY0x8zv2dOE6NdW4FGnV-aY8nXipSjKnjW6Lt0Yb6HZGyfm9x7QkwJM1gDA4Ecp2xLyO430a4Euj2yKiMuow7pR6p-OI3U2cV6deaRe7Y_Izw9dC4gysrRp3MN6OMFeCEvR8y3L_-IjlQXLwyjonA1GGt6SZM2DbaxIscAf", + "canonical": "" + }, + { + "id": 40, + "moves": "", + "facelets": "GAAAAApppppJhAAAAAAAAABChBAAAAAAABBChBBAAAAABBBChBBBAAABBBBChBBBBABBBBBCECCCCCDCCCCJECCCCDDDCCCJECCCDDDDDCCJECCDDDDDDDCJECDDDDDDDDDJDRRRRRGGGGGA BIIIIIDDDDDLTEEEEEEEEEFKTFEEEEEEEFFKTFFEEEEEFFFKTFFFEEEFFFFKTFFFFEFFFFFKMGGGGGHGGGGtMGGGGHHHGGGtMGGGHHHHHGGtMGGHHHHHHHGtMGHHHHHHHHHtObbbbbOOOOOW EQQQQQHHHHHCjIIIIIIIIIJSjJIIIIIIIJJSjJJIIIIIJJJSjJJJIIIJJJJSjJJJJIJJJJJSUKKKKKLKKKKNUKKKKLLLKKKNUKKKLLLLLKKNUKKLLLLLLLKNUKLLLLLLLLLNRZZZZZWWWWWN PYYYYYXXXXXMnMMMMMMMMMNanNMMMMMMMNNanNNMMMMMNNNanNNNMMMNNNNanNNNNMNNNNNacOOOOOPOOOOPcOOOOPPPOOOPcOOOPPPPPOOPcOOPPPPPPPOPcOPPPPPPPPPPSvvvvveeeeeV HgggggFFFFFFrQQQQQQQQQRirRQQQQQQQRRirRRQQQQQRRRirRRRQQQRRRRirRRRRQRRRRRikSSSSSTSSSSVkSSSSTTTSSSVkSSSTTTTTSSVkSSTTTTTTTSVkSTTTTTTTTTVUdddddmmmmmQ KoooooBBBBBILUUUUUUUUUVqLVUUUUUUUVVqLVVUUUUUVVVqLVVVUUUVVVVqLVVVVUVVVVVqsWWWWWXWWWWlsWWWWXXXWWWlsWWWXXXXXWWlsWWXXXXXXXWlsWXXXXXXXXXlXfffffuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 41, + "moves": "", + "facelets": "GAAAAApppppJhAAAAAAAAABChBAAAAAAABBChBBAAAAABBBChBBBAAABBBBChBBBBABBBBBCECCCCCDCCCCJECCCCDDDCCCJECCCDDDDDCCJECCDDDDDDDCJECDDDDDDDDDJDRRRRRGGGGGA BIIIIIDDDDDLTEEEEEEEEEFKTFEEEEEEEFFKTFFEEEEEFFFKTFFFEEEFFFFKTFFFFEFFFFFKMGGGGGHGGGGtMGGGGHHHGGGtMGGGHHHHHGGtMGGHHHHHHHGtMGHHHHHHHHHtObbbbbOOOOOW EQQQQQHHHHHCjIIIIIIIIIJSjJIIIIIIIJJSjJJIIIIIJJJSjJJJIIIJJJJSjJJJJIJJJJJSUKKKKKLKKKKNUKKKKLLLKKKNUKKKLLLLLKKNUKKLLLLLLLKNUKLLLLLLLLLNRZZZZZWWWWWN PYYYYYXXXXXMnMMMMMMMMMNanNMMMMMMMNNanNNMMMMMNNNanNNNMMMNNNNanNNNNMNNNNNacOOOOOPOOOOPcOOOOPPPOOOPcOOOPPPPPOOPcOOPPPPPPPOPcOPPPPPPPPPPSvvvvveeeeeV HgggggFFFFFFrQQQQQQQQQRirRQQQQQQQRRirRRQQQQQRRRirRRRQQQRRRRirRRRRQRRRRRikSSSSSTSSSSVkSSSSTTTSSSVkSSSTTTTTSSVkSSTTTTTTTSVkSTTTTTTTTTVUdddddmmmmmQ KoooooBBBBBILUUUUUUUUUVqLVUUUUUUUVVqLVVUUUUUVVVqLVVVUUUVVVVqLVVVVUVVVVVqsWWWWWXWWWWlsWWWWXXXWWWlsWWWXXXXXWWlsWWXXXXXXXWlsWXXXXXXXXXlXfffffuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 40, + "moves": "10U 10B 12F' 11F2 12R' 3L 5B' x2", + "facelets": "EYQYYYXXXXXMjMIMMMMMMMNajQIRRRSSSLTmjNJMMMMMNNNajNJNMMMNNNNajNJNNMNNNNNaUOKOOOPOOOOPOHXHHGFFEEEDUOKOPPPPPOOPMOGPPPPPPPOPUBLAAAAAAAAhRrZrrrkkkUkU SgOOCObbbnbOvQHHBHHHHNGMvRXXBXXXXMWsvRGHBHHHGMGMvRGGBHHGGMGMvRGGAHGGGMGMeSFFAFEFFMFTeSFFAEEEFMFTeSFFBEEEEMFTeKFEUEEEENFTeSEEBEEEENETVdDDBDIIIaIB PqYqqqlllVlTnVMVVVWWWSXuFURVVVWWWTXunUNVVVWWXTXunUNUVVWXXTXunUNUUVXXXTXucUOUUUWXXTXfcUOUUVWWXTXfcUOUVVWWWTXfcUOVVVWWWSXfCUAVVVWWWSWfHLrLLLsssksX IKqKKKtttltWBPVPPPPPPPOcBBVAAAAAABBCBBUAAAAABBBCVSDSTTTSSSFABBUBBABBBBBCoCUCCCDCCCCJoCUCCDDDCCCJoCUCDDDDDCCJoEUFFFGGGWHboCVDDDDDDDDJKRLRRRGGGGGA FiiiiiVPVNVQFRRRRRSOSKTmJCDDDDDODCCEFQQRRRSOTLTmFQQQRRSPTLTmFQQQQRTPTLTmgQQQQQSPTLTdgQQQQRSOTLTdgQQQRRSOSLTdgQQRRRSKSKTdOHWGGGFOFFFDJpppppAUAAAG NWWWWWZZZRZDNLLLLLLLLDKEtGHHHHHHHEGINKKLLLLLKCKENKKKLLLKKCKENKKKKLKKKCKESJJJJJIJJBJhSJJJJIIIJBJhSJJJIIIIIBJhSJJIIIIIIAJhSJIIIIIIIPIPCHHHHHQQQKQL", + "token": "HLRYbmwOhn8pU5RmKrokWjomy7AP5p3-kVrZtCvAAi4m58igaYH6Qr8hQOE7u08GrhraZNiBAwhBWghBwURTehd794lv_Cd4dLDj9ziYUdoxGF5En91qVOTnhR6qoTwn9IYQWUrI1jPPa1AzBTr2tMs4mrNQNmPr0pQNFwASBSU4CnzHsKlY_7zFfvqZoEb5vHm24m_DHVwT2w8s77nYc9PbHlfOEj4MjQl54kFvDu5gvI4JAmJkTn0Wn1QzinV2LES8r4_x-TmVQfZ13rn8bKIgMGX_GvhA_eACSGeQvsm-IT6FdHehjHhd1ZSUMoSGKr3CRttHSDYPkczl57T7MmBDkCb1KMVgckWPmyP0_jbNmJ67K8ouwZ8V56T9LQXrxVhVgzSRfus", + "canonical": "QmEmmmddddDGVTCTTTTTTTFANKCLLLLLLKFAVSDTTTTTSSFAPOOOPPPOOKOUVSDSSTSSSSFAiRDRRRQRRRGpiRDRRQQQRRGpiRDRQQQQQRGpiRDQQQQQQQWpiRCQQQQQQQHpFFJFFFggggOJ KoooooBVBBBIRCECCCBSBBPKLVUUUUUDUVVqRDFCCCBSAAPKRDFDCCBTAAPKRDFDDCATAAPKGDGDDDBTAAPtGDGDDCBSAAPtGDGDCCBSBAPtGDWCCCBSBBPlGDHCCCBFBBOtAJbJJJCACCcW DEIEEEhhhhPLZKGKKKJJJJIQRDECCCBBBAPKZLHKKKJJIIIQZLHLKKJIIIIQZLHLLKIIIIIQWLHLLLJIIIIHWLHLLKJJIIIHWLHLKKJJJIIHWLHKKKJJJJIHWLGKKKJJJJJHNNtNNNSSSSSC OMsMMMTTTTTBbGWGGGFFFFEInNMMMMMMMNNabHXGGGFFEEEIbHXHGGFEEEEIbHXHHGEEEEEIOHXHHHFEEEEDCBBBBAAABUBBOHXHGGFFFEEDOHXGGGFFFFEDgQRRRRSSSKSdSvvvvveeeeeV RUMUOUjjjjjErBOOHONNNQMYZLGKXKJJJIIQrAPOHONNMRMYrAPPHONMMRMYrAPPGOMMMRMYkAPPFPNMMSMXkAPPFONNMSMXkAPPEONNNSMXUAPOEONNNLMXkAOOEONNNTNXUhPPDPaaamaM HCccccnnnFnPLUUUUUUUUUVqrAOOOONNNRMYLVVUUUUUVVVqLVVVUUUVVVVqLVVVVUVVVVVqsWWWWWXWWWWlsWWWWXXXWWWlsWWWXXXXXWWlkSSTTTTTTTSVsWXXXXXXXXXlXfffffuuuuuT" + }, + { + "id": 41, + "moves": "10U 10B 12F' 11F2 12R' 3L 5B' x2", + "facelets": "EYQYYYXXXXXMjMIMMMMMMMNajQIRRRSSSLTmjNJMMMMMNNNajNJNMMMNNNNajNJNNMNNNNNaUOKOOOPOOOOPOHXHHGFFEEEDUOKOPPPPPOOPMOGPPPPPPPOPUBLAAAAAAAAhRrZrrrkkkUkU SgOOCObbbnbOvQHHBHHHHNGMvRXXBXXXXMWsvRGHBHHHGMGMvRGGBHHGGMGMvRGGAHGGGMGMeSFFAFEFFMFTeSFFAEEEFMFTeSFFBEEEEMFTeKFEUEEEENFTeSEEBEEEENETVdDDBDIIIaIB PqYqqqlllVlTnVMVVVWWWSXuFURVVVWWWTXunUNVVVWWXTXunUNUVVWXXTXunUNUUVXXXTXucUOUUUWXXTXfcUOUUVWWXTXfcUOUVVWWWTXfcUOVVVWWWSXfCUAVVVWWWSWfHLrLLLsssksX IKqKKKtttltWBPVPPPPPPPOcBBVAAAAAABBCBBUAAAAABBBCVSDSTTTSSSFABBUBBABBBBBCoCUCCCDCCCCJoCUCCDDDCCCJoCUCDDDDDCCJoEUFFFGGGWHboCVDDDDDDDDJKRLRRRGGGGGA FiiiiiVPVNVQFRRRRRSOSKTmJCDDDDDODCCEFQQRRRSOTLTmFQQQRRSPTLTmFQQQQRTPTLTmgQQQQQSPTLTdgQQQQRSOTLTdgQQQRRSOSLTdgQQRRRSKSKTdOHWGGGFOFFFDJpppppAUAAAG NWWWWWZZZRZDNLLLLLLLLDKEtGHHHHHHHEGINKKLLLLLKCKENKKKLLLKKCKENKKKKLKKKCKESJJJJJIJJBJhSJJJJIIIJBJhSJJJIIIIIBJhSJJIIIIIIAJhSJIIIIIIIPIPCHHHHHQQQKQL", + "token": "E21PklMf1pz2LeVQTpjOTp2fGBikdX9_Eh0g44H_wnmwL8i5E_lQB2llWzjNbFFmGU4dkiy1hrbUIiUPyhAfusBYaM7aZpp5X7W0NXu2D0pC8SmoE79XujZTYpRHK58zE2jafODMAt1irysfKb-WIouQlsuZgPlPrAEF4HfcrdnWlaltSf50ByLUoB3gvZdYbhFKr_yJAdZlyb9aQZpnGWpI9489PT7Ij_V0I7W8r2j02BKRhdkj6pLuyy7T83qp3xACRYZF4Rx-pOZSlUUjS-_cv5rTBICPlRZLqrFcJrSC5xgesg4clVBUXE0HxkV-ZB-mutWaNZOAUbP-TsSK8yvesIC14e9jmSre1iQ3mcgMhQLwy7umZVM12-vxMNNfuXIm0gNUff7T", + "canonical": "" + }, + { + "id": 40, + "moves": "3R2 5L 3L 4D' 8F' 11F' z 5D2 x y'", + "facelets": "EQAQAQHfHfHCjIAIAIIXIXJSjJAIAIIXIWJSjJBIAIITJWJSjJBJAIIWJWJSjJBJBIJWJWJSUKCKCKLVKVKNUKCKCLLUKVKNMGGGHHHUHGGtUKCLDLLULUKNUKDLDLLULULNRZRZRZWoWoWN OMMsMMTTTTTBbGGWGGFFFFEIbHGJGGFFFEEIbHHWGGFFEEEIbHHXGGFEEEEIbHHXHGEEEEEIOHHXHHFEEEEDcOKOKPPPOCOPOHHXGGFFFEEDOHHNGGFFFFEDcOLPLPPPPDPPWttlttKKKKKL PYQYQYXpXpXMnMIMIMMGMANanNIMIMMAMBNanNJMIMMANBNanNJNIMMHNBNanNJNJMNBNBNacOKOKOPCOCOPgQQQQRSDDTTdcOKOLPPDPCOPcOKPLPPDPDOPgQRRRRSDKSSdSvZvZveGeGeV TuYuYufHfHfXlXMXMXXIXIWslWMXMXXIXJWsVSSTTTTISSSklWNWMXXJWJWslWNWNXWJWJWsqVOVOVUKVKVLqVOVOUULVKVLqVOVPUUHUKVLqVOUPUULULVLqVPUPUULULULIBvBvBoWoWoK FiiiiiVVNVVQFRRRRRSSKSTmFQRRRRSSVTTmFQQRRRSSLTTmFQQQRRSTLTTmFQQQQRTTLTTmgQQQQQSTLTTdCBNBBAAXBWBhgQQQRRSSKTTdgQQRRRSSCSTdCBMAAAAXAXAhHrrrrrkkUkkU AGeGeGRBRBRDJDPDSDDUDUCEJCPDPDDUDVCEJCODPDDUCVCEJCOCSDDVCVCEJCOCODCVCVCECBNBNBAWBWBhOHHMNGFFEEEDCBNBMAAXAWBhCBNAMAAXAXBhOHGWMGFFFFFDJpXpXpAuAuAG", + "token": "AAAAAxGRc4mYtzlPR3yldKig0Z2x77TljHButp4WZsEriceApgHWKGISlfEF4qBDnqJAcl-N65iLxCep-Z3YJJpEGtp6fnCIE-6BBvJs8Hs9kr782skxBLIFsQz8HxH_DFAlHI4p5GTMOJM_-EjuojWoxkrHMJ1Q6npnx4pqBO3cnr4cVg2xAz0MHLIKz6oMfe190jaH0P5-VmiTscTecT68xJJrg5Ayw4yRUU5J5au69vJVrQXNrPIbsasjbC-18KyFceU77OIhvnFIkUgMiqmijvzyyt63xv9l2WZd-OcLHpScHmb-B90TACIzfRENZBGgR0-4lesTqD-Mxs0zbO743rmbeusxJjAfDYjcRh0HzoE8ei4UNp9SwmRg0kcqUxi-9UyQAAA", + "canonical": "GAuAuApXpXpJDFFFFFGMWGHOhBXAXAAMANBChBWAXAAMBNBCDEEEFFGNMHHOhBWBWABNBNBCECVCVCDOCOCJECVCVDDSCOCJECVCUDDPDOCJECVDUDDPDPCJECUDUDDSDPDJDRBRBRGeGeGA BIIIIIDPDDPLTEEEEEEOEEPKTFEEEEECEFDKTFFEEEEOFFPKTFFFEEEPFFPKTFFFFEFPFFPKMGGGGGHPGGPtMGGGGHHKGGLtsWJWXXXOXNPlMGGHHHHKHHLtMGHHHHHOHHOtObbbbbOcOOcW EQAQAQHfHfHCjIAIAIIXIXJSjJAIAIIXIWJSjJBIAIITJWJSjJBJAIIWJWJSjJBJBIJWJWJSUKCKCKLVKVKNUKCKCLLUKVKNMGGGHHHUHGGtUKCLDLLULUKNUKDLDLLULULNRZRZRZWoWoWN PYQYQYXpXpXMnMIMIMMGMANanNIMIMMAMBNanNJMIMMANBNanNJNIMMHNBNanNJNJMNBNBNacOKOKOPCOCOPgQQQQRSDDTTdcOKOLPPDPCOPcOKPLPPDPDOPgQRRRRSDKSSdSvZvZveGeGeV HCggCgFFFFFFrBQQBQQQQQRirMQQNQQQQRRirARQBQQQRRRirARRBQQRRRRirARRAQRRRRRikASSASTSSSSVkXSSXTTTSSSVUACKBLLLLVKNkXSTWTTTTTSVkATTBTTTTTTVUhddhdmmmmmQ KoWoWoBvBvBILULULUUPUPVqLVLULUUPUOVqLVKUHUUPVOVqLVKVLUUOVOVqLVKVKUVOVOVqsWJWJWXNWNWlsWJWJXXMWNWlkSSSITTTTSSVsWJXIXXMXMWlsWIXIXXMXMXlXfHfHfuYuYuT" + }, + { + "id": 41, + "moves": "3R2 5L 3L 4D' 8F' 11F' z 5D2 x y'", + "facelets": "EQAQAQHfHfHCjIAIAIIXIXJSjJAIAIIXIWJSjJBIAIITJWJSjJBJAIIWJWJSjJBJBIJWJWJSUKCKCKLVKVKNUKCKCLLUKVKNMGGGHHHUHGGtUKCLDLLULUKNUKDLDLLULULNRZRZRZWoWoWN OMMsMMTTTTTBbGGWGGFFFFEIbHGJGGFFFEEIbHHWGGFFEEEIbHHXGGFEEEEIbHHXHGEEEEEIOHHXHHFEEEEDcOKOKPPPOCOPOHHXGGFFFEEDOHHNGGFFFFEDcOLPLPPPPDPPWttlttKKKKKL PYQYQYXpXpXMnMIMIMMGMANanNIMIMMAMBNanNJMIMMANBNanNJNIMMHNBNanNJNJMNBNBNacOKOKOPCOCOPgQQQQRSDDTTdcOKOLPPDPCOPcOKPLPPDPDOPgQRRRRSDKSSdSvZvZveGeGeV TuYuYufHfHfXlXMXMXXIXIWslWMXMXXIXJWsVSSTTTTISSSklWNWMXXJWJWslWNWNXWJWJWsqVOVOVUKVKVLqVOVOUULVKVLqVOVPUUHUKVLqVOUPUULULVLqVPUPUULULULIBvBvBoWoWoK FiiiiiVVNVVQFRRRRRSSKSTmFQRRRRSSVTTmFQQRRRSSLTTmFQQQRRSTLTTmFQQQQRTTLTTmgQQQQQSTLTTdCBNBBAAXBWBhgQQQRRSSKTTdgQQRRRSSCSTdCBMAAAAXAXAhHrrrrrkkUkkU AGeGeGRBRBRDJDPDSDDUDUCEJCPDPDDUDVCEJCODPDDUCVCEJCOCSDDVCVCEJCOCODCVCVCECBNBNBAWBWBhOHHMNGFFEEEDCBNBMAAXAWBhCBNAMAAXAXBhOHGWMGFFFFFDJpXpXpAuAuAG", + "token": "DTmlnAGTALLF3eKONaCe41LbsG38cpKTa8dlna6FB109JUOcrOuvJNp3lfD_6E5iICei9XCf9ltvo2B7aduZ9ORKqZClLMTBxp4bws-UjtfWhrH62v69KPQcoF5Y836df7L8mJ7WbhSTlMB-uR5I7nQQIjQ7sPmKI6yUh0Cad4Q47vCaX0vUc7VDeF27v6B2q70koxgR6OZwVBrWzxyvxC8ub1DupRm_rLlSbKDHTa5_a6Aw9zfO0yZaYbj9pDjYX48gHDpJq7crSH5caNMIrxkHBXOGO1qmJem9zxjv3OIVHmS9OPzZSOXKvnTTDt6PVto0Dr3WvEw0uSHq4jeBMSbrjYheGYK8FyRtol0n40fteb1qNjhwHt8XSqOo_uTaIYOTehl4RLoT", + "canonical": "" + }, + { + "id": 44, + "moves": "", + "facelets": "GAAAAAGpppppJhAAAAAAAAAABChBAAAAAAAABBChBBAAAAAABBBChBBBAAAABBBBChBBBBAABBBBBCEBBBBBACCCCCAECCCCCDDCCCCJECCCCDDDDCCCJECCCDDDDDDCCJECCDDDDDDDDCJECDDDDDDDDDDJDRRRRRCGGGGGA BIIIIIBDDDDDLTEEEEEEEEEEFKTFEEEEEEEEFFKTFFEEEEEEFFFKTFFFEEEEFFFFKTFFFFEEFFFFFKRFFFFFEGGGGGXMGGGGGHHGGGGtMGGGGHHHHGGGtMGGGHHHHHHGGtMGGHHHHHHHHGtMGHHHHHHHHHHtObbbbbJOOOOOW EQQQQQDHHHHHCjIIIIIIIIIIJSjJIIIIIIIIJJSjJJIIIIIIJJJSjJJJIIIIJJJJSjJJJJIIJJJJJSSJJJJJIKKKKKQUKKKKKLLKKKKNUKKKKLLLLKKKNUKKKLLLLLLKKNUKKLLLLLLLLKNUKLLLLLLLLLLNRZZZZZLWWWWWN PYYYYYKXXXXXMnMMMMMMMMMMNanNMMMMMMMMNNanNNMMMMMMNNNanNNNMMMMNNNNanNNNNMMNNNNNaMNNNNNMOOOOOIcOOOOOPPOOOOPcOOOOPPPPOOOPcOOOPPPPPPOOPcOOPPPPPPPPOPcOPPPPPPPPPPPSvvvvvOeeeeeV HgggggFFFFFFFrQQQQQQQQQQRirRQQQQQQQQRRirRRQQQQQQRRRirRRRQQQQRRRRirRRRRQQRRRRRiVRRRRRQSSSSSTkSSSSSTTSSSSVkSSSSTTTTSSSVkSSSTTTTTTSSVkSSTTTTTTTTSVkSTTTTTTTTTTVUdddddNmmmmmQ KoooooHBBBBBILUUUUUUUUUUVqLVUUUUUUUUVVqLVVUUUUUUVVVqLVVVUUUUVVVVqLVVVVUUVVVVVqWVVVVVUWWWWWUsWWWWWXXWWWWlsWWWWXXXXWWWlsWWWXXXXXXWWlsWWXXXXXXXXWlsWXXXXXXXXXXlXfffffPuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 45, + "moves": "", + "facelets": "GAAAAAGpppppJhAAAAAAAAAABChBAAAAAAAABBChBBAAAAAABBBChBBBAAAABBBBChBBBBAABBBBBCEBBBBBACCCCCAECCCCCDDCCCCJECCCCDDDDCCCJECCCDDDDDDCCJECCDDDDDDDDCJECDDDDDDDDDDJDRRRRRCGGGGGA BIIIIIBDDDDDLTEEEEEEEEEEFKTFEEEEEEEEFFKTFFEEEEEEFFFKTFFFEEEEFFFFKTFFFFEEFFFFFKRFFFFFEGGGGGXMGGGGGHHGGGGtMGGGGHHHHGGGtMGGGHHHHHHGGtMGGHHHHHHHHGtMGHHHHHHHHHHtObbbbbJOOOOOW EQQQQQDHHHHHCjIIIIIIIIIIJSjJIIIIIIIIJJSjJJIIIIIIJJJSjJJJIIIIJJJJSjJJJJIIJJJJJSSJJJJJIKKKKKQUKKKKKLLKKKKNUKKKKLLLLKKKNUKKKLLLLLLKKNUKKLLLLLLLLKNUKLLLLLLLLLLNRZZZZZLWWWWWN PYYYYYKXXXXXMnMMMMMMMMMMNanNMMMMMMMMNNanNNMMMMMMNNNanNNNMMMMNNNNanNNNNMMNNNNNaMNNNNNMOOOOOIcOOOOOPPOOOOPcOOOOPPPPOOOPcOOOPPPPPPOOPcOOPPPPPPPPOPcOPPPPPPPPPPPSvvvvvOeeeeeV HgggggFFFFFFFrQQQQQQQQQQRirRQQQQQQQQRRirRRQQQQQQRRRirRRRQQQQRRRRirRRRRQQRRRRRiVRRRRRQSSSSSTkSSSSSTTSSSSVkSSSSTTTTSSSVkSSSTTTTTTSSVkSSTTTTTTTTSVkSTTTTTTTTTTVUdddddNmmmmmQ KoooooHBBBBBILUUUUUUUUUUVqLVUUUUUUUUVVqLVVUUUUUUVVVqLVVVUUUUVVVVqLVVVVUUVVVVVqWVVVVVUWWWWWUsWWWWWXXWWWWlsWWWWXXXXWWWlsWWWXXXXXXWWlsWWXXXXXXXXWlsWXXXXXXXXXXlXfffffPuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 44, + "moves": "7R 7U 7F' 7R2 x 7U'", + "facelets": "EQQQQQGHHHHHCjIIIIIAIIIIJSjJIIIIAIIIJJSjJJIIIAIIJJJSjJJJIIAIJJJJSjJJJJIAJJJJJSRFFFFFSGGGGGXUKKKKKDLKKKKNUKKKKLDLLKKKNUKKKLLDLLLKKNUKKLLLDLLLLKNUKLLLLDLLLLLNRZZZZZCWWWWWN OMMMMMWTTTTTBbGGGGGVFFFFEIbHGGGGVFFFEEIbHHGGGVFFEEEIbHHHGGVFEEEEIbHHHHGVEEEEEIFQQQQQBTTTTTNOHHHHHWFEEEEDOHHHHGWFFEEEDOHHHGGWFFFEEDOHHGGGWFFFFEDOHGGGGWFFFFFDWtttttUKKKKKL PYYYYYDXXXXXMnMMMMMIMMMMNanNMMMMIMMMNNanNNMMMIMMNNNanNNNMMIMNNNNanNNNNMINNNNNaACCCCCKBBBBBEcOOOOOLPOOOOPcOOOOPLPPOOOPcOOOPPLPPPOOPcOOPPPLPPPPOPcOPPPPLPPPPPPSvvvvvLeeeeeV TuuuuuKfffffXlXXXXXMXXXXWslWXXXXMXXXWWslWWXXXMXXWWWslWWWXXMXWWWWslWWWWXMWWWWWsTSSSSSERRRRRVqVVVVVPUVVVVLqVVVVUPUUVVVLqVVVUUPUUUVVLqVVUUUPUUUUVLqVUUUUPUUUUULIBBBBBOoooooK FiiiiiQVVVVVQFRRRRRKSSSSTmFQRRRRKSSSTTmFQQRRRKSSTTTmFQQQRRKSTTTTmFQQQQRKTTTTTmJHHHHHNEEEEEBgQQQQQJSTTTTdgQQQQRJSSTTTdgQQQRRJSSSTTdgQQRRRJSSSSTdgQRRRRJSSSSSdHrrrrrSkkkkkU AGGGGGHRRRRRDJDDDDDUDDDDCEJCDDDDUDDDCCEJCCDDDUDDCCCEJCCCDDUDCCCCEJCCCCDUCCCCCEMNNNNNWOOOOOICBBBBBXABBBBhCBBBBAXAABBBhCBBBAAXAAABBhCBBAAAXAAAABhCBAAAAXAAAAAhJpppppPAAAAAG", + "token": "WK_A9vaQpGd-zNb41g9VTIvUWXOMKfAxoO7K4KoGHAS0M5-QEBROsti7T-nkQ47zy7hrYrx8n9_O8iGMB9rkYxWtZx0AfQ679Eyxou_NPmfZrVfxH8QGopqB6Z-HsSgLQbbTQ3BtjiCaURLHGRgAtfahvGNKjoh2_MgjVbFK46Mm3fBnpi7L-3tiXkx1r28GDmfyG-myayw0TV2hB7Bp9YAuf6lXT8rkvlTFKDU29wrjZdHuNBg7yQ0KyOerYSoIi8K5EzL_Pddh2d3DB9JZq0yg9iN8Xz7j__wP3dhedilr6_9yrwKbXOy1-D-_W5NbJe29zlP63bePhcqbkGQEevcGhDz2YZZBVAUvcwPaj6bzH9eX1XdeNHgsL1DtNpXHpRibGqeLub_FddhpN-R7BmHDa0Cco8BCmBp3oyTpRRTWd5FLDdc_bkkl4ZWtM3YM_MEW7x_XfesO3lXMRclUc", + "canonical": "BIIIIINDDDDDLTEEEEETEEEEFKTFEEEETEEEFFKTFFEEETEEFFFKTFFFEETEFFFFKTFFFFETFFFFFKWVVVVVAWWWWWUMGGGGGQHGGGGtMGGGGHQHHGGGtMGGGHHQHHHGGtMGGHHHQHHHHGtMGHHHHQHHHHHtObbbbbFOOOOOW XsssssVLLLLLKfWWWWWRVVVVUofXWWWWRVVVUUofXXWWWRVVUUUofXXXWWRVUUUUofXXXXWRUUUUUoKMMMMMHPPPPPOuXXXXXSVUUUUBuXXXXWSVVUUUBuXXXWWSVVVUUBuXXWWWSVVVVUBuXWWWWSVVVVVBTlllllTqqqqqI MaaaaaEPPPPPVXNNNNNBOOOOPeXMNNNNBOOOPPeXMMNNNBOOPPPeXMMMNNBOPPPPeXMMMMNBPPPPPeDIIIIIJLLLLLLYMMMMMCOPPPPvYMMMMNCOOPPPvYMMMNNCOOOPPvYMMNNNCOOOOPvYMNNNNCOOOOOvPnnnnnAcccccS QmmmmmBdddddUVTTTTTETTTTSkVSTTTTETTTSSkVSSTTTETTSSSkVSSSTTETSSSSkVSSSSTESSSSSkQKKKKKMJJJJJSiRRRRRHQRRRRriRRRRQHQQRRRriRRRQQHQQQRRriRRQQQHQQQQRriRQQQQHQQQQQrFFFFFFJgggggH CSSSSSXNNNNNNHJJJJJGKKKKLWHIJJJJGKKKLLWHIIJJJGKKLLLWHIIIJJGKLLLLWHIIIIJGLLLLLWGAAAAARDDDDDCQIIIIIFKLLLLZQIIIIJFKKLLLZQIIIJJFKKKLLZQIIJJJFKKKKLZQIJJJJFKKKKKZEjjjjjRUUUUUR JCCCCCMJJJJJApBBBBBNCCCCDGpABBBBNCCCDDGpAABBBNCCDDDGpAAABBNCDDDDGpAAAABNDDDDDGPXXXXXXUUUUUHAAAAAAOCDDDDRAAAAABOCCDDDRAAAABBOCCCDDRAAABBBOCCCCDRAABBBBOCCCCCRGhhhhhIEEEEED" + }, + { + "id": 45, + "moves": "7R 7U 7F' 7R2 x 7U'", + "facelets": "EQQQQQGHHHHHCjIIIIIAIIIIJSjJIIIIAIIIJJSjJJIIIAIIJJJSjJJJIIAIJJJJSjJJJJIAJJJJJSRFFFFFSGGGGGXUKKKKKDLKKKKNUKKKKLDLLKKKNUKKKLLDLLLKKNUKKLLLDLLLLKNUKLLLLDLLLLLNRZZZZZCWWWWWN OMMMMMWTTTTTBbGGGGGVFFFFEIbHGGGGVFFFEEIbHHGGGVFFEEEIbHHHGGVFEEEEIbHHHHGVEEEEEIFQQQQQBTTTTTNOHHHHHWFEEEEDOHHHHGWFFEEEDOHHHGGWFFFEEDOHHGGGWFFFFEDOHGGGGWFFFFFDWtttttUKKKKKL PYYYYYDXXXXXMnMMMMMIMMMMNanNMMMMIMMMNNanNNMMMIMMNNNanNNNMMIMNNNNanNNNNMINNNNNaACCCCCKBBBBBEcOOOOOLPOOOOPcOOOOPLPPOOOPcOOOPPLPPPOOPcOOPPPLPPPPOPcOPPPPLPPPPPPSvvvvvLeeeeeV TuuuuuKfffffXlXXXXXMXXXXWslWXXXXMXXXWWslWWXXXMXXWWWslWWWXXMXWWWWslWWWWXMWWWWWsTSSSSSERRRRRVqVVVVVPUVVVVLqVVVVUPUUVVVLqVVVUUPUUUVVLqVVUUUPUUUUVLqVUUUUPUUUUULIBBBBBOoooooK FiiiiiQVVVVVQFRRRRRKSSSSTmFQRRRRKSSSTTmFQQRRRKSSTTTmFQQQRRKSTTTTmFQQQQRKTTTTTmJHHHHHNEEEEEBgQQQQQJSTTTTdgQQQQRJSSTTTdgQQQRRJSSSTTdgQQRRRJSSSSTdgQRRRRJSSSSSdHrrrrrSkkkkkU AGGGGGHRRRRRDJDDDDDUDDDDCEJCDDDDUDDDCCEJCCDDDUDDCCCEJCCCDDUDCCCCEJCCCCDUCCCCCEMNNNNNWOOOOOICBBBBBXABBBBhCBBBBAXAABBBhCBBBAAXAAABBhCBBAAAXAAAABhCBAAAAXAAAAAhJpppppPAAAAAG", + "token": "IUHoXJx2PabPjNCdUEW__LRvoYtUj7oSnFmMFD_CSoHDk1vWBgedgxFGPfe1mVWbbGUoRQauu_PtmsyUgvIVpSghBqrgLuWGe5zCnRns92bxoQD6a-mCfPnwt5vS4m8EOKSPOUopFUw53mcKqWkARDx8pqU79XMsnssNQCJ8FV0ukzom3lGMfk5E41ysIcmiRWb6ynei6DCTnQMcYuInvBARb9-AvewVx1_J7xP0nKQVRi65U4kWa2TkC1bgRG_DNGkFZzMftzDEsbMpIu7hoDy8XE1Oo7eVf_6F8zEjbE-IeH_LAaD6QtjEPRfnwldCLjknLV9-EyTV0iv6ViWBrhyicZbcZJhYf4HxyyFx9d6bK_DY8AzDU60Qkb5Y9Hgq3ek6Kf7UZafqDDEndPWuImSpSDg6vWgY-Qns3S3XeefQbNZ8JTC3yVtuNJgg80xE3shomavwzzglk2Asmit_qw", + "canonical": "" + }, + { + "id": 44, + "moves": "10F 13L2 1U 7R' 1R y 7U 9D'", + "facelets": "VGGGGGCRRRRRDPDDDDDDDDDDCEPCDDDDDDDDCCEDCCDDDDDDCCCEPCCCDDDDCCCCEPCCCCDDCCCCCEFUUUUUXXXXXXPaBBBBBAABBBBhaBBBBAAAABBBhaQQQRRRSSSTTdaBBAAAAAAAABhaBAAAAAAAAAAhKLLLLLWsssssO MggcggOFFFFFFpUUUUUPUUUUVqpVUUUUPUUUVVqpVVUUUGUUVVVqrRRPQQQQRRRRipVVVVUPVVVVVqVRRPRRQSSSSSTAWWWWWMXWWWWlAWWWWXMXXWWWlAWWWXXMXXXWWlAWWXXXMXXXXWlAWXXXXMXXXXXlGfffffKuuuuuT LKKKKKXtttttNDFFFFFGGGGGHoDEFFFFGGGGHHohBBAAAAAABBBopVVVUUPUVVVVqDEEEEFGHHHHHoGVVVVVOWWWWWUIEEEEEFGHHHHBIEEEEFFGGHHHBIEEEFFFGGGHHBIEEFFFFGGGGHBIEFFFFFGGGGGBBTTTTTRMMMMMI CSSSSSQNNNNNHXNNNNNOOOEOPeXMNNNNOOOEPPeXMMNNNOOOFPPeXMMMNNOOPFPPeXMMMMNOPPFPPeJIIIIILLLLLLLYMMMMMNOPGPPvYMMMMNNOOGPPvYMMMNNNOOHPPvYMMNNNNOOHOPvYMNNNNNOOHOOvPnnnnnMccOccS WOOCOOEbbbbbJjIIIIIBIIIIJCjJIIIIBIIIJJCjJJIIIBIIJJJgDEEEFFGGHHHHojJJJJIBJJJJJCBEEEEEHHHHHHHUKKKKKCLKKKKJUKKKKLCLLKKKJUKKKLLCLLLKKJUKKLLLCLLLLKJUKLLLLCLLLLLJRZZZZZIWWWWWA EQQQQQDHHHHHXrQQOQQQQQQQRirRQOQQQQQQRRirRROQQQQQRRRijJJJIIBIJJJJCrRRPRQQRRRRRiSJJJJJBKKKKKAkSSPSSTTSSSSVkSSPSTTTTSSSVkSSPTTTTTTSSVkSSOTTTTTTTSVkSTOTTTTTTTTVUddPddNmmmmmQ", + "token": "Q1Cld5H2wQaxyLg5iaqjHlLkqoln-SHmwPpSLMVq3f5V_eHShQcaxLr4f-vef1TkA2OTGeCm4bnj9yC5mHZaSivGlwPB7wHnvS5LDN8Hc2y82SYG-xPIHT4qFmLlDs2PRjIk-fhLUnFnP2PsFCiYLNnK7nu7bd49CqjrNgph2ilPzPWnhDv17A4MmlnxrEmJ9fxdeHAoorhaRWagMUL8MY4G-CJU53p5s-W8UmmfL4zQe-tWoWibswrUwMeXpkNBvhEoR1UrQKXATWa_FAcCBddTNGYbTFrWIY1sG2LheWXmm-bLqsUxiSGaA7IIVzfSEJKVVUyTSsJpSWlPiulK7DOun43PZDGVa0s_DyI7Z2P9SZ1foixcuOpel35PAMRGop49OD0prU3oa4PirGYBJA8JXppkfjcUpPSyKit5laDv4g18ZhnnAPtxlMApjrcAfPJfJZuuhDyN--2NtACcR", + "canonical": "EQQQQQDHHHHHXrQQOQQQQQQQRirRQOQQQQQQRRirRROQQQQQRRRijJJJIIBIJJJJCrRRPRQQRRRRRiSJJJJJBKKKKKAkSSPSSTTSSSSVkSSPSTTTTSSSVkSSPTTTTTTSSVkSSOTTTTTTTSVkSTOTTTTTTTTVUddPddNmmmmmQ RUUUUUBjDjjjWZKKKKKEJEJJIOZLKKKKEJEJIIOZLLKKKEJEIIICZLLLKKEJFIIIOZLLLLKEIFIIIOICCCCCEBGBBBEWLLLLLHJGIIIbWLLLLKHJHIIIbWLLLKKHJHJIIbWLLKKKHJHJJIbWLKKKKHJHJJJbAJJJJJHCogCCJ SccOccMnnnnnPvOOHOONNNNNMYvPOHOONNNNMMYvPPHOONNNMMMYvPPGOONNMMMMYvPPGPONMMMMMYLLLLLLJIIIIIJePPFPPONMMMMXePPFPOONNMMMXePPFOOONNNMMXePPEOOONNNNMXePOEOOONNNNNXHNNNNNQSSSSSC IMMMMMRTTTTTBBGGGGGFFFFFEIBHGGGGFFFFEEIBHHGGGFFFEEEIBHHHGGFFEEEEIBHHHHGFEEEEEIUWWWWWMVVVVVGoHHHHHGFEEEEDqVVVVUPUUVVVpoBBBAAAAAABBhoHHGGGGFFFFEDoHGGGGGFFFFFDNtttttXKKKKKL FqqqiqTlllllTFVVVRVSWWWWXuFUVVRVSWWWXXuFUUVRVSWWXXXuFUUURVSWXXXXuFUUUQVSXXXXXuOPPGQPTMMMMMKgUUUQURWXXXXfgUUUQVRWWXXXfcUUUPVPWWWXXfgUUVRVRWWWWXfgUVVRVRWWWWWfMppprpVAAAAAG VGGGGGCRRRRRDPDDDDDDDDDDCEPCDDDDDDDDCCEDCCDDDDDDCCCEPCCCDDDDCCCCEPCCCCDDCCCCCEFUUUUUXXXXXXPaBBBBBAABBBBhaBBBBAAAABBBhaQQQRRRSSSTTdaBBAAAAAAAABhaBAAAAAAAAAAhKLLLLLWsssssO" + }, + { + "id": 45, + "moves": "10F 13L2 1U 7R' 1R y 7U 9D'", + "facelets": "VGGGGGCRRRRRDPDDDDDDDDDDCEPCDDDDDDDDCCEDCCDDDDDDCCCEPCCCDDDDCCCCEPCCCCDDCCCCCEFUUUUUXXXXXXPaBBBBBAABBBBhaBBBBAAAABBBhaQQQRRRSSSTTdaBBAAAAAAAABhaBAAAAAAAAAAhKLLLLLWsssssO MggcggOFFFFFFpUUUUUPUUUUVqpVUUUUPUUUVVqpVVUUUGUUVVVqrRRPQQQQRRRRipVVVVUPVVVVVqVRRPRRQSSSSSTAWWWWWMXWWWWlAWWWWXMXXWWWlAWWWXXMXXXWWlAWWXXXMXXXXWlAWXXXXMXXXXXlGfffffKuuuuuT LKKKKKXtttttNDFFFFFGGGGGHoDEFFFFGGGGHHohBBAAAAAABBBopVVVUUPUVVVVqDEEEEFGHHHHHoGVVVVVOWWWWWUIEEEEEFGHHHHBIEEEEFFGGHHHBIEEEFFFGGGHHBIEEFFFFGGGGHBIEFFFFFGGGGGBBTTTTTRMMMMMI CSSSSSQNNNNNHXNNNNNOOOEOPeXMNNNNOOOEPPeXMMNNNOOOFPPeXMMMNNOOPFPPeXMMMMNOPPFPPeJIIIIILLLLLLLYMMMMMNOPGPPvYMMMMNNOOGPPvYMMMNNNOOHPPvYMMNNNNOOHOPvYMNNNNNOOHOOvPnnnnnMccOccS WOOCOOEbbbbbJjIIIIIBIIIIJCjJIIIIBIIIJJCjJJIIIBIIJJJgDEEEFFGGHHHHojJJJJIBJJJJJCBEEEEEHHHHHHHUKKKKKCLKKKKJUKKKKLCLLKKKJUKKKLLCLLLKKJUKKLLLCLLLLKJUKLLLLCLLLLLJRZZZZZIWWWWWA EQQQQQDHHHHHXrQQOQQQQQQQRirRQOQQQQQQRRirRROQQQQQRRRijJJJIIBIJJJJCrRRPRQQRRRRRiSJJJJJBKKKKKAkSSPSSTTSSSSVkSSPSTTTTSSSVkSSPTTTTTTSSVkSSOTTTTTTTSVkSTOTTTTTTTTVUddPddNmmmmmQ", + "token": "GT4-DNa8iGKCq0UVk5_9K18Vv_OG_Wy2iF3e0MoIEz9gPzSu8eKqCcYdL_hzb7_VgUVXKbQ-lKW1fKxFmSxh29BqeKFouaC25vFcJNOiy0jG0W5CnidrCvdPyGUV5Y0VulLN3b0cPuqGt8V4h485ENGsGW5mSTNW4_9YNEPksc997Nwe0ZZ8OIVEueG6oJuTvD6jDSoPPQUh2gZ8EnkekpVCnQzf1s3to3YmnuebsdTOLnhAfIc6YyQPyErY3lk4p0ZvGr_wOD4oHQaHp4KgwjC_M6ZKPKIQTJUIikUUjYY2enaMYAnyk2yZwWLDILTuxjb3__y3PAjne4d91Bd8GJNhe9UtxZKYCDw3pazWRsV--5sD3NCixVfjeM9doEmafPtW9RbvoP03KFF1AKZAbYWjg3nlr1SnvdvCz9BNmBxZ9MUOpkm2oF5Kl8gPlYSgLtrjrhphcZa1PnkVI4A6mm", + "canonical": "" + }, + { + "id": 44, + "moves": "x 7U2 13D' 9L' 1U 10R 13R' 4B x y'", + "facelets": "DaaaaaEPPPPPVRNNNNNBOOOOPeRMNNNNBOOOPPeRMMNNNBOOPPPeRXXXWWVVUUUUoRMMMMNBPPPPPeCMMMMMBPPPPPOGMMMMMCOPPPPvGMMMMNCOOPPPvuXXXWWWVVVUUBGMMNNNCOOOOPvGMNNNNCOOOOOvNNNNNNQSeSSSC AuuAuuPfpfffXnXXAXXXXAXXWsnWXAXXXXAXWWsnWWAXXXXAWWWsnWWBXXXXBWWWsnWWBWXXWBWWWsAWWOWWWVNVVVWcVVCVVUUCVVVLcVVCVUUUDVVVLgQQQRRRSSSTTdcVVDUUUUDUUVLcVUDUUUUDUUULSBBXBBHoioooK OMMMMMRTfTTTBFRRRRRSSSSSTmFQRRRRSSSSTTmFQQRRRSSSTTTmFQQQRRSSTTTTmFQQQQRSTTTTTmJHHHHHFEEEEEBgQQQQQRSTTTTdgQQQQRRSSTTTdUKKKLLLLLMKKhgQQRRRRSSSSTdgQRRRRRSSSSSdHrrrrrVkkkkkU IqqqqqUlllllTXCCCCCOBBBBAAXDCCCCOBBBAAAHIIJPJKKKLLLWXDDDCCOBAAAAAXDDDDCOAAAAAAKDDDDDPAAAAAGYDDDDDNBAAAApYJJJJIIIIJJJjYDDDCCNBBBAApYDDCCCNBBBBApYDCCCCNBBBBBpPJJJJJMCCCCCJ EQQQQQDHHvHHMjIIIIIIIIPIJEjJIIIIIIIPJJEjJJIIIIIIOJJETMMMNNBOPOPPEjJJJJIIJJOJJESJJJJJIKKCKKIUKKKKKLLKNKKhUKKKKLLLLNKKhKFFFEETEEEFFVUKKLLLLLLMLKhUKLLLLLLLMLLhRZZZZZLWWGWWG WOOOOOFbbbbbFtHHHHHQHHHHGitGHHHHQHHHGGitGGHHHQHHGGGitGGGHHQHGGGGStGGGGHQGGGGGiXGGGGGQFFFFFTKFFFFFTEFFFFVKFFFFETEEFFFVcVVCUUUUDUVVLKFFEEETEEEEFVKFEEEETEEEEEVLDDDDDNIIIIIQ", + "token": "MdVl7FnutFs4EgE2RR5pRgEwfRegKIPOt8Gze2d0wL1XwyJeQG17O6pAoOXmlqwRa3yNVyHSn7A1N2-ssVUv9Vkcd4P7d36K_VrvBjV278u0leaX7BlkW_28-VPKP5QT2FxZgqMps_2fFI2VlzA_E_fs6nzmiU880qDs5jKApFF-DFpleSFK7H72VGJ6ubVACEkalGFDKCcKdVwlIQC8jA2PG-nU5NdQlije7wmxoKZFJ5aLPJj_vf4cTYbjT-y7ThtolsGOlTRs5hgAznUZaBVcUyVlgmKlebnxzm0CEqi5Wvq4SLqGQ9PMO0yrfTNkwRydbjgXudEduKj6_PidLQTDDESJP_WsHtjG0mpBAvfaZ0c2yBRq9z3bFz9uKy6srCO6A1tKOb5NLJ8v8v9s4oEPuSMYMxPJoshNbtMEV8_8Gk5icXZZHFSZtCwYK75JiGgO6sr-XhLmT1ThmYxwE", + "canonical": "VeeeoeOvvBvvCPPPPUPPPPUPOSPOPPUPPPPUOOSPOOPUPPPPVOOSPOOOUPPPOVOOePOOOVPPOOVOOSEBBBVBACCWCCQaNNNWNMMNWNNNaNNNWMMMMWNNNaNNNXMMMMXNNNaNNMXMMMMXMNNaNMMXMMMMXMMNDRRRRRCGGuGGN OMMMMMRTfTTTBFRRRRRSSSSSTmFQRRRRSSSSTTmFQQRRRSSSTTTmFQQQRRSSTTTTmFQQQQRSTTTTTmJHHHHHFEEEEEBgQQQQQRSTTTTdgQQQQRRSSTTTdUKKKLLLLLMKKhgQQRRRRSSSSTdgQRRRRRSSSSSdHrrrrrVkkkkkU EQQQQQDHHvHHMjIIIIIIIIPIJEjJIIIIIIIPJJEjJJIIIIIIOJJETMMMNNBOPOPPEjJJJJIIJJOJJESJJJJJIKKCKKIUKKKKKLLKNKKhUKKKKLLLLNKKhKFFFEETEEEFFVUKKLLLLLLMLKhUKLLLLLLLMLLhRZZZZZLWWGWWG PYYYYYKXXHXXIJDDDJDDDDIDCqJCDDJDDDDICCqJCCDJDDDDJCCqJCCCJDDDCPCCqJCCCIDDCCJCCqMNNNINMOOKOOUCBBBIBAABKBBlCBBBIAAAAKBBlCBBBJAAAALBBlCBBAJAAAALABlCBAAJAAAALAAlJpppjpGAAWAAT WOOOOOFbbbbbFtHHHHHQHHHHGitGHHHHQHHHGGitGGHHHQHHGGGitGGGHHQHGGGGStGGGGHQGGGGGiXGGGGGQFFFFFTKFFFFFTEFFFFVKFFFFETEEFFFVcVVCUUUUDUVVLKFFEEETEEEEFVKFEEEETEEEEEVLDDDDDNIIIIIQ AuuAuuPfpfffXnXXAXXXXAXXWsnWXAXXXXAXWWsnWWAXXXXAWWWsnWWBXXXXBWWWsnWWBWXXWBWWWsAWWOWWWVNVVVWcVVCVVUUCVVVLcVVCVUUUDVVVLgQQQRRRSSSTTdcVVDUUUUDUUVLcVUDUUUUDUUULSBBXBBHoioooK" + }, + { + "id": 45, + "moves": "x 7U2 13D' 9L' 1U 10R 13R' 4B x y'", + "facelets": "DaaaaaEPPPPPVRNNNNNBOOOOPeRMNNNNBOOOPPeRMMNNNBOOPPPeRXXXWWVVUUUUoRMMMMNBPPPPPeCMMMMMBPPPPPOGMMMMMCOPPPPvGMMMMNCOOPPPvuXXXWWWVVVUUBGMMNNNCOOOOPvGMNNNNCOOOOOvNNNNNNQSeSSSC AuuAuuPfpfffXnXXAXXXXAXXWsnWXAXXXXAXWWsnWWAXXXXAWWWsnWWBXXXXBWWWsnWWBWXXWBWWWsAWWOWWWVNVVVWcVVCVVUUCVVVLcVVCVUUUDVVVLgQQQRRRSSSTTdcVVDUUUUDUUVLcVUDUUUUDUUULSBBXBBHoioooK OMMMMMRTfTTTBFRRRRRSSSSSTmFQRRRRSSSSTTmFQQRRRSSSTTTmFQQQRRSSTTTTmFQQQQRSTTTTTmJHHHHHFEEEEEBgQQQQQRSTTTTdgQQQQRRSSTTTdUKKKLLLLLMKKhgQQRRRRSSSSTdgQRRRRRSSSSSdHrrrrrVkkkkkU IqqqqqUlllllTXCCCCCOBBBBAAXDCCCCOBBBAAAHIIJPJKKKLLLWXDDDCCOBAAAAAXDDDDCOAAAAAAKDDDDDPAAAAAGYDDDDDNBAAAApYJJJJIIIIJJJjYDDDCCNBBBAApYDDCCCNBBBBApYDCCCCNBBBBBpPJJJJJMCCCCCJ EQQQQQDHHvHHMjIIIIIIIIPIJEjJIIIIIIIPJJEjJJIIIIIIOJJETMMMNNBOPOPPEjJJJJIIJJOJJESJJJJJIKKCKKIUKKKKKLLKNKKhUKKKKLLLLNKKhKFFFEETEEEFFVUKKLLLLLLMLKhUKLLLLLLLMLLhRZZZZZLWWGWWG WOOOOOFbbbbbFtHHHHHQHHHHGitGHHHHQHHHGGitGGHHHQHHGGGitGGGHHQHGGGGStGGGGHQGGGGGiXGGGGGQFFFFFTKFFFFFTEFFFFVKFFFFETEEFFFVcVVCUUUUDUVVLKFFEEETEEEEFVKFEEEETEEEEEVLDDDDDNIIIIIQ", + "token": "ErAGOKG5g6I1BsB0WetnekByLujcDzFthOijTkbLyEcA6SzjWCkONl_YPFY2eICGiE61AKyu--IT9Mngwn_x_AFqrNF-TM90HwIZolQMmexjuDZ4-ImFon8m3X9r19eHcSKhkP0vo38bp7UYGLIXp3z41-7Wc322zvxY1lLwPZ6PRKHmDWx8GK-cX6TuBaP4Axtp96R5Lw6j7AKN7GBGtIUVqnev1dC-OE9TmaOinD5Z7th0Nrlf5z9KnRKVPfjGPUpHOIiVd_Oo1kkATWvphwgCny4GEOT-DaW6rWjgxv9Fgh4FG0XyWW9sljzATvNFyGq7CVUI5a5rJT9eHt068OHJJJmzd_wgi5FKjufYYRzx5rq0iweoHLcyKLfJUDGAwI1lwUI71adc8Lux-x_I1PBl5W0pEydrnQsdCY8hoO3-id1k6oxhap-5o5CJEGdbkycFmAwfY0cWXb_UmZSqBh", + "canonical": "" + }, + { + "id": 48, + "moves": "", + "facelets": "GAAAAAAppppppJhAAAAAAAAAAABChBAAAAAAAAABBChBBAAAAAAABBBChBBBAAAAABBBBChBBBBAAABBBBBChBBBBBABBBBBBCECCCCCCDCCCCCJECCCCCDDDCCCCJECCCCDDDDDCCCJECCCDDDDDDDCCJECCDDDDDDDDDCJECDDDDDDDDDDDJDRRRRRRGGGGGGA BIIIIIIDDDDDDLTEEEEEEEEEEEFKTFEEEEEEEEEFFKTFFEEEEEEEFFFKTFFFEEEEEFFFFKTFFFFEEEFFFFFKTFFFFFEFFFFFFKMGGGGGGHGGGGGtMGGGGGHHHGGGGtMGGGGHHHHHGGGtMGGGHHHHHHHGGtMGGHHHHHHHHHGtMGHHHHHHHHHHHtObbbbbbOOOOOOW EQQQQQQHHHHHHCjIIIIIIIIIIIJSjJIIIIIIIIIJJSjJJIIIIIIIJJJSjJJJIIIIIJJJJSjJJJJIIIJJJJJSjJJJJJIJJJJJJSUKKKKKKLKKKKKNUKKKKKLLLKKKKNUKKKKLLLLLKKKNUKKKLLLLLLLKKNUKKLLLLLLLLLKNUKLLLLLLLLLLLNRZZZZZZWWWWWWN PYYYYYYXXXXXXMnMMMMMMMMMMMNanNMMMMMMMMMNNanNNMMMMMMMNNNanNNNMMMMMNNNNanNNNNMMMNNNNNanNNNNNMNNNNNNacOOOOOOPOOOOOPcOOOOOPPPOOOOPcOOOOPPPPPOOOPcOOOPPPPPPPOOPcOOPPPPPPPPPOPcOPPPPPPPPPPPPSvvvvvveeeeeeV HggggggFFFFFFFrQQQQQQQQQQQRirRQQQQQQQQQRRirRRQQQQQQQRRRirRRRQQQQQRRRRirRRRRQQQRRRRRirRRRRRQRRRRRRikSSSSSSTSSSSSVkSSSSSTTTSSSSVkSSSSTTTTTSSSVkSSSTTTTTTTSSVkSSTTTTTTTTTSVkSTTTTTTTTTTTVUddddddmmmmmmQ KooooooBBBBBBILUUUUUUUUUUUVqLVUUUUUUUUUVVqLVVUUUUUUUVVVqLVVVUUUUUVVVVqLVVVVUUUVVVVVqLVVVVVUVVVVVVqsWWWWWWXWWWWWlsWWWWWXXXWWWWlsWWWWXXXXXWWWlsWWWXXXXXXXWWlsWWXXXXXXXXXWlsWXXXXXXXXXXXlXffffffuuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 49, + "moves": "", + "facelets": "GAAAAAAppppppJhAAAAAAAAAAABChBAAAAAAAAABBChBBAAAAAAABBBChBBBAAAAABBBBChBBBBAAABBBBBChBBBBBABBBBBBCECCCCCCDCCCCCJECCCCCDDDCCCCJECCCCDDDDDCCCJECCCDDDDDDDCCJECCDDDDDDDDDCJECDDDDDDDDDDDJDRRRRRRGGGGGGA BIIIIIIDDDDDDLTEEEEEEEEEEEFKTFEEEEEEEEEFFKTFFEEEEEEEFFFKTFFFEEEEEFFFFKTFFFFEEEFFFFFKTFFFFFEFFFFFFKMGGGGGGHGGGGGtMGGGGGHHHGGGGtMGGGGHHHHHGGGtMGGGHHHHHHHGGtMGGHHHHHHHHHGtMGHHHHHHHHHHHtObbbbbbOOOOOOW EQQQQQQHHHHHHCjIIIIIIIIIIIJSjJIIIIIIIIIJJSjJJIIIIIIIJJJSjJJJIIIIIJJJJSjJJJJIIIJJJJJSjJJJJJIJJJJJJSUKKKKKKLKKKKKNUKKKKKLLLKKKKNUKKKKLLLLLKKKNUKKKLLLLLLLKKNUKKLLLLLLLLLKNUKLLLLLLLLLLLNRZZZZZZWWWWWWN PYYYYYYXXXXXXMnMMMMMMMMMMMNanNMMMMMMMMMNNanNNMMMMMMMNNNanNNNMMMMMNNNNanNNNNMMMNNNNNanNNNNNMNNNNNNacOOOOOOPOOOOOPcOOOOOPPPOOOOPcOOOOPPPPPOOOPcOOOPPPPPPPOOPcOOPPPPPPPPPOPcOPPPPPPPPPPPPSvvvvvveeeeeeV HggggggFFFFFFFrQQQQQQQQQQQRirRQQQQQQQQQRRirRRQQQQQQQRRRirRRRQQQQQRRRRirRRRRQQQRRRRRirRRRRRQRRRRRRikSSSSSSTSSSSSVkSSSSSTTTSSSSVkSSSSTTTTTSSSVkSSSTTTTTTTSSVkSSTTTTTTTTTSVkSTTTTTTTTTTTVUddddddmmmmmmQ KooooooBBBBBBILUUUUUUUUUUUVqLVUUUUUUUUUVVqLVVUUUUUUUVVVqLVVVUUUUUVVVVqLVVVVUUUVVVVVqLVVVVVUVVVVVVqsWWWWWWXWWWWWlsWWWWWXXXWWWWlsWWWWXXXXXWWWlsWWWXXXXXXXWWlsWWXXXXXXXXXWlsWXXXXXXXXXXXlXffffffuuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 48, + "moves": "9F 12U 6B 12B 12U2 1R2 4F' 8D2", + "facelets": "GAAAAAAppppppMhAAAAAAAAAAABahBAAAAAAAAABBmhBBAAAAAAABBBahBBBAAAAABBBBahBBBBAAABBBXBahBBBBBABBBBBBaECCCCCCDCCCCCPECCCCCDDDCCCCPECCCCDDDDDCCCPOHLHGGGFFFFEEDIEEFFFFGGGGWHPECDDDDDDDDDDDPDRRRRRRGGGGGGV WOOCOOObbbbnbOtHHNHHHHHHHNGMNKCNLLLLTLLKKUtGGNHHHHHHGMGMtGGMHHHHHGGMGMtGGMGHHHGGGMGMrRRRRRQRRRDDRiKFFMFFFEFFFMFTKFFMFFEEEFFMFTKFFMFEEEEEFMFTKFFMEEEEEEEMFTKFFNEEEEEEENFTKFENEEEEEEENETLDDnDDDIIIIaIB EQQQQQQHHHHHHXjIIIIIIIIIIIJsjJIIIIIIIIIJJMjJJIIIIIIIJJJsjJJJIIIIIJJJJsjJJJJIIIJJJJJsNVVVVVUVVVVVVqUKKKKKKLKKKKKLUKKKKKLLLKKKKLUKKKKLLLLLKKKLUKKKLLLLLLLKKLkSSTTOTTTTTTSLUKLLLLLLLLLLLLRZZZZZZWWWWWWK PYYYYYYXXXXXXJnMMMMMMMMMMMNCFQRRRRRSSSSLTCFQQRRRRSSSTXTmnNNNMMMMMNNNNCnNNNNMMMNNNNNCnNNNNNMNNNNNNCcOOOOOOPOOOOOJcOOOOOPPPOOLOJcOOOOPPPPPOOOJcOOOPPPPPPPOOJcOOPPPPPPPPPObcOPPPPPPPPPPPJSvvvvvveeeeeeA HggggggFFFPJFFrQQQQQQQQQCCRirRQQQQQQQQCDRirRRQQQQQQQDDRirRRRQQQQQRDDRirRRRRQQQRRDDRitGGMGGHGGGGMGMkSSSSSSTSSDDSVkSSSSSTTTSDDSVkSSSSTTTTTDDSVkSSSTTTTTTCDSVsWMXXXXXHXCXWlkSTTTTTTTTCCTVUddddddmmmEEmQ NooooooBBBBBBINUUUUUUUUUUUVqVVUUUUUUUUUVVqNVVUUUUUUUVVVqNVVVUUUUUVVVVqNVVVVUUUVVVVVqjJJJJJIJJJJJJsSWWWWWWXWWWWWlSWWWWWXXXWWWWlSWWWWXXXXXWWWlSWWWXXXXXXXWWlSGGHHHHHBHHHGtSWXXXXXXXXXXXlCffffffuuuuuuT", + "token": "lR8vn9zynpmd-7Bus8bSBa1JgPlnJVHB0Nfm__yBGyn4qQXLu_xgF5pt8TwKZRV_dJgfwZ38Joq9iMXpiJKHYiJyHEFlUsYvtYPYywuupt7KkZ5ev1dpSrNi5Q5ICzkkSekojb8xcGZwi3xiTAKmIMWe5PAQGJ3tT_W2KxBCsyUR5-lI6W-QR118a284LkQwGQCHiFcDE_d3E96Fi4pOi9tjQRd05GoDz-zYRrVavxKYGxG_bMyhuMDKowwN_ondMTaznBrBuiRlGZN3gxYFryYyrT31oHhM0M_Je0a5aUcp6taN5gOnBhCfjrgjc6chQQ22SJhBob98lSr5ZiDENOY_xMVbPBxxCpSmzmfFIbb9FFWo9VarciAKnepffEctialcE7qntXhuFqiyjRKvJToFeLYF_dr9knjYaAmm1GmbDb2y22wIYxm1FcppiL4ONhAmiNsIXlXl_bPYuWa9MkyLUHUHVByBbNEz5FZEqZjUfL9TE0Fo3mAiBNZj0XJqvcSWbyqbt2JOheXl8OKAjyt6eiCFOOZpJarxpBo", + "canonical": "" + }, + { + "id": 49, + "moves": "9F 12U 6B 12B 12U2 1R2 4F' 8D2", + "facelets": "GAAAAAAppppppMhAAAAAAAAAAABahBAAAAAAAAABBmhBBAAAAAAABBBahBBBAAAAABBBBahBBBBAAABBBXBahBBBBBABBBBBBaECCCCCCDCCCCCPECCCCCDDDCCCCPECCCCDDDDDCCCPOHLHGGGFFFFEEDIEEFFFFGGGGWHPECDDDDDDDDDDDPDRRRRRRGGGGGGV WOOCOOObbbbnbOtHHNHHHHHHHNGMNKCNLLLLTLLKKUtGGNHHHHHHGMGMtGGMHHHHHGGMGMtGGMGHHHGGGMGMrRRRRRQRRRDDRiKFFMFFFEFFFMFTKFFMFFEEEFFMFTKFFMFEEEEEFMFTKFFMEEEEEEEMFTKFFNEEEEEEENFTKFENEEEEEEENETLDDnDDDIIIIaIB EQQQQQQHHHHHHXjIIIIIIIIIIIJsjJIIIIIIIIIJJMjJJIIIIIIIJJJsjJJJIIIIIJJJJsjJJJJIIIJJJJJsNVVVVVUVVVVVVqUKKKKKKLKKKKKLUKKKKKLLLKKKKLUKKKKLLLLLKKKLUKKKLLLLLLLKKLkSSTTOTTTTTTSLUKLLLLLLLLLLLLRZZZZZZWWWWWWK PYYYYYYXXXXXXJnMMMMMMMMMMMNCFQRRRRRSSSSLTCFQQRRRRSSSTXTmnNNNMMMMMNNNNCnNNNNMMMNNNNNCnNNNNNMNNNNNNCcOOOOOOPOOOOOJcOOOOOPPPOOLOJcOOOOPPPPPOOOJcOOOPPPPPPPOOJcOOPPPPPPPPPObcOPPPPPPPPPPPJSvvvvvveeeeeeA HggggggFFFPJFFrQQQQQQQQQCCRirRQQQQQQQQCDRirRRQQQQQQQDDRirRRRQQQQQRDDRirRRRRQQQRRDDRitGGMGGHGGGGMGMkSSSSSSTSSDDSVkSSSSSTTTSDDSVkSSSSTTTTTDDSVkSSSTTTTTTCDSVsWMXXXXXHXCXWlkSTTTTTTTTCCTVUddddddmmmEEmQ NooooooBBBBBBINUUUUUUUUUUUVqVVUUUUUUUUUVVqNVVUUUUUUUVVVqNVVVUUUUUVVVVqNVVVVUUUVVVVVqjJJJJJIJJJJJJsSWWWWWWXWWWWWlSWWWWWXXXWWWWlSWWWWXXXXXWWWlSWWWXXXXXXXWWlSGGHHHHHBHHHGtSWXXXXXXXXXXXlCffffffuuuuuuT", + "token": "OHWHrIH12WZyIaHbwuaxbGjYLa_FKVc3K9ySHX5f75yUYHrtrBtBGwNaAShqD-EGK6traPqpVerndsIhEHP56xLo-d0OhvL_1nRZVdfFyxL0rhKYv-WBXqltanK-Rz1oIuNTZ0O-yxdZ3ih6v0xT30V6yaAViMr5yEVxC3yRm8RDoj6XaGJExhldd_6IkrdC8KQetwKIGeW1viBT54AVofJ0nWT0opN7c-R5nKFhDLJdaGErvXhX1jJqBRmNzRN1uOI9PYMDG9ljRlz_j_jDuGm7lzW9NUfQHWdcmYYtUz2P4QGwNnXEH0b7I8n1k5IrdHOh_e4a7SP2_4OWzp8g3e35bYG_gGCd3WWiEctUfgYhvdD4WVBDxGUgxZKBbqhM2QiNcE0A5_3F1nH_VF1evJToFeLYF_dr9knjYaAmm1GmbDb2y22wIYxm1FcppiL4ONhAmiNsIXlXl_bPYuWa9MkyLUHUHVByBbNEz5FZEqZjUfL9TE0Fo3mAiBNZj0XJqvcSWbyqbt2JOheXl8OKAjyt6eiCFOOZpJarxpBo", + "canonical": "" + }, + { + "id": 48, + "moves": "11L2 6D2 5B2 6B 3U 1B y 8D x y'", + "facelets": "VeeGeeSvvvvvvSDFVFFFJGSGGGHODEVFFFJGSGGHHODEUFFFWGSGHHHOdTUTSGJRRQQQQgPOUCOPJPOOOOOcDEUEEEJHTHHHHOIEUEEEIGTHHHHbIEUEEEJGSHHHHbIEUEEFJGSGHHHbIELEFFJGSGGHHbIEUFFFJGSGGGHbIEVFFFJGSGGGGbBTLTTTjMkMMMMO MaaaaaaPdhPPPUXNNNNNNOTBOOPkXMNNNNNOTBOPPkpAABBBBCTBDDDkXMMMNNNOTAPPPkXMMMMNNOGAPPPMXMMMMMNPSAPPPkYMMMMMMORAPPPrYMMMMMNOQAPPPrYMMMMNNOQBPPPrYMMMNNNOQNOPPrYMMNNNNOQBOOPjYMNNNNNOQBOOOrPnnnnnncgCcccH CSKSSSiNlNNNNNHJFJJJRKWKKKLWHIFJJJRKWKKLLWfXEWWWRVWVUUUoHITIJJRKWLLLLWHIOIIJRKXLLLLWHIEIIIRLXLLLLWQIEIIIQKXLLLLZQIEIIIAKWLLLLZQIEIIJGKWKLLLZQIEIJJRKJKKLLZQIEJJJRKWKKKLZQIFJJJRKWKKKKZEjTjjjpUsUUUUR FiSiiioVtVVVVQFRJRRRUSGSSSTmFQJRRRQSGSSTTmFQXRRRUSGSTTTmFQIQRRUSGTTTTmFQIQQRUSHTTTTmFQIQQQUTHTTTTmgQIQQQWSHTTTTdCBINBBLAABBBBhOHIHHSXFFFEEEDgQIQRRXSGSSTTdgQIRRRXSGSSSTdgQJRRRXSGSSSSdJppXppfAAAAAAG WCCCcOCJJJJJJAtBBBOHBCCCCCDGtABBOHBCCCCDDGtMMNOHNOOOPPPetAAAOHBCCDDDDGVAAAPTBCDDDDDGtAAAPGBDDDDDDGKAAAPFACDDDDDRKAAAPFBCCDDDDRKAAAPEBCCCDDDRKAAACEBCCCCDDRqAABOEBCCCCCDRKABBOEBCCCCCCRLhhhPDhEEEEEED TuuuuuuTffHffXlXXXXXXFXXIXWslWXXXXXFXXIWWslWWXXXXFXXJWWslWWWXXXFXWJWWsNKKVKLLFKKKKKUlWWWWWXEWWJWWsqVVVVVVFVVKVVLqVVVVVUPUVKVVLqVVVVUUSUUKVVLqVVVUUUFUULVVLiRRQQQQFBHQQRrqVUUUUUFUULUULIBBBBBBeooWooK", + "token": "KPhKeWflAz4EKXm-BQIRe4WOHQqUMZ4gyWIcACzavCEQwJCkR-S1weEScgKsDgpkfGMWdUPbRlBYgE4SGq-PnD-MbQMooTwtZNVI-KgjCP07v5YcSkyjtQS6xUIb5AzpaXnXOr5IVV2leTnIuWsAe1bfraUedJVmwdyhHRhYBRYRYaTzfEc91-m0elUVyYSioBgvyHLa7GGNaFdmYq12wxa2KDELf-rDP5hcGf4qU-WJmr8e-YFRSKFAeeDsGPhtJqifmn1Ef8_5GqGaPioN6KjvpyVkX4_wiawHRNELOgZiaJIcvzlzgTgknJeBS4t-Y1TXxfbF3Hvc002sm_K2JJGRJg4OojujxehCCkDL4-lad3h4QO3J0WNqBY8CHjOxqa5VbqcQEUpX2YbedzKBby2re8Dx6-Idyb80KTQfKN35NTzQcrdk3rZUk0Yj2D3W3X3q3GDanMIbpSL1w5xIzWoTPzkP7NZuOUdCOfhHJ1HagUO0PA-yJhzXCxrOmAESLoOu27Ugv3IXuwRJsL0crWFxaNP7euCZoKOzOmA", + "canonical": "GAAAAAAfppXppJdSSSSGSXRRRJQgdTSSSGSXRRRIQgdTTSSGSXRRQIQgDEEEFFFXSHHIHOhBBBBAALBBNIBCdTTTTHSWQQQIQgmTTTTHTUQQQIQFmTTTTHSURQQIQFmTTTTGSURRQIQFmTTTSGSURRRXQFmTTSSGSQRRRJQFmTSSSGSURRRJRFQVVVVtVoiiiSiF DEEEEEEhDPhhhLRCCCCCCBEOBBAKRDCCCCCBEOBAAqRDDCCCCBECAAAKRDDDCCCBEPAAAKRDDDDCCBFPAAAKRDDDDDCAFPAAAKGDDDDDDBGPAAAtGDDDDDCBTPAAAVGDDDDCCBHOAAAtePPPOOONHONMMtGDDCCCCBHOBBAtGDCCCCCBHOBBBtAJJJJJJCOcCCCW RUUUUsUpjjjTjEZKKKKWKRJJJFIQZLKKKWKRJJJEIQZLLKKJKRJJIEIQZLLLKWKGJIIEIQZLLLLWKAIIIEIQZLLLLXKQIIIEIQWLLLLXLRIIIEIHWLLLLXKRJIIOIHWLLLLWKRJJITIHoUUUVWVRWWWEXfWLLKKWKRJJJFIHWLKKKWKRJJJFJHNNNNNlNiSSSKSC OMMMMkMjTTTLTBbGGGGSGJFFFVEIbHGGGSGJFFFUEIbHHGGSGJFFELEIbHHHGSGJFEEUEIbHHHHSGJEEEUEIbHHHHTGIEEEUEIOHHHHTHJEEEUEDcOOOOOPJPOCUOPgQQQQRRJGSTUTdOHHHGSGWFFFUEDOHHGGSGJFFFVEDOHGGGSGJFFFVFDSvvvvvvSeeGeeV HcccCgcnnnnnnPrOOOBQONNNNNMYjPOOBQONNNNMMYrPPONQONNNMMMYrPPPBQONNMMMMYrPPPAQONMMMMMYrPPPAROMMMMMMYkPPPASPNMMMMMXMPPPAGONNMMMMXkPPPATONNNMMMXkDDDBTCBBBBAApkPPOBTONNNNNMXkPOOBTONNNNNNXUPPPhdPaaaaaaM KooWooeBBBBBBILUULUUFUUUUUVqrRQQHBFQQQQRRiLVVLUUFUUUVVVqLVVKUUSUUVVVVqLVVKVUPUVVVVVqLVVKVVFVVVVVVqsWWJWWEXWWWWWlUKKKKKFLLKVKKNsWWJWXFXXXWWWlsWWJXXFXXXXWWlsWWIXXFXXXXXWlsWXIXXFXXXXXXlXffHffTuuuuuuT" + }, + { + "id": 49, + "moves": "11L2 6D2 5B2 6B 3U 1B y 8D x y'", + "facelets": "VeeGeeSvvvvvvSDFVFFFJGSGGGHODEVFFFJGSGGHHODEUFFFWGSGHHHOdTUTSGJRRQQQQgPOUCOPJPOOOOOcDEUEEEJHTHHHHOIEUEEEIGTHHHHbIEUEEEJGSHHHHbIEUEEFJGSGHHHbIELEFFJGSGGHHbIEUFFFJGSGGGHbIEVFFFJGSGGGGbBTLTTTjMkMMMMO MaaaaaaPdhPPPUXNNNNNNOTBOOPkXMNNNNNOTBOPPkpAABBBBCTBDDDkXMMMNNNOTAPPPkXMMMMNNOGAPPPMXMMMMMNPSAPPPkYMMMMMMORAPPPrYMMMMMNOQAPPPrYMMMMNNOQBPPPrYMMMNNNOQNOPPrYMMNNNNOQBOOPjYMNNNNNOQBOOOrPnnnnnncgCcccH CSKSSSiNlNNNNNHJFJJJRKWKKKLWHIFJJJRKWKKLLWfXEWWWRVWVUUUoHITIJJRKWLLLLWHIOIIJRKXLLLLWHIEIIIRLXLLLLWQIEIIIQKXLLLLZQIEIIIAKWLLLLZQIEIIJGKWKLLLZQIEIJJRKJKKLLZQIEJJJRKWKKKLZQIFJJJRKWKKKKZEjTjjjpUsUUUUR FiSiiioVtVVVVQFRJRRRUSGSSSTmFQJRRRQSGSSTTmFQXRRRUSGSTTTmFQIQRRUSGTTTTmFQIQQRUSHTTTTmFQIQQQUTHTTTTmgQIQQQWSHTTTTdCBINBBLAABBBBhOHIHHSXFFFEEEDgQIQRRXSGSSTTdgQIRRRXSGSSSTdgQJRRRXSGSSSSdJppXppfAAAAAAG WCCCcOCJJJJJJAtBBBOHBCCCCCDGtABBOHBCCCCDDGtMMNOHNOOOPPPetAAAOHBCCDDDDGVAAAPTBCDDDDDGtAAAPGBDDDDDDGKAAAPFACDDDDDRKAAAPFBCCDDDDRKAAAPEBCCCDDDRKAAACEBCCCCDDRqAABOEBCCCCCDRKABBOEBCCCCCCRLhhhPDhEEEEEED TuuuuuuTffHffXlXXXXXXFXXIXWslWXXXXXFXXIWWslWWXXXXFXXJWWslWWWXXXFXWJWWsNKKVKLLFKKKKKUlWWWWWXEWWJWWsqVVVVVVFVVKVVLqVVVVVUPUVKVVLqVVVVUUSUUKVVLqVVVUUUFUULVVLiRRQQQQFBHQQRrqVUUUUUFUULUULIBBBBBBeooWooK", + "token": "IJQcM2I8QHJOwRYQK5AzyIm1L63Ge1g1o7LPY0qCSOXivZc1AKmV2poZGpn_A5VLZGwUOSsjg8v2mRoUvHq0ZVC2cWoXNNmbkEwd7o0qLW3xQ_cTCkQPK1oAZy5o_nRdh-ej5XScwa3lIqQfWTDVfxJJv-J80jwurLfLdukJb-kHIDXWgXYJlKWgYTcfJuvSeR11WTlb3xsMVwbngICAM2ZH9fDmKa1y-HWA1X74BpsgvVVSqpkxtflpr5wgmEmBUugrwnRK6rl7tcDJ9q1nbs7laQQMxd01j69dqVCifV2yXAk9NesoTmTYcue8ZQ0oCtUNRKwmTDmma5W_eqMc6ONTwvUKNxms0JpeSB-Kw8mWhpVk8CcUXgHM6-HtFi1H7PI1vm_28W2LRmtfsuQcuSk3-VR_LGBPnDMkPmbEewOy2iCkrkfB_Yj6lmxU-B-qmUEQWwWXLJoMFEAlqsPbLc3knRWSY2u1-irxh9gDPTCy8fM7kLEO_kavHmZ-gM857pGzKruMtl13glqhipei-6Qb1VK3Q94i4WJ_gYGx", + "canonical": "" + }, + { + "id": 52, + "moves": "", + "facelets": "GAAAAAAGppppppJhAAAAAAAAAAAABChBAAAAAAAAAABBChBBAAAAAAAABBBChBBBAAAAAABBBBChBBBBAAAABBBBBChBBBBBAABBBBBBCEBBBBBBACCCCCCAECCCCCCDDCCCCCJECCCCCDDDDCCCCJECCCCDDDDDDCCCJECCCDDDDDDDDCCJECCDDDDDDDDDDCJECDDDDDDDDDDDDJDRRRRRRCGGGGGGA BIIIIIIBDDDDDDLTEEEEEEEEEEEEFKTFEEEEEEEEEEFFKTFFEEEEEEEEFFFKTFFFEEEEEEFFFFKTFFFFEEEEFFFFFKTFFFFFEEFFFFFFKRFFFFFFEGGGGGGXMGGGGGGHHGGGGGtMGGGGGHHHHGGGGtMGGGGHHHHHHGGGtMGGGHHHHHHHHGGtMGGHHHHHHHHHHGtMGHHHHHHHHHHHHtObbbbbbJOOOOOOW EQQQQQQDHHHHHHCjIIIIIIIIIIIIJSjJIIIIIIIIIIJJSjJJIIIIIIIIJJJSjJJJIIIIIIJJJJSjJJJJIIIIJJJJJSjJJJJJIIJJJJJJSSJJJJJJIKKKKKKQUKKKKKKLLKKKKKNUKKKKKLLLLKKKKNUKKKKLLLLLLKKKNUKKKLLLLLLLLKKNUKKLLLLLLLLLLKNUKLLLLLLLLLLLLNRZZZZZZLWWWWWWN PYYYYYYKXXXXXXMnMMMMMMMMMMMMNanNMMMMMMMMMMNNanNNMMMMMMMMNNNanNNNMMMMMMNNNNanNNNNMMMMNNNNNanNNNNNMMNNNNNNaMNNNNNNMOOOOOOIcOOOOOOPPOOOOOPcOOOOOPPPPOOOOPcOOOOPPPPPPOOOPcOOOPPPPPPPPOOPcOOPPPPPPPPPPOPcOPPPPPPPPPPPPPSvvvvvvOeeeeeeV HggggggFFFFFFFFrQQQQQQQQQQQQRirRQQQQQQQQQQRRirRRQQQQQQQQRRRirRRRQQQQQQRRRRirRRRRQQQQRRRRRirRRRRRQQRRRRRRiVRRRRRRQSSSSSSTkSSSSSSTTSSSSSVkSSSSSTTTTSSSSVkSSSSTTTTTTSSSVkSSSTTTTTTTTSSVkSSTTTTTTTTTTSVkSTTTTTTTTTTTTVUddddddNmmmmmmQ KooooooHBBBBBBILUUUUUUUUUUUUVqLVUUUUUUUUUUVVqLVVUUUUUUUUVVVqLVVVUUUUUUVVVVqLVVVVUUUUVVVVVqLVVVVVUUVVVVVVqWVVVVVVUWWWWWWUsWWWWWWXXWWWWWlsWWWWWXXXXWWWWlsWWWWXXXXXXWWWlsWWWXXXXXXXXWWlsWWXXXXXXXXXXWlsWXXXXXXXXXXXXlXffffffPuuuuuuT", + "token": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 53, + "moves": "", + "facelets": "GAAAAAAGppppppJhAAAAAAAAAAAABChBAAAAAAAAAABBChBBAAAAAAAABBBChBBBAAAAAABBBBChBBBBAAAABBBBBChBBBBBAABBBBBBCEBBBBBBACCCCCCAECCCCCCDDCCCCCJECCCCCDDDDCCCCJECCCCDDDDDDCCCJECCCDDDDDDDDCCJECCDDDDDDDDDDCJECDDDDDDDDDDDDJDRRRRRRCGGGGGGA BIIIIIIBDDDDDDLTEEEEEEEEEEEEFKTFEEEEEEEEEEFFKTFFEEEEEEEEFFFKTFFFEEEEEEFFFFKTFFFFEEEEFFFFFKTFFFFFEEFFFFFFKRFFFFFFEGGGGGGXMGGGGGGHHGGGGGtMGGGGGHHHHGGGGtMGGGGHHHHHHGGGtMGGGHHHHHHHHGGtMGGHHHHHHHHHHGtMGHHHHHHHHHHHHtObbbbbbJOOOOOOW EQQQQQQDHHHHHHCjIIIIIIIIIIIIJSjJIIIIIIIIIIJJSjJJIIIIIIIIJJJSjJJJIIIIIIJJJJSjJJJJIIIIJJJJJSjJJJJJIIJJJJJJSSJJJJJJIKKKKKKQUKKKKKKLLKKKKKNUKKKKKLLLLKKKKNUKKKKLLLLLLKKKNUKKKLLLLLLLLKKNUKKLLLLLLLLLLKNUKLLLLLLLLLLLLNRZZZZZZLWWWWWWN PYYYYYYKXXXXXXMnMMMMMMMMMMMMNanNMMMMMMMMMMNNanNNMMMMMMMMNNNanNNNMMMMMMNNNNanNNNNMMMMNNNNNanNNNNNMMNNNNNNaMNNNNNNMOOOOOOIcOOOOOOPPOOOOOPcOOOOOPPPPOOOOPcOOOOPPPPPPOOOPcOOOPPPPPPPPOOPcOOPPPPPPPPPPOPcOPPPPPPPPPPPPPSvvvvvvOeeeeeeV HggggggFFFFFFFFrQQQQQQQQQQQQRirRQQQQQQQQQQRRirRRQQQQQQQQRRRirRRRQQQQQQRRRRirRRRRQQQQRRRRRirRRRRRQQRRRRRRiVRRRRRRQSSSSSSTkSSSSSSTTSSSSSVkSSSSSTTTTSSSSVkSSSSTTTTTTSSSVkSSSTTTTTTTTSSVkSSTTTTTTTTTTSVkSTTTTTTTTTTTTVUddddddNmmmmmmQ KooooooHBBBBBBILUUUUUUUUUUUUVqLVUUUUUUUUUUVVqLVVUUUUUUUUVVVqLVVVUUUUUUVVVVqLVVVVUUUUVVVVVqLVVVVVUUVVVVVVqWVVVVVVUWWWWWWUsWWWWWWXXWWWWWlsWWWWWXXXXWWWWlsWWWWXXXXXXWWWlsWWWXXXXXXXXWWlsWWXXXXXXXXXXWlsWXXXXXXXXXXXXlXffffffPuuuuuuT", + "token": "BBqR-bFCbKd7_wCFCVfPI3mXMUCJIX-Bw8klndC9B6lLd6wgSwcUZ7XypE8bvEsgLHR4BRcT2HTOa0fx3C3YeOrSyz6b6EjEx6Zi9_BixC2cQwt_xdnkezXucLpw9hUhC8Kv012367oiCaD3CZmYPiJwi1bsxDLvVrjLg7AhKyIczBHVpLwYW_vghpgu4gCPFbxeChuJCLLj8gTE3weSYhXP68dYuG35USQ755XODc8NZnz6zA_ek00VzZLn_dhhdqLyqNDNyYrBnWJhgJE4on-sHFiYeKUcYH3wXI-ACnt9wEqtMWF7qZexFHhGLdjoHhLhLMjcteHGJG_XWVKfGxECSz3SUrKuJO7io_iTl5QuFSEMYMXKeFSwl7wmiPPaPbG9i9MNNr3rtnGdzS3sI55vrm9YjUYh_dwMhNzcAnQ-x6h1xSq1abyhqvtKD7BMnKY-lN4GsZUvrebcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "canonical": "" + }, + { + "id": 52, + "moves": "8R 8U 8F' 8R2 x 8U'", + "facelets": "EQQQQQQGHHHHHHCjIIIIIIAIIIIIJSjJIIIIIAIIIIJJSjJJIIIIAIIIJJJSjJJJIIIAIIJJJJSjJJJJIIAIJJJJJSjJJJJJIAJJJJJJSRFFFFFFSGGGGGGXUKKKKKKDLKKKKKNUKKKKKLDLLKKKKNUKKKKLLDLLLKKKNUKKKLLLDLLLLKKNUKKLLLLDLLLLLKNUKLLLLLDLLLLLLNRZZZZZZCWWWWWWN OMMMMMMWTTTTTTBbGGGGGGVFFFFFEIbHGGGGGVFFFFEEIbHHGGGGVFFFEEEIbHHHGGGVFFEEEEIbHHHHGGVFEEEEEIbHHHHHGVEEEEEEIFQQQQQQBTTTTTTNOHHHHHHWFEEEEEDOHHHHHGWFFEEEEDOHHHHGGWFFFEEEDOHHHGGGWFFFFEEDOHHGGGGWFFFFFEDOHGGGGGWFFFFFFDWttttttUKKKKKKL PYYYYYYDXXXXXXMnMMMMMMIMMMMMNanNMMMMMIMMMMNNanNNMMMMIMMMNNNanNNNMMMIMMNNNNanNNNNMMIMNNNNNanNNNNNMINNNNNNaACCCCCCKBBBBBBEcOOOOOOLPOOOOOPcOOOOOPLPPOOOOPcOOOOPPLPPPOOOPcOOOPPPLPPPPOOPcOOPPPPLPPPPPOPcOPPPPPLPPPPPPPSvvvvvvLeeeeeeV TuuuuuuKffffffXlXXXXXXMXXXXXWslWXXXXXMXXXXWWslWWXXXXMXXXWWWslWWWXXXMXXWWWWslWWWWXXMXWWWWWslWWWWWXMWWWWWWsTSSSSSSERRRRRRVqVVVVVVPUVVVVVLqVVVVVUPUUVVVVLqVVVVUUPUUUVVVLqVVVUUUPUUUUVVLqVVUUUUPUUUUUVLqVUUUUUPUUUUUULIBBBBBBOooooooK FiiiiiiQVVVVVVQFRRRRRRKSSSSSTmFQRRRRRKSSSSTTmFQQRRRRKSSSTTTmFQQQRRRKSSTTTTmFQQQQRRKSTTTTTmFQQQQQRKTTTTTTmJHHHHHHNEEEEEEBgQQQQQQJSTTTTTdgQQQQQRJSSTTTTdgQQQQRRJSSSTTTdgQQQRRRJSSSSTTdgQQRRRRJSSSSSTdgQRRRRRJSSSSSSdHrrrrrrSkkkkkkU AGGGGGGHRRRRRRDJDDDDDDUDDDDDCEJCDDDDDUDDDDCCEJCCDDDDUDDDCCCEJCCCDDDUDDCCCCEJCCCCDDUDCCCCCEJCCCCCDUCCCCCCEMNNNNNNWOOOOOOICBBBBBBXABBBBBhCBBBBBAXAABBBBhCBBBBAAXAAABBBhCBBBAAAXAAAABBhCBBAAAAXAAAAABhCBAAAAAXAAAAAAhJppppppPAAAAAAG", + "token": "A-QhLueAlDmGaD_R0D190ZwqbVNWNjbSCo-WwPJxGx2jyiuWCfaDRU655x1Lxn95BOoizb_tK29nhCWBHQ7KbuZg2JG3lXDptv3xkz9CjGKhug1lqtbALRxfj3xs_svCYUeO6MvsMLw9y_hHTxE1ifpSrg53ubSbFqfSNrV5st0JuYjY5UMX41DFZymOdVTR70mU-P4jIEDWuNRVfaymkfzIPF2RZDe3kge94rWkm3FplkHWcWRG4BHJJDfIC0Iy796-nqLTEZDVtb_edjer9mIGMOGNtHxl8BRwfrkFB7ujPB7uI1MxmdXpltIEZcVrcYJhPt_J_zP8OMcS8COraatbixI9e87FH4VoCrEn4Vb0XXpDSsc9d-5DbbALypw7ozmKyErxWsOuehQAwtRBPfCzeEDElqYvD6l3OjwpoTT0KRQLAGvroP89fXFCzQRwZ7nLd2XV3Pg2DDqa_MJ2GFaPmG3wpx8BElbYS570VEgQONdaGaB0gOpZbWRmzv7XbMKMFElgJ4awWfIqs3tWbzH9eX1XdeNHgsL1DtNpXHpRibGqeLub_FddhpN-R7BmHDa0Cco8BCmBp3oyTpRRTWd5FLDdc_bkkl4ZWtM3YM_MEW7x_XfesO3lXMRclUc", + "canonical": "BIIIIIINDDDDDDLTEEEEEETEEEEEFKTFEEEEETEEEEFFKTFFEEEETEEEFFFKTFFFEEETEEFFFFKTFFFFEETEFFFFFKTFFFFFETFFFFFFKWVVVVVVAWWWWWWUMGGGGGGQHGGGGGtMGGGGGHQHHGGGGtMGGGGHHQHHHGGGtMGGGHHHQHHHHGGtMGGHHHHQHHHHHGtMGHHHHHQHHHHHHtObbbbbbFOOOOOOW XssssssVLLLLLLKfWWWWWWRVVVVVUofXWWWWWRVVVVUUofXXWWWWRVVVUUUofXXXWWWRVVUUUUofXXXXWWRVUUUUUofXXXXXWRUUUUUUoKMMMMMMHPPPPPPOuXXXXXXSVUUUUUBuXXXXXWSVVUUUUBuXXXXWWSVVVUUUBuXXXWWWSVVVVUUBuXXWWWWSVVVVVUBuXWWWWWSVVVVVVBTllllllTqqqqqqI MaaaaaaEPPPPPPVXNNNNNNBOOOOOPeXMNNNNNBOOOOPPeXMMNNNNBOOOPPPeXMMMNNNBOOPPPPeXMMMMNNBOPPPPPeXMMMMMNBPPPPPPeDIIIIIIJLLLLLLLYMMMMMMCOPPPPPvYMMMMMNCOOPPPPvYMMMMNNCOOOPPPvYMMMNNNCOOOOPPvYMMNNNNCOOOOOPvYMNNNNNCOOOOOOvPnnnnnnAccccccS QmmmmmmBddddddUVTTTTTTETTTTTSkVSTTTTTETTTTSSkVSSTTTTETTTSSSkVSSSTTTETTSSSSkVSSSSTTETSSSSSkVSSSSSTESSSSSSkQKKKKKKMJJJJJJSiRRRRRRHQRRRRRriRRRRRQHQQRRRRriRRRRQQHQQQRRRriRRRQQQHQQQQRRriRRQQQQHQQQQQRriRQQQQQHQQQQQQrFFFFFFFJggggggH CSSSSSSXNNNNNNNHJJJJJJGKKKKKLWHIJJJJJGKKKKLLWHIIJJJJGKKKLLLWHIIIJJJGKKLLLLWHIIIIJJGKLLLLLWHIIIIIJGLLLLLLWGAAAAAARDDDDDDCQIIIIIIFKLLLLLZQIIIIIJFKKLLLLZQIIIIJJFKKKLLLZQIIIJJJFKKKKLLZQIIJJJJFKKKKKLZQIJJJJJFKKKKKKZEjjjjjjRUUUUUUR JCCCCCCMJJJJJJApBBBBBBNCCCCCDGpABBBBBNCCCCDDGpAABBBBNCCCDDDGpAAABBBNCCDDDDGpAAAABBNCDDDDDGpAAAAABNDDDDDDGPXXXXXXXUUUUUUHAAAAAAAOCDDDDDRAAAAAABOCCDDDDRAAAAABBOCCCDDDRAAAABBBOCCCCDDRAAABBBBOCCCCCDRAABBBBBOCCCCCCRGhhhhhhIEEEEEED" + }, + { + "id": 53, + "moves": "8R 8U 8F' 8R2 x 8U'", + "facelets": "EQQQQQQGHHHHHHCjIIIIIIAIIIIIJSjJIIIIIAIIIIJJSjJJIIIIAIIIJJJSjJJJIIIAIIJJJJSjJJJJIIAIJJJJJSjJJJJJIAJJJJJJSRFFFFFFSGGGGGGXUKKKKKKDLKKKKKNUKKKKKLDLLKKKKNUKKKKLLDLLLKKKNUKKKLLLDLLLLKKNUKKLLLLDLLLLLKNUKLLLLLDLLLLLLNRZZZZZZCWWWWWWN OMMMMMMWTTTTTTBbGGGGGGVFFFFFEIbHGGGGGVFFFFEEIbHHGGGGVFFFEEEIbHHHGGGVFFEEEEIbHHHHGGVFEEEEEIbHHHHHGVEEEEEEIFQQQQQQBTTTTTTNOHHHHHHWFEEEEEDOHHHHHGWFFEEEEDOHHHHGGWFFFEEEDOHHHGGGWFFFFEEDOHHGGGGWFFFFFEDOHGGGGGWFFFFFFDWttttttUKKKKKKL PYYYYYYDXXXXXXMnMMMMMMIMMMMMNanNMMMMMIMMMMNNanNNMMMMIMMMNNNanNNNMMMIMMNNNNanNNNNMMIMNNNNNanNNNNNMINNNNNNaACCCCCCKBBBBBBEcOOOOOOLPOOOOOPcOOOOOPLPPOOOOPcOOOOPPLPPPOOOPcOOOPPPLPPPPOOPcOOPPPPLPPPPPOPcOPPPPPLPPPPPPPSvvvvvvLeeeeeeV TuuuuuuKffffffXlXXXXXXMXXXXXWslWXXXXXMXXXXWWslWWXXXXMXXXWWWslWWWXXXMXXWWWWslWWWWXXMXWWWWWslWWWWWXMWWWWWWsTSSSSSSERRRRRRVqVVVVVVPUVVVVVLqVVVVVUPUUVVVVLqVVVVUUPUUUVVVLqVVVUUUPUUUUVVLqVVUUUUPUUUUUVLqVUUUUUPUUUUUULIBBBBBBOooooooK FiiiiiiQVVVVVVQFRRRRRRKSSSSSTmFQRRRRRKSSSSTTmFQQRRRRKSSSTTTmFQQQRRRKSSTTTTmFQQQQRRKSTTTTTmFQQQQQRKTTTTTTmJHHHHHHNEEEEEEBgQQQQQQJSTTTTTdgQQQQQRJSSTTTTdgQQQQRRJSSSTTTdgQQQRRRJSSSSTTdgQQRRRRJSSSSSTdgQRRRRRJSSSSSSdHrrrrrrSkkkkkkU AGGGGGGHRRRRRRDJDDDDDDUDDDDDCEJCDDDDDUDDDDCCEJCCDDDDUDDDCCCEJCCCDDDUDDCCCCEJCCCCDDUDCCCCCEJCCCCCDUCCCCCCEMNNNNNNWOOOOOOICBBBBBBXABBBBBhCBBBBBAXAABBBBhCBBBBAAXAAABBBhCBBBAAAXAAAABBhCBBAAAAXAAAAABhCBAAAAAXAAAAAAhJppppppPAAAAAAG", + "token": "YX2uX2VQUgwVxPwwjxuayh2RcRCeNqMywT9HtItXknCma8IxOiNi5xdgTw42WD54ome7TxVP6uaCzswMlZDS3oPnGOfR6t6t73UIxd6f7WzFtE0Hyfvotd7j5GSo2S9aKngVpnvcfV_tKOemczadLZow3LImLSF5dnSApLOJ79sGMOYrIwZVq45iMn2J4fQ9hKJVYe7UDscFRezIpzcwEciVlIz6HacvAd4JKJ08oHDzfKkVbXaDlPfxMs2pDA0n84TRiBSXbx7Kp188lclXu7BAsX3hZE6q4mh8Pef4xBLLYzEAgS4iFaWXOCivuFr6wkv_EcPMosFrdxmd3KqxAyGXVPOV7hUpGXCjpJRQt7sW2JdbY3ONtqsC4D1Bh5lxixbAUtmtuRZHKFGwERQJ8i9C9IHGrNqLdb85-oDDH2sioYl9z0jMgaplbZmNSBrWVhFRxmoTaNpA02VjsjsSSB12Sk6PqugZuCRHFubn5sGFVDByZwrsFfhiQWmjZ-QyMj0h5uEDtKCIbrQA05AabK_DY8AzDU60Qkb5Y9Hgq3ek6Kf7UZafqDDEndPWuImSpSDg6vWgY-Qns3S3XeefQbNZ8JTC3yVtuNJgg80xE3shomavwzzglk2Asmit_qw", + "canonical": "" + }, + { + "id": 52, + "moves": "12D' 10R 15L' 7B2 5L2 x 12B 5F2", + "facelets": "EQQQuYQDHHHHHHMjIIIXMIIIIIIIJajJIIXMIIIIIIJJaTFFEXMEEEEEFFFajJJJXMIIIIJJJJajJJJWMIIIJJJJJajJJJWNIIJJJJJJaSJJJWNJIKKKKKKIUKKKVOKLLKKKKKPUKKKVOLLLLKKKKPCWWWWXXXXAIWWWlOHHHGGGGIFFVEEDUKKLUPLLLLLLLKPUKLLUPLLLLLLLLPRZZZBvZLWWWWWWV WOOCkOOJbbbbbbOtHHWSHHHHHHHHGMtGHWSHHHHHHHGGMtGGWSHHHHHHGGGMtGGXSHHHHHGGGGMtGGXTHHHHGGGGGMtGGXFGHHGGGGGGMXGGXTGGGFFFFFFRgQQXTQQRSTTTTTdKFFATFEEEEFFFFTKFFETEEEEEEFFFTqVVXSUUUUUUUVVLKFFWSEEEEEEEEFTKFEWSEEEEEEEEETLDDlVDDBIIIIIIB PYYYAuYKXXXXXXXnMMMAXMMMMMMMNsnNMMAXMMMMMMNNsnNNMAXMMMMMNNNsnNNNAXMMMMNNNNsnNNNBXMMMNNNNNsnNNNOWMMNNNNNNsMNNNBWNMOOOOOOWSBBBCBBAAJBBBBhcOOOCVPPPPOOOOLcOOOCUPPPPPOOOLcOOODQPPPPPPOOrcOOPDUPPPPPPPOLcOPPDUPPPPPPPPLSvvvRBvOeeeeeeK TuuuQAuPffffffJlXXXIAXXXXXXXWClWXXIAXXXXXXWWCVSSTTTTTFTTSSSkPKKKLLLLLPVKKKUlWWWJAXXXWWWWWClWWWJBXXWWWWWWCUWWWJBWWVVVVVVAqVVVKCVUUVVVVVJqVVVKCUUUUVVVVJqVVVKDUUUUUVVVJiRRRLDQQQQQQRRJqVVULDUUUUUUUVJqVUULDUUUUUUUUJIBBBZRBHooooooA FiiSiiiTVVDPVVQFRRJRRRSSSEKSTmFQRJRRRSSSEKTTmFQQJRRRSSSULTTmFQQIRRRSSSFLTTmFQQIQRRSSTFLTTmFQQIQQRSTTQLTTmFQQIQQQTTTGLTTNKFFFFFFEEFGLFFTgQQIQQRRSSGPTTdgQQIQRRRSSHQTTdgQQIRRRRSSHKTTdgQQJRRRRSSHKSTdgQRJRRRRSSHKSSdHrrjrrrVkkOUkkU NGGGGGGCRZvRRRDNDDDDDDDDLPDDCENCDDDDDDDLPDCCENCCDDDDDDLPCCCENCCCDDDDDLOCCCENCCCCDDDDKOCCCENCCCCCDDCKBCCCEQCCCCCCCBJNBBBEcOOOOVOPPONOOOLSBBBBBAAAINBBBhSBBBBAAAAIMBBBhKBBBAAAAAEMABBhSBBAAAAAAIMAABhSBAAAAAAAIMAAAhCppppppGAQYAAAG", + "token": "AiO4cUJbAX5yIgh17NX1wO4gUToTSMEs41bFZAoRXA5MHaSOZ5yiCrtjQfhbFfQPZEa-WvGNVLFzYbRhX2FW9ehWQQulg9KpKljUdmapw3utQjvU91R3sMOvxKyfjVyMuPqlS_yL9GlM4WS9fdF4ynItUSdg6qiwznKfF0jGAAhfCNOiGfIdUnO0uaUTq0fd8eRELnYt7NcHbj1LbXbz9XMPQeCAg5j9OL2bYj4w7Dp4RdKaot74KFO2h-ZGfq0Atm2xcAX_bDzpuIuW7_-814mZetwaWZmqsYkrpmTevzLbUgtIOrMrybMHV-JTLXPu7ku8zWXu2qLI8VD7UbIF8Ev_YZKwVrYuCvxOnAfndLGn86bAMoEUYeNeJrld3YMjNrNcZCxtQkmERkHa44jL4yIcLQ2QUGcn4wMjOefP3tEZ4GOqBx20wuV8ycZrBJR0b878QC1FPSpszGZ5Mux11rI-Ek4lQIpnaAArhKfLCI-AFuo2DjsBoH0q10L5fijbpJ6cxab-4-rryoh2lx3rhJLHMnq-SugOqYgWje_AmYE-YVrmjuaVxVDEN8E-T-e62sd4tZeMaB4YRhSEbPI0X8QH0_JUEzkKQIlL1U967D8afkNX--u7HDzLaZ8esAE", + "canonical": "GAAAYQAGppppppChAAAMIAAAAAAABShBAAMIAAAAAABBShBBAMEAAAAABBBKhBBBMIAAAABBBBShBBBNIAAABBBBBSLOOONOPPOVOOOOcEBBBNJBACCCCCCQECCCBKCDDCCCCCNECCCOKDDDDCCCCNECCCOLDDDDDCCCNECCCPLDDDDDDCCNECCDPLDDDDDDDCNECDDPLDDDDDDDDNDRRRvZRCGGGGGGN OMMMMMMRdTTLTTBbGGGGGGFTFFVFEIbHGGGGGFTFFVEEIbHHGGGGFTFFUEEIbHHHGGGFTFEUEEIbHHHHGGFTEEUEEIbHHHHHGFSEEUEEIJHHHHHHFREEUEEBOHHHHHHGQEEUEEDOHHHHHGGQFEUEEDkSSSSTFTTTTSSSVCWWWXXXXXAEXWWlOHHGGGGGQFFVFEDOHGGGGGGQFFVFFDWttttttXgKKqKKL EQQQuYQDHHHHHHMjIIIXMIIIIIIIJajJIIXMIIIIIIJJaTFFEXMEEEEEFFFajJJJXMIIIIJJJJajJJJWMIIIJJJJJajJJJWNIIJJJJJJaSJJJWNJIKKKKKKIUKKKVOKLLKKKKKPUKKKVOLLLLKKKKPCWWWWXXXXAIWWWlOHHHGGGGIFFVEEDUKKLUPLLLLLLLKPUKLLUPLLLLLLLLPRZZZBvZLWWWWWWV PYYYAuYKXXXXXXXnMMMAXMMMMMMMNsnNMMAXMMMMMMNNsnNNMAXMMMMMNNNsnNNNAXMMMMNNNNsnNNNBXMMMNNNNNsnNNNOWMMNNNNNNsMNNNBWNMOOOOOOWSBBBCBBAAJBBBBhcOOOCVPPPPOOOOLcOOOCUPPPPPOOOLcOOODQPPPPPPOOrcOOPDUPPPPPPPOLcOPPDUPPPPPPPPLSvvvRBvOeeeeeeK HgggggKFFFFFFFFrQQQQQFQQQQQQRirRQQQQFQQQQQRRijJJIIIFIIIIJJJSrRRRQQFQQQRRRRirRRRRQFQQRRRRRirRRRRRFQRRRRRRiVRRRRREQSSSSSSTkSSSSSETTSSSSSVkSSSSSFTTTSSSSVOHHHHGGGQFFUEEDUKKKQPLLLLLLKKPkSSTTTFTTTTTTSVkSTTTTFTTTTTTTVUdddddTNmmmmmmQ AooooooHBRZBBBIJUUUUUUUUDLUUVqJVUUUUUUUDLUVVqJRRQQQQQQDLRRRiJVVVUUUUUDKVVVqJVVVVUUUUCKVVVqJVVVVVUUVCKVVVqAVVVVVVUWBJWWWUCWWWWWWXXBJWWWlCWWWWWXXXAJWWWlUKKKVPLLLLLKKKPkSSSTTFTTTTTSSVCWWXXXXXXAIXXWlCWXXXXXXXAIXXXlJffffffPuAQuuuT" + }, + { + "id": 53, + "moves": "12D' 10R 15L' 7B2 5L2 x 12B 5F2", + "facelets": "EQQQuYQDHHHHHHMjIIIXMIIIIIIIJajJIIXMIIIIIIJJaTFFEXMEEEEEFFFajJJJXMIIIIJJJJajJJJWMIIIJJJJJajJJJWNIIJJJJJJaSJJJWNJIKKKKKKIUKKKVOKLLKKKKKPUKKKVOLLLLKKKKPCWWWWXXXXAIWWWlOHHHGGGGIFFVEEDUKKLUPLLLLLLLKPUKLLUPLLLLLLLLPRZZZBvZLWWWWWWV WOOCkOOJbbbbbbOtHHWSHHHHHHHHGMtGHWSHHHHHHHGGMtGGWSHHHHHHGGGMtGGXSHHHHHGGGGMtGGXTHHHHGGGGGMtGGXFGHHGGGGGGMXGGXTGGGFFFFFFRgQQXTQQRSTTTTTdKFFATFEEEEFFFFTKFFETEEEEEEFFFTqVVXSUUUUUUUVVLKFFWSEEEEEEEEFTKFEWSEEEEEEEEETLDDlVDDBIIIIIIB PYYYAuYKXXXXXXXnMMMAXMMMMMMMNsnNMMAXMMMMMMNNsnNNMAXMMMMMNNNsnNNNAXMMMMNNNNsnNNNBXMMMNNNNNsnNNNOWMMNNNNNNsMNNNBWNMOOOOOOWSBBBCBBAAJBBBBhcOOOCVPPPPOOOOLcOOOCUPPPPPOOOLcOOODQPPPPPPOOrcOOPDUPPPPPPPOLcOPPDUPPPPPPPPLSvvvRBvOeeeeeeK TuuuQAuPffffffJlXXXIAXXXXXXXWClWXXIAXXXXXXWWCVSSTTTTTFTTSSSkPKKKLLLLLPVKKKUlWWWJAXXXWWWWWClWWWJBXXWWWWWWCUWWWJBWWVVVVVVAqVVVKCVUUVVVVVJqVVVKCUUUUVVVVJqVVVKDUUUUUVVVJiRRRLDQQQQQQRRJqVVULDUUUUUUUVJqVUULDUUUUUUUUJIBBBZRBHooooooA FiiSiiiTVVDPVVQFRRJRRRSSSEKSTmFQRJRRRSSSEKTTmFQQJRRRSSSULTTmFQQIRRRSSSFLTTmFQQIQRRSSTFLTTmFQQIQQRSTTQLTTmFQQIQQQTTTGLTTNKFFFFFFEEFGLFFTgQQIQQRRSSGPTTdgQQIQRRRSSHQTTdgQQIRRRRSSHKTTdgQQJRRRRSSHKSTdgQRJRRRRSSHKSSdHrrjrrrVkkOUkkU NGGGGGGCRZvRRRDNDDDDDDDDLPDDCENCDDDDDDDLPDCCENCCDDDDDDLPCCCENCCCDDDDDLOCCCENCCCCDDDDKOCCCENCCCCCDDCKBCCCEQCCCCCCCBJNBBBEcOOOOVOPPONOOOLSBBBBBAAAINBBBhSBBBBAAAAIMBBBhKBBBAAAAAEMABBhSBBAAAAAAIMAABhSBAAAAAAAIMAAAhCppppppGAQYAAAG", + "token": "N3Pcl-nKkIIvL8uTPWbZOcyezrJX85u3E-um_sJdqQBuPxl6AbZFaUdA05elyyyRkxZQi7xTyReflDESzU3_hrLo5FYgRAigv_pOEZBNF8XaeKd29cUdDY5o4OlmNsJSY0Iu8wrW1ZlXKxK61TzrONawJwgCxAOCsXe1soKxK-sFn-kIE27YFtTR7hIG8L1dwyLCZS_XPNuWR8PXIi5xZN8-GNNlDsW2ourNHWpkM0pT8jt6EPciWyYyiymD3hBykOuVKWC_70Cq6niIAIrs12YPoPsQ3w0dBVoH9gRh90ANcVlysi2WkmBhUbAScLdOdSyUbldBNSScxAdnAgMtojDzcP5ac8b_LJZBRLZGiDvs7MMRHN-zpaWEORzzTT8nXoJm7_1LbaJSTJ4jIgEJbtET57TgFvHfRidZ8phY5g6r0QBmb_Onr0BUlZVSfZs3GA3kmRyEbY9i14I4xisMILXRt1N-DPmxwAQUb7sIzXQCJfURVYgnC7wELkdj09SXbt6yh6flXgYa_MseKs4UbcKsu4HXBcFf5MIdTnoOZB3ZIIWdZZ4Kf5JlOh3XfbmEgrNRBjUpwtJGkexqNrTo-mC73rfhzVj2DN8b_3OGJep71lA_nhmKpbMR5urggBk", + "canonical": "" + }, + { + "id": 52, + "moves": "x 13U' 11D 12L 10U2 1R 1L' 10L' x y'", + "facelets": "XssssssWLLLLLLKXNNNTNNOOBOOTPeXMNNTNNOOBOOSPeHIIJJJJKKBKLLLWXMMMTNNOOBPPSPefXXXXWWVVUUUUUoXMMMSMNOPAPPSPeKMMMSMMPPAPPRPOYMMMRMMNOAPPRPvYMMMRMNNOBPPRPvYMMMRNNNOBOPRPvYMMMQNNNOWOORPvYMMNQNNNOBOOQPvYMNNQNNNOBOOQOvTllllllUqqqqqqI GuuuuuuPfpfXffJhXXXXXXXXAXMXWCDWXXXXXXXGXMWWOhWWXXXXXXAXNWWChWWWXXXXXAWTWWCPWWWWXXXXOWNWWchWWWWWXXWBWNWWCEWWWWWWWVCVOVVAEVVVVVVUUCVOVVJEVVVVVUUUDVOVVJIVVVVUUUUGUOVVbEVVVUUUUUDUPVVJEVVUUUUUUDUSUVJEVUUUUUUUDUPUUJDBBBBBBHoGoeooA UkkkkkkVrrrrrrHdSSSSSSRRRRRRQghBAAAAAAAAAABBCdTTSSSSRRRRQQQgdTTTSSSRRRQQQQgDEEEEFFGGHHHHHOdTTTTTSRQQQQQQgNTTTTTTRQQQQQQFmTTTTTTSRQQQQQFmTTTTTSSRRQQQQFECCCCDDDDDDCCCJmTTTSSSSRRRRQQFmTTSSSSSRRRRRQFmTSSSSSSRRRRRRFQVVVVVVTiiiiiiF RUUUUUUSjjjjjjERCCCECCBBOBBEAARDCCECCBBOBBFAARDDCECCBBKBAFAARDDDECCBBOAAFAARDDDFCCBBPAAFAARDDDFDCBAPAAFAACDDDFDDBAPAAGAGGDDDGDDCBPAAGApWLLLLLKKJJIIIIHGDDDGCCCBOBAGApoUUUVVVVWOWWXXfGDDCHCCCBOBBHApGDCCHCCCBOBBHBpNNNNNNNQSSSSSSC VWWGWeWLZZZZZZSPLLDLPLLLLLLLKcdKLDLSLLLLLLKKgPKKDLPLLLLLKKKcPKKHLPLLLLKKKKchKKCKALLLKKKKKCPKKCKOLLKKKKKKcIKKCKOKKJJJJJJMaJJBJNJIIJJJJJnaJJBJNIIIIJJJJnmJJBJSIIIIIJJJFaJJBIMIIIIIIJJnaJJGIMIIIIIIIJnaJIAIMIIIIIIIInMHHpHXHDQQQQQQP LKKKKKKXttttttWDFFFFFFGGGGGGHOPOPPPPPPPPPPOOcDEEFFFFGGGGHHHODEEEFFFGGGHHHHOdTTTTSSRRQQQQQgDEEEEEFGHHHHHHOBEEEEEEHHHHHHHJIEEEEEEFGHHHHHbIEEEEEFFGGHHHHbaNNNNMMMMMMNNNnIEEEFFFFGGGGHHbIEEFFFFFGGGGGHbIEFFFFFFGGGGGGbBTTTTTTRMMMMMMO", + "token": "AlwKs90jLt4OmI8CN_VkfcPGeun6ygDLkDJVsK5D4Izh3GLnlcweiUNybj16RUnjLfCuwcciWUmpIpzQ40e9J66mnQHh7IgQ_oRaE4nmrCSSif_IVnKjHtXMUVbwqWJCycSzlpddWlzHVDnZpcWwDHt6JWut5oMWL4ddb04K8KbdyaqleN73dy7Ome50ZMxeCzvbdkBHbmlk5ndNPG2VDjt6v5xbcqjSTyNxlZjqPbjnFCabqHP7Usjir34TKkUU6hqT2zYa-bjiJPD7_1LiYw_Bp8VCN0MLKLQkP9KNxhA0DsLehdS9znrwUHF8n5KeGxNcl9hZd-Bo64keuB-EE3KAGncLsTdiBXCS3Ord4a-xszqtst0eXii6ft7QUjdxWyHur-f7eZnn0iGxXTZZCEJvBdQaKC12hsKnTYMKXyXr8T_jPhrrbpotVVxMLm2ovOi9dadXSgN94B-75nQOZqbQezzOLc-BEcul3cw9_y0ZnXf1ZlauGM1vcKt0NOLMCDRsTnWzwGNAIazaAf9dkzguP10k9bOSGjk-BOBrfS7a6lFMQd6NOsIgWw9--32Zos5K-f4abNlBXMND29FK5HqYDs-KocP5Z8AIx6jlQt9rooG0k7SGerO7QW8n2ME", + "canonical": "EAAAAAAGpHpfppCjAAAAAAAAIAXABSjEFFFFFGGIGXHHSjBBAAAAAAIAWBBSjBBBAAAAAIBWBBSjOOKOPPPPJOOOOSjBBBBBAABJBWBBSSBBBBBBACKCVCCQUCCCCCCDDKCVCCNUCCCCCDDDLCVCCNUEEEEFFFGLGVHHNUCCCDDDDDLDUCCNUCCDDDDDDLDUDCNUCDDDDDDDLDUDDNRRRRRRRCGWGoGGN OMMMMMMRTTTTTTBbGGGGGGFFFFFFEIbHGGGGGFFFFFEEIbHHGGGGFFFFEEEInNNNMMMMMMNNNNabHHHHGGFFEEEEEIbHHHHHGFEEEEEEIJHHHHHHFEEEEEEBOHHHHHHGFEEEEEDgQQQQQRRSSTTTTdOHHHHGGGFFFEEEDOHHHGGGGFFFFEEDcOOPPPPPPPPPPOPOHGGGGGGFFFFFFDWttttttXKKKKKKL PQQQQQQDHXHpHHMnIIIIIIIIMIAIJanJIIIIIIIMIGJJanJJIIIIIIMIBJJaFJJJIIIIISJBJJmnJJJJIIIINJBJJanJJJJJIIJNJBJJaMJJJJJJIKOKCKKIcKKKKKKLLOKCKKPCKKKKKLLLAKCKKhcKKKKLLLLPLHKKPcKKKLLLLLPLDKKPgKKLLLLLLSLDLKdcKLLLLLLLPLDLLPSZZZZZZLWeWGWWV TYYYYYYKXfXHXXXlMMMMMMMMXMIMNslNMMMMMMMXMINNslNNMMMMMMXMJNNslQQQRRRSSXTJTTslNNNNMMMMWNJNNslNNNNNMMNWNJNNsUNNNNNNMOVOKOOWqOOOOOOPPVOKOOLqBBWBBAAAUBBBBLqOOOOPPPPUPKOOLqOOOPPPPPUPLOOLqQQRRRRRSUSLSTLqOPPPPPPPUPLPPLIvvvvvvOeoeWeeK FiiiiiiTVVVVVVQFRRRRRRSSSSSSTmFQRRRRRSSSSSTTmFQQRRRRSSSSTTTmJCCCDDDDDDCCCCEFQQQQRRSSTTTTTmFQQQQQRSTTTTTTmFQQQQQQTTTTTTTNgQQQQQQRSTTTTTdOHHHHHGGFFEEEEDgQQQQRRRSSSTTTdgQQQRRRRSSSSTTdCBBAAAAAAAAAABhgQRRRRRRSSSSSSdHrrrrrrVkkkkkkU AooeoGoHBBBBBBDJUUPUDUUUUUUUVEJVUSUDUUUUUUVVEJVVPUDUUUUUVVVEbVVOUGUUUUVVVVIJVVOVDUUUVVVVVEJVVOVCUUVVVVVVEAVVOVCVUWWWWWWECWWNWBWXXWWWWWhcWWNWOXXXXWWWWPCWWTWAXXXXXWWWhCWWNXAXXXXXXWWhOWWMXGXXXXXXXWDCWXMXAXXXXXXXXhJffXfpfPuuuuuuG" + }, + { + "id": 53, + "moves": "x 13U' 11D 12L 10U2 1R 1L' 10L' x y'", + "facelets": "XssssssWLLLLLLKXNNNTNNOOBOOTPeXMNNTNNOOBOOSPeHIIJJJJKKBKLLLWXMMMTNNOOBPPSPefXXXXWWVVUUUUUoXMMMSMNOPAPPSPeKMMMSMMPPAPPRPOYMMMRMMNOAPPRPvYMMMRMNNOBPPRPvYMMMRNNNOBOPRPvYMMMQNNNOWOORPvYMMNQNNNOBOOQPvYMNNQNNNOBOOQOvTllllllUqqqqqqI GuuuuuuPfpfXffJhXXXXXXXXAXMXWCDWXXXXXXXGXMWWOhWWXXXXXXAXNWWChWWWXXXXXAWTWWCPWWWWXXXXOWNWWchWWWWWXXWBWNWWCEWWWWWWWVCVOVVAEVVVVVVUUCVOVVJEVVVVVUUUDVOVVJIVVVVUUUUGUOVVbEVVVUUUUUDUPVVJEVVUUUUUUDUSUVJEVUUUUUUUDUPUUJDBBBBBBHoGoeooA UkkkkkkVrrrrrrHdSSSSSSRRRRRRQghBAAAAAAAAAABBCdTTSSSSRRRRQQQgdTTTSSSRRRQQQQgDEEEEFFGGHHHHHOdTTTTTSRQQQQQQgNTTTTTTRQQQQQQFmTTTTTTSRQQQQQFmTTTTTSSRRQQQQFECCCCDDDDDDCCCJmTTTSSSSRRRRQQFmTTSSSSSRRRRRQFmTSSSSSSRRRRRRFQVVVVVVTiiiiiiF RUUUUUUSjjjjjjERCCCECCBBOBBEAARDCCECCBBOBBFAARDDCECCBBKBAFAARDDDECCBBOAAFAARDDDFCCBBPAAFAARDDDFDCBAPAAFAACDDDFDDBAPAAGAGGDDDGDDCBPAAGApWLLLLLKKJJIIIIHGDDDGCCCBOBAGApoUUUVVVVWOWWXXfGDDCHCCCBOBBHApGDCCHCCCBOBBHBpNNNNNNNQSSSSSSC VWWGWeWLZZZZZZSPLLDLPLLLLLLLKcdKLDLSLLLLLLKKgPKKDLPLLLLLKKKcPKKHLPLLLLKKKKchKKCKALLLKKKKKCPKKCKOLLKKKKKKcIKKCKOKKJJJJJJMaJJBJNJIIJJJJJnaJJBJNIIIIJJJJnmJJBJSIIIIIJJJFaJJBIMIIIIIIJJnaJJGIMIIIIIIIJnaJIAIMIIIIIIIInMHHpHXHDQQQQQQP LKKKKKKXttttttWDFFFFFFGGGGGGHOPOPPPPPPPPPPOOcDEEFFFFGGGGHHHODEEEFFFGGGHHHHOdTTTTSSRRQQQQQgDEEEEEFGHHHHHHOBEEEEEEHHHHHHHJIEEEEEEFGHHHHHbIEEEEEFFGGHHHHbaNNNNMMMMMMNNNnIEEEFFFFGGGGHHbIEEFFFFFGGGGGHbIEFFFFFFGGGGGGbBTTTTTTRMMMMMMO", + "token": "PLuS1myO0XjaTGi6SFk67dQytzAMEZKXSIEuLicaFN54IPHWVinxRgis-hKSOzhsewTaL8JMN1ypqfuHKueUMUpxg2vKFQpcpA7UzN4C55tZMwZH4JkvX0EWEOEA10tj9jOF8Y54ZGzR7w1elCIZabXkDW86YH0Dy2mN8wEnusbntBFY-aNLiF8-9PMYVSlgI1jxICI7YpRZjTQCiU2Lt6lR4m_reEGwvHbh7evD1yS3ShuSlu9uViJWQWSz9FJXaSDPNeNVMt_1E_oBcFZx6_nT1tbNpu4oMWFWWEzKnABfIo-JvVNHBR44nxv1I5e6p-OPaRE_8uucOUq4nEcA7c0Exmbq7-Pep3ynWf1gwAzW4qFVJYCjTCYsfJ-uPiZ6yOAeCZKgnSunaZo-ikZE6djYOlHMUYk-oBu7ySXU7gJdZaXDl7DHyDDhq6JuafgeUngBnI41oej8sOB5muFZn6OLjbNUS3QZqxeMyyXP7Dpmwz8BmCBSU0JykBLk9UMgxOonWwjaCU4DKDRwL_DFzURV8Lt3CNWydV3QdQoTvGSF958mLN09gjMIiXPnk8ZnQ1cHb9J6NF4gsk5cm58Fa35BY3T_Kl9hugDSt9V-RPIXPCjt2OybgNmOImu8Uhr", + "canonical": "" + } + ] +} diff --git a/test/cube/notation/orbit64V2Facelets.json b/test/cube/notation/orbit64V2Facelets.json index 6eac160..16a61ec 100644 --- a/test/cube/notation/orbit64V2Facelets.json +++ b/test/cube/notation/orbit64V2Facelets.json @@ -1,5 +1,5 @@ { - "source": "flix-orbit64@d7010088fd0d83d52be66672246189ba70705809: FORMAT-V2-SLOTS.md and test/TestV2FaceletData.flix (generated there by tools/format-v2/facelet_vectors.jl)", + "source": "flix-orbit64@a046e34adcdc212a69ce3dcc24b2e47b7fabfbe5: FORMAT-V2-SLOTS.md and test/TestV2FaceletData.flix (generated there by tools/format-v2/facelet_vectors.jl)", "generatedBy": "scripts/orbit64V2FaceletFixture.jl. Do not edit.", "slotText": "Facelets of each slot of an orbit, separated by spaces, slots separated by |, in slot order. Wings are keyed by depth, outermost first.", "rowText": "id: registry entry; moves: layer turns (depth, face, suffix) and whole-cube turns x y z from the solved cube; facelets: what the entry writes for the cube; token: its V2 token; canonical: the reference pose of that cube when the entry's facelets differ from it, else empty.", From e126bb9b9b55b89b2566b2830062954ccab9c406 Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 17:41:21 +0000 Subject: [PATCH 4/6] test(orbit64): check the V2 picture facelet bridge The reference rows encode and decode exactly, in both poses, and read back as the same cube in any orientation. Invalid pictures give the reference's exact reasons in the order of FORMAT-V2.md, including which failure wins when several happen in one step. A hostile cube (index getters, Proxy arrays that change between reads, hostile lengths, valueOf tricks) is copied once and never yields a picture of an unreachable cube; huge strings and lengths cost bounded work; nothing throws. Co-Authored-By: Claude Sonnet 5.5 --- .../notation/orbit64V2FaceletPictures.test.ts | 1107 +++++++++++++++++ 1 file changed, 1107 insertions(+) create mode 100644 test/cube/notation/orbit64V2FaceletPictures.test.ts diff --git a/test/cube/notation/orbit64V2FaceletPictures.test.ts b/test/cube/notation/orbit64V2FaceletPictures.test.ts new file mode 100644 index 0000000..135cc73 --- /dev/null +++ b/test/cube/notation/orbit64V2FaceletPictures.test.ts @@ -0,0 +1,1107 @@ +import { describe, expect, it } from 'vitest' +import { + decodeOrbit64V2State, + encodeOrbit64V2State, + type Orbit64V2Cube, + type Orbit64V2Pose, + orbit64V2Entries, + orbit64V2Entry, + orbit64V2Solved, +} from '../../../src/cube/notation/orbit64V2' +import { + decodeOrbit64V2Facelets, + encodeOrbit64V2Facelets, + orbit64V2FrameFaces, + orbit64V2FromFacelets, + orbit64V2Slots, + orbit64V2ToFacelets, +} from '../../../src/cube/notation/orbit64V2Facelets' +import pictures from './orbit64V2FaceletPictures.json' + +type Result = { TAG: 'Ok'; _0: T } | { TAG: 'Error'; _0: string } + +function ok(result: Result): T { + if (result.TAG === 'Error') throw new Error(result._0) + return result._0 +} + +const error = (result: Result): string => + result.TAG === 'Error' ? result._0 : 'accepted' + +const alphabet = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' +const pictureEntries = orbit64V2Entries.filter((e) => e.model === 'Picture') +const rows = pictures.rows + +// The solved picture of a size, from the reference rows (no moves). +function solvedPicture(n: number): string { + const entry = pictureEntries.find((e) => e.n === n && e.pose === 'Framed') + const row = rows.find((r) => r.id === entry?.id && r.moves === '') + if (row === undefined) throw new Error(`no solved row for ${n}`) + return row.facelets +} + +// The same string with the symbols at the given facelet indices (spaces not +// counted) replaced. +function put(text: string, edits: Record): string { + const compact = text.replaceAll(' ', '').split('') + for (const [index, symbol] of Object.entries(edits)) { + compact[Number(index)] = symbol + } + const n = Math.sqrt(compact.length / 6) + return [0, 1, 2, 3, 4, 5] + .map((face) => compact.slice(face * n * n, (face + 1) * n * n).join('')) + .join(' ') +} + +const at = (text: string, index: number): string => + text.replaceAll(' ', '')[index] + +// The string with the symbols at two facelets exchanged. +const swap = (text: string, i: number, j: number): string => + put(text, { [i]: at(text, j), [j]: at(text, i) }) + +const read = ( + n: number, + text: string, + pose: Orbit64V2Pose = 'Framed', +): string => error(orbit64V2FromFacelets(n, 'Picture', pose, text)) + +const slotsOf = (n: number) => { + const slots = orbit64V2Slots(n) + if (slots === undefined) throw new Error(`${n}`) + return slots +} + +describe('Orbit64 V2 picture facelet reference rows', () => { + it('has the rows of the reference repository', () => { + expect(rows).toHaveLength(pictures.rowCount) + expect(rows).toHaveLength(174) + // Every picture entry has rows, and no other entry has. + expect(new Set(rows.map((row) => row.id))).toEqual( + new Set(pictureEntries.map((e) => e.id)), + ) + expect(pictureEntries).toHaveLength(26) + }) + + // The generator checks this too. Pinned, because dropping both rows of a + // move sequence keeps the structure below intact. Change the numbers + // together with the reference input. + it('has the pinned number of move sequences per size', () => { + const sequences: Record = { + 3: 41, + 4: 4, + 5: 5, + 6: 4, + 7: 5, + 8: 3, + 9: 4, + 10: 3, + 11: 4, + 12: 3, + 13: 4, + 14: 3, + 15: 4, + } + const seen = new Map>() + for (const { id, moves } of rows) { + const n = orbit64V2Entry(id)?.n ?? 0 + seen.set(n, (seen.get(n) ?? new Set()).add(moves)) + } + expect( + Object.fromEntries([...seen].map(([n, set]) => [n, set.size])), + ).toEqual(sequences) + expect(Object.values(sequences).reduce((a, b) => a + b, 0) * 2).toBe(174) + }) + + it('has every move sequence once for each picture entry of its size', () => { + const bySequence = new Map() + for (const { id, moves } of rows) { + const n = orbit64V2Entry(id)?.n ?? 0 + const key = `${n} ${moves}` + bySequence.set(key, [...(bySequence.get(key) ?? []), id]) + } + for (const [key, ids] of bySequence) { + const n = Number(key.split(' ')[0]) + expect( + ids.sort((a, b) => a - b), + key, + ).toEqual(pictureEntries.filter((e) => e.n === n).map((e) => e.id)) + } + expect(bySequence.size).toBe(87) + }) + + it('writes every row at the length of its entry, in base64url', () => { + for (const { id, token, facelets } of rows) { + const entry = orbit64V2Entry(id) + expect(token).toHaveLength(entry?.length ?? -1) + const n = entry?.n ?? 0 + expect(facelets.split(' ')).toHaveLength(6) + expect(facelets.replaceAll(' ', '')).toHaveLength(6 * n * n) + expect(facelets).toMatch(/^[A-Za-z0-9_ -]+$/) + } + }) + + it('exercises whole-cube turns, slices and fixed center rotations', () => { + const three = rows.filter((row) => row.id === 4) + for (const moves of ['x', 'y', 'z', 'x2', "y'", '2R', "2U'", '2F2']) { + expect( + three.some((row) => row.moves === moves), + moves, + ).toBe(true) + } + // Some row of an odd cube has a quarter turned fixed center. + expect( + rows.some((row) => { + const entry = orbit64V2Entry(row.id) + if (entry === undefined || entry.n % 2 === 0) return false + const n = entry.n + const center = (n * n - 1) / 2 + const compact = row.facelets.replaceAll(' ', '') + return [0, 1, 2, 3, 4, 5].some( + (f) => alphabet.indexOf(compact[f * n * n + center]) % 4 !== 0, + ) + }), + ).toBe(true) + }) + + it('encodes every row facelet string to its token', () => { + for (const { id, moves, facelets, token } of rows) { + expect(encodeOrbit64V2Facelets(id, facelets), `${id} ${moves}`).toEqual({ + TAG: 'Ok', + _0: token, + }) + } + }) + + it('decodes every row token to its facelet string', () => { + for (const { id, moves, facelets, token, canonical } of rows) { + const decoded = ok(decodeOrbit64V2Facelets(token)) + expect(decoded.entry.id, `${id} ${moves}`).toBe(id) + expect(decoded.facelets, `${id} ${moves}`).toBe( + canonical === '' ? facelets : canonical, + ) + } + }) + + it('reads a canonical row in any orientation as the same cube', () => { + const turned = rows.filter((row) => row.canonical !== '') + expect(turned.length).toBeGreaterThan(0) + for (const { id, moves, facelets, canonical } of turned) { + const entry = orbit64V2Entry(id) + if (entry === undefined) throw new Error(`${id}`) + expect(entry.pose, `${id} ${moves}`).toBe('Canonical') + expect(facelets, `${id} ${moves}`).not.toBe(canonical) + const cube = ok( + orbit64V2FromFacelets(entry.n, 'Picture', 'Canonical', canonical), + ) + expect( + orbit64V2FromFacelets(entry.n, 'Picture', 'Canonical', facelets), + `${id} ${moves}`, + ).toEqual({ TAG: 'Ok', _0: cube }) + } + }) + + it('reads and writes the cube of every row in both directions', () => { + for (const { id, moves, facelets, token } of rows) { + const entry = orbit64V2Entry(id) + if (entry === undefined) throw new Error(`${id}`) + const cube = ok( + orbit64V2FromFacelets(entry.n, 'Picture', entry.pose, facelets), + ) + expect(decodeOrbit64V2State(token), `${id} ${moves}`).toEqual({ + TAG: 'Ok', + _0: { entry, cube }, + }) + if (entry.pose === 'Framed') { + expect( + orbit64V2ToFacelets(entry.n, 'Picture', 'Framed', cube), + `${id} ${moves}`, + ).toEqual({ TAG: 'Ok', _0: facelets }) + } + } + }) + + it('ignores spaces when reading', () => { + for (const { id, facelets, token } of rows) { + expect(encodeOrbit64V2Facelets(id, facelets.replaceAll(' ', ''))).toEqual( + { TAG: 'Ok', _0: token }, + ) + expect( + encodeOrbit64V2Facelets(id, ` ${facelets.replaceAll(' ', ' ')} `), + ).toEqual({ TAG: 'Ok', _0: token }) + } + }) + + it('writes the solved cube of every picture entry as the solved token', () => { + for (const e of pictureEntries) { + const text = solvedPicture(e.n) + expect( + ok( + orbit64V2ToFacelets( + e.n, + 'Picture', + e.pose, + orbit64V2Solved(e.n, 'Picture'), + ), + ), + ).toBe(text) + const token = ok(encodeOrbit64V2Facelets(e.id, text)) + expect(token).toBe(tokenOfOffset(e)) + expect(ok(decodeOrbit64V2Facelets(token))).toEqual({ + entry: e, + facelets: text, + }) + } + }) + + it('writes the documented solved 3x3x3', () => { + // FORMAT-V2.md, "Picture facelets": the example string. + expect(solvedPicture(3)).toBe( + 'GGJEAADCA BBLREXOJW EDCSIQRLN PKMMMISOV HFFVQTUNQ KHIWUUXPT', + ) + // The U field: the center is A (fixed center U, rotation 0), the corners G + // (6), J (9), D (3) and A (0), the midges G, E, A and C. + expect(solvedPicture(3).split(' ')[0]).toBe('GGJEAADCA') + }) + + it('round-trips a state in the middle of every picture entry', () => { + for (const e of pictureEntries) { + const rank = (e.count * 7919n) / 10007n + const digits = tokenOfValue(e.offset + rank, e.length) + const { cube } = ok(decodeOrbit64V2State(digits)) + const text = ok(orbit64V2ToFacelets(e.n, 'Picture', e.pose, cube)) + expect( + ok(orbit64V2FromFacelets(e.n, 'Picture', e.pose, text)), + `${e.id}`, + ).toEqual(cube) + expect(encodeOrbit64V2Facelets(e.id, text), `${e.id}`).toEqual({ + TAG: 'Ok', + _0: digits, + }) + expect(ok(decodeOrbit64V2Facelets(digits)).facelets, `${e.id}`).toBe(text) + } + }) +}) + +function tokenOfValue(value: bigint, length: number): string { + let token = '' + for (let v = value, i = 0; i < length; i++, v /= 64n) { + token = alphabet[Number(v % 64n)] + token + } + return token +} + +const tokenOfOffset = (e: { offset: bigint; length: number }): string => + tokenOfValue(e.offset, e.length) + +describe('Orbit64 V2 picture frames and fixed centers', () => { + const oddSizes = [3, 5, 7, 9, 11, 13, 15] + + it('shows the faces of each frame as the pieces of the fixed centers', () => { + for (const n of oddSizes) { + const center = (n * n - 1) / 2 + for (const [rank, faces] of orbit64V2FrameFaces.entries()) { + const cube = { ...orbit64V2Solved(n, 'Picture'), frame: rank } + const text = ok(orbit64V2ToFacelets(n, 'Picture', 'Framed', cube)) + const compact = text.replaceAll(' ', '') + // The piece of a fixed center is its home face: the symbol div 4. + const shown = [0, 1, 2, 3, 4, 5] + .map( + (f) => 'URFDLB'[alphabet.indexOf(compact[f * n * n + center]) >> 2], + ) + .join('') + expect(shown, `${n} frame ${rank}`).toBe(faces) + const back = ok(orbit64V2FromFacelets(n, 'Picture', 'Framed', text)) + expect(back, `${n} frame ${rank}`).toEqual(cube) + // A canonical read of the same string turns the frame away. + expect( + ok(orbit64V2FromFacelets(n, 'Picture', 'Canonical', text)).frame, + `${n} frame ${rank}`, + ).toBe(0) + } + } + }) + + it('writes a canonical fixed center with its rotation in the symbol', () => { + for (const n of oddSizes) { + const center = (n * n - 1) / 2 + for (let r = 0; r < 4; r++) { + // Rotation r on every face but two: the sum of the rotations is even. + const rotations = [r, r, 0, 0, 0, 0] + const cube = { ...orbit64V2Solved(n, 'Picture'), rotations } + const text = ok(orbit64V2ToFacelets(n, 'Picture', 'Canonical', cube)) + const compact = text.replaceAll(' ', '') + expect( + [0, 1, 2, 3, 4, 5].map((f) => compact[f * n * n + center]), + `${n} ${r}`, + ).toEqual([0, 1, 2, 3, 4, 5].map((f) => alphabet[4 * f + rotations[f]])) + expect( + ok(orbit64V2FromFacelets(n, 'Picture', 'Canonical', text)).rotations, + `${n} ${r}`, + ).toEqual(rotations) + } + } + }) + + it('shows the quarter turn of the documented example', () => { + // FORMAT-V2.md: turning the U center a quarter turn writes B there; with + // the R center turned too the symbols are B (4 * 0 + 1) and F (4 * 1 + 1). + const solved = solvedPicture(3) + const cube = { + ...orbit64V2Solved(3, 'Picture'), + rotations: [1, 1, 0, 0, 0, 0], + } + expect(ok(orbit64V2ToFacelets(3, 'Picture', 'Canonical', cube))).toBe( + put(solved, { 4: 'B', 13: 'F' }), + ) + expect(read(3, put(solved, { 4: 'B' }))).toBe( + 'fixed-center rotation parity must equal corner parity', + ) + }) + + it('turns the rotation of a fixed center with the whole cube', () => { + // After `x` the cube shows F R D B L U in its slots; the fixed centers + // keep their pieces and the U and D slots' symbols are measured again. + const row = rows.find((r) => r.id === 5 && r.moves === 'x') + const canonical = rows.find((r) => r.id === 4 && r.moves === 'x') + if (row === undefined || canonical === undefined) throw new Error('x') + const cube = ok(orbit64V2FromFacelets(3, 'Picture', 'Framed', row.facelets)) + expect(orbit64V2FrameFaces[cube.frame]).toBe('FRDBLU') + expect(canonical.canonical).toBe(solvedPicture(3)) + }) +}) + +describe('Orbit64 V2 picture facelet bridge rejects invalid input', () => { + const solved3 = solvedPicture(3) + + it('accepts the solved cubes', () => { + expect(read(3, solved3)).toBe('accepted') + expect(read(3, solved3, 'Canonical')).toBe('accepted') + for (let n = 3; n <= 15; n++) { + expect(read(n, solvedPicture(n)), `${n}`).toBe('accepted') + expect(read(n, solvedPicture(n), 'Canonical'), `${n}`).toBe('accepted') + } + }) + + it('says the 2x2x2 has no picture entry', () => { + const why = + 'the 2x2x2 has no picture entry: its picture model is the color model' + expect(read(2, 'AAAA')).toBe(why) + expect(read(2, 'UUUU RRRR FFFF DDDD LLLL BBBB', 'Canonical')).toBe(why) + expect( + error( + orbit64V2ToFacelets( + 2, + 'Picture', + 'Framed', + orbit64V2Solved(2, 'Color'), + ), + ), + ).toBe(why) + // The size comes first. + expect(read(16, solved3)).toBe('no V2 type for the 16x16x16') + expect(read(1, solved3)).toBe('no V2 type for the 1x1x1') + }) + + it('rejects a wrong number of symbols', () => { + expect(read(3, 'GGJ')).toBe('expected 54 facelets for the 3x3x3, got 3') + expect(read(3, '')).toBe('expected 54 facelets for the 3x3x3, got 0') + expect(read(3, `${solved3}A`)).toBe( + 'expected 54 facelets for the 3x3x3, got 55', + ) + expect(read(3, solved3.slice(0, -1))).toBe( + 'expected 54 facelets for the 3x3x3, got 53', + ) + expect(read(4, solved3)).toBe('expected 96 facelets for the 4x4x4, got 54') + expect(read(15, solved3)).toBe( + 'expected 1350 facelets for the 15x15x15, got 54', + ) + }) + + it('rejects a character outside the base64url alphabet', () => { + const bad = (text: string, shown: string) => + expect(read(3, text), JSON.stringify(shown)).toBe( + `facelet '${shown}' is not a base64url symbol`, + ) + for (const c of ['#', '=', '+', '/', '.', '%', '*', 'é', ' ']) { + bad(put(solved3, { 0: c }), c) + } + bad(solved3.replace(' ', '\t'), '\t') + bad(solved3.replace(' ', '\n'), '\n') + bad(solved3.replace(' ', '\r\n'), '\r') + bad(solved3.replace(' ', ' '), ' ') + bad(put(solved3, { 3: '\u{1f9ca}' }), '\u{1f9ca}') + bad(put(solved3, { 3: '\ud800' }), '\ud800') + bad(put(solved3, { 3: '\u0000' }), '\u0000') + // Every symbol of the alphabet is read. + for (const c of alphabet) { + expect(read(3, put(solved3, { 0: c })), c).not.toMatch(/base64url/) + } + }) + + it('names a bad character before a wrong length and the first of several', () => { + expect(read(3, '#')).toBe("facelet '#' is not a base64url symbol") + expect(read(3, `${solved3}A=`)).toBe( + "facelet '=' is not a base64url symbol", + ) + expect(read(3, put(solved3, { 7: '+', 2: '/' }))).toBe( + "facelet '/' is not a base64url symbol", + ) + expect(read(3, `${'A'.repeat(54)}#`)).toBe( + "facelet '#' is not a base64url symbol", + ) + }) + + it('rejects a sticker of the wrong piece type', () => { + // Symbol Y is 24: no corner, midge, center or fixed center has that many + // stickers. Facelets 0, 1 and 4 of the 3x3x3 are a corner, a midge and the + // U center. + expect(read(3, put(solved3, { 0: 'Y' }))).toBe( + "facelet 0 shows 'Y', which is no corner sticker", + ) + expect(read(3, put(solved3, { 1: 'Y' }))).toBe( + "facelet 1 shows 'Y', which is no midge sticker", + ) + expect(read(3, put(solved3, { 4: 'Y' }))).toBe( + "facelet 4 shows 'Y', which is no fixed center sticker", + ) + expect(read(3, put(solved3, { 0: '_' }))).toBe( + "facelet 0 shows '_', which is no corner sticker", + ) + const solved5 = solvedPicture(5) + // Wings have 48 stickers, so `v` (47) is in range and `w` (48) is not. + const wing = slotsOf(5).wings[0][0][0] + expect(read(5, put(solved5, { [wing]: 'w' }))).toBe( + `facelet ${wing} shows 'w', which is no wing sticker`, + ) + expect(read(5, put(solved5, { [wing]: 'v' }))).not.toMatch( + /no wing sticker/, + ) + const center = slotsOf(5).centers[0][0] + expect(read(5, put(solved5, { [center]: 'Y' }))).toBe( + `facelet ${center} shows 'Y', which is no center sticker`, + ) + expect(read(5, put(solved5, { [center]: 'X' }))).not.toMatch( + /no center sticker/, + ) + // The boundary of the corner class: X (23) is in range, Y (24) is not. + expect(read(3, put(solved3, { 0: 'X' }))).not.toMatch(/no corner sticker/) + }) + + it('rejects a sticker that is shown twice', () => { + // Facelet 0 gets the symbol of the corner sticker A at facelet 8. + expect(read(3, put(solved3, { 0: 'A' }))).toBe( + "corner sticker 'A' occurs more than once", + ) + expect(read(3, put(solved3, { 1: 'A' }))).toBe( + "midge sticker 'A' occurs more than once", + ) + // Two fixed centers that differ only in rotation are still one piece. + expect(read(3, put(solved3, { 4: 'B', 13: 'B' }))).toBe( + "fixed center sticker 'B' occurs more than once", + ) + expect(read(3, put(solved3, { 4: 'A', 13: 'B' }))).toBe( + "fixed center sticker 'A' occurs more than once", + ) + const solved4 = solvedPicture(4) + const slots4 = slotsOf(4) + const copy = (to: number, from: number) => + put(solved4, { [to]: at(solved4, from) }) + expect(read(4, copy(slots4.centers[0][0], slots4.centers[0][1]))).toBe( + `center sticker '${at(solved4, slots4.centers[0][1])}' occurs more than once`, + ) + expect(read(4, copy(slots4.wings[0][0][0], slots4.wings[0][1][0]))).toBe( + `wing sticker '${at(solved4, slots4.wings[0][1][0])}' occurs more than once`, + ) + }) + + it('names the first failure of a step in the order of the format', () => { + const solved5 = solvedPicture(5) + const slots5 = slotsOf(5) + // Classes: corner before midge. + expect(read(3, put(solved3, { 0: 'A', 1: 'Y' }))).toBe( + "corner sticker 'A' occurs more than once", + ) + // Midge before fixed center. + expect(read(3, put(solved3, { 1: 'Y', 4: 'Y' }))).toBe( + "facelet 1 shows 'Y', which is no midge sticker", + ) + // Within a class a range error comes before a repeat, wherever they sit. + expect(read(3, put(solved3, { 0: 'A', 2: 'Y' }))).toBe( + "facelet 2 shows 'Y', which is no corner sticker", + ) + expect(read(3, put(solved3, { 2: 'Y', 0: 'A' }))).toBe( + "facelet 2 shows 'Y', which is no corner sticker", + ) + // The first out-of-range facelet in slot order, not in facelet order: the + // URF corner's facelets 8, 9 and 20 come before facelet 17 of slot 1. + const corner = slotsOf(3).corners + expect( + read(3, put(solved3, { [corner[1][0]]: 'Z', [corner[0][2]]: 'Y' })), + ).toBe(`facelet ${corner[0][2]} shows 'Y', which is no corner sticker`) + // Wings: the outermost orbit before the next one, then center orbits in + // order, then the fixed centers. + const solved6 = solvedPicture(6) + const slots6 = slotsOf(6) + expect( + read( + 6, + put(solved6, { + [slots6.wings[1][0][0]]: 'w', + [slots6.wings[0][0][0]]: 'x', + }), + ), + ).toBe( + `facelet ${slots6.wings[0][0][0]} shows 'x', which is no wing sticker`, + ) + expect( + read( + 6, + put(solved6, { + [slots6.centers[1][0]]: 'Y', + [slots6.centers[0][0]]: 'Z', + }), + ), + ).toBe( + `facelet ${slots6.centers[0][0]} shows 'Z', which is no center sticker`, + ) + // Wings before centers: a repeated wing sticker beats a bad center. + expect( + read( + 6, + put(solved6, { + [slots6.wings[1][0][0]]: 'C', + [slots6.centers[0][0]]: 'Y', + }), + ), + ).toBe("wing sticker 'C' occurs more than once") + expect( + read( + 5, + put(solved5, { [slots5.centers[0][0]]: 'Y', [slots5.fixed[0]]: 'Y' }), + ), + ).toBe( + `facelet ${slots5.centers[0][0]} shows 'Y', which is no center sticker`, + ) + // Step 1 before step 2: a bad character before a repeated sticker. + expect(read(3, put(solved3, { 0: 'A', 5: '#' }))).toBe( + "facelet '#' is not a base64url symbol", + ) + // Step 2 before step 3: a repeated sticker before a broken slot. + expect(read(3, put(solved3, { 8: 'B', 9: 'A', 1: 'Y' }))).toBe( + "facelet 1 shows 'Y', which is no midge sticker", + ) + }) + + it('rejects a slot whose symbols do not spell one piece', () => { + // The U and R stickers of the URF corner exchanged: a mirrored corner. + expect(read(3, swap(solved3, 8, 9))).toBe( + 'corner slot 0 shows BAC, which is no corner of this cube', + ) + // The U sticker of UR and the R sticker of UF: two midges mixed. + expect(read(3, swap(solved3, 5, 19))).toBe( + 'midge slot 0 shows ' + + at(solved3, 19) + + at(solved3, 10) + + ', which is no midge of this cube', + ) + // A wing with its two stickers exchanged is a twisted wing, and wings have + // no twist. + const solved4 = solvedPicture(4) + const wing = slotsOf(4).wings[0][0] + expect(read(4, swap(solved4, wing[0], wing[1]))).toBe( + `wing slot 0 shows ${at(solved4, wing[1])}${at(solved4, wing[0])}, which is no wing of this cube`, + ) + // Corners are checked before midges, midges before wings. + expect(read(3, swap(swap(solved3, 8, 9), 5, 19))).toBe( + 'corner slot 0 shows BAC, which is no corner of this cube', + ) + }) + + it('rejects fixed centers that are no whole-cube frame', () => { + const why = 'the six fixed centers are not a right-handed whole-cube frame' + // The U and R centers exchanged: left-handed. + expect(read(3, swap(solved3, 4, 13))).toBe(why) + expect(read(3, swap(solved3, 4, 13), 'Canonical')).toBe(why) + const center5 = slotsOf(5).fixed + expect(read(5, swap(solvedPicture(5), center5[1], center5[4]))).toBe(why) + // A frame is found by the pieces, not by the rotations. + expect(read(3, put(solved3, { 4: 'B', 13: 'F' }))).toBe('accepted') + // The frame check comes before the laws: R = C is broken too here. + expect(read(3, put(swap(solved3, 4, 13), { 22: 'J' }))).toBe(why) + }) + + it('rejects a broken orientation law', () => { + const solved = solved3 + // The URF corner turned in place: stickers A B C become B C A. + expect(read(3, put(solved, { 8: 'B', 9: 'C', 20: 'A' }))).toBe( + 'corner twists must sum to 0 mod 3', + ) + expect(read(3, put(solved, { 8: 'B', 9: 'C', 20: 'A' }), 'Canonical')).toBe( + 'corner twists must sum to 0 mod 3', + ) + // The UR midge flipped: its two stickers exchanged. + expect(read(3, swap(solved, 5, 10))).toBe('midge flips must sum to 0 mod 2') + // The twist sum is checked before the flip sum. + expect(read(3, put(swap(solved, 5, 10), { 8: 'B', 9: 'C', 20: 'A' }))).toBe( + 'corner twists must sum to 0 mod 3', + ) + }) + + it('rejects a broken parity relation', () => { + // Two midges exchanged with everything on them: M odd, C even. + expect(read(3, swap(swap(solved3, 5, 7), 10, 19))).toBe( + 'midge parity must equal corner parity', + ) + // One fixed center turned a quarter: R odd, C even. + expect(read(3, put(solved3, { 4: 'B' }))).toBe( + 'fixed-center rotation parity must equal corner parity', + ) + expect(read(3, put(solved3, { 4: 'B' }), 'Canonical')).toBe( + 'fixed-center rotation parity must equal corner parity', + ) + // Two center stickers of the 4x4x4 exchanged: Z odd, C even. + const solved4 = solvedPicture(4) + const center = slotsOf(4).centers[0] + expect(read(4, swap(solved4, center[0], center[1]))).toBe( + 'a center orbit parity breaks the picture relation', + ) + // M = C comes before R = C, and both before the center orbits. + expect(read(3, put(swap(swap(solved3, 5, 7), 10, 19), { 4: 'B' }))).toBe( + 'midge parity must equal corner parity', + ) + // The 5x5x5: M = C is broken by exchanging two midges. + const solved5 = solvedPicture(5) + expect( + read(5, swap(solved5, slotsOf(5).midges[0][0], slotsOf(5).midges[1][0])), + ).not.toBe('accepted') + }) +}) + +describe('Orbit64 V2 picture facelet bridge writes only states', () => { + it('writes no facelets for a cube that is no state of its type', () => { + const cube = orbit64V2Solved(3, 'Picture') + expect( + error( + orbit64V2ToFacelets(3, 'Picture', 'Canonical', { + ...cube, + rotations: [1, 0, 0, 0, 0, 0], + }), + ), + ).toBe('fixed-center rotation parity must equal corner parity') + expect( + error( + orbit64V2ToFacelets(3, 'Picture', 'Canonical', { + ...cube, + rotations: [4, 0, 0, 0, 0, 0], + }), + ), + ).toBe( + 'rotations must be 6 values in 0..3 on odd picture cubes and empty otherwise', + ) + expect( + error( + orbit64V2ToFacelets(3, 'Picture', 'Canonical', { ...cube, frame: 1 }), + ), + ).toBe('frame must be 0..23 on odd framed cubes and 0 otherwise') + // A color cube is no picture cube: its centers are multisets, its + // rotations empty. + expect( + error( + orbit64V2ToFacelets( + 5, + 'Picture', + 'Framed', + orbit64V2Solved(5, 'Color'), + ), + ), + ).toBe('a center orbit is not a valid color multiset or permutation of 24') + expect( + error( + orbit64V2ToFacelets(4, 'Picture', 'Framed', { + ...orbit64V2Solved(4, 'Picture'), + centers: [[1, 0, ...Array.from({ length: 22 }, (_, i) => i + 2)]], + }), + ), + ).toBe('a center orbit parity breaks the picture relation') + }) + + it('does not write a color cube as a picture or the other way round', () => { + const color = orbit64V2Solved(3, 'Color') + const picture = orbit64V2Solved(3, 'Picture') + const rotations = + 'rotations must be 6 values in 0..3 on odd picture cubes and empty otherwise' + expect(error(orbit64V2ToFacelets(3, 'Picture', 'Framed', color))).toBe( + rotations, + ) + expect(error(orbit64V2ToFacelets(3, 'Color', 'Framed', picture))).toBe( + rotations, + ) + }) + + it('names an unknown entry and a color string for a picture entry', () => { + expect(error(encodeOrbit64V2Facelets(-1, solvedPicture(3)))).toBe( + 'unknown registry entry -1', + ) + expect(error(encodeOrbit64V2Facelets(54, solvedPicture(3)))).toBe( + 'unknown registry entry 54', + ) + // The solved color string of the 3x3x3 is, as symbols, a repeated sticker. + expect( + error( + encodeOrbit64V2Facelets( + 4, + 'UUUUUUUUU RRRRRRRRR FFFFFFFFF DDDDDDDDD LLLLLLLLL BBBBBBBBB', + ), + ), + ).toBe("corner sticker 'U' occurs more than once") + // A picture string is no color string: G is no face letter. + expect(error(encodeOrbit64V2Facelets(2, solvedPicture(3)))).toBe( + "facelet 'G' is not one of U R F D L B", + ) + }) +}) + +// A cube whose arrays answer differently on every read after the first. +describe('Orbit64 V2 picture facelet bridge reads a hostile cube once', () => { + const solved = orbit64V2Solved(5, 'Picture') + const expected = ok(orbit64V2ToFacelets(5, 'Picture', 'Framed', solved)) + + // An array whose entries answer `later` from the second read on. + function flip(values: number[], later: (i: number) => unknown): number[] { + const reads = new Map() + return new Proxy(values, { + get(target, key, receiver) { + const value = Reflect.get(target, key, receiver) as unknown + if (typeof key !== 'string' || !/^\d+$/.test(key)) return value + const index = Number(key) + const count = (reads.get(index) ?? 0) + 1 + reads.set(index, count) + return count === 1 ? value : later(index) + }, + }) + } + + it('paints the one-read view when entries change after the first read', () => { + const fields = [ + 'cornerPieces', + 'cornerTwists', + 'midgePieces', + 'midgeFlips', + ] as const + for (const field of fields) { + const hostile = { ...solved, [field]: flip(solved[field], () => 7) } + expect( + orbit64V2ToFacelets(5, 'Picture', 'Framed', hostile as Orbit64V2Cube), + field, + ).toEqual({ TAG: 'Ok', _0: expected }) + } + const arrays = { + ...solved, + wings: solved.wings.map((w) => flip(w, () => 3)), + centers: solved.centers.map((c) => flip(c, () => 5)), + rotations: flip(solved.rotations, () => 1), + } + expect( + orbit64V2ToFacelets(5, 'Picture', 'Framed', arrays as Orbit64V2Cube), + ).toEqual({ TAG: 'Ok', _0: expected }) + }) + + it('does not accept a cube that is valid only on a later read', () => { + const broken = { ...solved, rotations: [1, 0, 0, 0, 0, 0] } + const fields = ['cornerPieces', 'centers', 'wings', 'rotations'] as const + for (const field of fields) { + let reads = 0 + const hostile = { + ...broken, + get [field]() { + reads++ + return reads === 1 ? broken[field] : solved[field] + }, + } + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', hostile as Orbit64V2Cube), + ), + field, + ).toBe('fixed-center rotation parity must equal corner parity') + } + // Entries valid from the second read on: still the first read decides. + const later = { + ...solved, + rotations: flip([1, 0, 0, 0, 0, 0], () => 0), + } + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', later as Orbit64V2Cube), + ), + ).toBe('fixed-center rotation parity must equal corner parity') + }) + + it('reads the frame and every array length once', () => { + let reads = 0 + const hostile = { + ...solved, + get frame() { + reads++ + return reads === 1 ? 0 : 7 + }, + } + expect( + orbit64V2ToFacelets(5, 'Picture', 'Framed', hostile as Orbit64V2Cube), + ).toEqual({ TAG: 'Ok', _0: expected }) + expect(reads).toBe(1) + // A length that shrinks after the first read: the copy has the first length. + let lengthReads = 0 + const shrinking = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + if (key === 'length') { + lengthReads++ + return lengthReads === 1 ? 8 : 3 + } + return Reflect.get(target, key, receiver) as unknown + }, + }) + expect( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { + ...solved, + cornerPieces: shrinking, + }), + ).toEqual({ TAG: 'Ok', _0: expected }) + expect(lengthReads).toBe(1) + }) + + it('does not run caller code on an entry and does not accept a number object', () => { + const ran: string[] = [] + const trick = { + valueOf() { + ran.push('valueOf') + return 0 + }, + toString() { + ran.push('toString') + return '0' + }, + } + const variants: unknown[] = [ + trick, + new Number(0), + 0n, + '0', + Symbol('0'), + null, + undefined, + Number.NaN, + -0.5, + [0], + ] + for (const bad of variants) { + const twists = [...solved.cornerTwists] + twists[0] = bad as number + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { + ...solved, + cornerTwists: twists, + }), + ), + String(typeof bad), + ).toBe('corner twists must be 8 values in 0..2') + const rotations = [...solved.rotations] + rotations[3] = bad as number + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { ...solved, rotations }), + ), + String(typeof bad), + ).toBe( + 'rotations must be 6 values in 0..3 on odd picture cubes and empty otherwise', + ) + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { + ...solved, + frame: bad as number, + }), + ), + String(typeof bad), + ).toBe('frame must be 0..23 on odd framed cubes and 0 otherwise') + } + expect(ran).toEqual([]) + }) + + it('does not run caller code on a length', () => { + const ran: string[] = [] + const lengths: unknown[] = [ + { + valueOf() { + ran.push('valueOf') + return 8 + }, + }, + '8', + 8n, + Number.NaN, + -1, + 2.5, + Number.POSITIVE_INFINITY, + Symbol('8'), + ] + for (const length of lengths) { + const hostile = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + return key === 'length' + ? length + : (Reflect.get(target, key, receiver) as unknown) + }, + }) + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { + ...solved, + cornerPieces: hostile, + }), + ), + String(typeof length), + ).toBe('corner pieces are not a permutation of 8') + } + expect(ran).toEqual([]) + }) + + it('does bounded work for a huge length', () => { + const started = Date.now() + for (const length of [1e6, 2 ** 31, 2 ** 53, Number.MAX_VALUE]) { + const huge = new Proxy(solved.cornerPieces, { + get(target, key, receiver) { + return key === 'length' + ? length + : (Reflect.get(target, key, receiver) as unknown) + }, + }) + for (const field of ['cornerPieces', 'wings', 'centers', 'rotations']) { + const cube = { ...solved, [field]: huge } + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', cube as Orbit64V2Cube), + ), + `${field} ${length}`, + ).not.toBe('accepted') + } + expect( + error( + orbit64V2ToFacelets(5, 'Picture', 'Framed', { + ...solved, + wings: { length } as unknown as number[][], + }), + ), + ).toBe('malformed state') + expect( + encodeOrbit64V2State(20, { + ...orbit64V2Solved(7, 'Picture'), + centers: { length } as unknown as number[][], + }).TAG, + ).toBe('Error') + } + expect(Date.now() - started).toBeLessThan(2000) + }) +}) + +describe('Orbit64 V2 picture facelet bridge never throws', () => { + it('reads a hostile string without throwing or echoing it', () => { + const solved3 = solvedPicture(3) + for (const bad of [ + undefined, + null, + 5, + {}, + ['A'], + new String(solved3), + Symbol('x'), + 3n, + () => solved3, + ]) { + expect( + error( + orbit64V2FromFacelets( + 3, + 'Picture', + 'Framed', + bad as unknown as string, + ), + ), + ).toBe('facelets must be a string') + expect(error(encodeOrbit64V2Facelets(4, bad as unknown as string))).toBe( + 'facelets must be a string', + ) + } + }) + + it('names the length of a huge string without keeping it', () => { + const solved3 = solvedPicture(3) + const huge = 'A'.repeat(3_000_000) + const started = Date.now() + expect(read(3, huge)).toBe( + 'expected 54 facelets for the 3x3x3, got 3000000', + ) + expect(read(3, ' '.repeat(3_000_000))).toBe( + 'expected 54 facelets for the 3x3x3, got 0', + ) + // The first bad character wins over the length, wherever it is. + expect(read(3, `${huge}#`)).toBe("facelet '#' is not a base64url symbol") + expect(read(3, `${solved3}${' '.repeat(2_000_000)}`)).toBe('accepted') + expect(read(15, 'A'.repeat(1350 + 1_000_000))).toBe( + 'expected 1350 facelets for the 15x15x15, got 1001350', + ) + expect(Date.now() - started).toBeLessThan(5000) + }) + + it('returns an error for every mutated row, a bad pose or a bad size', () => { + // A deterministic walk over the rows with single and double mutations. + let seed = 12345 + const next = (bound: number): number => { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0 + return (seed >>> 8) % bound + } + const extra = ['#', '\t', '\n', '\u{1f9ca}', '\ud800', ' ', '=', 'é'] + let accepted = 0 + for (const { id, facelets } of rows) { + const entry = orbit64V2Entry(id) + if (entry === undefined) throw new Error(`${id}`) + const compact = facelets.replaceAll(' ', '').split('') + for (let k = 0; k < 12; k++) { + const mutated = compact.slice() + const edits = 1 + next(3) + for (let e = 0; e < edits; e++) { + const i = next(mutated.length) + const kind = next(4) + if (kind === 0) mutated[i] = alphabet[next(64)] + else if (kind === 1) mutated.splice(i, 1) + else if (kind === 2) mutated.splice(i, 0, extra[next(extra.length)]) + else { + const j = next(mutated.length) + ;[mutated[i], mutated[j]] = [mutated[j], mutated[i]] + } + } + const text = mutated.join('') + const results = [ + orbit64V2FromFacelets(entry.n, 'Picture', entry.pose, text), + orbit64V2FromFacelets( + entry.n, + 'Picture', + 'Bogus' as Orbit64V2Pose, + text, + ), + encodeOrbit64V2Facelets(id, text), + ] + for (const r of results) { + expect(['Ok', 'Error']).toContain(r.TAG) + if (r.TAG === 'Error') expect(typeof r._0).toBe('string') + } + if (results[0].TAG === 'Ok') accepted++ + } + } + // Some mutations (an exchange of equal symbols) are no change at all. + expect(accepted).toBeLessThan(rows.length) + }) +}) From a9ce4528ec7f78f1df58736b7f42d0a23004bcc9 Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 17:41:21 +0000 Subject: [PATCH 5/6] docs(orbit64): describe the V2 picture facelet bridge Co-Authored-By: Claude Sonnet 5.5 --- docs/architecture.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index f2637b8..faa65ae 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -100,10 +100,19 @@ cross-project vectors in `test/cube/notation/orbit64Vectors.json` and The Orbit64 V2 state codec for 2x2-15x15 cubes ([`Orbit64V2.res`](../src/cube/notation/Orbit64V2.res)) works on orbit coordinates. [`Orbit64V2Facelets.res`](../src/cube/notation/Orbit64V2Facelets.res) -is its facelet bridge for the color model, both poses: slot tables, cube to -facelets and back, and token to facelets and back, with every failure an error -value. The picture model has no facelet letters and is an error there. Its -fixture `test/cube/notation/orbit64V2Facelets.json` (the slot tables of -`FORMAT-V2-SLOTS.md` and the facelet rows of `TestV2FaceletData.flix`) is written -by `julia scripts/orbit64V2FaceletFixture.jl ` and -regenerates byte for byte. +is its facelet bridge, both poses: slot tables, cube to facelets and back, and +token to facelets and back, with every failure an error value. The color model +writes the letters `U R F D L B`. The picture model (3x3x3 and up; the 2x2x2 +has no picture entry) writes one base64url symbol per facelet, the sticker's +index within its class, so a picture string is as long as a color string. Both +read in the validation order of `FORMAT-V2.md` and give the reference's reason. +A cube handed to the bridge is copied once, by index, before it is checked, and +a string is walked once, so hostile arrays, getters and huge strings cost +bounded work and cannot change between the check and the write. The fixtures +`test/cube/notation/orbit64V2Facelets.json` (the slot tables of +`FORMAT-V2-SLOTS.md` and the color rows of `TestV2FaceletData.flix`) and +`test/cube/notation/orbit64V2FaceletPictures.json` (the picture rows of +`TestV2FaceletPictureData.flix`) are written by +`julia scripts/orbit64V2FaceletFixture.jl `, which checks the +reference rows for dropped, duplicated and unparsed entries and regenerates both +files byte for byte. From a3027351ffa22c0bf16d0d05967f420332784c6b Mon Sep 17 00:00:00 2001 From: Werner Stein Date: Sun, 1 Nov 2026 18:31:42 +0000 Subject: [PATCH 6/6] docs(orbit64): state the cost of a huge string and fail the generator on stray lines The architecture note said huge strings cost bounded work; the walk is linear in time and bounded in memory, and now says so. The picture test named "returns an error for every mutated row" only checks that nothing throws and that results are well formed, and is renamed accordingly. The fixture generator now fails on any line of TestV2FaceletPictureData.flix that is neither a row nor one of the file's known structural lines. Co-Authored-By: Claude Sonnet 5.5 --- docs/architecture.md | 5 +++-- scripts/orbit64V2FaceletFixture.jl | 8 ++++++++ test/cube/notation/orbit64V2FaceletPictures.test.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index faa65ae..66ffea2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,8 +107,9 @@ has no picture entry) writes one base64url symbol per facelet, the sticker's index within its class, so a picture string is as long as a color string. Both read in the validation order of `FORMAT-V2.md` and give the reference's reason. A cube handed to the bridge is copied once, by index, before it is checked, and -a string is walked once, so hostile arrays, getters and huge strings cost -bounded work and cannot change between the check and the write. The fixtures +a string is walked once, so hostile arrays and getters cost bounded work, a +huge string costs linear time but no memory beyond the symbols of one cube, +and nothing can change between the check and the write. The fixtures `test/cube/notation/orbit64V2Facelets.json` (the slot tables of `FORMAT-V2-SLOTS.md` and the color rows of `TestV2FaceletData.flix`) and `test/cube/notation/orbit64V2FaceletPictures.json` (the picture rows of diff --git a/scripts/orbit64V2FaceletFixture.jl b/scripts/orbit64V2FaceletFixture.jl index 356f92c..a5ec372 100644 --- a/scripts/orbit64V2FaceletFixture.jl +++ b/scripts/orbit64V2FaceletFixture.jl @@ -153,6 +153,14 @@ picture_tuple_lines = length(collect(eachmatch(r"^\s*\(\d+,"m, picture_text))) picture_rows = [Row(parse(Int, m.captures[1]), m.captures[2], m.captures[3], m.captures[4], m.captures[5]) for m in eachmatch(picture_pattern, picture_text)] isempty(picture_rows) && error("TestV2FaceletPictureData.flix: no rows") +# Everything that is not a row must be one of the known lines of the file; a +# stray line (a half row, a note) fails instead of being skipped. +let rest = replace(picture_text, picture_pattern => "") + known = r"^(//.*|mod \w+ \{|\s*///.*|\s*pub def rows\w*\(\): .*|\s*\}\)?|\s*List\.flatten\(.*|\s*rows\w+\(\).*|\s*)$" + for line in split(rest, '\n') + occursin(known, line) || error("TestV2FaceletPictureData.flix: unexpected line: $line") + end +end length(picture_rows) == picture_tuple_lines || error("TestV2FaceletPictureData.flix: $picture_tuple_lines tuple lines, $(length(picture_rows)) parsed as rows") # The same structure as the color rows: a picture entry per pose, each move diff --git a/test/cube/notation/orbit64V2FaceletPictures.test.ts b/test/cube/notation/orbit64V2FaceletPictures.test.ts index 135cc73..e7f29b7 100644 --- a/test/cube/notation/orbit64V2FaceletPictures.test.ts +++ b/test/cube/notation/orbit64V2FaceletPictures.test.ts @@ -1056,7 +1056,7 @@ describe('Orbit64 V2 picture facelet bridge never throws', () => { expect(Date.now() - started).toBeLessThan(5000) }) - it('returns an error for every mutated row, a bad pose or a bad size', () => { + it('never throws on mutated rows and returns well-formed results', () => { // A deterministic walk over the rows with single and double mutations. let seed = 12345 const next = (bound: number): number => {