diff --git a/docs/architecture.md b/docs/architecture.md index f2637b8..66ffea2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -100,10 +100,20 @@ 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 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 +`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. diff --git a/scripts/orbit64V2FaceletFixture.jl b/scripts/orbit64V2FaceletFixture.jl index 1035a2c..a5ec372 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,77 @@ 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") +# 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 +# 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 +272,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/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/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/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) + }) }) 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/orbit64V2FaceletPictures.test.ts b/test/cube/notation/orbit64V2FaceletPictures.test.ts new file mode 100644 index 0000000..e7f29b7 --- /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('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 => { + 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) + }) +}) 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.", 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 -}