aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbuild.sh5
-rw-r--r--src/blog/2022-06-04-gemini/gemini.md2
-rw-r--r--src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md2
-rw-r--r--src/blog/2024-09-20-c-scripting/c-scripting.md5
-rw-r--r--src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md2
-rw-r--r--src/blog/2025-04-04-qt-minimal/qt-minimal.md35
-rw-r--r--src/blog/2025-04-05-learned-rewrite/learned-rewrite.md4
-rw-r--r--src/blog/2025-06-06-webdev/blocking.pngbin0 -> 29516 bytes
-rw-r--r--src/blog/2025-06-06-webdev/callback.jpgbin0 -> 27472 bytes
-rw-r--r--src/blog/2025-06-06-webdev/callback2.jpgbin0 -> 60575 bytes
-rw-r--r--src/blog/2025-06-06-webdev/hello.pngbin0 -> 70582 bytes
-rw-r--r--src/blog/2025-06-06-webdev/logos.pngbin0 -> 31951 bytes
-rw-r--r--src/blog/2025-06-06-webdev/spectre.pngbin0 -> 28193 bytes
-rw-r--r--src/blog/2025-06-06-webdev/storage.pngbin0 -> 74806 bytes
-rw-r--r--src/blog/2025-06-06-webdev/threads.jpgbin0 -> 65580 bytes
-rw-r--r--src/blog/2025-06-06-webdev/wasm.pngbin0 -> 8457 bytes
-rw-r--r--src/blog/2025-06-06-webdev/webdev.md1186
-rw-r--r--src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md332
-rw-r--r--src/blog/2025-06-26-borrow-checker/borrow-checker.md150
-rw-r--r--src/blog/2025-08-16-alpine-declarative/alpine-declarative.md106
-rw-r--r--src/git/git.md14
-rw-r--r--src/research/research.md4
-rw-r--r--src/series/series.md10
-rw-r--r--src/speedcubing/bldsheets/corners.csv42
-rw-r--r--src/speedcubing/bldsheets/edges.csv36
-rw-r--r--src/speedcubing/coordinates/coordinates.md4
-rw-r--r--src/speedcubing/figure-it-out/comm1.svg39
-rw-r--r--src/speedcubing/figure-it-out/comm2.svg39
-rw-r--r--src/speedcubing/figure-it-out/edgecomm.svg39
-rw-r--r--src/speedcubing/figure-it-out/face.pngbin0 -> 10744 bytes
-rw-r--r--src/speedcubing/figure-it-out/figure-it-out.md88
-rw-r--r--src/speedcubing/figure-it-out/layer.pngbin0 -> 10509 bytes
-rw-r--r--src/speedcubing/figure-it-out/pair.pngbin0 -> 10334 bytes
-rw-r--r--src/speedcubing/figure-it-out/setup.svg39
-rw-r--r--src/speedcubing/figure-it-out/twist.svg39
-rw-r--r--top.html2
36 files changed, 1947 insertions, 277 deletions
diff --git a/build.sh b/build.sh
index 67776d5..93f0294 100755
--- a/build.sh
+++ b/build.sh
@@ -22,7 +22,8 @@ copyfile() {
22 md) 22 md)
23 t="$(markdowntitle "$file")" 23 t="$(markdowntitle "$file")"
24 sed "s/TITLE/$t/" < top.html > "$ind" 24 sed "s/TITLE/$t/" < top.html > "$ind"
25 lowdown "$file" >> "$ind" 25 lowdown --html-no-skiphtml --html-no-escapehtml \
26 "$file" >> "$ind"
26 cat bottom.html >> "$ind" 27 cat bottom.html >> "$ind"
27 ;; 28 ;;
28 html) 29 html)
@@ -64,7 +65,7 @@ makeblogindexandfeed() {
64 65
65 f="src/blog/$i/*.md" 66 f="src/blog/$i/*.md"
66 d="$(echo "$i" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}')" 67 d="$(echo "$i" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}')"
67 t="$(head -n 1 $f | sed 's/# //')" 68 t="$(markdowntitle $f)"
68 link="https://sebastiano.tronto.net/blog/$i" 69 link="https://sebastiano.tronto.net/blog/$i"
69 70
70 thisyear="$(echo "$d" | sed 's/-.*//')" 71 thisyear="$(echo "$d" | sed 's/-.*//')"
diff --git a/src/blog/2022-06-04-gemini/gemini.md b/src/blog/2022-06-04-gemini/gemini.md
index b04a8ac..6e228c5 100644
--- a/src/blog/2022-06-04-gemini/gemini.md
+++ b/src/blog/2022-06-04-gemini/gemini.md
@@ -192,4 +192,4 @@ and, more annoyingly for the few gemini users, I am going to use
192http(s) links even when a gemini counterpart is available. 192http(s) links even when a gemini counterpart is available.
193 193
194*Update: as of August 2023, my website is still available on gemini, 194*Update: as of August 2023, my website is still available on gemini,
195but new blog posts are no longer mirrore there* 195but new blog posts are no longer mirrored there*
diff --git a/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md b/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md
index 45da3cb..6a2f782 100644
--- a/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md
+++ b/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md
@@ -93,7 +93,7 @@ projects, or probably it will be never done. And that's OK.
93 93
94My plan is to split the work into the following parts: 94My plan is to split the work into the following parts:
95 95
96* Starting from the [stable branch](https://nissy.tronto.net/download) 96* Starting from the [stable branch](https://nissy.tronto.net)
97 of nissy, remove all the code that is needed only by the optimal 97 of nissy, remove all the code that is needed only by the optimal
98 solver and other unnecessary steps. Work on a GUI and other simple 98 solver and other unnecessary steps. Work on a GUI and other simple
99 features useful for assisting fewest moves solvers. 99 features useful for assisting fewest moves solvers.
diff --git a/src/blog/2024-09-20-c-scripting/c-scripting.md b/src/blog/2024-09-20-c-scripting/c-scripting.md
index de70812..49ea366 100644
--- a/src/blog/2024-09-20-c-scripting/c-scripting.md
+++ b/src/blog/2024-09-20-c-scripting/c-scripting.md
@@ -75,6 +75,11 @@ systems! The command is also included in FreeBSD (since 4.3) and
75MacOS (since 13), and there are probably workarounds to make the 75MacOS (since 13), and there are probably workarounds to make the
76same concept work without it. 76same concept work without it.
77 77
78*Edit 2025-06-09: as a reader points out, `realpath` is included
79in the most recent POSIX standard,
80[POSIX.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/realpath.html),
81so it should be more widely available in the future.*
82
78## Credits 83## Credits
79 84
80I did not come up with this trick - I read about it in at least 85I did not come up with this trick - I read about it in at least
diff --git a/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md b/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md
index fe1ca5d..bce440f 100644
--- a/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md
+++ b/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md
@@ -269,7 +269,7 @@ A classic example is
269[`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple), which 269[`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple), which
270works similarly to `std::pair`, but accepts any number of items. 270works similarly to `std::pair`, but accepts any number of items.
271 271
272## Contraints and concepts 272## Constraints and concepts
273 273
274To explain the last features I want to talk about, I am going to use a 274To explain the last features I want to talk about, I am going to use a
275simlpe, albeit slightly unusual, example: let's implement a class 275simlpe, albeit slightly unusual, example: let's implement a class
diff --git a/src/blog/2025-04-04-qt-minimal/qt-minimal.md b/src/blog/2025-04-04-qt-minimal/qt-minimal.md
index 6f112bb..4cc0e18 100644
--- a/src/blog/2025-04-04-qt-minimal/qt-minimal.md
+++ b/src/blog/2025-04-04-qt-minimal/qt-minimal.md
@@ -98,3 +98,38 @@ And finally you can enjoy your new app:
98``` 98```
99./run 99./run
100``` 100```
101
102## Update 2025-04-07 - now with AI!
103
104When I shared this post on LinkedIn, I asked if anyone could prompt an
105LLM to build a QT app without QT Creator or CMake. A colleague did,
106and the catbot's (pun intended) response can be found
107[here](https://chat.mistral.ai/chat/fa3e3a76-0e8c-4135-94a1-ae7735174e93).
108
109This solution is different from the one I found. The bot opted for using
110[qmake](https://doc.qt.io/qt-6/qmake-manual.html), another tool bundled
111with QT, like moc and uic. This tool reads a `.pro` file that describes
112the project's structure and build configuration, and it generates a
113`Makefile`. This method is a bit more black-boxy than the one I found,
114but it does not require more external dependencies; I think I might use
115this other approach in the future.
116
117The chat bot's response is great, but it is "only" 99%
118accurate. Unfortunately this means that if one blindly follows these
119instructions, they won't get a working QT application. The problem is
120with the commands:
121
122```
123qmake -project
124qmake
125```
126
127The first one is completely useless, and actually prevents the project
128from being built by generating a second project file. If one simply
129ignores the `qmake -project` line, the instructions become correct.
130
131This story is quite insightful: the chat bot gave me instructions that are
132almost correct (so technically they are *incorrect*). With my experience
133(a whole 5 hours of CV-worthy QT development) I was able to fix these
134instructions easily in a couple of minutes, but someone without the same
135experience would have probably struggled for much longer.
diff --git a/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md b/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md
index 6a7a3ed..0973dd3 100644
--- a/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md
+++ b/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md
@@ -17,8 +17,8 @@ than as fast as possible. Both the old and the new versions are written
17in C, but not all the things I discuss in this post are language-specific. 17in C, but not all the things I discuss in this post are language-specific.
18 18
19You can find the new version of this project, the "Big Rewrite", 19You can find the new version of this project, the "Big Rewrite",
20in [this repository](https://git.tronto.net/h48) - or 20in [this repository](https://git.tronto.net/nissy-core) - or
21[on github](https://github.com/sebastianotronto/h48), if you prefer. 21[on github](https://github.com/sebastianotronto/nissy-core), if you prefer.
22I decided to rename it "H48" back when I planned to have separate projects 22I decided to rename it "H48" back when I planned to have separate projects
23for the different features of the old version, but I may change it back 23for the different features of the old version, but I may change it back
24to Nissy at some point. 24to Nissy at some point.
diff --git a/src/blog/2025-06-06-webdev/blocking.png b/src/blog/2025-06-06-webdev/blocking.png
new file mode 100644
index 0000000..6d8f1c6
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/blocking.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/callback.jpg b/src/blog/2025-06-06-webdev/callback.jpg
new file mode 100644
index 0000000..21057be
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/callback.jpg
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/callback2.jpg b/src/blog/2025-06-06-webdev/callback2.jpg
new file mode 100644
index 0000000..c59aa7c
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/callback2.jpg
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/hello.png b/src/blog/2025-06-06-webdev/hello.png
new file mode 100644
index 0000000..d10658e
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/hello.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/logos.png b/src/blog/2025-06-06-webdev/logos.png
new file mode 100644
index 0000000..4c70d89
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/logos.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/spectre.png b/src/blog/2025-06-06-webdev/spectre.png
new file mode 100644
index 0000000..d86271e
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/spectre.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/storage.png b/src/blog/2025-06-06-webdev/storage.png
new file mode 100644
index 0000000..aab5de8
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/storage.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/threads.jpg b/src/blog/2025-06-06-webdev/threads.jpg
new file mode 100644
index 0000000..80befcf
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/threads.jpg
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/wasm.png b/src/blog/2025-06-06-webdev/wasm.png
new file mode 100644
index 0000000..c2222c7
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/wasm.png
Binary files differ
diff --git a/src/blog/2025-06-06-webdev/webdev.md b/src/blog/2025-06-06-webdev/webdev.md
new file mode 100644
index 0000000..6b049e5
--- /dev/null
+++ b/src/blog/2025-06-06-webdev/webdev.md
@@ -0,0 +1,1186 @@
1# A masochist's guide to web development
2
3## Table of contents
4
5* [Introduction](#introduction)
6* [Setting things up](#setting-things-up)
7* [Hello world](#hello-world)
8* [Intermezzo I: What is WebAssembly?](#intermezzo-i-what-is-webassembly)
9* [Building a library](#building-a-library)
10* [Intermezzo II: JavaScript and the DOM](#intermezzo-ii-javascript-and-the-dom)
11* [Loading the library and making it a module](#loading-the-library-and-making-it-a-module)
12* [Multithreading](#multithreading)
13* [Intermezzo III: Web Workers and Spectre](#intermezzo-iii-web-workers-and-spectre)
14* [Don't block the main thread!](#dont-block-the-main-thread)
15* [Callback functions](#callback-functions)
16* [Persistent storage](#persistent-storage)
17* [Closing thoughts](#closing-thoughts)
18
19## Introduction
20
21I have recently worked on making a web application out of
22[my latest Rubik's cube optimal solver](https://git.tronto.net/nissy-core/file/README.md.html).
23This involved building a rather complex C code base (with
24multithreading, SIMD, callback functions and whatnot) to
25[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) via
26[Emscripten](https://emscripten.org/), and writing a minimal amount of
27JavaScript and HTML for the frontend.
28
29This whole process was complex, tiring and at times frustrating -
30but eventually [it was a success](https://tronto.net:48)! Not only
31I accomplished my goal, but I have learnt a lot along the way. After
32finishing the work, I decided to write down all that I have learnt and
33share it with the world with this post.
34
35You may be wondering why one should do such a thing instead of either
36rewriting their code base in a more web-friendly language, or distributing
37their app using a native GUI framework. The main reason to use WebAssembly
38is that it can provide near-native performance (or so they claim) while
39running inside a web browser; this gives you all the portability of a
40web app without too much of a performance drawback, something that would
41not be possible with an interpreted language such as JavaScript.
42
43So, what is this blog post? A tutorial for web development? I am not sure
44about this, but if it is, it is definitely not a normal one. As the title
45suggests, you should not start from this guide unless you just *love*
46banging your head against the wall. If you are looking for a *sane*
47guide to web development, I strongly advise you head on to the
48[Mozilla Developer Network tutorials page](https://developer.mozilla.org/en-US/docs/MDN/Tutorials)
49and start from there.
50
51But if you are a C or C++ developer looking to port a program or library
52to the web, then you are in the right place. With this post I am going
53to walk you through the process of building an increasingly complex
54library that can run in a web browser. Make sure you are
55sitting comfortably and be ready to sweat, because I am not going to
56shy away from the hard stuff and the complicated details.
57
58To follow this tutorial you won't need much experience with web
59development, but some familiarity with HTML and an idea of what JavaScript
60will be useful. It will also help to know that you can access your
61browser's JavaScript console and other developer tools by pressing F12,
62at least on Firefox or Chrome - but I guess I have literally just taught
63you that, if you did not already know it. For all the rest, I'll make
64sure to add many hyperlinks throughout the text, so you can follow them
65if something is new to you.
66
67A little disclaimer: although I am a somewhat experienced C developer,
68I had very little web development experience before embarking in
69this adventure. If you are a web developer, you may find errors in
70this post that are going to make you laugh at my ignorance. If you do,
71I'd appreciate it if you could report them to me by sending an email to
72`sebastiano@tronto.net`!
73
74With this out of the way, let's get started!
75
76## Setting things up
77
78The examples used in this tutorial are all contained in a git repository,
79which you can find either on
80[my git page](https://git.tronto.net/emscripten-tutorial/file/README.md.html) or
81[on github](https://github.com/sebastianotronto/emscripten-tutorial).
82
83In order to follow them you are going to need:
84
85* A working installation of [Emscripten](https://emscripten.org/)
86 (which also includes Node.js). Refer to the official website for
87 installation instructions.
88* A web server such [darkhttpd](https://github.com/emikulic/darkhttpd)
89 or the Python `http.server` package; the examples will use darkhttpd.
90
91I have only tested all of this on Linux, but everything should work
92exactly the same on any UNIX system. If you are a Windows user, you can
93either run everything inside
94[WSL](https://learn.microsoft.com/en-us/windows/wsl/), or you can try and
95adjust the examples to your system - if you choose this second option,
96I'll happily accept patches or pull requests :)
97
98## Hello world
99
100Let's start with the classic Hello World program:
101
102```
103#include <stdio.h>
104
105int main() {
106 printf("Hello, web!\n");
107}
108```
109
110You can compile the code above with
111
112```
113emcc -o index.html hello.c
114```
115
116And if you now start a web server in the current folder, for example with
117`darkhttpd .` (the dot at the end is important), and open a web browser to
118[localhost:8080](http://localhost:8080) (or whatever port your web server
119uses), you should see something like this:
120
121![Hello world in a browser](hello.png)
122
123As you can see, the compiler generated a bunch of extra stuff around
124you print statement. You may or may not want this, but for now we can
125take it as a convenient way to check that our program works as expected.
126
127There are other ways to run this compiled code. With the command above,
128the compiler should have generated for you 3 files:
129
130* `index.html` - the web page in the screenshot above.
131* `index.wasm` - the actual compiled code of your program; this file contains
132 WebAssembly bytecode.
133* `index.js` - some JavaScript *glue code* to make it possible for `index.wasm`
134 to actually run in a browser.
135
136If you don't specify `-o index.html`, or if your specify `-o` followed
137by a filename ending in `.js`, the `.html` page is not going to be
138generated. In this case (but also if you *do* generate the html page),
139you can run the JavaScript code in your terminal with:
140
141```
142node index.js
143```
144
145In later examples, the same code may not work seamlessly in both a web
146browser and in Node.js - for example, when dealing with persistent data
147storage. But until then, we can generate all three files with a single
148command and run our code in either way.
149
150It is also possible to ask Emscripten to generate only the `.wasm` file,
151in case you want to write the JavaScript glue code by yourself. To do
152this, you can pass the `-sSTANDALONE_WASM` option to `emcc`. However,
153in some cases the `.js` file is going to be generated even when this
154option is used, for example when building a source file without a `main()`
155entry point. Since this is something we'll do soon, we can forget about
156this option and just take it as a fact that the `.wasm` files generated
157by emscripten require some glue JavaScript code to actually run,
158but in case you are interested you can check out
159[the official documentation](https://emscripten.org/docs/tools_reference/settings_reference.html#standalone-wasm).
160
161You can find the code for this example, as well as scripts to
162build it and run the web server, in the directory `00_hello_world`
163of the git repository
164([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
165[github](https://github.com/sebastianotronto/emscripten-tutorial)).
166
167Anyway, now we can build our C code to run in a web page. But this is
168probably not the way we want to run it. First of all, we don't want to
169use the HTML template provided by Emscripten; but more importantly, we
170probably don't want to write a program that just prints stuff to standard
171output. More likely, we want to write some kind of library of functions
172that can be called from the front-end, so that the user can interact with
173our program via an HTML + JavaScript web page. Before going into that,
174let's take a break to discuss what we are actually compiling our code to.
175
176## Intermezzo I: What is WebAssembly?
177
178![The logo of WebAssembly](wasm.png)
179
180[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) is a low-level
181language meant to run in a virtual machine inside a web browser. The main
182motivation behind it is running higher-performance web applications compared
183to JavaScript; this is made possible, by its
184compact bytecode and its stack-based virtual machine.
185
186WebAssembly (or WASM for short) is supported by all major browsers
187since around 2017. Interestingly, Emscripten, the compiler we are
188using to translate our C code to WASM, first appeared in 2011,
189predating WASM by a few years. Early on, Emscripten would compile
190C and C++ code into JavaScript, or rather a subset thereof called
191[asm.js](https://en.wikipedia.org/wiki/Asm.js).
192
193Just like regular
194[assembly](https://en.wikipedia.org/wiki/Assembly_language), WASM
195also has a text-based representation. This means that one could write
196WASM code directly, assemble it to bytecode, and then run it. We are
197not going to do it, but if you are curious here is a simple example
198(computing the factorial of a number, taken from Wikipedia):
199
200```
201(func (param i64) (result i64)
202 local.get 0
203 i64.eqz
204 if (result i64)
205 i64.const 1
206 else
207 local.get 0
208 local.get 0
209 i64.const 1
210 i64.sub
211 call 0
212 i64.mul
213 end)
214```
215
216As you can see, it looks like a strange mix of assembly and
217[Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)).
218If you want to try and run WASM locally, outside of a web browser,
219you could use something like [Wasmtime](https://wasmtime.dev/).
220
221Until early 2025, the WASM "architecture" was 32-bit only. One big
222limitation that this brings is that you cannot use more that 4GB
223(2<sup>32</sup> bytes) of memory, because pointers are only 32 bits
224long; moreover, your C / C++ code may need some adjustments if it
225relied on the assumption that e.g. `sizeof(size_t) == 8`. At the
226time writing a new standard that enables 64 bit pointers, called
227WASM64, is supported on Firefox and Chrome, but not on Webkit-based
228browsers such as Safari yet. Depending on when you are reading this,
229this may have changed - you can check the status of WASM64 support
230[here](https://webassembly.org/features/).
231
232## Building a library
233
234Back to the main topic. Where were we? Oh yes, we wanted to build
235a C *library* to WASM and call it from JavaScript. Our complex,
236high-performance, math-heavy library probably looks something like this:
237
238library.h (actually, we are not going to need this):
239
240```
241int multiply(int, int);
242```
243
244library.c:
245
246```
247int multiply(int a, int b) {
248 return a * b;
249}
250```
251
252Or maybe it is a bit more complicated than that. But we said we are
253going to build up in complexity, and this is just the beginning, so
254let's stick to `multiply()`.
255
256To build this library you can use:
257
258```
259emcc -o library.js library.c
260```
261
262As we saw before, this is going to generate both a `library.js` and a
263`library.wasm` file. Now we would like to call our library function
264with something like this
265
266program.js:
267
268```
269var library = require("./library.js");
270const result = library.multiply(6, 7);
271console.log("The answer is " + result);
272```
273
274*(The `require()` syntax above is valid when running this code in Node.js,
275but not, for example when running in a browser. We'll see in the next
276session what to do in that case, but for now let's stick to this.)*
277
278Unfortunately, this will not work for a couple of reasons. The reason
279first is that Emscripten is going to add an underscore `_` to all our
280function names; so we'll have to call `library._multiply()`. But this
281still won't work, because by default the compiler does not *export* all
282the functions in your code - that is, it does not make them visible to
283the outside. To specify which functions you want to
284export, you can use the `-sEXPORTED_FUNCTIONS` flag, like so:
285
286```
287emcc -sEXPORTED_FUNCTION=_multiply -o library.js library.c
288```
289
290And now we finally have access to our `multiply()` function...
291
292```
293$ node program.js
294Aborted(Assertion failed: native function `multiply` called before runtime initialization)
295```
296
297...or maybe not. If you are new to JavaScript like I was a few weeks
298ago, you may find this error message surprising. Some runtime must be
299initialized, but can't it just, like... initialize *before* trying to
300run the next instruction?
301
302Things are not that simple. A lot of things in JavaScript happen
303*asynchronously*, and in these situations you'll have to either use
304[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
305or a
306[*callback function*](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function).
307So we'll have to do something like this:
308
309```
310var library = require("./build/library.js");
311
312library.onRuntimeInitialized = () => {
313 const result = library._multiply(6, 7);
314 console.log("The answer is " + result);
315};
316```
317
318And now we can finally run our program:
319
320```
321$ node program.js
322The answer is 42
323```
324
325The code for this example can be found in the `01_library` folder in
326the git repository
327([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
328[github](https://github.com/sebastianotronto/emscripten-tutorial)).
329
330## Intermezzo II: JavaScript and the DOM
331
332![The logos of HTML, CSS and JavaScript](logos.png)
333
334If we want to build an interactive web page using JavaScript, we'll
335need a way for our script to communicate with the page, i.e. a way
336to access the HTML structure from JavaScript code. What we are looking
337for is called
338*[Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)*,
339or DOM for short.
340
341For example, if you have a paragraph with some text in your HTML:
342
343```
344<p id="myParagraph">Hello!</p>
345```
346
347you can access this text from JavaScript like this:
348
349```
350var paragraph = document.getElementById("myParagraph");
351paragraph.innerText = "New text!";
352```
353
354Here we are selecting the paragraph HTML element using its ID, and we
355are changing its text via its `innerText` property, all from JavaScript.
356
357Let's see a more complex example:
358
359HTML:
360
361```
362<button id="theButton">Press me!</button>
363```
364
365JS:
366
367```
368var button = document.getElementById("theButton");
369var counter = 0;
370
371button.addEventListener("click", () => {
372 counter++;
373 button.innerText = "I have been pressed " + counter + " times!";
374});
375```
376
377In the example above we add an
378*[event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)*
379to a button: the (anonymous) function we defined is going to be called
380every time the button is clicked. And since this is a web page, I guess
381I can show you what this actually looks like.
382
383Behold, the dynamic button:
384
385<div style="text-align:center">
386<button id="theButton">Press me!</button>
387</div>
388
389<script>
390window.onload = () => {
391 var button = document.getElementById("theButton");
392 var count = 0;
393
394 button.addEventListener("click", () => {
395 count++;
396 button.innerText = "I have been pressed " + count + " times!"
397 });
398};
399</script>
400
401If you are completely new to web development, you may be wondering
402where you should write this JavaScript code. One option is to write it
403in the same HTML file as the rest of the page, inside a `<script>` tag;
404this is how I did it in the example above, as you can check by viewing
405the source of this page (press Ctrl+U, or right-click and select
406"view source", or prepend `view-source:` to this page's URL; hopefully
407at least one of these methods should work in your browser).
408
409However, if the script gets too large you may want to split it off in
410a separate file, which we'll demonstrate in this next example.
411
412Let's now make a template web page for using our powerful library. Let's
413start with the HTML, which is in large part boilerplate:
414
415index.html:
416
417```
418<!doctype html>
419<html lang="en-US">
420<head>
421 <meta charset="utf-8">
422 <meta name="viewport" content="width=device-width">
423 <title>Multiply two numbers</title>
424 <script src="./script.js" defer></script>
425</head>
426
427<body>
428 <p>
429 <input id="aInput"> x <input id="bInput">
430 <button id="goButton">=</button>
431 <span id="resultText"></span>
432 </p>
433</body>
434
435</html>
436```
437
438Besides the `<body>` element, the only important line for us is line
4397, which loads the script from a file. Notice that we use the `defer`
440keyword here: this is telling the browser to wait until the whole page
441has been loaded before executing the script. If we did not do this, we
442could run in the situation where we `document.getElementById()` returns
443`null`, because the element we are trying to get is not loaded yet (yes,
444this happened to me while I was writing this post). If you want to know
445more, check out this
446[MDN page](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#defer).
447
448Now to the JavaScript code. For now we are going to use the built-in
449`*` operator to multiply the two numbers, but in the next section we
450are going to replace it with our own library.
451
452script.js (in the same folder as index.html):
453
454```
455var aInput = document.getElementById("aInput");
456var bInput = document.getElementById("bInput");
457var button = document.getElementById("goButton");
458var resultText = document.getElementById("resultText");
459
460button.addEventListener("click", () => {
461 var a = Number(aInput.value);
462 var b = Number(bInput.value);
463 resultText.innerText = a * b;
464});
465```
466
467The final result will look something like this:
468
469<p style="text-align:center">
470<input id="aInput"> x <input id="bInput">
471<button id="goButton">=</button>
472<span id="resultText"></span>
473</p>
474
475<script>
476var aInput = document.getElementById("aInput");
477var bInput = document.getElementById("bInput");
478var button = document.getElementById("goButton");
479var resultText = document.getElementById("resultText");
480
481button.addEventListener("click", () => {
482 var a = Number(aInput.value);
483 var b = Number(bInput.value);
484 resultText.innerText = a * b;
485});
486</script>
487
488In a real-world scenario you would probably want to check that the text
489provided in the input fields is actually a number, or perhaps use the
490[`type="number"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number)
491attribute for the input fields. But we'll ignore these issues here -
492we are going to have more serious problems to deal with.
493
494## Loading the library and making it a module
495
496With what we have learned in the previous intermezzo (you are not skipping
497those, right?) we can finally run our library code in a real web page. The
498code is pretty much the same as above; we just need to include both the
499library and the script file in the HTML:
500
501```
502 <script src="./library.js" defer></script>
503 <script src="./script.js" defer></script>
504```
505
506and of course we have to change the line where we perform the multiplication:
507
508```
509 resultText.innerText = Module._multiply(a, b);
510```
511
512Here `Module` is the default name given to our library by
513Emscripten. Apart from being too generic a name, this leads to another
514problem: we can't include more than one Emscripten-built library in our
515page in this way - otherwise, both are going to be called `Module`.
516
517Luckily, there is another way: we can build a
518[modularized](https://emscripten.org/docs/compiling/Modularized-Output.html)
519library, i.e. obtain a
520[JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).
521This may sound a bit strange, because the name `Module` kind of implies
522there is already a module. The way I understand it is that by default
523Emscripten produces a *script* that *contains* a module named `Module`;
524when building a modularized library, the whole resulting file is a module.
525
526Modularizing our build is not necessary right now, but
527there are a couple of other advantages to it:
528
529* As mentioned above, we can change the name of our module and include
530 more than one Emscripten-built library, if we want.
531* We will be able to use the module in the same way in Node.js and in
532 our web page script. This way we can minimize the differences between
533 the two versions of our code, which can be useful for testing.
534* In case we want to build a more complex layer of JavaScript between
535 our library and our web page, with a modularized build we can easily
536 include the module in another file, which can then be included in the
537 main script.
538
539So let's go ahead and build our library like so:
540
541```
542emcc -sEXPORTED_FUNCTION=_multiply -sMODULARIZE -sEXPORT_NAME=MyLibrary \
543 -o library.mjs library.c
544```
545
546Notice I have changed the extension from `.js` to `.mjs`. Don't worry,
547either extension can be used. And you are going to run into issues with
548either choice:
549
550* If you run your code in Node.js, it will understand that the library
551 file is a module only if you use the `.mjs` extension. Alternatively,
552 you can change some settings in a local configuration file to
553 enforce this.
554* If you run your code in a web page, your web server may not be
555 configured to serve `.mjs` files as JavaScript files. This can
556 easily be changed by adding a configuration line somewhere.
557
558In my examples I chose to use the `.mjs` extensions to make Node.js
559happy, and I changed the configuration of my web servers as needed. For
560example, for darkhttpd I added a file called `mime.txt` with a single
561line `text/javascript mjs`, and launched the server with the
562`--mimetypes mime.txt` option.
563
564Now we have to make a couple of changes. Our `program.js`, for running
565in node, becomes:
566
567```
568import MyLibrary from "./library.mjs"
569
570var myLibraryInstance = await MyLibrary();
571
572const result = myLibraryInstance(6, 7);
573console.log("The answer is " + result);
574```
575
576By the way, I have renamed this file to `program.mjs`. This is because
577only modules can use the
578[static `import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
579statement; alternatively, I could have used the
580[dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)
581and kept the `.js` extension.
582
583Similary, we have to update our `script.js` (or `script.mjs`) to import
584the module and create an instance. Moreover, we have to specify in the
585HTML that the script is now a module:
586
587```
588 <script src="./script.mjs" type="module" defer></script>
589```
590
591And we can get rid of the other `<script>` tag, since now the library
592is included directly in `script.mjs`.
593
594You can find the full the code for this example the folder
595`02_library_modularized` in the git repository
596([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
597[github](https://github.com/sebastianotronto/emscripten-tutorial)).
598
599## Multithreading
600
601!["Gotta go fast" meme](threads.jpg)
602
603Let's move on to a more interesting example. If one of the goals of
604WebAssembly is performance, there is no point in using only 1/16th of
605your CPU - let's port a multithreaded application to the web!
606
607As a more complicated example, let's write a function that counts how
608many prime numbers there are in a given range. This function takes two
609integers as input and returns a single integer as output, but it does
610a non-trivial amount of work under the hood. A simple implementation of
611this routine would be something like this:
612
613```
614bool isprime(int n) {
615 if (n < 2)
616 return false;
617
618 for (int i = 2; i*i <= n; i++)
619 if (n % i == 0)
620 return false;
621 return true;
622}
623
624int primes_in_range(int low, int high) {
625 if (low < 0 || high < low)
626 return 0;
627
628 int count = 0;
629 for (int i = low; i < high; i++)
630 if (isprime(i))
631 count++;
632
633 return count;
634}
635```
636
637This algorithm is
638[embarassingly parallelizable](https://en.wikipedia.org/wiki/Embarrassingly_parallel):
639we can split the interval `[low, high)` into smaller sub-intervals and
640process each one of them in a separate thread; then we just need to add
641up the results of the sub-intervals.
642
643For the actual implementation, we are going to use
644[pthreads](https://en.wikipedia.org/wiki/Pthreads), for the simple reason
645that it is
646[supported by Emscripten](https://emscripten.org/docs/porting/pthreads.html).
647In practice, assuming we are working on a UNIX platform, we could also
648use C11's [threads.h](https://en.cppreference.com/w/c/header/threads) or
649C++'s [std::thread](https://en.cppreference.com/w/cpp/thread/thread.html),
650but only because they happen to be wrappers around pthreads. On other
651platforms, or in other implementations of the C and C++ standard library,
652this may not be the case; so we'll stick to old-school pthreads.
653
654This is my parallel version of `primes_in_range()`:
655
656primes.c:
657
658```
659#include <stdbool.h>
660#include <pthread.h>
661
662#define NTHREADS 16
663
664bool isprime(int);
665void *pthread_routine(void *);
666
667struct interval { int low; int high; int count; };
668
669int primes_in_range(int low, int high) {
670 pthread_t threads[NTHREADS];
671 struct interval args[NTHREADS];
672
673 if (low < 0 || high < low)
674 return 0;
675
676 int interval_size = (high-low)/NTHREADS + 1;
677 for (int i = 0; i < NTHREADS; i++) {
678 args[i].low = low + i*interval_size;
679 args[i].high = args[i].low + interval_size;
680 pthread_create(&threads[i], NULL, pthread_routine, &args[i]);
681 }
682
683 int result = 0;
684 for (int i = 0; i < NTHREADS; i++) {
685 pthread_join(threads[i], NULL);
686 result += args[i].count;
687 }
688
689 return result;
690}
691
692bool isprime(int n) {
693 if (n < 2)
694 return false;
695
696 for (int i = 2; i*i <= n; i++)
697 if (n % i == 0)
698 return false;
699 return true;
700}
701
702void *pthread_routine(void *arg) {
703 struct interval *interval = arg;
704
705 interval->count = 0;
706 for (int i = interval->low; i < interval->high; i++)
707 if (isprime(i))
708 interval->count++;
709
710 return NULL;
711}
712```
713
714*(Pro tip: if you take the number of threads as an extra parameter for
715your function, you can pass to it the value
716[`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency)
717from the JavaScript front-end and use exactly the maximum number of
718threads that can run in parallel on the host platform.)*
719
720To build this with Emscripten we'll have to pass the `-pthread` option and,
721optionally, a suitable value for
722[`-sPTHREAD_POOL_SIZE`](https://emscripten.org/docs/tools_reference/settings_reference.html#pthread-pool-size).
723
724If we want to run our multithreaded code in an actual browser, we'll
725have to scratch our head a bit harder. The code we are supposed to
726write is exactly what we expect, but once again we have to tinker with
727our web server configuration. For technical reasons that we'll cover in
728the next intermezzo, in order to run multithreaded code in a browser
729we must add a couple of HTTP headers:
730
731```
732Cross-Origin-Opener-Policy: same-origin
733Cross-Origin-Embedder-Policy: require-corp
734```
735
736These headers are part of the response your browser will receive when
737it requests any web page from the server. The way you set these depends on
738the server you are using; with darkhttpd you can use the `--header` option.
739
740With your server correctly set up, you can enjoy a multithreaded program
741running in your browser! As always, you can check out this example from
742the `03_threads` folder of the git repository
743([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
744[github](https://github.com/sebastianotronto/emscripten-tutorial)).
745
746## Intermezzo III: Web Workers and Spectre
747
748![The logo of the Spectre vulnerability](spectre.png)
749
750On a low level, threads are implemented by Emscripten using
751[web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API),
752which are processes separated from the main web page process and
753communicate with it and with each other by
754[passing messages](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage).
755Web workers are commonly used to run slow operations in the background
756without blocking the UI threads, so the web page remains responsive
757while these operations run - we'll do this in the next section.
758
759Web workers do not have regular access to the same memory as the main
760process, and this is something that will give us some issues in later
761sections. However, there are ways around this limitation. One of these
762ways is provided by
763[SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer),
764which we won't use directly in this tutorial, but is used by
765Emscripten under the hood.
766
767And this is why we had to set the `Cross-Origin-*` headers. In 2018, a
768CPU vulnerability called [Spectre](https://spectreattack.com) was found,
769and it was shown that an attacker could take advantage of shared memory
770between the main browser thread and web workers to
771[execute code remotely](https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)#Remote_exploitation).
772As a counter-measure, most browsers now require your app to be in a
773[secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)
774and
775[cross-origin isolated](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated)
776to allow using `SharedArrayBuffer`s.
777
778Even if you do not plan to use web workers directly, it is still good to
779have a rough idea of how they work, because of the
780[law of leaky abstractions](https://en.wikipedia.org/wiki/Leaky_abstraction):
781*all abstractions are leaky*.
782The fact that we had to mess around with our `Cross-Origin-*` headers
783despite not caring at all about `SharedArrayBuffer`s is a blatant example
784of this.
785
786## Don't block the main thread!
787
788If you have run the previous example, may have noticed a scary warning
789like this in your browser's console:
790
791![A warning saying "Blocking on the main thread is very dangerous, see [link]"](blocking.png)
792
793*The link points to
794[this page](https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread)
795in Emscripten's documentation.*
796
797The issue here is that our heavy computation is not running "in the
798background", but its main thread (the one spawning the other threads)
799coincides with the browser's main thread, the one that is responsible
800for drawing the UI and handling user interaction. So if our computation
801really takes long, the browser is going to freeze - and after a few
802seconds it will ask us if we want to kill this long-running script.
803
804As we anticipated in the previous intermezzo, we are going to solve this
805with a web worker. We will structure this solution as follows:
806
807* The main script will be responsible for reading the user input, sending
808 a message to the worker to ask it to compute the result, and handling
809 the result that the worker is going to send back once it is done. No
810 slow operation is performed by this script, so that it won't block
811 the main thread.
812* The worker will be responsible for receiving mesages from the main
813 script, handling them by calling the library, and sending a message
814 with the response back once it is done computing.
815
816In practice, this will look like this:
817
818script.mjs:
819
820```
821var aInput = document.getElementById("aInput");
822var bInput = document.getElementById("bInput");
823var button = document.getElementById("goButton");
824var resultText = document.getElementById("resultText");
825
826var worker = new Worker("./worker.mjs", { type: "module" });
827
828button.addEventListener("click", () => worker.postMessage({
829 a: Number(aInput.value),
830 b: Number(bInput.value)
831}));
832
833worker.onmessage = (e) => resultText.innerText = "There are " +
834 e.data.result + " primes between " + e.data.a + " and " + e.data.b;
835```
836
837worker.mjs:
838
839```
840import Primes from "./build/primes.mjs";
841
842var primes = await Primes();
843
844onmessage = (e) => {
845 const count = primes._primes_in_range(e.data.a, e.data.b);
846 postMessage({ result: count, a: e.data.a, b: e.data.b });
847};
848```
849
850More complicated than before, but nothing crazy. Notice how we are using
851[`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage)
852and
853[`onmessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event)
854to pass events back and forth. The argument of `postMessage()` is the
855actual data we want to send in
856[JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)
857format, while the argument of `onmessage()` is an
858[event](https://developer.mozilla.org/en-US/docs/Web/API/Event)
859whose `data` property contains the object that was sent with `postMessage()`.
860
861You can check out this example in the directory `04_no_block` in the
862repository
863([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
864[github](https://github.com/sebastianotronto/emscripten-tutorial)).
865Try also large numbers, in the range of millions or tens of millions, and
866compare it with the previous example - but not don't go too large, we
867only support 32-bit integers for now. Notice how, with this new setup,
868the browser remains responsive while it is loading the response.
869
870Oh and by the way, a nice exercise for you now could be making the
871script show some kind of `"Loading result..."` message while the worker
872is working. This is not hard to do, but a huge improvement in user
873experience!
874
875## Callback functions
876
877![The hand of a person using a phone](callback.jpg)
878
879For one reason or another, your library function may take as parameter
880another function. For example, you may use this other function to print
881log messages regardless of where your library code is run: a command-line
882tool may pass `printf()` to log to console, while a GUI application
883may want to show these messages to some text area in a window, and it
884will pass the appropriate function pointer parameter. This is the use case
885that we are going to take as an example here, but it is not the only one.
886
887Implementing this was probably the step that took me the longest in my
888endeavor to port my Rubik's cube solver to the web. Luckily for you,
889when writing this post I found a simpler method, so you won't have to
890endure the same pain.
891
892First, we'll have to adapt our library function like this:
893
894```
895int primes_in_range(int low, int high, void (*log)(const char *)) {
896 /* The old code, with calls to log() whenever we want */
897};
898```
899
900*Tip: when using callback functions like this, it is good practice
901to have them accept an extra `void *` parameter, and the library
902function should also accept an extra `void *` parameter that it then
903passes on to the callback. So our function would look something like
904this: `int primes_int_range(int low, int high, void (*log)(const char *, void *), void *log_data)`.
905This makes the setup extremely flexible, and allows passing callback
906functions in situation where this may be tricky. For example, this
907way you could pass a C++ member function by passing an object as
908`log_data` and a function that call `log_data`'s member function
909as `log`. Since we are not going to use this in this example, I'll stick
910to the simpler setup.*
911
912Now, to call our function from the JavaScript side we would like
913to do something like this:
914
915```
916int result = primes_in_range(a, b, console.log); // Logging to console
917```
918
919Unfortunately, this will not work, because `console.log`, a JavaScript
920[function object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function),
921does not get automatically converted to a function *pointer*, which is
922what C expects. So we'll have to do something slightly more complicated:
923
924```
925import Primes from "./build/primes.mjs"
926
927var primes = await Primes();
928const logPtr = primes.addFunction((cstr) => {
929 console.log(primes.UTF8ToString(cstr));
930}, "vp");
931
932const count = primes._primes_in_range(1, 100, logPtr);
933```
934
935Here `addFunction()` is a function generated by Emscripten. Notice also
936that we are wrapping our `console.log()` in a call to `UTF8ToString()`,
937an Emscripten utility to convert C strings to JavaScript strings, and
938that we are passing the function's signature `"vp"` (returns `void`,
939takes a `pointer`) as an argument; see
940[here](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#function-signatures)
941for more information.
942
943Other than that, you just need to add a couple of compiler flags:
944
945* `-sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString` to tell the
946 compiler to make these two methods available.
947* `-sALLOW_TABLE_GROWTH` to make it possible to add functions to
948 out module at runtime with `addFunction()`.
949
950And as you can check by running the example `05_callback` from the repo
951([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
952[github](https://github.com/sebastianotronto/emscripten-tutorial)),
953everything works as expected, both in Node.js and in a web page. To make
954the examples more interesting, the web page one is not only not logging the
955messages to console, but it also shows them as text in the web page.
956
957*Note: you must be careful where you call this callback function from.
958If you try to call it from outside the main thread - for example, in one
959of the threads that are spawned to count the primes in the sub-intervals
960- you'll get a horrible crash. This is because web workers do not have
961access to the functions that reside in another worker's memory.*
962
963## Persistent storage
964
965![3D rendering of a spinning hard drive](storage.png)
966
967Our multithreaded implementation of `primes_in_range()` is not slow, but
968it could be faster. One possible way to speed it up is to use a look-up
969table to make `is_prime()` run in constant time; for this we'll need to
970memorize which numbers below 2<sup>31</sup> (the maximum value of 32-bit
971signed integer) are prime. This will require 2<sup>31</sup> bits of data,
972or 256MB. It would be nice if we could store this data persistently in
973the user's browser, so that if they use our app again in the future we
974won't need to repeat expensive calculations or re-download large files.
975
976Putting aside the question of whether any of the above is a good idea,
977and assuming you know how to generate such a table, in C you would
978read and store the data like this:
979
980```
981#include <stdio.h>
982
983#define FILENAME "./build/primes_table"
984
985void read_table(unsigned char *table) {
986 FILE *f = fopen(FILENAME, "rb");
987 fread(table, TABLESIZE, 1, f);
988 fclose(f);
989}
990
991void store_table(const unsigned char *table) {
992 FILE *f = fopen(FILENAME, "wb");
993 fwrite(table, TABLESIZE, 1, f);
994 fclose(f);
995}
996```
997
998*Note: the code snippet above is extremely simplified, you probably want
999to add some error-handling code if you implement something like this.*
1000
1001The good news is that we can use the same code when building with
1002Emscripten! The bad news is that... well, it's a bit more complicated
1003than that.
1004
1005First of all, it is important to know that
1006[Emscripten's File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html)
1007supports different "backends", by which I mean ways of translating the
1008C / C++ file operations to WASM / JavaScript. I am not going to discuss
1009all of them here, but I want to highlight a few key points:
1010
1011* The default backend is called `MEMFS`. It is a virtual file system
1012 that resides in RAM, and all data written to it is lost when the
1013 application is closed.
1014* Only one of these backends (`NODERAWFS`) gives access to the actual
1015 local file system, and it is only usable when running your app with
1016 Node.js. Browsers are *sandboxed*, and the filesystem is not normally
1017 accessible to them. There are ways, such as the
1018 [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API),
1019 to access files, but as far as I understand each file you want to
1020 access requires explicit actions from the user. We would like to manage
1021 our data automatically, so we are not going to use this API.
1022* The backend we are going to use is called `IDBFS`. It provides access
1023 to the [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API),
1024 which allows to persistently store large quantities of data in the
1025 browser's cache. The data is only removed if the user asks for it,
1026 for example by cleaning it from the browser's settings page.
1027
1028To activate the `IDBFS` backend, we are going to add `--lidbfs.js`
1029to our compiler options. The Indexed DB is not the only way to store
1030data persistently in the browser. For an overview of all the options,
1031you can take a look at
1032[this page on MDN](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage).
1033
1034The compiler flag is not enough, however. We also need to:
1035
10361. Create a directory (for the virtual file system) where our data file
1037 is going to be stored. We are going to call this directory `assets`,
1038 but you can pick any other name; it does not have to coincide with the
1039 name of a directory that exists on your local file system.
10402. Mount the directory we have just created in the indexed DB.
10413. Synchronize the virtual file system, so that our script is able to
1042 read pre-existing files.
1043
1044All of the above has to be done from JavaScript, which makes things a
1045little bit complicated, because we are reading our files from C code.
1046We have a couple of ways to work around this issue:
1047
1048* Using
1049 [inline JavaScript](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native)
1050 in our C code with the `EM_JS()` or `EM_ASYNC_JS()` Emscripten macros.
1051* Setting up the indexed DB file system when the module loads using
1052 the `--pre-js` compiler option.
1053
1054Here we are going to use the second solution, but the first option is
1055good to keep in mind, because it allows us to call JavaScript code at
1056any point rather than just at startup.
1057
1058*Note: if you do end up using `EM_ASYNC_JS()` to make asynchronous
1059JavasScript functions callable from C, keep in mind that any C
1060function that, directly or indirectly, calls an async JavaScript
1061function, will now return a
1062[promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
1063when called from JavaScript. But wether an async function is called is
1064determined at runtime, so you C function may return a value one time
1065and a promise another time, depending on how exactly it runs!*
1066
1067So we are going to add `--pre-js init_idbfs.js` to our compiler options,
1068with `init_idbfs.js` containing the following:
1069
1070```
1071Module['preRun'] = [
1072 async () => {
1073 const dir = "/assets";
1074
1075 FS.mkdir(dir);
1076 FS.mount(IDBFS, { autoPersist: true }, dir);
1077
1078 Module.fileSystemLoaded = new Promise((resolve, reject) => {
1079 FS.syncfs(true, (err) => {
1080 if (err) reject(err);
1081 else resolve(true);
1082 });
1083 });
1084
1085 }
1086];
1087```
1088
1089As you can see, the syncing operation is more complicated, the main
1090reason being that it is an
1091[asynchronous operation](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Async_JS).
1092For this reason, we are wrapping it in a
1093[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),
1094so we can detect when this operation is done and react accordingly.
1095We are going to do so from our worker script, which will send a message to
1096the main script to communicate that the file system is ready to go:
1097
1098```
1099primes.fileSystemLoaded.then(() => {
1100 postMessage({ type: "readySignal" });
1101});
1102```
1103
1104The main script can then handle this signal as it prefers, for example by
1105enabling the `Compute` button, if it was previously marked as `disabled`.
1106
1107One last thing: since we are now using a large amount of memory and
1108loading the virtual file system at the start, the compiler will complain
1109that we are not reserving enough memory for our application. Adding a
1110`-sINITIAL_MEMORY=272629760` compiler flag will do the trick (watch out:
1111the number you provide must be a multiple of 2<sup>16</sup>). I am not
1112entirely sure why this is the case, since we are not loading the file in
1113memory statically, but only at runtime, and only when the
1114`primes_in_range()` function is called. I would expect that using
1115[`-sALLOW_MEMORY_GROWTH`](https://emscripten.org/docs/tools_reference/settings_reference.html#allow-memory-growth)
1116would be enough - and indeed this is the case if we use the `EM_ASYNC_JS()`
1117macro to load the file system on-demand.
1118
1119And with all this, we are ready to run our optimized version of the
1120`primes_in_range()` algorithm, all from within our browser! As always,
1121you can check out the complete code in the folder `06_storage` of
1122the repository
1123([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html),
1124[github](https://github.com/sebastianotronto/emscripten-tutorial)).
1125
1126If generating this data on the user's side seems redundant, you can
1127also have it downloaded from the server. I won't explain how to it here,
1128since there are many possible ways to achieve this - after all, the indexed
1129DB is also accessible from JavaScript. If you want to experiment more
1130with Emscripten you can try to use the
1131[Fetch API](https://emscripten.org/docs/api_reference/fetch.html); in my
1132project I was not able to make its synchronous version work together with
1133`-sMODULARIZE`, so I ended up using
1134[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
1135directly from within an `EM_ASYNC_JS()` function. This tutorial is already
1136too long, so I am going to leave this as an exercise for the reader.
1137
1138## Closing thoughts
1139
1140I have discussed almost everything that I have learned about building a
1141webapp in C / C++ with Emscripten. I ended up using C, not C++, for all
1142of my example, so I did not have a chance to discuss some neat C++-specific
1143features such as
1144[`EMBIND()`](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html)
1145and
1146[`emscripten::val`](https://emscripten.org/docs/api_reference/val.h.html)
1147- do check them out if you plan to use C++ for your web app!
1148
1149Even if this page is structured like a tutorial, this is probably better
1150described as a collection of personal notes, a "brain dump" that I wrote
1151for myself as is the case with many of my blog posts. Writing this piece
1152was a great occasion for me to review the work that I have done and the
1153things I have learned. And while reflecting on all of this I was able to
1154isolate a specific impression that I had while working on this,
1155and I summarized it in on sentence:
1156
1157<center><strong><i>
1158It's leaky abstractions all the way down.
1159</i></strong></center>
1160
1161If you have not encountered this term before (but you should, I have already
1162used it in this post), *leaky abstraction* is a term used to describe the
1163failure of an abstraction to hide the low-level details it is abstracting.
1164The so-called
1165[law of leaky abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/)
1166says that all abtractions are leaky. But, in my opinion not all
1167abstractions leak in the same way - some leak way more than others.
1168
1169Emscripten is a great project that tries to abstract away all the web
1170(JavaScript, WASM, web workers, local storage...) so that you can build
1171and run your C / C++ code in a web browser. Frankly, this is mind-blowing,
1172and I have mad respect for the Emscripten developers.
1173
1174But as soon as the complexity of your codebase bumps up a notch, you
1175immediately find out that the abstractions don't hold anymore. If yor
1176app is multithreaded, you have to learn what a web worker is. If you
1177want to read some data from a file, welcome to the world of client-side
1178storage. You need 64-bit memory support because you are processing more
1179than 2GB of data? Sure, but first make sure that your users are not
1180using Safari.
1181
1182But I am not complaining about this. A browser is a very different beast
1183from a bare-metal operating system, and it is to be expected that you
1184have to know something about the system you are deploying to. I am happy
1185that I could learn about all of this, and I believe this knowledge is
1186going to give me an extra edge whenever I'll work on the web again.
diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md
new file mode 100644
index 0000000..1c65e39
--- /dev/null
+++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md
@@ -0,0 +1,332 @@
1# Cargo culture shock 🦀
2
3After a long adventure
4[porting my cube solver to the web](../2025-06-06-webdev), I decided to
5try out something completely different, like learning a new language.
6Rust is one on my to-do list, and it has been there for more than 10
7years - I remember reading about it in my first year in university,
8so it must have been 2013 or early 2014.
9
10So I started by quickly reading through the first few chapters of
11[the book](https://doc.rust-lang.org/book/) to get an idea of the basic
12syntax. Before I move on to implementing something, I thought I could
13share my very early impression of the language and the tooling around it.
14
15*Note: this is a relaxed write-up and I have a very superficial
16understanding of the topic. I am going to use strong words for the things
17that I did not like, but there are many things I like about Rust so
18far. In fact, my first impression of the language, the documentation and
19the tools is very positive! Keep this in mind while reading this post.*
20
21## What is Rust about?
22
23Before getting started with the Rust book, my impression was that Rust
24was mostly about *safety* as in *memory safety*, and that it achieved
25this by enforcing strict rules, leading to more correct programs overall.
26
27But [the foreword](https://doc.rust-lang.org/book/foreword.html) says "the
28Rust programming language is fundamentally about *empowerment*". Uh, that's
29weird. I was expecting "something something *safety*". And actually,
30I was hoping for "something something *correctness*". Definitely not
31"something something *empowerment*". *Empowerment* sounds like one of
32those meaningless words that managers use when they have nothing to say.
33
34Anyway, empowerement it is. Let's move on to actually using the thing.
35
36## Installation
37
38The officially endorsed way of installing Rust is the following:
39
40```
41curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
42```
43
44The horror, the horror! Is this really what they suggest? The very thing
45that *everyone* told you not to do in nerd forums until a few years ago,
46even worse than copy-pasting commands from the internet, is now THE
47suggested way of installing software? Seriously, piping a random web
48page into `sh`?
49
50I don't know what this script does, is it going to download even more
51random shit from the internet? (Answer: yes, it is!) Is it going to fuck
52up my environment by puking configuration lines into my .bashrc? (Answer:
53yes, it is!)
54
55But ok, what do I know. Maybe I am just living in the past, I should
56embrace the future, because package managers and `make && make install`
57*are so 1999, man*. So I took a deep breath an run the command.
58Luckily the script kindly asked "do you want me to fuck up your
59environment? [default: Yes]". I may be paraphrasing this, it probably
60mentioned `.bash_rc` and `.profile`. Anyway, you can easily say "no"
61and skip this fuckery. And then manually update a couple of environment
62variables in your shell configuration file, which is what the script was
63trying to do. I guess developers today are not expected to know how to
64do this.
65
66The installation was quick and everything went fine. Moving on!
67
68## Cargo cult
69
70[Cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) is
71Rust's build system and all-in-one tool. It can create a new project
72with a default folder structure, compile code (running `rustc` under
73the hood`), run tests, and more. Oh and it is also a *package manager*,
74which apparently is something modern programming languages decided they
75needed - more on that later.
76
77I don't always like these kind of tools, becase they tend to do a lot of
78things that you don't understand with the files in the current directory.
79Like vomiting generated files that you are supposed to check into your
80[VCS](https://en.wikipedia.org/wiki/Version_control) - which kinda
81defeats the purpose of *generating* files in the first place, doesn't it?
82
83Luckily, Cargo does not do too much of this. It generates an `src` folder
84with a simple "hello world" program, a pretty minimal `Cargo.toml`
85configuration file, and it initializes a git repo. All reasonable,
86and most importantly easy to understand.
87
88On the first run of `cargo build` it also generates a `target` folder
89for the build artifacts (already in `.gitignore`) and it generates
90only one of those vomit files that I am supposed to check into git
91(`Cargo.lock`), which I promptly added to .gitignore. Apparently it is
92mainly about dependencies, and I'd rather not use any for now. So all
93in all I am satisfied with this process.
94
95About dependencies, you can specify some packages, or *crates*, that your
96projects depends on. These *crates* are going to be downloaded as they are
97needed from [crates.io](https://crates.io/). You can of course specify
98some constraints on the version for each specific crate, like "at least
992.0" or "at least 0.8.5, but less than 0.9.0". Here is where `Cargo.lock`
100comes into play: if there is more than one version available for a given
101crate that satisfies the contraints you impose in `Cargo.toml`, the first
102time a specific version is used it gets written to `Cargo.lock`, so that
103you will keep using that version even if a new one becomes available.
104You are supposed to check in this file to git so other developers working
105on the same project will use exactly the same version of each dependency.
106
107I can't quite understand this. If I don't want to update to a later
108(minor) version of the dependency, I can already specify this in
109`Cargo.toml`. If instead I impose more relaxed requirements, then it
110means that any version satisfying those requirements is fine, and I
111in this case *I do want* to try different versions within the allowed
112range, to make sure that my assumptions are correct. So I am confused
113about why this Cargo.lock business is needed at all. But I guess if
114the time ever comes that I need to include dependencies in my project -
115probably in the far future, *right?* - then I can just specify an exact
116version and happily ignore `Cargo.lock`.
117
118Anyway, coming from a mostly C and C++ background where there is no
119standard way of including dependencies, all of this is certainly quite
120interesting.
121
122## Dependencies?
123
124Chapter 2 contains a simple code example.
125[This part](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#generating-a-secret-number)
126caught my attention:
127
128"Rust doesn't yet include random number functionality in its standard library."
129
130Uh, ok. And then people complain about C having a small standard library.
131It then continues:
132
133"However, the Rust team does provide a
134[`rand` crate](https://crates.io/crates/rand) with said functionality."
135
136Ok, but... if this is provided by the Rust team, why not including it in
137the standard library? Maybe it is still in beta, Rust is not a "finished"
138project as far as I understand. I would think 15 years is enough to ship
139a random number generator, though.
140
141The tutorial tell us to add `rand` to our dependencies in `Cargo.toml`.
142Simple enough. But then, if we launch `cargo build` again:
143
144```
145cargo build
146 Updating crates.io index
147 Locking 15 packages to latest Rust 1.85.0 compatible versions
148 Adding rand v0.8.5 (available: v0.9.0)
149 Compiling proc-macro2 v1.0.93
150 Compiling unicode-ident v1.0.17
151 Compiling libc v0.2.170
152 Compiling cfg-if v1.0.0
153 Compiling byteorder v1.5.0
154 Compiling getrandom v0.2.15
155 Compiling rand_core v0.6.4
156 Compiling quote v1.0.38
157 Compiling syn v2.0.98
158 Compiling zerocopy-derive v0.7.35
159 Compiling zerocopy v0.7.35
160 Compiling ppv-lite86 v0.2.20
161 Compiling rand_chacha v0.3.1
162 Compiling rand v0.8.5
163 Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
164 Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s
165```
166
167Woho wait a minute. I have only added one dependency! Just a random
168number generator! Why is it building 15 packages now? Where does all
169of this come from? Surely these are not dependencies dragged in by
170the `rand` crate, *right?*
171
172Well, apparently they are. If we want to generate a random number, we
173need 15 external packages. I would imagine this makes Rust completely
174unusable in a professional setting, because nobody would want to audit
17515 separate crates just to include a random number generator. And no
176professional programmer would include dependencies in a serious project
177without first auditing them, *right?* *RIGHT?!*
178
179I mean, each dependency introduces an extra liability in your project;
180it is code that you can't control and you never know when it is going to
181break. And this is why serious programmers, select dependencies carefully
182and only use them when absolutely necessary... *right?*
183
184Ahah, of course they do not. Remember the
185[letf-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident)?
186Apparently Cargo is of the same breed as npm. And then we wonder why
187the software industry is such a shitshow. *Just keep building on top
188of the house of cards, man. It's going to be fine, man, don't bother
189implementing low-level stuff, that's hard. Just trust random people on
190the internet and ship their code, man.*
191
192Bleah.
193
194## The Rust language
195
196So far I talked mostly about the tooling around Rust, but I said nothing
197about the language itself. I want to write some code before making a
198more informed opinion about it, but the first impression is that it is
199really nice!
200
201I love the type system, especially
202[enums](https://doc.rust-lang.org/book/ch06-00-enums.html)
203and `match`. I like that this and stuff like `Option<T>` are
204first-class citizens; you can do something similar in C++ with
205[`std::variant`](https://doc.rust-lang.org/book/ch06-00-enums.html) and
206[`std::optional`](https://en.cppreference.com/w/cpp/utility/optional.html),
207but the syntax quickly gets messy. I guess this shows the advantage of
208making a new language from scratch instead of being forced to live with
209decades-old syntax for backwards compatibility.
210
211I especially like this mechanism - Rust's enums, or `std::variant`, or
212tagged unions, whatever you like to call them - for error handling. I find
213them a better solution than exception, because they enforce correctness:
214if your function can fail, your coding must handle it; you can't just
215let an error message bubble up from the depths of Hell, wreak havoc in
216your control flow and face the user with an "Object reference not set
217to an instance of an object". I think it is great that Rust endorses
218this more thorough way of error handling from the start.
219
220On the less-nice side of things, apparently
221[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow)
222is still an issue. Eh. At least when compiling in debug mode overflows
223are caught, which is nice. But in release mode the program is
224going to "panic", which I was not expecting. I mistakenly thought
225this was one of the issues that Rust was solving at compile time,
226or maybe even at the level of language specification.
227
228This brings me to another problem that I thought Rust would solve,
229but it does not: accessing a out-of-bound index of an array also leads
230to a panic. I was expecting "don't allow indexes out of bound" to be
231included in Rust's compile-time checks, somehow. For example in Ada you
232can declare an array to accept only values of a specific
233[range type](https://en.wikibooks.org/wiki/Ada_Programming/Types/range),
234so that out-of-bound errors are completely eliminated. Apparently a
235"panic" is memory safe behavior, but it is definitely not *correct*
236behavior. I guess this was a big misunderstanding from my side: memory
237*safety* does not mean memory *correctness* - Rust still allows you to
238make mistakes related to memory.
239
240## Documentation
241
242A quick note on the documentation: it is very nice. The book is well
243written, and I am enjoying reading through it, even though it is very
244basic for me. It is also available offline - at least when installing
245rust via `rustup` - which is something I always appreciate.
246
247The compiler error messages, which I consider to be part of the
248documentation as well, are simply amazing. Not only they are usually very
249precise, not only they often suggest a fix, but they also point you out to
250some piece of documentation so that you can learn why your code is wrong.
251For example, if you try to compile this code with `rustc filename.rs`:
252
253```
254fn main() {
255 println("Hello, world!");
256}
257```
258
259You get:
260
261```
262error[E0423]: expected function, found macro `println`
263 --> hello.rs:2:2
264 |
2652 | println("Hello, world!");
266 | ^^^^^^^ not a function
267 |
268help: use `!` to invoke the macro
269 |
2702 | println!("Hello, world!");
271 | +
272
273error: aborting due to 1 previous error
274
275For more information about this error, try `rustc --explain E0423`.
276```
277
278And if you run `rustc --explain E0423` as suggested:
279
280```
281An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace.
282
283Erroneous code example:
284
285struct Foo { a: bool };
286
287let f = Foo();
288// error: expected function, tuple struct or tuple variant, found `Foo`
289// `Foo` is a struct name, but this expression uses it like a function name
290
291Please verify you didn't misspell the name of what you actually wanted to use here. Example:
292
293fn Foo() -> u32 { 0 }
294
295let f = Foo(); // ok!
296
297It is common to forget the trailing ! on macro invocations, which would also yield this error:
298
299println("");
300// error: expected function, tuple struct or tuple variant,
301// found macro `println`
302// did you mean `println!(...)`? (notice the trailing `!`)
303
304Another case where this error is emitted is when a value is expected, but something else is found:
305
306pub mod a {
307 pub const I: i32 = 1;
308}
309
310fn h1() -> i32 {
311 a.I
312 //~^ ERROR expected value, found module `a`
313 // did you mean `a::I`?
314}
315```
316
317I would not be surprised if the main reason why Rustaceans are so
318enthusiastic about the language was actually how nice `rustc` is.
319I expect working with this tool will be very pleasant.
320
321## Moving on!
322
323Apart from the
324[culture shock](https://en.wikipedia.org/wiki/Culture_shock) of the
325installation process and the dependency management, my first impression
326of Rust is quite positive. But as I said, I am just getting started
327and my judgement is very superficial. I do want to write some small
328project in Rust, and I think I'll start from re-writing a simple
329[math library for modular arithmetic](../2025-01-21-taming-cpp-templates/#constraints-and-concepts)
330that I wrote in C++ some time ago.
331
332Stay tuned for more 🦀
diff --git a/src/blog/2025-06-26-borrow-checker/borrow-checker.md b/src/blog/2025-06-26-borrow-checker/borrow-checker.md
new file mode 100644
index 0000000..b22d741
--- /dev/null
+++ b/src/blog/2025-06-26-borrow-checker/borrow-checker.md
@@ -0,0 +1,150 @@
1# Stunned by the borrow checker 🦀
2
3As I mentioned in my [last post](../2025-06-13-cargo-culture-shock),
4in the last couple of weeks I have been learning Rust. I have written
5[a small library](https://git.tronto.net/zmodn-rs/file/README.md.html) for
6[integers modulo N](https://en.wikipedia.org/wiki/Modular_arithmetic)
7(the original C++ version was mentioned in
8[this post](../2025-01-21-taming-cpp-templates), rewritten
9[my implementation](https://git.tronto.net/ecm/file/README.md.html)
10of the
11[ECM algorithm](https://en.wikipedia.org/wiki/Lenstra_elliptic-curve_factorization)
12(mentioned in [this other post](../2025-02-27-elliptic-curves-javascript))
13and I am now playing around with some past
14[Advent of Code](https://adventofcode.com/) problems.
15
16But today I won't talk about any of the above. Instead, I just want to show
17you a small example of code that kept me confused for a couple of hours.
18
19First, I need to very briefly explain Rust's concept of ownership.
20
21## The borrow checker
22
23The *borrow checker* is a unique feature of Rust that prevents certain
24kinds of memory errors and data races. Without going into too much
25detail, the borrow checker is a compile-time mechanism that ensures that,
26at any given point, a given object is owned by at most one reference,
27unless all references to it are *immutable* (that is, they don't allow
28modifying the object).
29
30As a simple example, the following code is not valid:
31
32```
33fn main() {
34 let mut x = 2; // mut means mutable, without it x would be a constant
35 let y = &x; // & means reference
36 x = 3;
37 println!("{x} {y}");
38}
39```
40
41And the compiler gives a clear explanation:
42
43```
44error[E0506]: cannot assign to `x` because it is borrowed
45 --> t.rs:4:5
46 |
473 | let y = &x;
48 | -- `x` is borrowed here
494 | x = 3;
50 | ^^^^^ `x` is assigned to here but it was already borrowed
515 | println!("{x} {y}");
52 | --- borrow later used here
53```
54
55What happens is that creating a (mutable) reference `y` that refers to
56`x`, *borrows* the object referred to by the name `x`, so `x` cannot be
57used directly anymore until `y` goes out of scope.
58
59If you want to know more, check out the
60[ownership chapter in the book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html).
61
62## A tricky example
63
64Let's say we have a vector of vectors, and we want to copy an element from
65one of the internal vectors to another. We could try something like this:
66
67```
68fn main() {
69 let mut v = vec![vec![23], vec![42]]; // v is now {{23}, {42}}
70 v[0].push(v[1][0]);
71}
72```
73
74But this won't compile. Indeed we get:
75
76```
77error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
78 --> a.rs:3:15
79 |
803 | v[0].push(v[1][0]);
81 | - ---- ^ immutable borrow occurs here
82 | | |
83 | | mutable borrow later used by call
84 | mutable borrow occurs here
85 |
86 = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
87```
88
89However, the following works just fine:
90
91```
92fn main() {
93 let mut v = vec![vec![23], vec![42]];
94 let x = v[1][0];
95 v[0].push(x);
96}
97```
98
99And at this point I was very confused. Whatever the borrow checker does,
100shouldn't the two pieces of code do exactly the same? I got stuck for
101a while thinking that for some reason the function argument of `push()`
102was passed by reference in the first case, while it was copied in the
103second, but this is not where the problem lies.
104
105I was finally able to understand the problem when I realized that my first
106piece of code is equivalent to the following, which also does not compile:
107
108```
109fn main() {
110 let mut v = vec![vec![23], vec![42]];
111 let mut v0 = &mut v[0];
112 let x = v[1][0];
113 v0.push(x);
114}
115```
116
117Can you see the issue now?
118
119## Explanation
120
121Like in C++ and many other languages, the square
122bracket operator is a method on the vector object.
123More precisely, in Rust it is syntactic sugar for either the
124[`index()`](https://doc.rust-lang.org/std/ops/trait.Index.html) or the
125[`index_mut()`](https://doc.rust-lang.org/std/ops/trait.IndexMut.html)
126functions, depending if mutability is requested in our usage. In our
127example, when we call `v[0].push(...)` this will be translated to a call
128to `index_mut()`, because `push()` requires a mutable reference; when
129we do e.g. `let x = v[1][0]`, the immutable version will be call instead.
130
131But the details of `[]` are not important for us. The cause of the problem
132is that `let mut v0 = &mut v[0]` creates a mutable reference to *part
133of* the object `v`. At this point, `v` is borrowed and cannot be used
134directly anymore, even if we just want to immutably access some other
135parts of it to make a copy. Thus, when we try to do `let x = v[1][0]`,
136the borrow checker complains.
137
138In the first version of my code all of this happens in the same line,
139and this makes it confusing, because the order in which the various
140statements of that line are executed is very important.
141
142## Solution
143
144Solving the problem is easy, I can just use the second version of my code.
145Alternatively I could also try to use `split_at_mut()` as suggested
146by the compiler, but this seems overkill in this case; good to keep in
147mind though.
148
149But sometimes understanding a problem is more important than finding
150a solution.
diff --git a/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md b/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md
new file mode 100644
index 0000000..f186ea2
--- /dev/null
+++ b/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md
@@ -0,0 +1,106 @@
1# Declarative package management with Alpine Linux
2
3It has been almost two months since my last update, and it feels a
4bit weird because I have done plenty of tinkering that would be a
5good fit for this blog! But for one reason or another, I did not
6feel like writing about any of this in the usual level of detail.
7
8However, one of the last things I did is quite simple to explain, so here
9is a short post about it.
10
11## Alpine linux
12
13A couple of days ago I decided to try out
14[Alpine Linux](https://www.alpinelinux.org), a lightweight distro that
15uses [musl libc](https://www.musl-libc.org/) instead of GNU libc and
16[busybox](https://busybox.net) instead of GNU coreutils.
17
18Alpine's package manager is called Alpine Package Keeper (APK, not to
19be confused with Android Package, which also goes by APK). It is very
20fast and simple to use, and it has an especially cool feature: the
21list of all manually installed packages is kept in the plain text file
22`/etc/apk/world`. This file can be edited by hand, and running `apk fix`
23will then add or remove packages to satisfy the list in the file. Neat!
24
25## Declarative package management
26
27Unfortunately, APK will also overwrite `/etc/apk/world` by removing
28empty lines and sorting the packages in alphabetical order, one per
29line. Moreover, there is no option to write comments in this file.
30So one cannot really use it as a (commented) list of the packages they
31want to keep installed.
32
33At first I thought I could keep such a list in a separate file, and write
34a script to parse this file, remove the comments, write the result to
35`/etc/apk/world` and then run `apk fix`.
36
37But then I thought it would be even better to merge the two, and here
38is the result:
39
40```
41#!/bin/sh
42
43# Make a backup copy of the previous /etc/apk/world
44cp /etc/apk/world /etc/apk/world.backup
45
46echo "
47
48# Base system packages (installed by default)
49alpine-base busybox-mdev-openrc doas grub-efi openssh openssl
50
51# Firmware (installed by default, system-dependent)
52linux-firmware-i915 linux-firmware-intel linux-firmware-mediatek linux-firmware-other
53linux-firmware-rtl_bt linux-firmware-rtl_nic linux-firmware-xe linux-lts
54
55# Documentation
56docs mandoc-apropos
57
58# Wifi and other hardware control
59iwd openresolv pciutils bluez
60
61# Audio
62pulseaudio pulseaudio-bluez pulseaudio-alsa alsa-plugins-pulse pulseaudio-utils pulsemixer
63
64# Core tools (non-X)
65coreutils-fmt curl imagemagick ffmpeg tmux ghostscript ncurses shellcheck
66kbd # For console keyboard configuration
67syncthing fzf sfeed yt-dlp mblaze msmtp oath-toolkit
68
69# Development tools
70build-base git gdb valgrind clang20 python3 rust cargo hare
71lowdown darkhttpd # Both used for updating my website
72libx11-dev libxft-dev libxinerama-dev
73
74# Xorg xorg-server
75xinit eudev mesa-dri-gallium xf86-video-intel xf86-input-libinput xf86-input-synaptics
76setxkbmap xsel xbanish xsetroot xwallpaper xev slock
77
78# X applications
79firefox libreoffice telegram-desktop vlc imv-x11
80zathura-djvu zathura-pdf-mupdf zathura-ps
81arandr
82
83# Fonts, but like a gazillion of them
84font-terminus font-noto font-noto-extra font-arabic-misc
85font-misc-cyrillic font-mutt-misc font-screen-cyrillic
86font-winitzki-cyrillic font-cronyx-cyrillic font-noto-arabic
87font-noto-armenian font-noto-cherokee font-noto-devanagari
88font-noto-ethiopic font-noto-georgian font-noto-hebrew font-noto-lao
89font-noto-malayalam font-noto-tamil font-noto-thaana font-noto-thai
90
91" | sed 's/#.*//' | grep -v '^[:space:]*$' > /etc/apk/world
92apk fix
93```
94
95The bulk of the file is a
96[here document](https://en.wikipedia.org/wiki/Here_document) with a long
97list of all the packages I want installed - Alpine is really minimal! The
98few lines of code above and below this list are there to make a backup a
99copy of the old `/etc/apk/world`, filter out the comment from the new list
100using [sed](../2023-12-03-sed) and [grep](../2023-08-20-grep),
101copy the new list to `/etc/apk/world` and finally run `apk fix`.
102
103Now when I want to add a new piece of software to my system
104I edit this file and then run it as root. A dead simple
105way to do declarative package management - take that,
106[Nix](https://en.wikipedia.org/wiki/Nix_(package_manager))!
diff --git a/src/git/git.md b/src/git/git.md
index 15f32f6..cbfbf80 100644
--- a/src/git/git.md
+++ b/src/git/git.md
@@ -16,16 +16,12 @@ Here are some highlights of what you can find in my git repositories.
16 16
17* [nissy](https://git.tronto.net/nissy): A Rubik's cube solver, 17* [nissy](https://git.tronto.net/nissy): A Rubik's cube solver,
18 intended primarily as a tool for practicing FMC. Since april 2023 18 intended primarily as a tool for practicing FMC. Since april 2023
19 the project has been split into the FMC trainer 19 the stable version of the project has been moved to
20 [nissy-fmc](https://git.tronto.net/nissy-fmc) and the optimal solver 20 [nissy-classic](https://git.tronto.net/nissy-classic), while I kept
21 [nissy-nx](https://git.tronto.net/nissy-nx). Both are work in 21 working on the H48 optimal solver. This new solver, which will be
22 progress. The stable version is available at 22 integrated in a future version of nissy, can be found
23 [nissy-classic](https://git.tronto.net/nissy-classic), and is 23 [here](https://git.tronto.net/nissy-core).
24 maintained with bugfixes.
25 Check out also [the project's homepage](https://nissy.tronto.net). 24 Check out also [the project's homepage](https://nissy.tronto.net).
26* [h48](https://git.tronto.net/h48): Another Rubik's cube optimal solver,
27 intended to replace the engine of nissy in the future. Written in
28 collaboration with [Enrico](https://github.com/enricotenuti).
29* [scripts](https://git.tronto.net/scripts): Various scripts 25* [scripts](https://git.tronto.net/scripts): Various scripts
30 for Unix-like systems. 26 for Unix-like systems.
31* [zmodn](https://git.tronto.net/zmodn): A simple C++ library for working 27* [zmodn](https://git.tronto.net/zmodn): A simple C++ library for working
diff --git a/src/research/research.md b/src/research/research.md
index fbacd09..9820074 100644
--- a/src/research/research.md
+++ b/src/research/research.md
@@ -33,11 +33,11 @@ for a list of slides and notes of my talks.
33 33
34* Tronto, Sebastiano. 34* Tronto, Sebastiano.
35 *Division in modules and Kummer theory.* 35 *Division in modules and Kummer theory.*
36 [arXiv preprint](https://arxiv.org/abs/2111.14363). 36 [Journal de Thórie des Nombres de Bordeaux (2025) - open access](https://jtnb.centre-mersenne.org/articles/10.5802/jtnb.1326)
37 37
38* Lombardo, Davide; Tronto, Sebastiano. 38* Lombardo, Davide; Tronto, Sebastiano.
39 *Some uniform bounds for elliptic curves over Q.* 39 *Some uniform bounds for elliptic curves over Q.*
40 [Pacific Journal of Mathematics, (2022)](https://msp.org/pjm/2022/320-1/p06.xhtml). 40 [Pacific Journal of Mathematics (2022)](https://msp.org/pjm/2022/320-1/p06.xhtml).
41 41
42* Lombardo, Davide; Tronto, Sebastiano. 42* Lombardo, Davide; Tronto, Sebastiano.
43 *Effective Kummer theory for elliptic curves.* 43 *Effective Kummer theory for elliptic curves.*
diff --git a/src/series/series.md b/src/series/series.md
index afbf3dd..a98b85c 100644
--- a/src/series/series.md
+++ b/src/series/series.md
@@ -50,3 +50,13 @@ My adventures in learning C++ as a C programmer.
50* Episode 1: [motivation](../blog/2024-04-30-taming-cpp-motivation) 50* Episode 1: [motivation](../blog/2024-04-30-taming-cpp-motivation)
51* Episode 2: [RAII](../blog/2024-12-26-taming-cpp-raii) 51* Episode 2: [RAII](../blog/2024-12-26-taming-cpp-raii)
52* Episode 3: [templates, constraints and concepts](../blog/2025-01-21-taming-cpp-templates) 52* Episode 3: [templates, constraints and concepts](../blog/2025-01-21-taming-cpp-templates)
53
54## Learning Rust
55
56I have recently started learning Rust, documenting the process in a
57small series of blog posts. These posts are all marked by the crab emoji
58'🦀', which is something of a meme in the Rust community, as far as
59I understand.
60
61* [Cargo culture shock](../blog/2025-06-13-cargo-culture-shock)
62* [Stunned by the borrow checker](../blog/2025-06-26-borrow-checker)
diff --git a/src/speedcubing/bldsheets/corners.csv b/src/speedcubing/bldsheets/corners.csv
index 58ff0f8..447c3c5 100644
--- a/src/speedcubing/bldsheets/corners.csv
+++ b/src/speedcubing/bldsheets/corners.csv
@@ -1,22 +1,22 @@
1"","UBR","UBL","UFL","DFR","DFL","DBL","DBR","RUB","RDF","RDB","LUF","LUB","LDB","LDF","FUL","FDL","FDR","BUL","BUR","BDR","BDL", 1"","UBR","UBL","UFL","DFR","DFL","DBL","DBR","RUB","RDF","RDB","LUF","LUB","LDB","LDF","FUL","FDL","FDR","BUL","BUR","BDR","BDL",
2"UBR","","[R' B' R: U', R D R']","[R F R': R' D' R, U]","[R' D R U' R D' R', U]","[R F': R' U' R, D]","[R' B: D', R U R']","[U', R D' R' U R' D R]","","[R' D' R, U]","[U', R D R']","[R': R' D' R, U']","[R: U, R D R']","[U', R D' R']","[R' D R, U]","[R: U2, R D R']","[D R' D' R, U]","[D' R' D R, U]","[R': R' D' R, U2]","","[U', D R D' R']","[U': D' R D R']", 2"UBR","","[R' B' R: U', R D R']","[R F R': R' D' R, U]","[R' D R U' R D' R', U]","[R F': R' U' R, D]","[R' B: D', R U R']","[U', R D' R' U R' D R]","","[R' D' R, U]","[U', R D R']","[R': R' D' R, U']","[R: U, R D R']","[U', R D' R']","[R' D R, U]","[R: U2, R D R']","[D R' D' R, U]","[D' R' D R, U]","[R': R' D' R, U2]","","[U', D R D' R']","[U', D' R D R']",
3"UBL","[R' B' R: R D R', U']","","[R' D' R U': U', R' D R]","[U R' D R: U2, R D' R']","[R F' R' U': R D R', U2]","[R' D' R: R U2 R', D]","[R D' R': R' D R, U2]","[U R D' U: R' D' R, U2]","[R' D' R, U2]","[U': R D R', U2]","[F: U2, R' D' R]","","[U': R D' R', U2]","[R' D R, U2]","[R' F R D': R D' R', U] go back to this","[D R' D' R, U2]","[D' R' D R, U2]","","[R': U2, D' R' D R]","[D': R' D' R, U2] but maybe switch","[D: R' D R, U2] but maybe switch to (see second row)", 3"UBL","[R' B' R: R D R', U']","","[R' D' R U': U', R' D R]","[U R' D R: U2, R D' R']","[R F' R' U': R D R', U2]","[R' D' R: R U2 R', D]","[R D' R': R' D R, U2]","[U R D' U: R' D' R, U2]","[R' D' R, U2]","[U': R D R', U2]","[F: U2, R' D' R]","","[U': R D' R', U2]","[R' D R, U2]","[R' F R D': U, R D' R']","[D R' D' R, U2]","[D' R' D R, U2]","","[R': U2, D' R' D R]","[D U': R D' R', U2]","[D' U': R D R', U2]",
4"UFL","[R F R': U, R' D' R]","[R' D' R U': R' D R, U']","","[R' D R U' R D' R', U']","[U': U R' U' R, F2] sledge","[R' D' R: R U' R', D]","[R D' R': R' D R, U']","[U' R': U2, R' D' R]","[R' D' R, U']","[U': R D R', U']","","[U' R: R D R', U2]","[U': R D' R', U']","[R' D R, U']","","[D: R' D' R, U']","[D' R' D R, U']","[U' R': U', R' D' R]","[R': U', D' R' D R]","[D': R' D' R, U']","[D: R' D R, U']", 4"UFL","[R F R': U, R' D' R]","[R' D' R U': R' D R, U']","","[R' D R U' R D' R', U']","[U' R F': D, R' U' R]","[R' D' R: R U' R', D]","[R D' R': R' D R, U']","[U' R': U2, R' D' R]","[R' D' R, U']","[U': R D R', U']","","[U' R: R D R', U2]","[U': R D' R', U']","[R' D R, U']","","[D: R' D' R, U']","[D' R' D R, U']","[U' R': U', R' D' R]","[R': U', D' R' D R]","[D': R' D' R, U']","[D: R' D R, U']",
5"DFR","[U, R' D R U' R D' R']","[U R' D R: R D' R', U2]","[U': R' D R U' R D' R']","","[U' R' U R: R U' R', D]","[R U' R' U: R D' R', U2]","[R U' R' D R' U R, D']","[U' D' R': U2, R' D R]","","[D' R: F2, R' U R U'] sledge","[R U' R': U2, R' D' R]","[U2 R': R' U R, D] or U R' D'...","[U2 R': R' U R, D']","[U D R: D' R' D R, U']","[R U' R': U2, D' R' D R]","[R U' R': U2, R' D R]","","[U' D' R': U', R' D R]","[U R': D', U' R U R']","[D' R: D R' D' R, U]","[U' D' R': U, R' D R]", 5"DFR","[U, R' D R U' R D' R']","[U R' D R: R D' R', U2]","[U', R' D R U' R D' R']","","[U' R' U R: R U' R', D]","[R U' R' U: R D' R', U2]","[R U' R' D R' U R, D']","[U' D' R': U2, R' D R]","","[D' R: F2, R' U R U']","[R U' R': U2, R' D' R]","[U2 R': R' U R, D]","[U2 R': R' U R, D']","[U D R: D' R' D R, U']","[R U' R': U2, D' R' D R]","[R U' R': U2, R' D R]","","[U D R: R U' R', D']","[U R': D', U' R U R']","[D' R: D R' D' R, U]","[U' D' R': U, R' D R]",
6"DFL","[R F': D, R' U' R]","[R F' R' U: R D R', U2]","[U': F2, U R' U' R] sledge","[U' R' U R: D, R' U' R]","","[D' R U' R': D', R' U R]","[R U' R': D2, R' U R] or nightmare? (see below)","[U' R': U2, R' D R]","[R: D R' D' R, U]","[U' R': U, R' D R]","[U' R': D, U R U' R']","[D' R: U, R D' R']","[D R U' R': U2, R' D R]","","[R U' D: R' D' R, U2]","","[R: F2, R' U R U'] sledge","[U' R': U', R' D R]","[U D R' U': D', R U R'] or D' R D'...","[D' R: U', R D' R']","[U' D' R': U, R' D2 R]", 6"DFL","[R F': D, R' U' R]","[R F' R' U: R D R', U2]","[U' R F': R' U' R, D]","[U' R' U R: D, R U' R']","","[D' R U' R': D', R' U R]","[R U' R': D2, R' U R]","[U' R': U2, R' D R]","[R: D R' D' R, U]","[U' R': U, R' D R]","[U' R': D, U R U' R']","[D' R: U, R D' R']","[D R U' R': U2, R' D R]","","[R U' D: R' D' R, U2]","","[R: F2, R' U R U']","[U' R': U', R' D R]","[U D R' U': D', R U R']","[D' R: U', R D' R']","[U' D' R': U, R' D2 R]",
7"DBL","[R' B: R U R', D']","[R' D' R: D, R U2 R']","[R' D' R: D, R U' R']","[R U' R' U': R D' R', U2]","[D' R U' R': R' U R, D']","","[R U' R': D', R' U R]","[U' D R': U2, R' D2 R]","[R: U', R D' R']","[U' R': U, R' D2 R]","[F R: R D' R', U']","[R: U, R D' R']","","[D R: F2, R' U R U'] sledge","[R: U2, R D' R']","[D R: D R' D' R, U]","[R' D' R: D, R U R'] new!","[U' D R': U', R' D R]","[R: D', U' R' U R]","[R D': U' R' U R, D2]","", 7"DBL","[R' B: R U R', D']","[R' D' R: D, R U2 R']","[R' D' R: D, R U' R']","[R U' R' U': R D' R', U2]","[D' R U' R': R' U R, D']","","[R U' R': D', R' U R]","[U' D R': U2, R' D R]","[R: U', R D' R']","[U' R': U, R' D2 R]","[F R: R D' R', U']","[R: U, R D' R']","","[D R: F2, R' U R U']","[R: U2, R D' R']","[D R: D R' D' R, U]","[R' D' R: D, R U R']","[U' D R': U', R' D R]","[R: D', U' R' U R]","[R D': U' R' U R, D2]","",
8"DBR","[R D' R' U R' D R, U']","[R D' R': U2, R' D R]","[R D' R': U', R' D R]","[D': R U' R' D R' U R]","[R U' R': R' U R, D2] or nightmare? see NM","[R U' R': R' U R, D']","","[R': U, R' D R U' R D' R']","[R: R' D R U' R D' R', U]","","[R U' R': R' U R, D]","[D R: U, R D' R']","[R D' R': U, R' D R]","[U R: R U' R', D]","[D R: U2, R D' R']","[D R: U', R D' R']","[U R: D' R' D R, U']","[U R: R U' R', D']","[D R: U' R' U R, D']","","[U R: R U' R', D2]", 8"DBR","[R D' R' U R' D R, U']","[R D' R': U2, R' D R]","[R D' R': U', R' D R]","[D', R U' R' D R' U R]","[R U' R': R' U R, D2]","[R U' R': R' U R, D']","","[R': U, R' D R U' R D' R']","[R: R' D R U' R D' R', U]","","[R U' R': R' U R, D]","[D R: U, R D' R']","[R D' R': U, R' D R]","[U R: R U' R', D]","[D R: U2, R D' R']","[D R: U', R D' R']","[U R: D' R' D R, U']","[U R: R U' R', D']","[D R: D', U' R' U R]","","[U R: R U' R', D2]",
9"RUB","","[U R U' D': R' D' R, U2]","[U' R': R' D' R, U2]","[U' D' R': R' D R, U2]","[U' R': R' D R, U2]","[U' D R': R' D R, U2]","[R': R' D R U' R D' R', U]","","[R: U', R D' R' U R' D R]","[U' D R' U': R D' R', U2] new!","[R U' D' R': R' U R, D] new!","[R: U, R D' R' U R' D R]","[U R: U2, R D' R']","[U R U': R' D R, U2]","[R': U', R' D R U' R D' R']","[U D' R: U2, R D' R']","[U' R' U': R D R', U2]","[U R: U2, R D R']","","[U D R: U2, R D' R']","[U' R' U': R D' R', U2]", 9"RUB","","[U R U' D': R' D' R, U2]","[U' R': R' D' R, U2]","[U' D' R': R' D R, U2]","[U' R': R' D R, U2]","[U' D R': R' D R, U2]","[R': R' D R U' R D' R', U]","","[R: U', R D' R' U R' D R]","[U R U': R' D' R, U2]","[R U' D' R': R' U R, D]","[R: U, R D' R' U R' D R]","[U R: U2, R D' R']","[U R U': R' D R, U2]","[R': R' D R U' R D' R', U']","[U D' R: U2, R D' R']","[U' R' U': R D R', U2]","[U R: U2, R D R']","","[U D R: U2, R D' R']","[U' R' U': R D' R', U2]",
10"RDF","[U, R' D' R]","[U2, R D' R]","[U', R' D' R]","","[R: U, D R' D' R]","[R: R D' R', U']","[R: U, R' D R U' R D' R']","[R: R D' R' U R' D R, U']","","[R: U, D' R' D R]","[R', U' R U R', D']","[U R' U': R D R', U']","[R U R', D2]","[U R' D': R U' R', D2]","[D' R U': R' D R, U2]","[R U R', D]","","[R': D, R' U R]","[R': U R U' R', D']","[R U R', D']","[U R' D': R U' R', D']", 10"RDF","[U, R' D' R]","[U2, R' D' R]","[U', R' D' R]","","[R: U, D R' D' R]","[R: R D' R', U']","[R: U, R' D R U' R D' R']","[R: R D' R' U R' D R, U']","","[R: U, D' R' D R]","[R': U' R U R', D']","[U R' U': R D R', U']","[R U R', D2]","[U R' D': R U' R', D2]","[D' R U': R' D R, U2]","[R U R', D]","","[R': D, R' U R]","[R': U R U' R', D']","[R U R', D']","[U R' D': R U' R', D']",
11"RDB","[R D R', U']","[U': R D R', U2]","[U': U', R D R']","[D' R: R' U R U', F2] sledge","[U' R': R' D R, U]","[U' R': R' D2 R, U]","","[U' D R' U: R D' R', U2] new!","[R D': R' D R, U]","","[R' U': R' D' R, U2]","[U' R: D', R U' R']","[R D: R' U' R, D2]","[D': U R U' R', D2]","[R U': D' R' D R, U2]","[R D: R' U' R, D]","[D', U R U' R']","[R' U: R' D' R, U]","[U' R: U R' U' R, D]","","[U' R' U' R, D']", 11"RDB","[R D R', U']","[U: R D R', U2]","[U': U', R D R']","[D' R: R' U R U', F2]","[U' R': R' D R, U]","[U' R': R' D2 R, U]","","[U R U: R' D' R, U2]","[R D': R' D R, U]","","[R' U: R' D' R, U2]","[U' R: D', R U' R']","[R D: R' U' R, D2]","[D': U R U' R', D2]","[R U': D' R' D R, U2]","[R D: R' U' R, D]","[D', U R U' R']","[R' U: R' D' R, U]","[U' R: U R' U' R, D]","","[U' R' U' R, D']",
12"LUF","[R': U', R' D' R]","[F: R' D' R, U2]","","[R U' R': R' D' R, U2]","[U' R': U R U' R', D]","[F R: U', R D' R']","[R U' R': D, R' U R]","[R U' D' R': D, R' U R] new!","[R': D', U' R U R']","[R': R' D' R, U2]","","[U' R' U, L]","[F: D2, R U R'] or RUD see below","[R': D, U' R U R']","","[D R': D', U' R U R']","[R: F, R' U R U']","[R' U': R' D' R, U']","[R' F: R U R', D]","[F: D', R U R']","[D R': D, U' R U R']", 12"LUF","[R': U', R' D' R]","[F: R' D' R, U2]","","[R U' R': R' D' R, U2]","[U' R': U R U' R', D]","[F R: U', R D' R']","[R U' R': D, R' U R]","[R U' D' R': D, R' U R]","[R': D', U' R U R']","[R' U': R' D' R, U2]","","[U' R' U, L]","[F: D2, R U R']","[R': D, U' R U R']","","[D R': D', U' R U R']","[R: F, R' U R U']","[R' U': R' D' R, U']","[R' F: R U R', D]","[F: D', R U R']","[D R': D, U' R U R']",
13"LUB","[R: R D R', U]","","[U' R: U2, R D R']","[U2 R': D, R' U R] or U R' D'...","[D' R: R D' R', U]","[R: R D' R', U]","[D R: R D' R', U]","[R: R D' R' U R' D R, U]","[U R' U': U', R D R']","[U' R: R U' R', D']","[L, U' R' U]","","[R: D' R D R', U]","[U R': U', R' D R]","[R' B' R2 U': R' D' R, U2]","[R U: R' D R, U]","[R U R': R' D' R, U']","","[R', F' L F]","[R U: R' D' R, U]","[U D R': U', R' D R]", 13"LUB","[R: R D R', U]","","[U' R: U2, R D R']","[U2 R': D, R' U R]","[D' R: R D' R', U]","[R: R D' R', U]","[D R: R D' R', U]","[R: R D' R' U R' D R, U]","[U R' U': U', R D R']","[U' R: R U' R', D']","[L, U' R' U]","","[R: D' R D R', U]","[U R': U', R' D R]","[R' B' R2 U': R' D' R, U2]","[R U: R' D R, U]","[R U R': R' D' R, U']","","[R', F' L F]","[R U: R' D' R, U]","[U D R': U', R' D R]",
14"LDB","[R D' R', U']","[U: R D' R', U2]","[U': U', R D' R']","[U2 R': D', R' U R]","[D R U' R': R' D R, U2]","","[R D' R': R' D R, U]","[U R: R D' R', U2]","[D2, R U R']","[R D': R' U' R, D2]","[F: R U R', D2] or RUD see jw","[R: U, D' R D R']","","[D R': R D' R' D, F']","[D R U': R' D R, U2]","[D: D, R U R']","[U R U': R D' R', U']","[U2: R: R D' R', U]","[U' R: U R' U' R, D']","[D': D', R U R']","", 14"LDB","[R D' R', U']","[U: R D' R', U2]","[U': U', R D' R']","[U2 R': D', R' U R]","[D R U' R': R' D R, U2]","","[R D' R': R' D R, U]","[U R: R D' R', U2]","[D2, R U R']","[R D': R' U' R, D2]","[F: R U R', D2]","[R: U, D' R D R']","","[D R': R D' R' D, F']","[D R U': R' D R, U2]","[D: D, R U R']","[U R U': R D' R', U']","[U2 R: R D' R', U]","[U' R: U R' U' R, D']","[D': D', R U R']","",
15"LDF","[U, R' D R]","[U2, R' D R]","[U', R' D R]","[U D R: U', D' R' D R]","","[D R: R' U R U', F2] sledge","[U R: D, R U' R']","[U R U: R' D R, U2]","[U R' D: R U' R', D2]","[D: U R U' R', D2]","[R': U' R U R', D]","[U R': R' D R, U']","[D R': F', R D' R' D] sledge","","[R' D: D' R D R', F2] sledge","","[D, U R U' R']","[U' R': U', D R' D' R]","[R': U R U' R', D]","[R' D: R' U' R, D]","[D: U R U' R', D]", 15"LDF","[U, R' D R]","[U2, R' D R]","[U', R' D R]","[U D R: U', D' R' D R]","","[D R: R' U R U', F2]","[U R: D, R U' R']","[U R U: R' D R, U2]","[U R' D: R U' R', D2]","[D: U R U' R', D2]","[R': U' R U R', D]","[U R': R' D R, U']","[D R': F', R D' R' D]","","[R' D: D' R D R', F2]","","[D, U R U' R']","[U' R': U', D R' D' R]","[R': U R U' R', D]","[R' D: R' U' R, D]","[D: U R U' R', D]",
16"FUL","[R: R D R', U2]","[R' F R D': U, R D' R'] go back to this","","[R U R': D' R' D R, U2]","[R U D: R' D' R, U2]","[R: R D' R', U2]","[D R: R D' R', U2]","[R': R' D R U' R D' R', U']","[D R U: R' D' R, U2]","[R U: D' R' D R, U2]","","[R' B' R2 U: R' D' R, U2]","[D R U: R' D R, U2] or R U D (see below)","[D R': R D' R' D, F2] sledge","","[R U: R' D R, U2]","[R': R D' R' D, F2] sledge","[R U R' D': U2, R' D R]","[R D' U: R' D' R, U2]","[R U: R' D' R, U2]","[D' R U: D' R' D R, U2]", 16"FUL","[R: R D R', U2]","[R' F R D': R D' R', U]","","[R U' R': D' R' D R, U2]","[R U D: R' D' R, U2]","[R: R D' R', U2]","[D R: R D' R', U2]","[R': U', R' D R U' R D' R']","[D R U: R' D' R, U2]","[R U: D' R' D R, U2]","","[R' B' R2 U: R' D' R, U2]","[D R U: R' D R, U2]","[D R': R D' R' D, F2]","","[R U: R' D R, U2]","[R': R D' R' D, F2]","[R U R' D': U2, R' D R]","[R D' U: R' D' R, U2]","[R U: R' D' R, U2]","[D' R U: D' R' D R, U2]",
17"FDL","[U, D R' D' R]","[U2, D R' D' R]","[U', D R' D' R]","[R U' R': R' D R, U2]","","[D R: U, D R' D' R]","[D R: U', R D' R']","[U D' R: R D' R', U2]","[D, R U R']","[R D: R' U' R, D]","[D R': U R U' R', D']","[R U: U, R' D R]","[D: R U R', D]","","[R U': R' D R, U2]","","[R': R D' R' D, F']","[D R': D, R' U R]","[R' F: R U' R', D] different from Elliot","[D: R U R', D2]","[R' F': R U R', D2] or D' R D'...", 17"FDL","[U, D R' D' R]","[U2, D R' D' R]","[U', D R' D' R]","[R U' R': R' D R, U2]","","[D R: U, D R' D' R]","[D R: R D' R', U']","[U D' R: R D' R', U2]","[D, R U R']","[R D: D, R' U' R]","[D R': U' R U R', D']","[R U: U, R' D R]","[D: R U R', D]","","[R U': R' D R, U2]","","[R': R D' R' D, F']","[U R' F': D, R U' R']","[R' F: R U' R', D]","[D: R U R', D2]","[R' F': R U R', D2]",
18"FDR","[U, D' R' D R]","[U2, D' R' D R]","[U', D' R' D R]","","[R: R' U R U', F2] sledge","[R' D' R: R U R', D] new!","[U R D': U', R' D R]","[U' R' U: R D R', U2]","","[U R U' R', D']","[R: R' U R U', F] sledge","[R U R': U', R' D' R]","[U R U': U', R D' R']","[U R U' R', D]","[R': F2, R D' R' D] sledge","[R': F', R D' R' D] sledge","","[U' R' U': R' D R', U]","[D' R': U R U' R', D]","[R U R' U': D', R U R'] or D R D...","[U R U' R', D2]", 18"FDR","[U, D' R' D R]","[U2, D' R' D R]","[U', D' R' D R]","","[R: R' U R U', F2]","[R' D' R: R U R', D]","[U R D': U', R' D R]","[U' R' U: R D R', U2]","","[U R U' R', D']","[R: R' U R U', F] sledge","[R U R': U', R' D' R]","[U R U': U', R D' R']","[U R U' R', D]","[R': F2, R D' R' D]","[R': F', R D' R' D]","","[U' R' U': U', R D R']","[D' R': U R U' R', D]","[R U R' U': D', R U R']","[U R U' R', D2]",
19"BUL","[R': U2, R' D' R]","","[U' R': R' D' R, U']","[U R' U': R U R', D']","[U' R': U', R' D R]","[U' D R': R' D R, U']","[U R: D', R U' R']","[U R: R D R', U2]","[R': R' U R, D]","[R' U: U, R' D' R]","[R' U': U', R' D' R]","","[U2 R: U, R D' R']","[U' R': D R' D' R, U']","[R U R' D': R' D R, U2]","[D R': R' U R, D]","[U' R' U': R D R', U']","","[U' L' U, R]","[D' R': R' U R, D]","[U' R' U': R D' R', U']", 19"BUL","[R': U2, R' D' R]","","[U' R': R' D' R, U']","[U D R: D', R U' R']","[U' R': R' D R, U']","[U' D R': R' D R, U']","[U R: D', R U' R']","[U R: R D R', U2]","[R': R' U R, D]","[R' U: U, R' D' R]","[R' U': U', R' D' R]","","[U2 R: U, R D' R']","[U' R': D R' D' R, U']","[R U R' D': R' D R, U2]","[U R' F': R U' R', D]","[U' R' U': R D R', U']","","[U' L' U, R]","[D' R': R' U R, D]","[U' R' U': R D' R', U']",
20"BUR","","[R': D' R' D R, U2]","[R': D' R' D R, U']","[U R': U' R U R', D']","[U D R' U': R U R', D'] or D' R U'...","[R: U' R' U R, D']","[D R: U' R' U R, D']","","[R': D', U R U' R']","[U' R: D, U R' U' R]","[R' F: D, R U R']","[F' L F, R']","[U' R: D', U R' U' R]","[R': D, U R U' R']","[R D' U': R' D' R, U2]","[R' F: D, R U' R'] different from Elliot","[D' R': D, U R U' R']","[R, U' L' U]","","[R': D' R' D R, U]","[D R': D, U R U' R']", 20"BUR","","[R': D' R' D R, U2]","[R': D' R' D R, U']","[U R': U' R U R', D']","[U D R' U': R U R', D']","[R: U' R' U R, D']","[D R: U' R' U R, D']","","[R': D', U R U' R']","[U' R: D, U R' U' R]","[R' F: D, R U R']","[F' L F, R']","[U' R: D', U R' U' R]","[R': D, U R U' R']","[R D' U': R' D' R, U2]","[R' F: D, R U' R']","[D' R': D, U R U' R']","[R, U' L' U]","","[R': D' R' D R, U]","[D R': D, U R U' R']",
21"BDR","[D R D' R', U']","[D': R' D' R, U2] but maybe switch to D U'...","[D': U', R' D' R]","[D' R: U, D R' D' R]","[D' R: R D' R', U']","[R D: U' R' U R, D2]","","[U D R: R D' R', U2]","[D', R U R']","","[F: R U R', D']","[R U: U, R' D' R]","[D': R U R', D']","[R' D: D, R' U' R]","[R U': R' D' R, U2]","[D': R U R', D2]","[R U R' U': R U R', D'] or D R D'...","[D' R': D, R' U R]","[R': U, D' R' D R]","","[R' D': R' U' R, D2] or D' R...", 21"BDR","[D R D' R', U']","[D U: R D' R', U2]","[D': U', R' D' R]","[D' R: U, D R' D' R]","[D' R: R D' R', U']","[R D: U' R' U R, D2]","","[U D R: R D' R', U2]","[D', R U R']","","[F: R U R', D']","[R U: U, R' D' R]","[D': R U R', D']","[R' D: D, R' U' R]","[R U': R' D' R, U2]","[D': R U R', D2]","[R U R' U': R U R', D']","[D' R': D, R' U R]","[R': U, D' R' D R]","","[R' D': R' U' R, D2]",
22"BDL","[D' R D R', U']","[D: U2, R' D R] but maybe switch to D' U'","[D: U', R' D R]","[U' D' R': R' D R, U]","[U' D' R': R' D2 R, U]","","[U R: D2, R U' R']","[U' R' U: R D' R', U2]","[U R' D': D', R U' R']","[U' R' U' R, D']","[D R': U R U R', D]","[U D R': R' D R, U']","","[D: D, U R U' R']","[D' R U': D' R' D R, U2]","[R' F': D2, R U R'] or D' R D...","[D2, U R U' R']","[U' R' U': U', R D' R']","[D R': U R U' R', D]","[R' D: R' U' R, D2] or D' R...","", 22"BDL","[D' R D R', U']","[D' U: R D R', U2]","[D: U', R' D R]","[U' D' R': R' D R, U]","[U' D' R': R' D2 R, U]","","[U R: D2, R U' R']","[U' R' U: R D' R', U2]","[U R' D': D', R U' R']","[D', U' R' U' R]","[D R': U' R U R', D]","[U D R': R' D R, U']","","[D: D, U R U' R']","[D' R U': D' R' D R, U2]","[R' F': D2, R U R']","[D2, U R U' R']","[U' R' U': U', R D' R']","[D R': U R U' R', D]","[R' D: R' U' R, D2]","",
diff --git a/src/speedcubing/bldsheets/edges.csv b/src/speedcubing/bldsheets/edges.csv
index 6c296dc..827ec7f 100644
--- a/src/speedcubing/bldsheets/edges.csv
+++ b/src/speedcubing/bldsheets/edges.csv
@@ -1,23 +1,23 @@
1"","UB","UL","UF","DR","DF","DL","DB","RB","RF","RD","LF","LU","LB","LD","FR","FU","FL","FD","BL","BU","BR","BD", 1"","UB","UL","UF","DR","DF","DL","DB","RB","RF","RD","LF","LU","LB","LD","FR","FU","FL","FD","BL","BU","BR","BD",
2"UB","","[M2 U: M', U2]","[R' B' R: U', R E R']","[R U' R': S, R2]","[R U' R' D: S, R2]","[S R B' R': S, R2]","[U', M' U2 M']","[U': U', R' S' R]","[U': U', R S' R']","[R E R', U]","[R' E R, U]","[S, U R' U' R]","[R E' R', U]","[U': U', R' S R]","[E': R' E R, U]","[R U R' U', M']","[R E R2 E' R, U]","[U: M', R U' R' U]","[R' E' R2 E R', U]","","[E: R E' R', U]","[R' U R U', M]", 2"UB","","[R' E R U: R' E' R, U]","[R' E R U: R' E' R, U2]","[R E' R2 E R, U]","[R U' R' D: S, R2]","[S R B' R': S, R2]","[U', M' U2 M']","[U': U', R' S' R]","[U': U', R S' R']","[R E R', U]","[R' E R, U]","[S: U, R E R']","[R E' R', U]","[U': U', R' S R]","[U': U', R' S2 R]","[R U R' U', M']","[R E2 R', U]","[U: M', R U' R' U]","[R' E2 R, U]","","[U': U', R S2 R']","[R' U R U', M]",
3"UL","[M2 U': M', U2]","","[M2 U: M, U2]","[S, R2]","[U': M', U2]","[L2, S']","[U: M, U2]","[U2, R' S' R]","[U2, R S' R']","[R E R', U2]","[R' E R, U2]","","[R E' R', U2]","[U2, R' S R]","[R, S' l2 S]","[M U': M', U2]","[S R2 S', l']","[M U: M, U2]","[S R2 S', l]","[M' U: M, U2]","[R', S' l2 S]","[M' U': M', U2]", 3"UL","[R' E R U: U, R' E' R]","","[R' E R U': U', R' E' R]","[S, R2]","[U': M', U2]","[L2, S']","[U: M, U2]","[U2, R' S' R]","[U2, R S' R']","[R E R', U2]","[R' E R, U2]","","[R E' R', U2]","[U2, R' S R]","[U2, R' S2 R]","[M U': M', U2]","[R E2 R', U2]","[M U: M, U2]","[R' E2 R, U2]","[M' U: M, U2]","[U2, R S2 R']","[M' U': M', U2]",
4"UF","[R' B' R: R E R', U']","[M2 U': M, U2]","","[R U R': S, R2]","[U, M U2 M]","[S R' F R: S, R2]","[R U R' D': R2, S]","[U: U, R' S' R]","[U: U, R S' R']","[R' E' R, U']","[R' E R, U']","[S, U' R' U R]","[R E' R', U']","[U: U, R' S R]","[E': R' E R, U']","","[R E R2 E' R, U']","[R U' R' U, M']","[R' E' R2 E R', U']","[R' U' R U, M]","[E: R E' R', U']","M U' M' U' M U' M' U'", 4"UF","[R' E R U': R' E' R, U2]","[R' E R U': R' E' R, U']","","[R E' R2 E R, U']","[U, M U2 M]","[S R' F R: S, R2]","[R U R' U: M, U2]","[U: U, R' S' R]","[U: U, R S' R']","[R' E' R, U']","[R' E R, U']","[S: U', R E R']","[R E' R', U']","[U: U, R' S R]","[U: U, R' S2 R]","","[R E2 R', U']","[R U' R' U, M']","[R' E2 R, U']","[R' U' R U, M]","[U: U, R S2 R']","M U' M' U' M U' M' U'",
5"DR","[R' U' R': S, R2]","[R2, S]","[R' U R': S, R2]","","[R D' R': S, R2]","[S', R2]","[R D R': S, R2]","[R' S' R': S, R2]","[R S' R: S, R2]","","[S' l' S, R2]","[l': S' l S, R2]","R2 S R' E' R2 E R S'","[R: E l E', R2]","[R U2 R: S, R2]","[r U' R: S, R2]","[R: E, R2]","[S' U: M', U2]","[R': E', R2]","[r' U R': S, R2]","[R' U2 R': S, R2]","[S' U: M, U2]", 5"DR","[U, R E' R2 E R]","[R2, S]","[U', R E' R2 E R]","","[R D' R': S', R2]","[S', R2]","[R D R': S', R2]","[R' S' R': S, R2]","[R S' R: S, R2]","","[S' l' S, R2]","[l': S' l S, R2]","R2 S R' E' R2 E R S'","[R: E l E', R2]","[R U2 R: S, R2]","[r U' R: S, R2]","[R: E, R2]","[S' U: M', U2]","[R': E', R2]","[r' U R': S, R2]","[R' U2 R': S, R2]","[S' U': M, U2]",
6"DF","[R U' R' D: R2, S]","[U: M', U2]","[M U2 M, U]","[R D' R: S', R2]","","[S R' F' R: S, R2]","[D': R2, S']","[R' S' R U: M', U2]","[R S' R' U: M', U2]","[R F' R', S]","[R F R': R' E R, U']","[U2: R' F' R, S]","[S R' F': R2, E']","[R' S R U: M', U2]","[R, U' M2 U]","[R' F': R U' R', E]","[D R: E, R2]","","[D R': E', R2]","[r' U' r U': M', U2]","[R', U' M2 U]","[r' U' r U: M', U2]", 6"DF","[R U' R' D: R2, S]","[U: M', U2]","[M U2 M, U]","[R D' R: S', R2]","","[S R' F' R: S, R2]","[D': R2, S']","[R' S' R U: M', U2]","[R S' R' U: M', U2]","[R F' R', S]","[R F R': U', R' E R]","[U2: R' F' R, S]","[S R' F': R2, E']","[R' S R U: M', U2]","[R, U' M2 U]","[R' F': R U' R', E]","[D R: E, R2]","","[D R': E', R2]","[r' U r U: M', U2]","[R', U' M2 U]","[r' U' r U: M', U2]",
7"DL","[S' R B' R': S, R2]","[S', L2]","[S' R' F R: S, R2]","[R2, S']","[S' R' F' R: S, R2]","","[S' R B R': S, R2]","[R' S' R: R S' R', U2]","[R S' R': R' S' R, U2]","[U: S', R F R']","[S U2 R': E, R2]","[R' F' R: R' S R, U]","[S U2 R: E', R2]","","[U R' U': R2, S']","[R F R: S', R2]","[l': l', S R2 S']","[R F' R: S', R2]","[l: l, S R2 S']","[R' B' R': S', R2]","[U R U': R2, S']","[R' B R': S, R2]", 7"DL","[S' R B' R': S, R2]","[S', L2]","[S' R' F R: S, R2]","[R2, S']","[S' R' F' R: S, R2]","","[S' R B R': S, R2]","[R' S' R: R S' R', U2]","[R S' R': R' S' R, U2]","[U: S', R F R']","[S U2 R': E, R2]","[R' F' R: R' S R, U]","[S U2 R: E', R2]","","[U R' U': R2, S']","[R F R: S', R2]","[l': l', S R2 S']","[R F' R: S', R2]","[l: l, S R2 S']","[R' B' R': S', R2]","[U R U': R2, S']","[R' B R': S', R2]",
8"DB","[M' U2 M', U']","[U': M, U2]","[R U R' D': S, R2]","[R D R: S, R2]","[D': R2, S']","[S R B R': S, R2]","","[R' S' R U': M, U2]","[R S' R' U': M, U2]","[R' B R, S]","[S R B: R2, E]","[U2: R B R', S]","[R' B' R: U, R E' R']","[R' S R U': M, U2]","[R, U M2 U']","[r U' r' U': M, U2]","[D' R: E, R2]","[r U r' U': M, U2]","[D' R': E', R2]","[U D': S', R' F R]","[R', U M2 U']","", 8"DB","[M' U2 M', U']","[U': M, U2]","[R U R' U': M, U2]","[R D R: S', R2]","[D: R2, S']","[S R B R': S, R2]","","[R' S' R U': M, U2]","[R S' R' U': M, U2]","[R' B R, S]","[S R B: R2, E]","[U2: R B R', S]","[R' B' R: U, R E' R']","[R' S R U': M, U2]","[R, U M2 U']","[r U' r' U': M, U2]","[D' R: E, R2]","[r U r' U': M, U2]","[D' R': E', R2]","[U D': S', R' F R]","[R', U M2 U']","",
9"RB","[U': R' S' R, U']","[R' S' R, U2]","[U: R' S' R, U]","[R' S' R: S, R2]","[R' S' R U': M', U2]","[R' S' R: U2, R S' R']","[R' S' R U: M, U2]","","[S R: S, R2]","[U' R U, S]","[R' S': E, R U2 R']","[R' U2 R, S']","[R' S' R: R E' R', U2]","[R' S': R U2 R', S2]","[u: R U' R', E]","[R' U: U, R' S' R]","[S' R': U2, R' E R]","[S' R' U: M', U2]","[E, R B2 R']","[R' U': U', R S' R']","","[S' R' U': M, U2]", 9"RB","[U': R' S' R, U']","[R' S' R, U2]","[U: R' S' R, U]","[R' S' R: S, R2]","[R' S' R U': M', U2]","[R' S' R: U2, R S' R']","[R' S' R U: M, U2]","","[S' R: S, R2]","[U' R U, S]","[R' S': E, R U2 R']","[R' U2 R, S']","[R' S' R: R E' R', U2]","[R' S': R U2 R', S2]","[u: R U' R', E]","[R' U: U, R S' R']","[R' U' B': R2, E]","[S' R' U: M', U2]","[E, R B2 R']","[R' U': U', R S' R']","","[S' R' U': M, U2]",
10"RF","[U': R S' R', U']","[R S' R', U2]","[U: R S' R', U]","[R S' R': S, R2]","[R S' R' U': M', U2]","[R S' R': U2, R' S' R]","[R S' R' U: M, U2]","[S R': S, R2]","","[U' R' U, S]","[R S' R': R' E R, U2]","[R U2 R', S']","[R S': E', R' U2 R]","[R S': R' U2 R, S2]","","[R U: U, R' S' R]","[E': R' F2 R]","[S' R U: M', U2]","[S' R: U2, R E' R']","[R U': U', R' S' R]","[u': R' U R, E']","[S' R U': M, U2]", 10"RF","[U': R S' R', U']","[R S' R', U2]","[U: R S' R', U]","[R S' R': S, R2]","[R S' R' U': M', U2]","[R S' R': U2, R' S' R]","[R S' R' U: M, U2]","[S' R': S, R2]","","[U' R' U, S]","[R S' R': R' E R, U2]","[R U2 R', S']","[R S': E', R' U2 R]","[R S': R' U2 R, S2]","","[R U: U, R' S' R]","[E', R' F2 R]","[S' R U: M', U2]","[R U F: R2, E']","[R U': U', R' S' R]","[u': R' U R, E']","[S' R U': M, U2]",
11"RD","[U, R E R']","[U2, R E R']","[U', R' E' R]","","[S, R F' R']","[U: R F R', S']","[S, R' B R]","[S, U' R U]","[S, U' R' U]","","[S, R F2 R']","[D': S, R' F' R]","[S, R' B2 R]","[U R' F R: S', R2]","[R U R' U', S']","[R' F R, S']","[R' F2 R, S']","[R' F' R, S']","[R B2 R', S']","[R B' R', S']","[R' U R U', S']","[R B R', S']", 11"RD","[U, R E R']","[U2, R E R']","[U', R' E' R]","","[S, R F' R']","[U: R F R', S']","[S, R' B R]","[S, U' R U]","[S, U' R' U]","","[S, R F2 R']","[D': S, R' F' R]","[S, R' B2 R]","[U R' F R: S', R2]","[R U R' U', S']","[R' F R, S']","[R' F2 R, S']","[R' F' R, S']","[R B2 R', S']","[R B' R', S']","[R' U R U', S']","[R B R', S']",
12"LF","[U, R' E R]","[U2, R' E R]","[U', R' E R]","[R2, S' l' S]","[R F R': U', R' E R]","[S U2 R: E, R2]","[S R B: E, R2]","[R' S': R U2 R', E]","[R S' R': U2, R' E R]","[R F2 R', S]","","[E R' E', l]","[R' B': R U R', E]","[E R' E', l']","[u': R U' R', E']","[S U' R: E, R2]","","[D: R F2 R', S]","[E', R B2 R']","[S U R: E, R2]","[R' u': R U R', E']","[D': R F2 R', S]", 12"LF","[U, R' E R]","[U2, R' E R]","[U', R' E R]","[R2, S' l' S]","[R F R': R' E R, U']","[S U2 R: E, R2]","[S R B: E, R2]","[R' S': R U2 R', E]","[R S' R': U2, R' E R]","[R F2 R', S]","","[E R' E', l]","[R' B': R U R', E]","[E R' E', l']","[u': R U R', E']","[S U' R: E, R2]","","[D: R F2 R', S]","[E', R B2 R']","[S U R: E, R2]","[R' U': R U R', E]","[D': R F2 R', S]",
13"LU","[U R' U' R, S]","","[U' R' U R, S]","[l: S l S', R2]","[U2: S, R' F' R]","[R' F' R: U, R' S R]","[U2: S, R B R']","[S', R' U2 R]","[S', R U2 R']","[D': R' F' R, S]","[l, S R' S']","","[l', S R S']","[D: R' F' R, S]","[R, U' M' U]","[R' F R, S]","[R' F2 R, S]","[R' F' R, S]","[R B2 R', S]","[S', R U' R']","[R', U' M' U]","[R B R', S]", 13"LU","[S: R E R', U]","","[S: R E R', U']","[l: S l S', R2]","[U2: S, R' F' R]","[R' F' R: U, R' S R]","[U2: S, R B R']","[S', R' U2 R]","[S', R U2 R']","[D': R' F' R, S]","[l, E R' E']","","[l', E' R E]","[D: R' F' R, S]","[R, U' M' U]","[R' F R, S]","[R' F2 R, S]","[R' F' R, S]","[R B2 R', S]","[S', R U' R']","[R', U' M' U]","[R B R', S]",
14"LB","[U, R E' R']","[U2, R E' R']","[U', R E' R']","[R2, S' l S]","[S R' F': E', R2]","[S U2 R': E', R2]","[R' B' R: R E' R', U]","[R' S' R: U2, R E' R']","[R S': R' U2 R, E']","[R' B2 R, S]","[R' B': E, R U R']","[E' R E, l']","","[E' R E, l]","[R U: R' U' R, E']","[S U' R': E', R2]","[E, R' F2 R]","[D: R' B2 R, S]","","[S U R': E', R2]","[u: R' U' R, E]","[D': R' B R, S]", 14"LB","[U, R E' R']","[U2, R E' R']","[U', R E' R']","[R2, S' l S]","[S R' F': E', R2]","[S U2 R': E', R2]","[R' B' R: R E' R', U]","[R' S' R: U2, R E' R']","[R S': R' U2 R, E']","[R' B2 R, S]","[R' B': E, R U R']","[E' R E, l']","","[E' R E, l]","[R U: R' U' R, E']","[S U' R': E', R2]","[E, R' F2 R]","[D: R' B2 R, S]","","[S U R': E', R2]","[u: R' U' R, E]","[D': R' B2 R, S]",
15"LD","[U': R' S R, U']","[R' S R, U2]","[U: R' S R, U]","[R': E l E', R2]","[R' S R U': M', U2]","","[R' S R U: M, U2]","[R' S: R U2 R', S2]","[R S: R' U2 R, S2]","[U R' F R': S', R2]","[l', E R' E']","[D: S, R' F' R]","[l, E' R E]","","[E' l' E, R']","[R': F, R' S' R]","[R': F2, R' S' R]","[D: S', R' F' R]","[R: B2, R S' R']","[R: B', R S' R']","[E l E', R]","[D': S', R B R']", 15"LD","[U': R' S R, U']","[R' S R, U2]","[U: R' S R, U]","[R': E l E', R2]","[R' S R U': M', U2]","","[R' S R U: M, U2]","[R' S: R U2 R', S2]","[R S: R' U2 R, S2]","[U R' F R': S', R2]","[l', E R' E']","[D: S, R' F' R]","[l, E' R E]","","[E' l' E, R']","[R': F, R' S' R]","[R': F2, R' S' R]","[D: S', R' F' R]","[R: B2, R S' R']","[R: B', R S' R']","[E l E', R]","[D': S', R B R']",
16"FR","[u: R' E R, U']","[S' l2 S, R]","[E': U', R' E R]","[R U2 R': S, R2]","[U' M2 U, R]","[U R' U': S', R2]","[U M2 U', R]","[U: R U' R', E']","","[S', R U R' U']","[U': R U R', E]","[U' M' U, R]","[R u: R' U' R, E]","[R', E' l' E]","","[S': U, R S' R']","[U R' U' R: E, R2]","[U M' U', R]","[U R' U' R': E', R2]","[S': U', R S' R']","[U2 R': S, R2]","[U' M U, R]", 16"FR","[U': R' S2 R, U']","[R' S2 R, U2]","[U: R' S2 R, U]","[R U2 R': S, R2]","[U' M2 U, R]","[U R' U': S', R2]","[U M2 U', R]","[U: R U' R', E']","","[S', R U R' U']","[U': R U R', E]","[U' M' U, R]","[R u: R' U' R, E]","[R', E' l' E]","","[S': U, R S' R']","[U R' U' R: E, R2]","[U M' U', R]","[U R' U' R': E', R2]","[S': U', R S' R']","[U2 R': S, R2]","[U' M U, R]",
17"FU","[M', R U R' U']","[M U: M', U2]","","[r U' R': S, R2]","[R' F': E, R U' R']","[R F R': S', R2]","[r U' r' U: M, U2]","[R' U: R S' R', U]","[R U: R' S' R, U]","[S', R' F R]","[S U' R': E, R2]","[S, R' F R]","[S U' R: E', R2]","[R': R' S' R, F]","[S': R S' R', U]","","R E R' U' R' E2 R U R' E R","[D: S', R' F R]","[R F: R2, E']","[U' M' U: M, U2]","[S': R' S' R, U]","[D': S', R' F R]", 17"FU","[M', R U R' U']","[M U: M', U2]","","[r U' R': S, R2]","[R' F': E, R U' R']","[R F R': S', R2]","[r U' r' U: M, U2]","[R' U: R S' R', U]","[R U: R' S' R, U]","[S', R' F R]","[S U' R': E, R2]","[S, R' F R]","[S U' R: E', R2]","[R': R' S' R, F]","[S': R S' R', U]","","R E R' U' R' E2 R U R' E R","[D: S', R' F R]","[R F: R2, E']","[U' M' U: M, U2]","[S': R' S' R, U]","[D': S', R' F R]",
18"FL","[U, R E R2 E' R]","[l', S R2 S']","[U', R E R2 E' R]","[R': E, R2]","[D R': E, R2]","[l': S R2 S', l']","[D' R': E, R2]","[S' R': R' E R, U2]","[R' F2 R, E']","[S', R' F2 R]","","[S, R' F2 R]","[R' F2 R, E]","[R': R' S' R, F2]","[U R' U' R': E, R2]","R' E' R U' R' E2 R U R E' R'","","[D: S', R' F2 R]","[L: S', L2]","[R' B': E, R2]","[U R U' R': E, R2]","[R' B: R2, E]", 18"FL","[U, R E2 R']","[U2, R E2 R']","[U', R E2 R']","[R': E, R2]","[D R': E, R2]","[l': S R2 S', l']","[D' R': E, R2]","[R' U' B': E, R2]","[R' F2 R, E']","[S', R' F2 R]","","[S, R' F2 R]","[R' F2 R, E]","[R': R' S' R, F2]","[U R' U' R': E, R2]","R' E' R U' R' E2 R U R E' R'","","[D: S', R' F2 R]","[L: S', L2]","[R' B': E, R2]","[U R U' R': E, R2]","[R' B: E, R2]",
19"FD","U' M' U' M U' M' U' M","[M U': M, U2]","[M': R U' R' U]","[S' U': M', U2]","","[R F' R': S', R2]","[r U r' U: M, U2]","[S' R' U': M', U2]","[S' R U': M', U2]","[S', R' F' R]","[D: S, R F2 R']","[S, R' F' R]","[D: S, R' B2 R]","[D: R' F' R, S']","[R, U M' U']","[D: R' F R, S']","[D: R' F2 R, S']","","[R F': R2, E']","[D: R B' R', S']","[R', U M' U']","[M' U M, d2]", 19"FD","U' M' U' M U' M' U' M","[M U': M, U2]","[M', R U' R' U]","[S' U': M', U2]","","[R F' R': S', R2]","[r U r' U: M, U2]","[S' R' U': M', U2]","[S' R U': M', U2]","[S', R' F' R]","[D: S, R F2 R']","[S, R' F' R]","[D: S, R' B2 R]","[D: R' F' R, S']","[R, U M' U']","[D: R' F R, S']","[D: R' F2 R, S']","","[R F': R2, E']","[D: R B' R', S']","[R', U M' U']","[M' U M, d2]",
20"BL","[U, R' E' R2 E R']","[l, S R2 S']","[U', R' E' R2 E R']","[R: E', R2]","[D R: E', R2]","[l: S R2 S', l]","[D' R: E', R2]","[R B2 R', E]","[S' R: R E' R', U2]","[S', R B2 R']","[R B2 R', E']","[S, R B2 R']","","[R: R S' R', B2]","[U R' U' R: E', R2]","[R F: E', R2]","[L': S', L2]","[R F': E', R2]","","R E R' U R E2 R' U' R' E R","[U R U' R: E', R2]","[D': S', R B2 R']", 20"BL","[U, R' E2 R]","[U2, R' E2 R]","[U', R' E2 R]","[R: E', R2]","[D R: E', R2]","[l: S R2 S', l]","[D' R: E', R2]","[R B2 R', E]","[R U F: E', R2]","[S', R B2 R']","[R B2 R', E']","[S, R B2 R']","","[R: R S' R', B2]","[U R' U' R: E', R2]","[R F: E', R2]","[L': S', L2]","[R F': E', R2]","","R E R' U R E2 R' U' R' E R","[U R U' R: E', R2]","[D': S', R B2 R']",
21"BU","","[M' U': M, U2]","[M, R' U' R U]","[r' U R: S, R2]","[r' U r U': M', U2]","[R' B' R: S', R2]","[U D': R' F R, S']","[R' U': R S' R', U']","[R U': R' S' R, U']","[S', R B' R']","[S U R': E, R2]","[R U' R', S']","[S U R: E', R2]","[R: R S' R', B']","[S': R S' R', U']","[U' M' U': M, U2]","[R' B': R2, E]","[D: S', R B' R']","R' E' R U R E2 R' U' R E' R'","","[S': R' S' R, U']","[D': S', R B' R']", 21"BU","","[M' U': M, U2]","[M, R' U' R U]","[r' U R: S, R2]","[r' U r U': M', U2]","[R' B' R: S', R2]","[U D': R' F R, S']","[R' U': R S' R', U']","[R U': R' S' R, U']","[S', R B' R']","[S U R': E, R2]","[R U' R', S']","[S U R: E', R2]","[R: R S' R', B']","[S': R S' R', U']","[U' M' U': M, U2]","[R' B': R2, E]","[D: S', R B' R']","R' E' R U R E2 R' U' R E' R'","","[S': R' S' R, U']","[D': S', R B' R']",
22"BR","[E: U, R E' R']","[S' l2 S, R']","[u': R E' R', U]","[R' U2 R: S, R2]","[U' M2 U, R']","[U R U': S', R2]","[U M2 U', R']","","[U': R' U R, E]","[S', R' U R U']","[R' u': R U R', E']","[U' M' U, R']","[U: R' U' R, E']","[R, E l E']","[U2 R: S, R2]","[S': U, R' S' R]","[U R U' R: E, R2]","[U M' U', R']","[U R U' R': E', R2]","[S': U', R' S' R]","","[U' M U, R']", 22"BR","[U': R S2 R', U']","[R S2 R', U2]","[U: R S2 R', U]","[R' U2 R: S, R2]","[U' M2 U, R']","[U R U': S', R2]","[U M2 U', R']","","[U': R' U R, E]","[S', R' U R U']","[R' u': R U R', E']","[U' M' U, R']","[U: R' U' R, E']","[R, E l E']","[U2 R: S, R2]","[S': U, R' S' R]","[U R U' R: E, R2]","[U M' U', R']","[U R U' R': E', R2]","[S': U', R' S' R]","","[U' M U, R']",
23"BD","[M, R' U' R U]","[M' U: M', U2]","[D': U', R E R']","[S' U: M, U2]","[r' U' r U': M', U2]","[R' B R: S', R2]","","[S' R' U: M, U2]","[S' R U: M, U2]","[S', R B R']","[D': S, R F2 R']","[S, R B R']","[D': S, R' B2 R]","[D': R B R', S']","[R, U' M U]","[D': R' F R, S']","[R' B: R2, E]","[M U' M', d2]","[D': R B2 R', S']","[D': R B' R', S']","[R', U' M U]","", 23"BD","[M, R' U R U']","[M' U: M', U2]","[D': U', R E R']","[S' U: M, U2]","[r' U' r U': M', U2]","[R' B R: S', R2]","","[S' R' U: M, U2]","[S' R U: M, U2]","[S', R B R']","[D': S, R F2 R']","[S, R B R']","[D': S, R' B2 R]","[D': R B R', S']","[R, U' M U]","[D': R' F R, S']","[R' B: R2, E]","[M U' M', d2]","[D': R B2 R', S']","[D': R B' R', S']","[R', U' M U]","",
diff --git a/src/speedcubing/coordinates/coordinates.md b/src/speedcubing/coordinates/coordinates.md
index afae46f..e1e6785 100644
--- a/src/speedcubing/coordinates/coordinates.md
+++ b/src/speedcubing/coordinates/coordinates.md
@@ -276,5 +276,5 @@ See [fst.c](https://git.tronto.net/nissy-nx/file/src/fst.c.html#l113)
276for an implementation. 276for an implementation.
277 277
278For these reasons, I am not using the coordinate approach described here 278For these reasons, I am not using the coordinate approach described here
279for my new work-in-progress solver (temporarily named 279for my new work-in-progress solver (which can be found
280[h48](https://git.tronto.net/h48)). 280[here](https://git.tronto.net/nissy-core)).
diff --git a/src/speedcubing/figure-it-out/comm1.svg b/src/speedcubing/figure-it-out/comm1.svg
deleted file mode 100644
index 6c41aad..0000000
--- a/src/speedcubing/figure-it-out/comm1.svg
+++ /dev/null
@@ -1,39 +0,0 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8">
4 <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/>
5 <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1">
6 <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/>
7 <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/>
8 <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/>
9 </g>
10 <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round">
11 <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/>
12 <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/>
13 <polygon fill="#FFA100" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/>
14 <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/>
15 <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/>
16 <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/>
17 <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/>
18 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/>
19 <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/>
20 <polygon fill="#0000F2" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/>
21 <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/>
22 <polygon fill="#FEFE00" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/>
23 <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/>
24 <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/>
25 <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/>
26 <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/>
27 <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/>
28 <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/>
29 <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/>
30 <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/>
31 <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/>
32 <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/>
33 <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/>
34 <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/>
35 <polygon fill="#EE0000" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/>
36 <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/>
37 <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/>
38 </g>
39</svg> \ No newline at end of file
diff --git a/src/speedcubing/figure-it-out/comm2.svg b/src/speedcubing/figure-it-out/comm2.svg
deleted file mode 100644
index e69065b..0000000
--- a/src/speedcubing/figure-it-out/comm2.svg
+++ /dev/null
@@ -1,39 +0,0 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8">
4 <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/>
5 <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1">
6 <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/>
7 <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/>
8 <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/>
9 </g>
10 <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round">
11 <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/>
12 <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/>
13 <polygon fill="#FFA100" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/>
14 <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/>
15 <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/>
16 <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/>
17 <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/>
18 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/>
19 <polygon fill="#0000F2" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/>
20 <polygon fill="#EE0000" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/>
21 <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/>
22 <polygon fill="#FEFE00" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/>
23 <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/>
24 <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/>
25 <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/>
26 <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/>
27 <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/>
28 <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/>
29 <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/>
30 <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/>
31 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/>
32 <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/>
33 <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/>
34 <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/>
35 <polygon fill="#FFFFFF" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/>
36 <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/>
37 <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/>
38 </g>
39</svg> \ No newline at end of file
diff --git a/src/speedcubing/figure-it-out/edgecomm.svg b/src/speedcubing/figure-it-out/edgecomm.svg
deleted file mode 100644
index 6cffd65..0000000
--- a/src/speedcubing/figure-it-out/edgecomm.svg
+++ /dev/null
@@ -1,39 +0,0 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8">
4 <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/>
5 <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1">
6 <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/>
7 <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/>
8 <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/>
9 </g>
10 <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round">
11 <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/>
12 <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/>
13 <polygon fill="#FFFFFF" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/>
14 <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/>
15 <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/>
16 <polygon fill="#EE0000" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/>
17 <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/>
18 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/>
19 <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/>
20 <polygon fill="#EE0000" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/>
21 <polygon fill="#00D800" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/>
22 <polygon fill="#EE0000" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/>
23 <polygon fill="#00D800" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/>
24 <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/>
25 <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/>
26 <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/>
27 <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/>
28 <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/>
29 <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/>
30 <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/>
31 <polygon fill="#00D800" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/>
32 <polygon fill="#FFFFFF" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/>
33 <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/>
34 <polygon fill="#FFA100" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/>
35 <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/>
36 <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/>
37 <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/>
38 </g>
39</svg> \ No newline at end of file
diff --git a/src/speedcubing/figure-it-out/face.png b/src/speedcubing/figure-it-out/face.png
new file mode 100644
index 0000000..e342999
--- /dev/null
+++ b/src/speedcubing/figure-it-out/face.png
Binary files differ
diff --git a/src/speedcubing/figure-it-out/figure-it-out.md b/src/speedcubing/figure-it-out/figure-it-out.md
index 4690c76..1c38657 100644
--- a/src/speedcubing/figure-it-out/figure-it-out.md
+++ b/src/speedcubing/figure-it-out/figure-it-out.md
@@ -1,3 +1,5 @@
1<script src="https://cdn.cubing.net/v0/js/cubing/twisty" type="module"></script>
2
1# Rubik's cube: how to figure it out 3# Rubik's cube: how to figure it out
2 4
3So you decided to try and solve a 5So you decided to try and solve a
@@ -89,7 +91,7 @@ say that two or more adjacent pieces form a block when adjacent stickers
89of different pieces have the same color. For example, the simplest kind 91of different pieces have the same color. For example, the simplest kind
90of block is a **pair**, that you can see in the picture below: 92of block is a **pair**, that you can see in the picture below:
91 93
92![A corner-edge pair](pair.svg) 94![A corner-edge pair](pair.png)
93 95
94To be precise, the one above is a coner-edge pair. There are also center-edge 96To be precise, the one above is a coner-edge pair. There are also center-edge
95pairs, consisting of a center and an edge, but they are rarely referred to 97pairs, consisting of a center and an edge, but they are rarely referred to
@@ -99,11 +101,11 @@ A more complex example of a block is a **layer**, which is the result of
99the first two steps of the classic "layer by layer" method. This one is 101the first two steps of the classic "layer by layer" method. This one is
100a correctly solved layer: 102a correctly solved layer:
101 103
102![A layer](layer.svg) 104![A layer](layer.png)
103 105
104On the other hand, this is **not a layer**: 106On the other hand, this is **not a layer**:
105 107
106![A non-solved layer, but with a solve "face"](face.svg) 108![A non-solved layer, but with a solve "face"](face.png)
107 109
108It is worth pausing here to reflect a bit. The last two pictures both 110It is worth pausing here to reflect a bit. The last two pictures both
109clearly show a solved white face, don't they? For a most people, they 111clearly show a solved white face, don't they? For a most people, they
@@ -124,6 +126,14 @@ If you want some more hints, Ryan Heise's website contains some nice
124examples about building blocks in his 126examples about building blocks in his
125[fundamental techniques page](https://www.ryanheise.com/cube/fundamental_techniques.html). 127[fundamental techniques page](https://www.ryanheise.com/cube/fundamental_techniques.html).
126 128
129*Note: I have updated this page to use [twizzle](https://alpha.twizzle.net)
130applets instead of simple pictures. These applets can be played to show the
131solution. To experiment around with this solution, click on
132the TW at the bottom right to open them in
133[twizzle](https://alpha.twizzle.net/). In this way you can also
134check which moves correspond to the written notation, if you
135are not familiar with it.*
136
127## Commutators 137## Commutators
128 138
129*In the rest of this page I am going to use the standard 139*In the rest of this page I am going to use the standard
@@ -161,7 +171,13 @@ If you have not done it already, you should have a look at my page on
161 171
162Suppose that you manage, via blockbuilding, to reach the following state: 172Suppose that you manage, via blockbuilding, to reach the following state:
163 173
164![A commutator](comm1.svg) 174<center>
175<twisty-player experimental-setup-alg="[U, R' D R]"
176alg="R' D R // Insertion
177 U // Interchange
178 R' D' R // Inverse insertion
179 U' // Inverse interchange"></twisty-player>
180</center>
165 181
166First of all, this would be an amazing achievement! The whole cube is 182First of all, this would be an amazing achievement! The whole cube is
167solved except for three corners. The bottom-left corner (only one red 183solved except for three corners. The bottom-left corner (only one red
@@ -220,21 +236,28 @@ the insertion sequence; then we have D', the inverse of the second move;
220and finally R, the inverse of the first move of the insertion sequence. 236and finally R, the inverse of the first move of the insertion sequence.
221* **U'**: the inverse of the interchange move. 237* **U'**: the inverse of the interchange move.
222 238
223To help understanding all of this, you can visualize this commutator
224[alg.cubing.net](https://alg.cubing.net/?setup=%5BU,_R-DR%5D&alg=R-_D_R_%2F%2FInsertion%0AU_%2F%2FInterchange%0AR-_D-_R_%2F%2FInverse_insertion%0AU-_%2F%2FInverse_interchange).
225
226**Note:** looking at the position of the pieces is not enough to 239**Note:** looking at the position of the pieces is not enough to
227determine a correct commutator to permute them. Their **orientation** 240determine a correct commutator to permute them. Their **orientation**
228is also important. For example, consider the following case: 241is also important. For example, consider the following case:
229 242
230![Another commutator](comm2.svg) 243<center>
244<twisty-player experimental-setup-alg="[R', U L' U']"
245alg="R' D R // Insertion
246 U // Interchange
247 R' D' R // Inverse insertion
248 U' // Inverse interchange"></twisty-player>
249</twisty-player>
250</center>
231 251
232The three corners are permuted in exactly the same way, so everything 252The three corners are permuted in exactly the same way, so everything
233we said above could be repeated word by word, move by move. However, 253we said above could be repeated word by word, move by move. However,
234if you apply the commutator we constructed to this case, you'll get 254if you apply the commutator we constructed to this case, you'll get
235something like this: 255something like this (you can also see this by playing the applet above):
236 256
237![Two twisted corners](twist.svg) 257<center>
258<twisty-player experimental-setup-alg="[U, R' D' R D R' D' R]">
259</twisty-player>
260</center>
238 261
239What's wrong here? Well, obviously the cube is not solved. All the pieces 262What's wrong here? Well, obviously the cube is not solved. All the pieces
240are in their correct position, but two corners are twisted in place! 263are in their correct position, but two corners are twisted in place!
@@ -246,7 +269,13 @@ but sometimes it is important to keep track of both.
246 269
247Let's highlight the difference between the two 3-cycles. In the first one: 270Let's highlight the difference between the two 3-cycles. In the first one:
248 271
249![A commutator](comm1.svg) 272<center>
273<twisty-player experimental-setup-alg="[U, R' D R]"
274alg="R' D R // Insertion
275 U // Interchange
276 R' D' R // Inverse insertion
277 U' // Inverse interchange"></twisty-player>
278</center>
250 279
2511. The red-green-white corner must go to the place of the white-red-blue one, 2801. The red-green-white corner must go to the place of the white-red-blue one,
252*with the white sticker of the first going to the place of the white sticker 281*with the white sticker of the first going to the place of the white sticker
@@ -260,7 +289,14 @@ white sticker of the latter*.
260 289
261While in the second case: 290While in the second case:
262 291
263![Another commutator](comm2.svg) 292<center>
293<twisty-player experimental-setup-alg="[R', U L' U']"
294alg="U L' U' // Insertion
295 R' // Interchange
296 U L U' // Inverse insertion
297 R // Inverse interchange"></twisty-player>
298</twisty-player>
299</center>
264 300
2651. The red-green-white corner must go to the place of the white-red-blue one, 3011. The red-green-white corner must go to the place of the white-red-blue one,
266*with the white sticker of the first going to the place of the* **blue** *sticker 302*with the white sticker of the first going to the place of the* **blue** *sticker
@@ -281,16 +317,19 @@ white sticker of the red-green-white corner to the position of the
281red sticker of the white-red-blue one, while it should move it 317red sticker of the white-red-blue one, while it should move it
282to the position of the blue sticker! 318to the position of the blue sticker!
283 319
284I won't repeat the whole construction for the second commutator,
285but you can visualize a solution
286[here](https://alg.cubing.net/?setup=%5BR-,_UL-U-%5D&alg=U_L-_U-_%2F%2FInsertion%0AR-_%2F%2FInterchange%0AU_L_U-_%2F%2FInverse_insertion%0AR_%2F%2FInverse_interchange).
287
288### Edge commutators 320### Edge commutators
289 321
290So far I have only talked about *corner* commutators, but what if you 322So far I have only talked about *corner* commutators, but what if you
291are also left with some unsolved edges? For example, consider this case: 323are also left with some unsolved edges? For example, consider this case:
292 324
293![A edge 3-cycle](edgecomm.svg) 325<center>
326<twisty-player experimental-setup-alg="[L' U2 L, E']"
327alg="E' // Interchange
328 L' U2 L // Insertion
329 E // Inverse interchange
330 L' U2 L // Inverse insertion"></twisty-player>
331</twisty-player>
332</center>
294 333
295The picture shows a 3-cycle of edges. You might think that the same 334The picture shows a 3-cycle of edges. You might think that the same
296reasoning can be applied and that you can use commutators to solve 335reasoning can be applied and that you can use commutators to solve
@@ -301,7 +340,7 @@ Let's see how to solve the case above. As interchange move, you can use
301the **inner-layer move** E' (check out the [notation page](../notation) 340the **inner-layer move** E' (check out the [notation page](../notation)
302if you are unfamiliar with these). The insertion sequence to be used 341if you are unfamiliar with these). The insertion sequence to be used
303with it is L' U2 L. Putting everything together, you get 342with it is L' U2 L. Putting everything together, you get
304[E' L' U2 L E L' U2 L](https://alg.cubing.net/?setup=%5BL-U2L,E-%5D&alg=E-_%2F%2FInterchange%0AL-_U2_L_%2F%2FInsertion%0AE_%2F%2FInverse_interchange%0AL-_U2_L_%2F%2FInverse_insertion). 343E' L' U2 L E L' U2 L.
305 344
306### Commutators with set-up moves 345### Commutators with set-up moves
307 346
@@ -323,7 +362,15 @@ you need to use **set-up moves**, also known as
323 362
324Consider the following case: 363Consider the following case:
325 364
326![A 3-cycle of corners requiring a set-up move](setup.svg) 365<center>
366<twisty-player experimental-setup-alg="[L: [R D2 R', U']]"
367alg="L // Set-up
368 U' // Interchange
369 R D2 R' // Insertion
370 U // Inverse interchange
371 R D2 R' // Inverse insertion
372 L' // Inverse set-up"></twisty-player>
373</center>
327 374
328No matter how much you try, you are not going to find valid interchange 375No matter how much you try, you are not going to find valid interchange
329and insertion moves as above. The fundamental problem is that you would 376and insertion moves as above. The fundamental problem is that you would
@@ -353,9 +400,6 @@ putting it all together you get:
353**Note:** in this case the insertion coincides with its inverse. This 400**Note:** in this case the insertion coincides with its inverse. This
354can happen and there is nothing particular about it. 401can happen and there is nothing particular about it.
355 402
356As usual, you can visualize the final result on
357[alg.cubing.net](https://alg.cubing.net/?setup=L2B2R-F-RB2R-FRL2&alg=L_%2F%2FSet%26%2345%3Bup%0AU-_%2F%2FInterchange%0AR_D2_R-_%2F%2FInsertion%0AU_%2F%2FInverse_interchange%0AR_D2_R-_%2F%2FInverse_insertion%0AL-_%2F%2FInverse_set%26%2345%3Bup)
358
359## Conclusion 403## Conclusion
360 404
361With what you have learned so far, you can now try and solve the Rubik's 405With what you have learned so far, you can now try and solve the Rubik's
diff --git a/src/speedcubing/figure-it-out/layer.png b/src/speedcubing/figure-it-out/layer.png
new file mode 100644
index 0000000..3957cf3
--- /dev/null
+++ b/src/speedcubing/figure-it-out/layer.png
Binary files differ
diff --git a/src/speedcubing/figure-it-out/pair.png b/src/speedcubing/figure-it-out/pair.png
new file mode 100644
index 0000000..516cd1f
--- /dev/null
+++ b/src/speedcubing/figure-it-out/pair.png
Binary files differ
diff --git a/src/speedcubing/figure-it-out/setup.svg b/src/speedcubing/figure-it-out/setup.svg
deleted file mode 100644
index fa2360a..0000000
--- a/src/speedcubing/figure-it-out/setup.svg
+++ /dev/null
@@ -1,39 +0,0 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8">
4 <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/>
5 <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1">
6 <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/>
7 <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/>
8 <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/>
9 </g>
10 <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round">
11 <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/>
12 <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/>
13 <polygon fill="#FFFFFF" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/>
14 <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/>
15 <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/>
16 <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/>
17 <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/>
18 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/>
19 <polygon fill="#FFFFFF" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/>
20 <polygon fill="#0000F2" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/>
21 <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/>
22 <polygon fill="#FFA100" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/>
23 <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/>
24 <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/>
25 <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/>
26 <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/>
27 <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/>
28 <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/>
29 <polygon fill="#EE0000" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/>
30 <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/>
31 <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/>
32 <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/>
33 <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/>
34 <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/>
35 <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/>
36 <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/>
37 <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/>
38 </g>
39</svg> \ No newline at end of file
diff --git a/src/speedcubing/figure-it-out/twist.svg b/src/speedcubing/figure-it-out/twist.svg
deleted file mode 100644
index 79e0a02..0000000
--- a/src/speedcubing/figure-it-out/twist.svg
+++ /dev/null
@@ -1,39 +0,0 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="-0.9 -0.9 1.8 1.8">
4 <rect fill="#FFFFFF" x="-0.9" y="-0.9" width="1.8" height="1.8"/>
5 <g style="stroke-width:0.1;stroke-linejoin:round;opacity:1">
6 <polygon fill="#000000" stroke="#000000" points="-4.9165444344952E-17,-0.71734170954349 0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 -0.70405037145575,-0.41272706360467"/>
7 <polygon fill="#000000" stroke="#000000" points="6.3108540577985E-17,-0.021725090572532 0.70405037145575,-0.41272706360467 0.62948028357061,0.36901272915735 5.5589468959362E-17,0.81107056444244"/>
8 <polygon fill="#000000" stroke="#000000" points="-0.70405037145575,-0.41272706360467 6.3108540577985E-17,-0.021725090572532 5.5589468959362E-17,0.81107056444244 -0.62948028357061,0.36901272915735"/>
9 </g>
10 <g style="opacity:1;stroke-opacity:0.5;stroke-width:0;stroke-linejoin:round">
11 <polygon fill="#FFFFFF" stroke="#000000" points="-4.9439549272153E-17,-0.74757064564692 0.19598754651203,-0.66277461469571 -1.6979580126642E-17,-0.57123720961754 -0.19598754651203,-0.66277461469571"/>
12 <polygon fill="#FFFFFF" stroke="#000000" points="0.23200530924361,-0.64654708450724 0.44357407294753,-0.55500967942906 0.24823152717746,-0.45589370157761 0.03601776273158,-0.55500967942906"/>
13 <polygon fill="#0000F2" stroke="#000000" points="0.48258385553552,-0.53743199405155 0.71166883865722,-0.4383160162001 0.51778341539206,-0.3306396536664 0.28724130976545,-0.43831601620009"/>
14 <polygon fill="#FFFFFF" stroke="#000000" points="-0.23200530924361,-0.64654708450724 -0.03601776273158,-0.55500967942906 -0.24823152717746,-0.45589370157761 -0.44357407294753,-0.55500967942906"/>
15 <polygon fill="#FFFFFF" stroke="#000000" points="-1.1906711768916E-17,-0.53743199405155 0.21221376444588,-0.43831601620009 1.3125827139793E-17,-0.3306396536664 -0.21221376444588,-0.43831601620009"/>
16 <polygon fill="#FFFFFF" stroke="#000000" points="0.25135344771691,-0.4192120352454 0.48189555334352,-0.31153567271171 0.27050899589682,-0.1941398664099 0.03913968327103,-0.31153567271171"/>
17 <polygon fill="#FFFFFF" stroke="#000000" points="-0.48258385553552,-0.53743199405155 -0.28724130976545,-0.43831601620009 -0.51778341539206,-0.3306396536664 -0.71166883865722,-0.43831601620009"/>
18 <polygon fill="#FFFFFF" stroke="#000000" points="-0.25135344771691,-0.4192120352454 -0.03913968327103,-0.31153567271171 -0.27050899589682,-0.1941398664099 -0.48189555334352,-0.31153567271171"/>
19 <polygon fill="#00D800" stroke="#000000" points="1.9219742927126E-17,-0.29069716027551 0.23136931262579,-0.1733013539737 6.3251582941518E-17,-0.044807908897155 -0.23136931262579,-0.1733013539737"/>
20 <polygon fill="#FFFFFF" stroke="#000000" points="0.019572311898468,-0.01096266104553 0.25094162452426,-0.13945610612208 0.24139184674777,0.12672756322786 0.019572311898468,0.26171620101574"/>
21 <polygon fill="#EE0000" stroke="#000000" points="0.28930534489087,-0.16171652284677 0.50069190233757,-0.27911232914857 0.48317508531013,-0.019324131300046 0.27975556711438,0.10446714650317"/>
22 <polygon fill="#FFFFFF" stroke="#000000" points="0.53586287162127,-0.29952357725519 0.72974829488643,-0.40719993978888 0.70556381569579,-0.15366705164652 0.51834605459384,-0.039735379406663"/>
23 <polygon fill="#EE0000" stroke="#000000" points="0.018796486168384,0.30740091161998 0.24061602101769,0.1724122738321 0.23182332941007,0.41749346528561 0.018796486168384,0.55752511994163"/>
24 <polygon fill="#EE0000" stroke="#000000" points="0.27748498154717,0.14913168479738 0.48090449974292,0.025340406994164 0.46472097442439,0.26535483831193 0.26869228993956,0.39421287625089"/>
25 <polygon fill="#EE0000" stroke="#000000" points="0.51482272047627,0.0039202514920256 0.70204048157822,-0.11001142074783 0.6796261786066,0.12496419825545 0.49863919515774,0.24393468280979"/>
26 <polygon fill="#EE0000" stroke="#000000" points="0.018079821190449,0.59957070958542 0.23110666443214,0.4595390549294 0.22298446134887,0.68593152923737 0.018079821190449,0.82982754001392"/>
27 <polygon fill="#EE0000" stroke="#000000" points="0.26659321028119,0.43546258559693 0.46262189476603,0.30660454765797 0.44762501691219,0.52902006212629 0.25847100719792,0.6618550599049"/>
28 <polygon fill="#EE0000" stroke="#000000" points="0.49537315323635,0.28438159074811 0.67636013668521,0.16541110619376 0.65552852022793,0.38379496484358 0.4803762753825,0.50679710521643"/>
29 <polygon fill="#00D800" stroke="#000000" points="-0.73033661801848,-0.40664800069716 -0.53645119475332,-0.29897163816347 -0.51893437772588,-0.039183440314942 -0.70615213882784,-0.1531151125548"/>
30 <polygon fill="#00D800" stroke="#000000" points="-0.50139487638912,-0.27854802283962 -0.29000831894242,-0.16115221653782 -0.28045854116593,0.10503145281212 -0.48387805936169,-0.018759824991097"/>
31 <polygon fill="#EE0000" stroke="#000000" points="-0.25178425197513,-0.13888300088285 -0.020414939349334,-0.010389555806295 -0.020414939349334,0.26228930625497 -0.24223447419864,0.12730066846709"/>
32 <polygon fill="#00D800" stroke="#000000" points="-0.70259025607703,-0.10956681966126 -0.51537249497507,0.0043648525785978 -0.49918896965654,0.24437928389636 -0.68017595310541,0.12540879934202"/>
33 <polygon fill="#00D800" stroke="#000000" points="-0.48155663212918,0.025787474064964 -0.27813711393343,0.14957875186818 -0.26934442232581,0.39465994332169 -0.46537310681065,0.26580190538273"/>
34 <polygon fill="#00D800" stroke="#000000" points="-0.24139184674777,0.17285724590871 -0.019572311898468,0.30784588369658 -0.019572311898468,0.55797009201823 -0.23259915514016,0.41793843736221"/>
35 <polygon fill="#00D800" stroke="#000000" points="-0.67687496656198,0.16576684053069 -0.49588798311312,0.28473732508503 -0.48089110525928,0.50715283955335 -0.6560433501047,0.38415069918051"/>
36 <polygon fill="#00D800" stroke="#000000" points="-0.46322848371637,0.30695545587912 -0.26719979923154,0.43581349381808 -0.25907759614827,0.66220596812605 -0.44823160586253,0.52937097034744"/>
37 <polygon fill="#00D800" stroke="#000000" points="-0.23182332941007,0.45988002752827 -0.018796486168384,0.59991168218429 -0.018796486168384,0.83016851261279 -0.2237011263268,0.68627250183624"/>
38 </g>
39</svg> \ No newline at end of file
diff --git a/top.html b/top.html
index 62ec655..67f55e7 100644
--- a/top.html
+++ b/top.html
@@ -2,7 +2,7 @@
2<html lang="en"> 2<html lang="en">
3<head> 3<head>
4 <title> TITLE | Sebastiano Tronto </title> 4 <title> TITLE | Sebastiano Tronto </title>
5 <meta name="viewport" content="width=device-width" /> 5 <meta name="viewport" content="width=device-width">
6 <link rel="stylesheet" type="text/css" href="/style-5.css"> 6 <link rel="stylesheet" type="text/css" href="/style-5.css">
7 <link rel="icon" href="/favicon.png"> 7 <link rel="icon" href="/favicon.png">
8 <meta charset="utf-8"> 8 <meta charset="utf-8">

Generated with cgit - Back to sebastiano.tronto.net