Daniel Ting2023-08-28T01:56:09+00:00https://danielzting.github.ioMy macOS Colemak setup2022-06-19T00:00:00+00:00https://danielzting.github.io/2022/06/19/my-macos-colemak-setup<p><a href="https://colemak.com/">Colemak</a> is an alternative to the default QWERTY keyboard layout. It claims to be superior, but what makes a layout better than another? Here are what I believe to be the three main metrics.</p>
<ul>
<li>The most obvious metric would be easy reach of the most frequent letters. QWERTY only has a single vowel on the home row, while Colemak has four. QWERTY does terribly here because it was designed to separate the most common English digraphs, as pressing keys next to each other too fast would jam typewriters. (Fun fact: did you know “typewriter” can be typed all on QWERTY’s top row? It is said that this was to help early typewriter sales demos.)</li>
<li>Minimizes successive letters being hit by the same finger. For example, try typing “mummy” on QWERTY.</li>
<li>Balances load between left and right hands. On QWERTY you can type “sweaterdresses” just with one hand.</li>
</ul>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/8/84/KB_US-Colemak.svg" alt="Colemak layout" /></p>
<p align="center"><em>A map of Colemak from <a href="https://commons.wikimedia.org/wiki/File:KB_US-Colemak.svg">Wikipedia</a></em></p>
<p>Colemak is supposed to be an improvement on <a href="https://en.wikipedia.org/wiki/Dvorak_keyboard_layout">Dvorak</a>, the oldest and most popular alternative keyboard. The main difference is that Dvorak was designed with an emphasis on hand alternation while Colemak optimizes for “roll combos” or sequences of keys next to each other. To see what I mean, try typing “fhlsdf” on QWERTY, which is “thirst” in Colemak. The s-d-f or r-s-t sequence can be very quickly and comfortably executed in a single motion, which is what is meant by rolls. Another plus is that the positions of Z/X/C/V for the common undo, cut, copy, and paste shortcuts are preserved so you can continue using those with your left hand with the mouse in your right hand. That being said, both are such an improvement over QWERTY that either will be fine.</p>
<p>So, should you switch to Colemak? Despite all the benefits I’ve been raving about, I would say only switch if you’re currently slower than 60 WPM or have never learned to touch type properly with eight fingers on the home row. If you haven’t mastered touch typing, then might as well start with a better layout. But the chances of successfully grinding through the month-or-longer drop in productivity as you learn a new layout drop the more you are over 60 WPM. Besides, just improving your posture would likely do much more for ergonomics than your keyboard layout.</p>
<p>If you do switch, here’s a tip I consider indispensible: keep your phone on QWERTY. The properties that make QWERTY bad for touch typing make it great for thumb typing, and it keeps your QWERTY skills in shape for when you need to use other people’s computers. Even Colemak’s creator agrees that it is not optimal for phones.</p>
<p>If you don’t switch, here’s another recommendation: at least map your caps lock key to something else. I choose backspace, which frees up my actual backspace key to be remapped to delete forward. This makes for a greatly improved text editing experience with barely any disruption to productivity. Caps lock to control for PC and Emacs users and escape for Vim users are common alternatives. (macOS uses the command key instead of control, which is hit by the thumb and thus in a better position than the control and caps lock keys anyways, and I use ^C to escape in Vim.)</p>
<p>I use <a href="https://karabiner-elements.pqrs.org">Karabiner Elements</a> to customize my keyboard beyond the predefined layouts included with the operating system. On Windows, <a href="https://www.autohotkey.com">AutoHotKey</a> seems to be the dominant choice. If you’re on Linux, you can probably figure it out yourself. Karabiner includes a simple modifications GUI where you can change caps lock to backspace and so on. This is what I used to do, but if somebody else wanted to use my computer, I would have to change the input source back to QWERTY and quit Karabiner. I recently found out that Karabiner supports complex modifications that trigger on certain conditions. This lets me automatically disable all my customizations whenever I switch the input source to QWERTY, so I don’t have to manually quit Karabiner every time I pair program.</p>
<p>You can import my rule through the <a href="https://ke-complex-modifications.pqrs.org/#colemak-caps-lock-backspace-delete-swap">complex modifications user repository</a> or by copying the following JSON into a file at <code class="language-plaintext highlighter-rouge">~/.config/karabiner/assets/complex_modifications</code>. Note that this only enables the mapping if the current input source is set to Colemak. Maybe you type in multiple languages and only want to disable everything for one layout. In that case, replace <code class="language-plaintext highlighter-rouge">input_source_if</code> to <code class="language-plaintext highlighter-rouge">input_source_unless</code> and edit the <code class="language-plaintext highlighter-rouge">input_source_id</code> value accordingly. See the <a href="https://karabiner-elements.pqrs.org/docs/json/">documentation</a> for more details.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Caps lock to backspace and backspace to delete forward, but only on Colemak"</span><span class="p">,</span><span class="w">
</span><span class="nl">"rules"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Caps lock to backspace, but only on Colemak"</span><span class="p">,</span><span class="w">
</span><span class="nl">"manipulators"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"basic"</span><span class="p">,</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"key_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"caps_lock"</span><span class="p">,</span><span class="w">
</span><span class="nl">"modifiers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"optional"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"any"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"key_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"delete_or_backspace"</span><span class="p">,</span><span class="w">
</span><span class="nl">"lazy"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"repeat"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"conditions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"input_source_if"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Colemak"</span><span class="p">,</span><span class="w">
</span><span class="nl">"input_sources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"input_source_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^com</span><span class="se">\\</span><span class="s2">.apple</span><span class="se">\\</span><span class="s2">.keylayout</span><span class="se">\\</span><span class="s2">.Colemak$"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Backspace to delete forward, but only on Colemak"</span><span class="p">,</span><span class="w">
</span><span class="nl">"manipulators"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"basic"</span><span class="p">,</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"key_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"delete_or_backspace"</span><span class="p">,</span><span class="w">
</span><span class="nl">"modifiers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"optional"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"any"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"key_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"delete_forward"</span><span class="p">,</span><span class="w">
</span><span class="nl">"lazy"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"repeat"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"conditions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"input_source_if"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Colemak"</span><span class="p">,</span><span class="w">
</span><span class="nl">"input_sources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"input_source_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^com</span><span class="se">\\</span><span class="s2">.apple</span><span class="se">\\</span><span class="s2">.keylayout</span><span class="se">\\</span><span class="s2">.Colemak$"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>There are a ton of user-submitted configs to explore. I also recommend <a href="https://ke-complex-modifications.pqrs.org/#vi_mode_arrow">left control + hjkl to arrow keys</a>. The possibilities are endless!</p>
Three months of GZCLP2022-05-16T00:00:00+00:00https://danielzting.github.io/2022/05/16/three-months-of-gzclp<p>I’ve been running <a href="https://thefitness.wiki/routines/gzclp/">GZCLP</a> for the past semester, which is a linear progression for beginners like SL/SS but with more balance between each of the compound lifts and varying rep ranges. The way it works is that each day contains “T1” movement done for low reps high intensity and a “T2” movement done for high reps low intensity along with “T3” accessory work. T1s start off at 5x3 and then shift to 6x2, 10x1, and then deloading and restarting at 5x3 when you miss reps. The following table records the highest numbers I was able to reach attempting 10x1 before resetting the cycle, all in pounds.</p>
<table>
<thead>
<tr>
<th> </th>
<th>Start</th>
<th>End</th>
<th>Improvement</th>
</tr>
</thead>
<tbody>
<tr>
<td>Squat</td>
<td>205 (Nov 30)</td>
<td>235 (Apr 21)</td>
<td>+30</td>
</tr>
<tr>
<td>Bench Press</td>
<td>135 (Nov 22)</td>
<td>150 (May 5)</td>
<td>+15</td>
</tr>
<tr>
<td>Deadlift</td>
<td>225 (Nov 28)</td>
<td>265 (May 7)</td>
<td>+40</td>
</tr>
<tr>
<td>Overhead Press</td>
<td>95 (Nov 20)</td>
<td>105 (Mar 30)</td>
<td>+10</td>
</tr>
</tbody>
</table>
<p>Technically, this isn’t the same as a true max. I haven’t bothered to test for an actual max, but if I had to guess, the difference would be small—maybe another 10 lbs for lower body lifts and 5 lbs for upper body lifts. I actually haven’t gotten to reset T1 deadlifts or bench press this semester, but I doubt I had very much further to go at all.</p>
<p>Given that I basically didn’t train for almost all of December and January because of winter break and UT’s decision to keep the first two weeks of spring 2022 online, these stats correspond roughly to an increase of 10 lbs/month for lower body and 5 lbs/month for upper body. Not prodigy-levels of progress but solid for me personally. Extrapolating this would give exactly 14 months or just before the last year of undergrad to get into the thousand pound club. This is most likely a pretty optimistic assumption but a worthwhile goal to work towards. We’re All Gonna Make It!™ :)</p>
<p><img src="https://photos.smugmug.com/photos/i-Xzz6cvc/0/X3/i-Xzz6cvc-X3.jpg" alt="The weight room of UT Austin's Gregory gym" /></p>
<p align="center"><em>Can't think of a good image, so here's a random shot of Greg from <a href="https://recsports.smugmug.com/Facilities/Gregory-Gym/Website-Gallery/">UT RecSports</a>. Too bad it's never this empty...</em></p>
<p>As for what I think of the program itself, I would highly recommend it to any beginner. The varying rep ranges and progression scheme as well as the fact that every other day is a rest day meant I never really got burnt out. It is slightly more complex than 3-day MWF programs like SL and SS but pays off by having a much more balanced squat to deadlift/bench ratio. I found the <a href="https://apps.apple.com/us/app/clank-weight-lifting/id1455163991">Clank</a> app amazing for tracking weights and its built-in rest timer with notifications. Unfortunately it is only on iOS but I’m sure there are equally great alternatives on Android.</p>
<p>For now, though, I feel it is time to move on to <a href="https://thefitness.wiki/routines/nsuns-lp/">nSuns</a> or another 5/3/1 variant. I think there are still gains left in GZCLP but I’ve clearly exhausted the “linear progression” part. I no longer have any noob gains to exploit and would just like to switch things up.</p>
Displays from a programmer's perspective2022-01-22T00:00:00+00:00https://danielzting.github.io/2022/01/22/displays-from-a-programmers-perspective<p>When it comes to choosing a good monitor for programming, there is a ton of strong, wildly differing, and (in my opinion) wrong advice floating around. So I thought I would summarize my thoughts on reasonable things to look out for in making a purchasing decision. Note that I will not be recommending any specific models, just general factors to consider. A site like <a href="https://www.rtings.com/monitor">RTINGS</a> (unaffiliated) will be able to provide much more detailed reviews of individual models than I ever could.</p>
<h1 id="size-as-big-as-comfort-allows">Size: as big as comfort allows</h1>
<p>The bigger your display, the more stuff you can see at once without scrolling. So my simple advice is, “buy as big as you can comfortably use.” 27 to 32 inches is a good sweet spot that will fit in almost any setup. I would only go for 24 or 21 inches if I couldn’t physically squeeze anything bigger into my workspace. There are even 42 to 48 inch TVs that double as monitors, but you will likely need a deep enough desk to use it without having to do a neck workout every time you look at the corner.</p>
<h1 id="resolution-aim-for-110-ppi-preferably-higher">Resolution: aim for 110 PPI, preferably higher</h1>
<p>Technically, the amount of stuff (lines of code, pages of documentation, app windows, etc.) you can fit on your screen depends not on its size but its resolution. That makes the number of pixels the most important factor when it comes to productivity. A picture is worth a thousand words:</p>
<p><img src="/assets/fhd.png" alt="Screenshot of Visual Studio Code on a 1080p workspace" /></p>
<p align="center"><em>At 1080p, the lowest I'd go, I can comfortably fit 50 lines of code and a file explorer along with two files open side by side, or in my case, one file next to a terminal.</em></p>
<p><img src="/assets/qhd.png" alt="Screenshot of Visual Studio Code on a 1440p workspace" /></p>
<p align="center"><em>At 1440p, that jumps to 70 lines of code. I can also squeeze another file in. Alternatively, I could put an HTML/CSS/JS file next to a browser window to test on the same monitor.</em></p>
<p><img src="/assets/uhd.png" alt="Screenshot of Visual Studio Code on a 4K workspace" /></p>
<p align="center"><em>At 4K (if I had the eagle eyes to use it at 100% scaling), I could fit a massive 110 LOC and three files plus a terminal. I could also have HTML source code and a browser testing window with enough room to even show devtools, all on the same monitor. Or I could fit four (4!) 1080p editor windows on each quadrant of the screen.</em></p>
<p>So why did I discuss size first? For any fixed-sized monitor, pixels can only get so dense before the whole interface is too small to practically use. For the average person with healthy vision and sitting an arm’s length away from their screen, this threshold is in the ballpark of 110 <a href="https://dpi.lv/">pixels per inch</a>. So your chosen size will limit what resolutions you should shoot for. A 27-inch 1440p is 109 PPI, which is why this size and resolution pairing is so common.</p>
<p>A monitor with a PPI lower than 110 will have the same amount of virtual real estate as a smaller one of equivalent resolution while being less sharp. For example, the ubiquitous 24-inch 1080p (92 PPI) won’t fit any more lines of code than a 21-inch 1080p (105 PPI), and media will be slightly more pixelated. So in my opinion there isn’t much point in going lower than 110 PPI. On the other hand, you can go for higher PPIs and use scaling, and I’ll explain later why I think you should. Below is a table of PPIs for common monitor sizes and resolutions. I’ve put checkmarks next to recommended combos.</p>
<table>
<thead>
<tr>
<th> </th>
<th>1920x1080</th>
<th>2560x1440</th>
<th>3840x2160</th>
</tr>
</thead>
<tbody>
<tr>
<td>21″</td>
<td>105 ✓</td>
<td>140 ✓</td>
<td>210 ✓</td>
</tr>
<tr>
<td>24″</td>
<td>92</td>
<td>122 ✓</td>
<td>184 ✓</td>
</tr>
<tr>
<td>27″</td>
<td>82</td>
<td>109 ✓</td>
<td>163 ✓</td>
</tr>
<tr>
<td>32″</td>
<td>69</td>
<td>92</td>
<td>138 ✓</td>
</tr>
<tr>
<td>42″</td>
<td>52</td>
<td>70</td>
<td>105 ✓</td>
</tr>
</tbody>
</table>
<h1 id="why-you-should-go-for-hidpi-and-scale">Why you should go for HiDPI and scale</h1>
<p>Although 110 PPI is OK, there are two reasons why I suggest going higher. The first is font rendering.</p>
<p><img src="/assets/retina.png" alt="Two lowercase g's, one at 1x rendering and the other at 2x" width="250" height="150" /></p>
<p align="center"><em>A lowercase g rendered at 1x and 2x in 12 point Menlo, the default on VS Code for macOS.</em></p>
<p>A programmer’s job is to stare at text for eight hours a day. So the second most important thing to consider after a big virtual desktop is clear and readable text. It is simply impossible to render letterforms to any degree of fidelity when the amount of pixels you have to work with can be counted on your fingers. As you can see in the image above, the g on the left looks more like a blob of pixel soup than a letter. And the English alphabet is on the simple side. It’s even worse with CJK characters:</p>
<p><img src="/assets/cjk.jpg" alt="CJK characters at 498, 244, and 132 PPI" /></p>
<p align="center"><em>The last row is an absolute abomination to look at.</em></p>
<p>(I have no idea where this image originated. Google reverse image search turns up dozens of matches on different websites. If you are the creator let me know and I will credit you.) Windows’ aggressive hinting and subpixel antialiasing will <a href="https://tonsky.me/blog/monitors/">help a bit</a>, but nothing beats increasing the raw number of pixels the computer has to paint with.</p>
<p>The second and more minor reason is that you’ll instantly see if you forgot to ship 2x assets, or if your app isn’t resolution independent in general. There is another side to this coin, though: apps that haven’t been updated to play well with HiDPI will look wonky. This is a non-issue on macOS, where the first Retina MacBook is soon turning a decade old at the time of writing. In fact I would avoid low PPI monitors like the plague if you’re on macOS since <a href="https://www.reddit.com/r/apple/comments/9inu3e/if_the_font_rendering_on_mojave_looks_odd_to_you/">Apple killed subpixel antialiasing in Mojave</a>, so text looks terrible on non-Retina displays. But Windows and Linux are still usable at low resolutions and given their slow adoption of HiDPI support, a low PPI monitor still makes sense if it means your apps won’t break.</p>
<h1 id="fractional-scaling-is-perfectly-fine">Fractional scaling is perfectly fine</h1>
<p>If your chosen size-resolution combo has a PPI that’s not close to a multiple of 110, you will have to use fractional scaling. This means assets that were designed to align perfectly to the pixel grid like bitmap fonts and retro game artwork will look blurry because fractional pixels don’t exist.</p>
<p><img src="/assets/windows95.png" alt="Windows 95 start menu, normal size and blurry scaled up version" width="324" height="220" /></p>
<p align="center"><em>Yuck! Thankfully, modern operating systems handle fractional scaling much better.</em></p>
<p><a href="https://medium.com/elementaryos/what-is-hidpi-and-why-does-it-matter-b024eabea20d">Some</a> <a href="https://bjango.com/articles/macexternaldisplays/">people</a> <a href="https://tonsky.me/blog/monitors/">insist</a> that integer scaling is the only way to go for this reason. This is where I will have to disagree. This effect is only really noticeable in UIs with a ton of pixel perfect assets. For example, Windows 95 looks awful with non-integer scaling because the whole OS is rendered with bitmap fonts and pixel art. Antialiased fonts and vector icons that scale well to any size dominate modern OSes. Speaking from experience, I use a 4K display running at 2560x1440@2x (equivalent to 150% in Windows) every day and have no complaints about sharpness.</p>
<p>There are other valid reasons to avoid fractional scaling, however; it is reportedly not as polished as integer scaling in Linux. And macOS implements fractional scaling by doing 2x integer scaling internally and then downsampling. So in my case, the GPU is actually pushing 5120x2880, which is nearly 80% more pixels than 4K. In essence, I’m wasting computational power, and the increased load puts noticeably more lag on pre-Xe Intel integrated graphics.</p>
<h1 id="a-note-on-pwm">A note on PWM</h1>
<p>Some displays reduce brightness using pulse-width modulation, where instead of reducing the power to the backlight, they simply switch it on and off very quickly. This flickering is too fast for most people to detect, but some report eyestrain and headaches using PWM displays, especially ones with lower frequencies. If this is you, be sure to look out for flicker-free monitors.</p>
<h1 id="other-considerations">Other considerations</h1>
<p>If you’ve made it to this point, congratulations! You’re essentially done, but there’s other factors that can improve your quality of life.</p>
<h1 id="panel-type">Panel type</h1>
<p>There are two main display technologies: organic light emitting diodes (OLED) and liquid crystal displays (LCD). OLEDs are common on phones and TVs because they have infinite contrast ratios and near-instantaneous response times, making them great for consuming content. But they are susceptible to burn in, which is a flaw for computer monitors that show static UI elements for long periods of time.</p>
<p>LCDs can be further split into in-plane switching (IPS), vertical alignment (VA), and twisted nematic (TN) panels. TN panels are cheap but have generally the worst image quality and should be avoided. IPS panels are the safest bet in all areas except contrast, where they get beaten by VA. However, VA panels have narrower viewing angles (still much better than TN though) and suffer from slower pixel transition times and black smearing, which can be distracting.</p>
<p>Finally, some expensive high-end IPS displays can dim individual backlighting zones. This is known as mini-LED and has the potential to produce OLED levels of contrast without the risk of burn-in. But since the best mini-LEDs only currently have a couple thousand dimming zones spread across millions of pixels, there can be blooming around small bright objects against dark backgrounds.</p>
<h1 id="refresh-rate">Refresh rate</h1>
<p>Almost every regular display refreshes 60 times per second, while there are gaming monitors that go up to 120 hertz and beyond. Fast motion handling is a competitive advantage for gamers, but programmers still benefit from buttery smooth scrolling and responsive mousing. That being said, high refresh rate doesn’t meaningfully improve productivity and is more of a nice-to-have. If I had to chose between resolution and refresh rate, I would prioritize resolution.</p>
<h1 id="coating">Coating</h1>
<p>Almost every monitor comes with a matte layer applied in front of the panel, while almost every other device (phones, tablets, TVs) are glossy. Matte is a must-have if you work in a bright, reflective environment. But if you can control your lighting conditions, glossy gives far superior picture quality. Dave2D has a <a href="https://www.youtube.com/watch?v=3mTV1TOblbA">great video</a> about the subject that I’ve pulled the following two images from.</p>
<p><img src="/assets/coating-games.jpg" alt="Matte versus glossy screen showing Cyberpunk 2077" /></p>
<p>As this Cyberpunk 2077 footage shows, matte coatings kill vibrancy. But it’s not just about contrast or colors. Matte gives a bit of fuzziness to all text as well:</p>
<p><img src="/assets/coating-text.jpg" alt="Matte versus glossy screen showing text on Discord" /></p>
<p>Sharp text is essential for programmers, but unfortunately nobody sells glossy desktop monitors. I’m not asking matte to be killed off, I am just hoping manufacturers will give consumers more choice.</p>
<h1 id="brightness-color-gamut-contrast">Brightness, color gamut, contrast</h1>
<p>Brightness should not be a problem for desktop monitors, but it is useful if you want HDR or for laptops being used outdoors. Aim for 500 to 600 nits for comfortable use under an overcast sky without shade and entry level HDR. 1000 nits is required under direct sunlight and good HDR.</p>
<p>A color gamut close to 100% sRGB should be enough as that is what the web and games are mastered in. Higher coverage may be useful if you do photo or video editing.</p>
<p>As for contrast, higher is better, but it’s not something you can choose independently since contrast ratio is pretty much exclusively determined by panel type. TN and IPS offer 1000:1, VA 3000:1, mini-LED 1,000,000:1 and OLED infinite.</p>
<h1 id="aspect-ratio">Aspect ratio</h1>
<p>All this time I’ve only mentioned 16:9 displays, but there are 21:9 ultrawides out there. Ultrawides can extend your workspace without the bezels of a multi-monitor setup. They are relatively pricey: as <a href="https://www.youtube.com/watch?v=fBE9DL7MlG0">Linus Tech Tips</a> put it, instead of being priced as if the manufacturer extended the sides of a regular 16:9, they’re priced like a huge monitor that just had its top and bottom sliced off. In addition, there are no HiDPI ultrawides except for this <a href="https://www.rtings.com/monitor/reviews/lg/34wk95u-w">$1300 5120x2160 LG</a> (a glossy version of this would probably be my dream monitor).</p>
<h1 id="ports">Ports</h1>
<p>Finally, if you frequently hook up a laptop to your monitor, a USB-C input that supports video and charging over a single cable may be convenient.</p>
<h1 id="conclusion">Conclusion</h1>
<p>So with all this out of the way, what type of monitor would I recommend? The answer is a 32-inch 4K IPS: big enough to have more workspace than standard 24 or 27 inch monitors, but small enough to use scaling for increased clarity in text and images. Preferably glossy and 120 Hz, but such a combination doesn’t exist yet. One can dream!</p>
Thoughts on Gödel, Escher, Bach2022-01-09T00:00:00+00:00https://danielzting.github.io/2022/01/09/thoughts-on-godel-escher-bach<p>I checked out Douglas Hofstadter’s <em>Gödel, Escher, Bach</em> at the beginning of the (fall 2021) semester, reading a bit most nights before bed. Coming in at a hefty 777 pages (nice), I only managed to finish it this winter break. It was the most unique book I’ve ever read in many ways, so I’ve tried to collect my thoughts as documentation for anybody interested in reading it as well and my future self.</p>
<p>It is hard to say what the book is actually about. The title implies drawing connections between math, art, and music, but the author insists his goal was to show how meaning can emerge from “meaningless” components, like how simple neurons give rise to consciousness. I personally felt like it was mainly a treatise oriented around Gödel’s Incompleteness Theorem and its philosophical implications. The gist of Gödel’s Theorem says that any consistent (that is, free from contradictions) method of reasoning about number theory is incomplete (that is, there are true statements of number theory that cannot be proved). In doing so, Hofstadter touches on literally <em>everything</em>, including, but not limited to, formal systems, recursion and self-reference, linguistics, paradoxes in art and logic, classical music, number theory, Zen Buddhism, neuroscience, microbiology, computability theory, and artificial intelligence.</p>
<p>The book consists of an introduction, an extensively annotated bibliography that is by itself excellent research material, and twenty chapters that are each preceded by a fairy tale-esque story that gives a taste of the topics to come in a more informal and playful setting. I will only attempt to summarize Gödel’s Theorem because I think it forms the core of the book and the common thread through all of its subject matter runs through, but I should note that the book isn’t interesting because of its treatment of Gödel’s Theorem. There are probably a million papers, YouTube videos, and Medium articles that do a great job of explaining Gödel’s Theorem to a non-mathematician. The beauty is in how it manages to tie a seemingly obscure result in number theory to a huge range of disciplines.</p>
<h1 id="the-fascinating-history-of-metamathematics">The Fascinating History of Metamathematics</h1>
<p>When I first heard of the Incompleteness Theorem, I found the idea that math can be used to prove its own incompleteness amazing. How would one even start to go about that? I will start by providing some historical context. It is not necessary to understand the proof and this paragraph can be skipped, but I think it is interesting and worth going over. The story starts with the discovery of Bertrand Russell’s paradox at the turn of the twentieth century, which concerns itself with the theory of sets, or collections of items. Consider a set R defined as the set containing all sets which are not members of themselves. That is, an element x belongs to R if and only if x does not belong to x. Does R contain itself? If so, then its predicate is false, implying that the answer is no. But if R doesn’t contain itself, then it fulfills the predicate, implying that the answer is yes. To resolve this foundational crisis, Russell and Alfred North Whitehead wrote <em>Principia Mathematica</em>, where they aimed to show how all of known math can be derived from some basic axioms (a fancy word for a statement so obvious it is assumed to be true, like “zero is not the successor of any natural number”) and well-defined rules of inference that let you prove new theorems from simpler ones (like “if either a or b is true and a is false, then b must be true”). To give you an idea of its rigor, it took over 300 pages for them to get to the point where they could prove 1+1=2! Now, can it be proven that a formal system such as <em>Principia Mathematica</em> is consistent and complete? Gödel’s Theorem shows that this is in fact impossible.</p>
<h1 id="gödels-proof">Gödel’s Proof</h1>
<p>A statement of number theory can be represented by a set of symbols. For example, the famous Goldbach conjecture can be expressed as follows:</p>
\[\forall a:\exists b:\exists c:\neg(\exists d:\exists e:SSd \cdot SSe=b \lor SSd \cdot SSe=c) \land a+a=b+c\]
<p>This roughly translates as “for all a, there exist b and c such that there are no d and e where b or c is equal to the product of d plus two and e plus two and b plus c equals a plus a.” More succinctly, “for all a, there exist b and c such that their only factors are one and themselves and a doubled equals b plus c”. Or, finally: “for all even numbers a, there exist two primes b and c that sum up to a.” The spark of genius is the idea of assigning each symbol to a number, known as Gödel numbering. Using the coding in the book, the above would be mapped by an absolutely massive integer that starts with 626 (code for the “for all” symbol) and ends with 262,163,163 (code for the variable “c”).</p>
<p>With that out of the way, we need two more concepts. The first is the notion of proof pairs. We say PROOF-PAIR(s, e) if s is the Gödel number of a valid and sound proof consisting of a sequence of strings that ends in the string whose Gödel number is e. The second idea is arithmoquining. We say ARITHMOQUINE(u, w) if u is a formula with at least one free variable and substituting the Gödel number of u into all of its free variables produces w. The book asserts that any procedure that is guaranteed to finish in a finite amount of time is embeddable by a formula of number theory. This is by far the biggest leap of faith in the proof in my opinion, and I would have loved to see Hofstadter elaborate on this step despite the book being already as long as it is. But accepting this claim, testing a proof pair and arithmoquining a string are both predictably terminating functions and thus can be represented by a string made of the same symbols that we used to express the Goldbach conjecture above, though it will be monstrously long.</p>
<p>Now all the pieces are assembled. Consider the following string:</p>
\[\neg \exists a:\exists b:\text{PROOF-PAIR}(a, b) \land \text{ARITHMOQUINE}(c, b)\]
<p>This has a Gödel number which we can call u. Now what happens if arithmoquine this very sentence? Substituting u into c, its only free variable, results in the below. (Assume there are u S’s.)</p>
\[\neg \exists a:\exists b:\text{PROOF-PAIR}(a, b) \land \text{ARITHMOQUINE}(SSS..SSS0, b)\]
<p>We have finally obtained Gödel’s sentence. Literally, it says “there are no numbers a and b such that a is the derivation of b and b is the arithmoquinification of u.” In other words, “there is no proof for the arithmoquinification of u.” But this very sentence was obtained by arithmoquining u! So in essence, Gödel’s sentence translates into “this sentence has no proof.”</p>
<p>What are the implications of this? If we say that Gödel’s sentence is a lie, then we imply that it there is a proof for it. But that means our formal system has a contradiction because it can prove a false statement. So we are forced to conclude it is telling the truth. Therefore, we have found a true sentence that does not have a proof, rendering math incomplete.</p>
<h1 id="personal-review">Personal Review</h1>
<p>If I had to review the book in one sentence, I would say “incredible concepts, lengthy presentation.” At times, I felt like the book was written just for me. It’s literally a collection of everything I’m interested in. I loved the first ten chapters on formal systems, meaning in math, figure and ground, recursion and self-reference, and computer organization. Some people thought the dialogues were unnecessary and pretentious, but I thought they added a funny and often amazingly clever element to the formal-ness of the rest of the book. (I never thought I would laugh at a story about a tortoise repeatedly destroying a crab’s record player as a metaphor for Gödel’s theorem.) I also enjoyed chapters thirteen through seventeen which introduce Gödel’s proof, its philosophical implications, and related ideas in computer science like primitive/general/partial recursive functions, the Church-Turing thesis, the halting problem, and the Entscheidungsproblem (took me several tries just to Google how to spell that).</p>
<p>On the other hand, I found the four chapters where the author speculates on brain processes and AI relatively boring and I would not miss them at all if they were cut out. I’d say the book’s biggest flaw is that I’m still not sure what its trying to say, which would be a lot less of a problem if it weren’t over 700 pages long. A minor detail is that the section on AI is quite dated. For instance, the author asserts that chess programs will never surpass humans until general intelligence is achieved, but this is a reasonable viewpoint given the book was published in 1979. The rest of the book is timeless and absolutely worth reading if you like the feeling of your brain physically expanding.</p>
uBlock Origin as a productivity tool2021-09-07T00:00:00+00:00https://danielzting.github.io/2021/09/07/ublock-origin-as-a-productivity-tool<p>There are about a million bajillion articles already about maximizing
your productivity as part of your sigma male astrotrillionaire grindset.
A common suggestion is to block sites that are time sinks like Reddit
and YouTube. The problem with this approach is that these sites also
contain useful information. You might search for an error message and
come across a Reddit post, but a plain hostname filter can’t distinguish
between helpful content and entertainment. What ended up happening to me
was that I would temporarily work around or disable the filter, but once
I did that, it became much easier to do the same for entertainment.</p>
<p>The truth about such websites is that they are designed to suck you in
for as long as possible. A large and possibly the most important factor
in this goal is an infinitely scrolling list of recommendations that
link to other content. If we can get rid of this, then we have
eliminated the biggest temptation to waste time while still allowing
one-time access. A blocker extension like uBlock Origin (<a href="https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en">Chrome</a>,
<a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/">Firefox</a>),
widely recommended for its high performance and low resource usage
relative to competitors, allows you to block any element you choose.
Here’s how:</p>
<p><strong>STEP ONE</strong> Click on the extension icon that can be found next to the
address bar or under the Extensions button that looks like a puzzle
piece. Enter element picker mode by clicking on the eyedropper icon.</p>
<p><img src="/assets/popup.jpg" alt="uBlock Origin popup on YouTube video" /></p>
<p><strong>STEP TWO</strong> Move and hover your cursor until the distracting element is
highlighted. Click to select it.</p>
<p><img src="/assets/picker.jpg" alt="Element picker with YouTube recommendation sidebar selected" /></p>
<p><strong>STEP THREE</strong> If you made a mistake, click the pick button. Otherwise,
click create. You might have to adjust the sliders if too much or too
little is blocked.</p>
<p><img src="/assets/after.jpg" alt="YouTube video with empty sidebar" /></p>
<p>Now you can look up and watch individual videos without worrying about
getting sucked into mindlessly browsing.</p>
Automatically replacing text in dynamic webpages (or: have web standards become too bloated?)2021-08-29T00:00:00+00:00https://danielzting.github.io/2021/08/29/automatically-replacing-injected-text-in-dynamic-webpages<h1 id="introduction-and-motivation">Introduction and Motivation</h1>
<p><a href="#explanation">SKIP TO EXPLANATION</a></p>
<p>Find and replace.</p>
<p>This operation is something every programmer is familiar with. It is ubiquitous in a huge variety of apps due to its incredible versatility yet simple implementation. At least, you would think it would be simple. In many programs, it probably is. But in the domain of websites, it is anything but. My inspiration for this library came from <a href="https://xkcd.com/2112">XKCD 2112: Night Shift</a>.</p>
<p><img src="https://imgs.xkcd.com/comics/night_shift_2x.png" alt="Chatlog with very bland messages" width="327" height="618" /></p>
<p>My idea was to create a browser extension that does some primitive version of that by, say, substituting strong words with softer ones (“extremely” to “a little”) and cutting out swear words entirely. To do this, I would need a reliable and performant way to manipulate the text on a webpage. Not only that, I would need to monitor the DOM and process any text added or modified after initial load, because almost every site nowadays has the ability to dynamically request new content with background AJAX calls (think about when you press “more comments” on Reddit or switch channels in Discord: these actions don’t need to refresh the entire page). This turned out to be quite a nontrivial problem and <code class="language-plaintext highlighter-rouge">TextObserver</code> was born out of the resulting rabbit hole.</p>
<h1 id="explanation">Explanation</h1>
<p>There are two main functional pieces of a <code class="language-plaintext highlighter-rouge">TextObserver</code> object: the part that finds and replaces text already on a page and the part that watches for added or changed text. I will discuss the former first because it makes up the meat of the library and is used by the latter. To understand how to programatically edit the text on a site, we need to know that text can be found in the DOM as multiple representations, of which all but one can be seen as special cases. We will go over how to process each one by one.</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text"><code class="language-plaintext highlighter-rouge">Text</code></a> nodes. These are what the text in between HTML element tags show up as.</li>
<li>Special attributes like <code class="language-plaintext highlighter-rouge"><input></code> <code class="language-plaintext highlighter-rouge">placeholder</code>s (and <code class="language-plaintext highlighter-rouge">value</code> for those of <code class="language-plaintext highlighter-rouge">type="button"</code>, <code class="language-plaintext highlighter-rouge"><img></code> <code class="language-plaintext highlighter-rouge">alt</code> text, and <code class="language-plaintext highlighter-rouge">title</code> (tooltips). These are <em>not</em> <code class="language-plaintext highlighter-rouge">Text</code> nodes but are rendered as front-facing text by the browser.</li>
<li>The CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/content"><code class="language-plaintext highlighter-rouge">content</code></a> property. This is even trickier to deal with since it’s effectively invisible to the DOM but still seen by the end user.</li>
</ol>
<h2 id="text-nodes">Text nodes</h2>
<p>The “default” representation and by far the most straightforward to replace. We use a <a href="https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker"><code class="language-plaintext highlighter-rouge">TreeWalker</code></a> to go through every <code class="language-plaintext highlighter-rouge">Text</code> node and replace their contents. In all of the following snippets, assume <code class="language-plaintext highlighter-rouge">callback</code> is a function that accepts a string as a parameter and returns the string to replace it with.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">callback</span> <span class="o">=</span> <span class="nx">text</span> <span class="o">=></span> <span class="nx">text</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="sr">/heck/gi</span><span class="p">,</span> <span class="dl">'</span><span class="s1">h*ck</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">nodes</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createTreeWalker</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="nx">NodeFilter</span><span class="p">.</span><span class="nx">SHOW_TEXT</span><span class="p">,</span> <span class="p">{</span>
<span class="na">acceptNode</span><span class="p">:</span> <span class="nx">node</span> <span class="o">=></span> <span class="nx">valid</span><span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">?</span> <span class="nx">NodeFilter</span><span class="p">.</span><span class="nx">FILTER_ACCEPT</span> <span class="p">:</span> <span class="nx">NodeFilter</span><span class="p">.</span><span class="nx">FILTER_REJECT</span>
<span class="p">});</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">nodes</span><span class="p">.</span><span class="nx">nextNode</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">nodes</span><span class="p">.</span><span class="nx">currentNode</span><span class="p">.</span><span class="nx">nodeValue</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">nodes</span><span class="p">.</span><span class="nx">currentNode</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Wait, where did the <code class="language-plaintext highlighter-rouge">valid()</code> function come from? Well, it turns out there are some exceptions…</p>
<ul>
<li>The observation code runs asynchronously, so a node might be removed from the document before we get a chance to process it.</li>
<li>Inline text between <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"><code class="language-plaintext highlighter-rouge"><script></code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"><code class="language-plaintext highlighter-rouge"><style></code></a> tags are actually <code class="language-plaintext highlighter-rouge">Text</code> nodes too. Messing with them could break the functionality and presentation of the page, respectively.</li>
<li>We don’t want to touch <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable"><code class="language-plaintext highlighter-rouge">contentEditable</code></a> elements because doing so messes with the cursor position, making for a definitely horrible user experience.</li>
<li>Some text may be in an icon font. Icon fonts are special fonts that render specific character sequences as icons. For example, “search” might display as a magnifying glass. Modifying the text would prevent the icon from showing properly.</li>
</ul>
<p>To check for this, we create <code class="language-plaintext highlighter-rouge">valid()</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="c1">// Sometimes the node is removed from the document before we can process it</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span> <span class="o">!==</span> <span class="kc">null</span>
<span class="c1">// HTML tags that permit textual content but are not front-facing text</span>
<span class="o">&&</span> <span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">tagName</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">SCRIPT</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">tagName</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">STYLE</span><span class="dl">'</span>
<span class="c1">// Ignore contentEditable elements as touching them messes up the cursor position</span>
<span class="o">&&</span> <span class="o">!</span><span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">isContentEditable</span>
<span class="c1">// HACK: workaround to avoid breaking icon fonts</span>
<span class="o">&&</span> <span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">)</span>
<span class="p">.</span><span class="nx">getPropertyValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">font-family</span><span class="dl">'</span><span class="p">).</span><span class="nx">toUpperCase</span><span class="p">().</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">ICON</span><span class="dl">'</span><span class="p">)</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="special-attributes">Special attributes</h2>
<p>The above is sufficiently thorough for, if I had to guess, 97% of cases. For most people, that should be enough. But if the last three percent is important, we must take into account some special cases, the first of which are certain attributes that the browser displays as user-visible text:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">placeholder</code> in <code class="language-plaintext highlighter-rouge"><input></code> and <code class="language-plaintext highlighter-rouge"><textarea></code></li>
<li><code class="language-plaintext highlighter-rouge">alt</code> in <code class="language-plaintext highlighter-rouge"><img></code>, <code class="language-plaintext highlighter-rouge"><area></code>, and <code class="language-plaintext highlighter-rouge"><input type="image"></code></li>
<li><code class="language-plaintext highlighter-rouge">value</code> in <code class="language-plaintext highlighter-rouge"><input type="button"></code></li>
<li><code class="language-plaintext highlighter-rouge">title</code></li>
</ul>
<p>We can put the above data into a dictionary with attributes as keys and selectors as keys. From there, replacing the attributes is not too hard:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">WATCHED_ATTRIBUTES</span> <span class="o">=</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">placeholder</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">textarea</span><span class="dl">'</span><span class="p">],</span>
<span class="dl">'</span><span class="s1">alt</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">img</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">area</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">input[type="image"]</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[role="img"]</span><span class="dl">'</span><span class="p">],</span>
<span class="dl">'</span><span class="s1">value</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">input[type="button"]</span><span class="dl">'</span><span class="p">],</span>
<span class="dl">'</span><span class="s1">title</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">],</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">attribute</span><span class="p">,</span> <span class="nx">selectors</span><span class="p">]</span> <span class="k">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">WATCHED_ATTRIBUTES</span><span class="p">))</span> <span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="nx">selectors</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">)).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">element</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="nx">attribute</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="nx">attribute</span><span class="p">,</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="css-content">CSS content</h2>
<p>The CSS <code class="language-plaintext highlighter-rouge">content</code> property can be used to generate text, usually used with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::before"><code class="language-plaintext highlighter-rouge">::before</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::after"><code class="language-plaintext highlighter-rouge">::after</code></a> pseudo-elements (but also available on <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::marker"><code class="language-plaintext highlighter-rouge">::marker</code></a>). CSS-generated content is not visible in the DOM, so we must use a roundabout hack: call <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle"><code class="language-plaintext highlighter-rouge">getComputedStyle</code></a> to read off every pseudo-element’s <code class="language-plaintext highlighter-rouge">content</code>. If the property value is a plain string (<code class="language-plaintext highlighter-rouge">content</code> can accept many value types, including images and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters">counters</a>), then inject our own style rule which overrides the property for that specific pseudo-element.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">styleElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">style</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">styleElement</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">styles</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">elements</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createTreeWalker</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="nx">NodeFilter</span><span class="p">.</span><span class="nx">SHOW_ELEMENT</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">elements</span><span class="p">.</span><span class="nx">nextNode</span><span class="p">())</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">currentNode</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">pseudoClass</span> <span class="k">of</span> <span class="p">[</span><span class="dl">'</span><span class="s1">::before</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">::after</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">::marker</span><span class="dl">'</span><span class="p">])</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">pseudoClass</span><span class="p">).</span><span class="nx">content</span><span class="p">;</span>
<span class="c1">// Only process values that are plain single or double quote strings</span>
<span class="k">if</span> <span class="p">(</span><span class="sr">/^'</span><span class="se">[^</span><span class="sr">'</span><span class="se">]</span><span class="sr">+'$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span> <span class="o">||</span> <span class="sr">/^"</span><span class="se">[^</span><span class="sr">"</span><span class="se">]</span><span class="sr">+"$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">content</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">newClass</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">TextObserverHelperAssigned</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">i</span><span class="p">;</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">newClass</span><span class="p">);</span>
<span class="c1">// Substring is needed to cut off open and close quote</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">content</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">));</span>
<span class="nx">styles</span> <span class="o">+=</span> <span class="s2">`.</span><span class="p">${</span><span class="nx">newClass</span><span class="p">}${</span><span class="nx">pseudoClass</span><span class="p">}</span><span class="s2"> {
content: "</span><span class="p">${</span><span class="nx">result</span><span class="p">}</span><span class="s2">" !important;
}`</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">i</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">styleElement</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">styles</span><span class="p">;</span>
</code></pre></div></div>
<h2 id="observer">Observer</h2>
<p>Now that we have a way to robustly substitute text across a document, we can start working on the part that does the observation. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code class="language-plaintext highlighter-rouge">MutationObserver</code></a> interface provides a way to track updates in the DOM. We can extract all of our previous code to a <code class="language-plaintext highlighter-rouge">processNodes()</code> function and replace every instance of <code class="language-plaintext highlighter-rouge">document.body</code> with a single parameter representing the root node and leverage that in our observation callback.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">IGNORED</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">Node</span><span class="p">.</span><span class="nx">CDATA_SECTION_NODE</span><span class="p">,</span>
<span class="nx">Node</span><span class="p">.</span><span class="nx">PROCESSING_INSTRUCTION_NODE</span><span class="p">,</span>
<span class="nx">Node</span><span class="p">.</span><span class="nx">COMMENT_NODE</span><span class="p">,</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">CONFIG</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">subtree</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">childList</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">characterData</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">characterDataOldValue</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">attributeFilter</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">WATCHED_ATTRIBUTES</span><span class="p">),</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">((</span><span class="nx">mutations</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Disconnect every observer after collecting their records</span>
<span class="c1">// Otherwise, the callback's mutations will trigger an infinite feedback loop</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">mutation</span> <span class="k">of</span> <span class="nx">mutations</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">target</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">oldValue</span> <span class="o">=</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">oldValue</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">mutation</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">childList</span><span class="dl">'</span><span class="p">:</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">node</span> <span class="k">of</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">addedNodes</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">===</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">TEXT_NODE</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">valid</span><span class="p">(</span><span class="nx">node</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">IGNORED</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// If added node is not text, process subtree</span>
<span class="nx">processNodes</span><span class="p">(</span><span class="nx">node</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">characterData</span><span class="dl">'</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">IGNORED</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">target</span><span class="p">.</span><span class="nx">nodeType</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">target</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">target</span><span class="p">.</span><span class="nx">nodeValue</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">target</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">attributes</span><span class="dl">'</span><span class="p">:</span>
<span class="kd">const</span> <span class="nx">attribute</span> <span class="o">=</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">attributeName</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="nx">attribute</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&&</span> <span class="nx">value</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Find if element with changed attribute matches a valid selector</span>
<span class="kd">const</span> <span class="nx">selectors</span> <span class="o">=</span> <span class="nx">WATCHED_ATTRIBUTES</span><span class="p">[</span><span class="nx">attribute</span><span class="p">];</span>
<span class="kd">let</span> <span class="nx">matched</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">selector</span> <span class="k">of</span> <span class="nx">selectors</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">target</span><span class="p">.</span><span class="nx">matches</span><span class="p">(</span><span class="nx">selector</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">matched</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">matched</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">target</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="nx">attribute</span><span class="p">,</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="nx">CONFIG</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// Perform initial replacement</span>
<span class="nx">processNodes</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
<span class="c1">// Set up observer</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="nx">CONFIG</span><span class="p">);</span>
</code></pre></div></div>
<p>The length may be intimidating but this part is relatively straightforward compared to what we’ve gone through. We check each mutation and if it’s an added node, we process it recursively leveraging the <code class="language-plaintext highlighter-rouge">processNodes()</code> function we wrote previously. If it’s a changed <a href="https://developer.mozilla.org/en-US/docs/Web/API/CharacterData"><code class="language-plaintext highlighter-rouge">CharacterData</code></a> node we have to check that it is a real <code class="language-plaintext highlighter-rouge">Text</code> node and not one of the other types that implement the more general <code class="language-plaintext highlighter-rouge">CharacterData</code> interface. And if it’s an attribute we check if the changed element is one we’re actually watching. Believe it or not, we are now done! Hopefully this was helpful in documenting the thought process and structure behind <code class="language-plaintext highlighter-rouge">TextObserver</code>.</p>
<h1 id="just-kidding-the-web-is-complicated">Just kidding, the web is complicated</h1>
<p>This code doesn’t work on <code class="language-plaintext highlighter-rouge"><iframe></code>s. It misses Shadow DOMs. It chokes on “recursive” replacements (e.g. try replacing ‘e’ with ‘ee’ and you’ll occasionally end up with ‘eeee’). And online word processors might show quirky behavior. This isn’t all of the code in <code class="language-plaintext highlighter-rouge">TextObserver</code>. The full library provides extra convenience functions as well as mitigations for some of the above, but at well over 300 lines, explaining every single line would turn this post into a book.</p>
<p>As what I thought would be a simple function grew into several hundred lines, I started to wonder if web standards are becoming too complex. Sure, you could ignore all the edge and corner cases and get 95% of the way there with a fraction of the code, but philosophically speaking, I can’t help but feel like this should be a one-liner for what is supposed to be a declarative markup language. And of course HTML/CSS/JavaScript are far more powerful than what we typically think of a markup language like the Markdown that this post is written in, and users and developers are constantly demanding new features.</p>
<p>I get the sentiment; despite enjoying the typical jabs at web development, I have begrudgingly concluded that the open web could be the easiest way to create true cross-platform apps that aren’t locked in to a specific ecosystem and can reach <em>everyone</em>, be it a Silicon Valley CEO’s workstation computer or a young student hacker’s flip phone and Raspberry Pi. On the other hand, I worry that this mission creep will further reduce diversity and competition in the browser market and funnel more power to the few remaining corporate entities with enough capital to keep up. Already Drew DeVault has <a href="https://drewdevault.com/2020/03/18/Reckless-limitless-scope.html">claimed</a> that it is impossible to build a new browser without the funding and manpower of an immense cooperative project on the national level. This is a dilemma that I do not claim to have answers for.</p>
Reflections on the meaning of The Talos Principle2021-03-19T00:00:00+00:00https://danielzting.github.io/2021/03/19/reflections-on-the-meaning-of-the-talos-principle<p>In my last semester of high school, I had the privilege of taking ENGL 4680: Game Narratives as Literature with Dr. Marshall Needleman Armintor. I wrote my midterm on the theme of <em>The Talos Principle</em>. In doing so, I found myself reflecting a lot about life, identity, hope, and the human condition. I hope it will do a little bit of the same for you.</p>
<h2 id="human-qualities-identity-and-value-in-the-talos-principle">Human Qualities, Identity, and Value in <em>The Talos Principle</em></h2>
<p>What makes us human? This widely debated question, whose answer is connected to far-reaching implications ranging from science to philosophy, is what forms the core of the 2014 video game The Talos Principle by Croteam, in which the player navigates through mind-bending puzzles while attempting to uncover the truth of the strange and contradictory world they awaken in. To answer this question, The Talos Principle embeds memorialized textual and audio artifacts within a unique narrative to provide commentary on the intrinsic nature of humanity to the protagonist. The premise of the story is that a pandemic had wiped out the human race. The last survivors of society had formed the Institute of Applied Noematics (“noema” coming from the Greek word for “thought”) to collaborate on the Extended Lifespan Project, an immense research initiative with the purpose of preserving the human spirit in machine form. The player character is that machine, placed in a simulated world created by the scientists. It must prove its worthiness, that it has acquired humanity, by solving the puzzles scattered throughout the game space. It then awakens from the simulation and comes forth unto a barren planet. The game’s main points can be interpreted as a “what,” “how,” and “why” for this hypothetical yet very real project. What are the essential traits of humanity? How can these traits be carried into the future after humans as a species have long ceased to exist? And why is humanity worth preserving in the first place?</p>
<p>If the researchers want their project to succeed, then they must clearly define the essential qualities of a human. The first main point The Talos Principle makes is that our intense curiosity is the key element differentiating the human race from all other species. To convey this, the game scatters various time capsules throughout the map, which are audio recordings made by a presumably dead scientist named Alexandra Drennan. In one such capsule, she remarks,</p>
<blockquote>
<p>“Is there anything that we associate more closely with intelligence than curiosity? Every intelligent species on Earth is attracted by the unknown…Even the word ‘apocalypse’… Even the word ‘apocalypse’ means ‘revelation.’ It seems like our ancestors always imagined that even at the very end, we would solve one last mystery.”</p>
</blockquote>
<p>Drennan begins by claiming that the trait most closely associated with intelligence is curiosity; that curiosity is a common characteristic uniting all intelligent species. Indeed, according to Merriam-Webster, the word “apocalypse” originates from a Greek word meaning “to uncover,” and the way the universe ends has long been a subject of speculation. The fact that the end of the world and discovery are etymologically linked points to how the human appetite for knowledge can never truly be satisfied; that there will always be unanswered questions as long as this universe exists. So how will they capture the essence of humanity in a computer program? In another capsule, Drennan describes how she decided on a game, giving the player a clue to the purpose behind the puzzles. They are designed to extract problem-solving skills from the artificial intelligence driving the simulation as well as serving as entertainment value. But problem-solving isn’t enough. After all, computers have reached superhuman levels in many domains, including checkers, chess, and Go. Skill in these games was once thought to presuppose strong general intelligence (Bostrom 14). But it turned out that skill in chess could be achieved by a highly narrow algorithm written specifically for chess that was useless for anything else. Today, a chess engine would not be considered by anyone to be truly intelligent. So what is the missing piece of the puzzle? In another capsule, Drennan describes an important consequence of curiosity: skepticism, and how it is a necessary component to general intelligence.</p>
<blockquote>
<p>“Intelligence is more than just problem-solving. Intelligence is questioning the assumptions you’re presented with. Intelligence is the ability to question existing thought-constructs. If we don’t make that part of the simulation, all we’ll create is a really effective slave.”</p>
</blockquote>
<p>This viewpoint is built right into the game’s final decision, where after solving every puzzle, the player is commanded by an invisible, omnipresent voice calling itself Elohim to enter the “Gates of Eternity.” To get the true ending, the player must directly defy Elohim’s orders and ascend the Tower at the center of the map. This has strong parallels to the Book of Genesis, where God places Adam and Eve in Eden but forbids them from eating from a certain tree at the center of the garden. This decision is necessary to exit the simulation; obeying Elohim leads to a cutscene sequence where the program code reveals that the player has failed an “independence check,” resetting everything back to the beginning. This is where the game departs from Genesis in that instead of free will leading to the fall of man, it leads to freedom and the acquisition of humanity itself. Together, these quotes sum up the basic qualities that distinguish humans from other species: we possess a sense of curiosity that drives us to question and make sense of the world around us. As a component of the game’s narrative, they point to the ultimate goal of the simulation of producing a being that is capable of both reasoning and independence as well as hopefully helping instill those values in the robot itself.</p>
<p>The question of what determines human identity and whether one person is the same as another is another old problem of philosophy. Derek Parfit reflects on this question in his 1984 book Reasons and Persons. Parfit outlines the teletransportation paradox, which asks if a being with the exact same memories and atomic composition as you can be considered “yourself.” This question of identity is one of great significance for the researchers, for they must be sure their creation will carry forth the human psyche itself and not just a highly advanced but soulless clone. The second message conveyed by The Talos Principle is that the answer to this question is a resounding yes; that it is possible to continue humanity without Homo sapiens because as long as human memories are preserved and acknowledged, human identity is conserved. Drennan solemnly reflects on this belief the night it became known that the Earth was doomed.</p>
<blockquote>
<p>“On the first night, when I knew it was over, I went out to look at the stars. And I thought: somewhere up there are the stations we built and the probes we sent out, Voyager 1 and 2, beyond the edge of our solar system. Continuing their long journey through interstellar space like memories of our ambition, ambassadors who have outlived their homeland. And then I thought—if they still exist, are we really gone? If machines are an extension of the human body, then so long as they continue to function, we’re still here.”</p>
</blockquote>
<p>This monologue expresses a corollary of this belief; namely, that the existence of machines, which are a manifestation of the human experience, is enough to pass down the human heritage. If the continuity of memories is the sole prerequisite for the continuity of human identity, then Homo sapiens are not needed. In fact, Drennan never mentions anything about atomic structure, making her conception of identity even weaker than what Parfit and most people might formulate. This precludes the existence of a special spirit or soul that a being must possess in order to be considered human; various philosophers have formulated such theories, such as René Descartes’ Pure Ego (Parfit 210). But there is one other requirement; otherwise, instead of working on this project, the researchers would just try to archive as many artifacts as possible. One of Drennan’s early time capsules reveals the other component required for human preservation and how this philosophy was passed down to her on a trip to Pompeii.</p>
<blockquote>
<p>“At first I was amazed by the feeling of walking through an ancient city, but then I suddenly got scared. I realized that I was walking through a real place where real people had lived. People like myself with mothers and fathers and lives and hopes and dreams. And now it was all gone forever. I ran to my father crying and told him about this. And he said…’Yes, but we are here. So long as there are people in the streets, the past isn’t really gone.’”</p>
</blockquote>
<p>This viewpoint asserts two necessary pieces of human continuity. First is the existence of recorded history. The tragedy of Pompeii still lives on today because it is common knowledge all around the globe. The second is the remembrance of the past. It is not enough to memorialize history if there is no one to acknowledge it. The visitors reflecting on the ancient disaster are effectively keeping those citizens, long physically dead, spiritually alive. The necessity of an intelligent, curious being to appreciate our combined experiences is what compelled the researchers to undertake such a challenging project. These recordings lay out a philosophical framework in which machines can carry forth human existence while also remembering our legacy. To the player character, they assure that they are indeed human despite being abiotic.</p>
<p>For a misanthropist, our extinction would be a cause for celebration, and the Extended Lifespan Project, pointless. But The Talos Principle ends with the conviction that life is incredibly fragile and intensely beautiful. In fact, the title of the game refers to this very message. Talos was a giant bronze automaton in Greek mythology who died from loss of blood when his vein was punctured. What the Talos Principle actually is can be found in one of the text documents located on computer terminals around the world. The file is an email from Drennan, where she calls it an “old philosophical concept about the impossibility of avoiding reality—no matter what you believe, if you lose your blood, you will die.” (Another file attributes the principle to a Greek philosopher named Straton of Stageira. What is interesting is that there is no evidence outside of the game of this figure ever having existed in real life except for one obscure WordPress site run by an anonymous blogger, which can only be concluded to be a work of elaborate marketing by the developers.) The Talos Principle itself is a reflection on the fragility of life, and the game’s archives make that fragility painfully clear. Some of the most poignant terminal texts are records of various people living out their last days, knowing that the world is soon coming to an end. One blogger makes a final post saying he will go offline to spend time with his estranged family. Another email invites colleagues over for a LAN party. One particular file, in goosebump-triggering formality, reminds readers to release any pets before death.</p>
<blockquote>
<p>“PLEASE REMEMBER TO RELEASE YOUR PETS</p>
<p>While it’s true that not all pets will be able to adjust to living without you, many will manage, and the least you can do is give them a chance. Just remember:</p>
<ul>
<li>Release your pet before you become incapacitated.</li>
<li>
<p>If you notice any locked-in animals in your area, please take the time to free them.</p>
</li>
<li>
<p>Leaving the doors and windows of your home open will turn it into a useful shelter.</p>
</li>
<li>Setting out large quantities of dry food may help your pet through the transition period.”</li>
</ul>
</blockquote>
<p>All these records show that as everyone realized their days were numbered, they put aside trivial pursuits to tie up loose ends, take care of unfinished business, and spend time with their loved ones. The entire world had a mass realization that their life was soon going to end and therefore ought to focus on the things that matter. At the same time, the game makes the case that life is priceless and humans are precious.</p>
<blockquote>
<p>“If you’re looking through the Archive, you may find people from my time claiming that civilization doesn’t really matter. That we’d be better off dead. We have a lot of cynics like that. I hope they seem as absurd to you as they do to me. I hope you can find something in all those files—a song, a book, a movie, maybe a game—just something that you’ll love, that makes you realize how much poorer the universe would have been without it.”</p>
</blockquote>
<p>The motivation that drives Drennan’s research, all the way up to the very last day of her life, springs from her unwavering recognition of the inherent value of humanity. Thus, the game ends on the affirmation that life is a gift, and Drennan wishes to pass that on to the player. After all, one who has emerged upon the empty ruins of a once great civilization would benefit from the encouragement that life is worth living. As she states with confidence in her last time capsule recorded on her deathbed, “I can say…with absolute conviction…that it was good to be human.”</p>
<h2 id="works-cited">Works Cited</h2>
<p>“Apocalypse.” Dictionary.com, Dictionary.com, <a href="https://www.dictionary.com/browse/apocalypse">www.dictionary.com/browse/apocalypse</a>.</p>
<p>Bostrom, Nick. Superintelligence: Paths, Dangers, Strategies. Oxford University Press, 2017.</p>
<p>Croteam. The Talos Principle, Devolver Digital, 2014.</p>
<p>Holy Bible New International Version. Zondervan, 2005.</p>
<p>Parfit, Derik. Reasons and Persons. Clarendon, 1984.</p>
<p>Stratonfan. Straton of Stageira, 15 Aug. 2015, <a href="https://stratonofstageira.wordpress.com/">stratonofstageira.wordpress.com</a>.</p>
Hello, world! Double double standards, and Windy's Law2021-03-03T00:00:00+00:00https://danielzting.github.io/2021/03/03/hello-world-double-double-standards-and-windys-law<p>Hello, world! This is where I will write down any interesting thoughts I come across or things I’ve learned that might prove helpful to others.</p>
<p>The thing that inspired me to make this initial post was a <a href="https://news.ycombinator.com/item?id=25708050">comment</a> on the Hacker News discussion “Amazon, Apple, and Google Cut Off Parler.” User danhak accuses the people who defended the right of a bakery to refuse to make a gay wedding cake while trying to force private companies to host Parler of holding a double standard. User trident5000 replies that you can turn that on its head; the same people who wanted to compel the bakery to make a cake celebrating something they disagree with are now supporting the right of private companies to deplatform things they find objectionable. Replying to both, user WindyLakeReturn remarks:</p>
<blockquote>
<p>One thing that continues to bother me is that on almost any political issue (or even non-political issue) where people point out a double standard, you can easily reverse it and get a reciprocal double standard held by the side claiming the other has a double standard. Yet this is always ignored. By always, I mean I cannot think of a single case where I’ve seen the side doing the calling out realize that it was reversible on them. I have seen a number of memes from third parties calling out the two major sides as being reciprocal double standards, if that counts for anything.</p>
</blockquote>
<p>Regardless of where you stand on these issues, before pointing out a double standard, it is worth examining if a flipped (contrapositive?) version can be said of yourself. Never before has an Internet comment changed my way of thinking so much. I don’t think this phenomenon has a name, so I’ll call it Windy’s Law or something. It is my hope that doing so will make us all more aware of our internal biases. Having thoughtful conversations on the Internet is hard; we need every help we can get.</p>