# Hacker News Top 30 — 2026-04-25

Generated on 2026-04-25 03:22 UTC

## [HN-TITLE] 1. Google plans to invest up to $40B in Anthropic

- **Source**: [https://www.bloomberg.com/news/articles/2026-04-24/google-plans-to-invest-up-to-40-billion-in-anthropic](https://www.bloomberg.com/news/articles/2026-04-24/google-plans-to-invest-up-to-40-billion-in-anthropic)
- **Site**: Bloomberg
- **Author**: Julia Love, Shirin Ghaffary
- **Published**: 2026-04-24
- **HN activity**: 389 points · [426 comments](https://news.ycombinator.com/item?id=47892074)
- **Length**: 106 words (~1 min read)
- **Language**: en

April 24, 2026 at 3:50 PM UTC

Google will invest $10 billion in Anthropic PBC, with another $30 billion potentially to follow, strengthening the relationship between two companies that are at once partners and rivals in the race to build artificial intelligence.

Anthropic said that Google is committing to invest $10 billion now in cash at a $350 billion valuation, the same amount it was valued at in a funding round in February, not including the recent money raised. The Alphabet Inc.-owned company will invest another $30 billion if Anthropic hits performance targets, the startup said Friday, and support a significant expansion of Anthropic’s computing capacity.

---

## [HN-TITLE] 2. Paraloid B-72

- **Source**: [https://en.wikipedia.org/wiki/Paraloid\_B-72](https://en.wikipedia.org/wiki/Paraloid_B-72)
- **Site**: Wikimedia Foundation, Inc.
- **Author**: Contributors to Wikimedia projects
- **Published**: 2012-04-23
- **HN activity**: 44 points · [14 comments](https://news.ycombinator.com/item?id=47858939)
- **Length**: 496 words (~3 min read)
- **Language**: en

From Wikipedia, the free encyclopedia

Paraloid B-72

[![](https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Paraloid_B-72.png/250px-Paraloid_B-72.png)](https://en.wikipedia.org/wiki/File:Paraloid_B-72.png)

Names Other names

B-72  
Acryloid B-72 (obsolete)

Identifiers [ChemSpider](https://en.wikipedia.org/wiki/ChemSpider "ChemSpider")

- none

Properties [Density](https://en.wikipedia.org/wiki/Density "Density") 9.6 lb gal−1 (1.15 g cm−3) Hazards [Safety data sheet](https://en.wikipedia.org/wiki/Safety_data_sheet "Safety data sheet") (SDS) [MSDS](https://www.collectioncare.org/MSDS/b72MSDS.pdf)

Except where otherwise noted, data are given for materials in their [standard state](https://en.wikipedia.org/wiki/Standard_state "Standard state") (at 25 °C \[77 °F], 100 kPa).

[Infobox references](https://en.wikipedia.org/wiki/Wikipedia:Chemical_infobox#References "Wikipedia:Chemical infobox")

**Paraloid B-72** or **B-72** is a [thermoplastic](https://en.wikipedia.org/wiki/Thermoplastic "Thermoplastic") resin that was created by [Rohm and Haas](https://en.wikipedia.org/wiki/Rohm_and_Haas "Rohm and Haas") for use as a surface coating and as a vehicle for [flexographic ink](https://en.wikipedia.org/wiki/Flexographic_ink "Flexographic ink"). Subsequently, it has found popular use as an adhesive by [conservator-restorers](https://en.wikipedia.org/wiki/Conservator-restorer "Conservator-restorer"), specifically in the [conservation and restoration of ceramic objects](https://en.wikipedia.org/wiki/Conservation_and_restoration_of_ceramic_objects "Conservation and restoration of ceramic objects"), [glass objects](https://en.wikipedia.org/wiki/Conservation_and_restoration_of_glass_objects "Conservation and restoration of glass objects"),[\[1\]](#cite_note-1) the preparation of [fossils](https://en.wikipedia.org/wiki/Fossils "Fossils"), the hardening of piano hammers,[\[2\]](#cite_note-2)[\[3\]](#cite_note-3) and can also be used for labeling museum objects.[\[4\]](#cite_note-4)

[![](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Paraloid_b72.jpg/250px-Paraloid_b72.jpg)](https://en.wikipedia.org/wiki/File:Paraloid_b72.jpg)

Paraloid B-72 in pellet form

B-72 is a durable and non-yellowing acrylic resin, which can be described chemically as an [ethyl methacrylate](https://en.wikipedia.org/wiki/Ethyl_methacrylate "Ethyl methacrylate")–[methyl acrylate](https://en.wikipedia.org/wiki/Methyl_acrylate "Methyl acrylate") [copolymer](https://en.wikipedia.org/wiki/Copolymer "Copolymer"). It is soluble in [acetone](https://en.wikipedia.org/wiki/Acetone "Acetone"), [ethanol](https://en.wikipedia.org/wiki/Ethanol "Ethanol"), [toluene](https://en.wikipedia.org/wiki/Toluene "Toluene"), and [xylenes](https://en.wikipedia.org/wiki/Xylene "Xylene"), among other solvents and solvent mixtures.[\[5\]](#cite_note-5)

One of the major advantages of B-72 as a [consolidant](https://en.wiktionary.org/wiki/consolidant "wikt:consolidant") is that it is stronger and harder than [polyvinyl acetate](https://en.wikipedia.org/wiki/Polyvinyl_acetate "Polyvinyl acetate") without being extremely brittle. This adhesive is more flexible than many of the other typically used adhesives and tolerates more stress and strain on a join than most others. The major drawbacks to using B-72 are related to its handling properties: as in the case of other acrylic resins it is difficult to apply as an adhesive and to manipulate with precision.[\[6\]](#cite_note-Koob-6)

The most suitable solvent for B-72 is acetone. However, solvent mixtures with various proportions of acetone, ethanol, and toluene are frequently used to alter the working time of the resin and to produce slightly different properties (hardness and flexibility, e.g.) in the set resin. Unlike [cellulose nitrate](https://en.wikipedia.org/wiki/Cellulose_nitrate "Cellulose nitrate"), B-72 does not need additives like [plasticizers](https://en.wikipedia.org/wiki/Plasticizer "Plasticizer") to stabilize its durability. Fumed [colloidal silica](https://en.wikipedia.org/wiki/Colloidal_silica "Colloidal silica") can be added to help with the workability of the resin. Research shows that the silica better distributes the stress and strain that occurs during evaporation of a solvent and during the setting of the adhesive film.[\[6\]](#cite_note-Koob-6): p.9 

Because of its transparency and versatility, conservators, led by Stephen Koob of the [Corning Museum of Glass](https://en.wikipedia.org/wiki/Corning_Museum_of_Glass "Corning Museum of Glass"), have recently begun to use cast sheets of B-72 as a fill material in glass objects.[\[7\]](#cite_note-Corning-7)

1. [**^**](#cite_ref-1) [Paraloid B-72: Museum of Fine Arts Boston: Material Database](http://cameo.mfa.org/wiki/Paraloid_B-72)
2. [**^**](#cite_ref-2) [Paraloid B-72 in Voicing Pianos: How and Where to Apply It, How and Where to Get it, and What Effects Can Be Achieved](https://medium.com/@eathankeyboards/paraloid-b-72-in-voicing-pianos-how-and-where-to-apply-it-how-and-where-to-get-it-and-what-b8a4b321578)
3. [**^**](#cite_ref-3) [Paraloid B72 as hammer hardner](https://my.ptg.org/communities/community-home/digestviewer/viewthread?GroupId=43&MessageKey=df4be278-2582-4410-8d05-8fbdcf3cc412&CommunityKey=6265a40b-9fd2-4152-a628-bd7c7d770cbf&tab=digestviewer&ReturnUrl=%2Fcommunities%2Fcommunity-home%2Fdigestviewer%3FListKey%3D2bb4ebe8-4dba-4640-ae67-111903beaddf)
4. [**^**](#cite_ref-4) ["Use Of Acryloid B-72 Lacquer For Labeling Museum Objects"](https://www.nps.gov/museum/publications/conserveogram/01-04.pdf) (PDF). *www.nps.gov/*. Retrieved 2017-01-10.
5. [**^**](#cite_ref-5) Phenix, A. 1992. Solvents for Paraloid B-72. Conservation News 48:21–3.
6. ^ [***a***](#cite_ref-Koob_6-0) [***b***](#cite_ref-Koob_6-1) Koob, Stephen (30 April 1986). "The Use of Paraloid B-72 as an adhesive. Its application for archaeological ceramics and other materials". *Studies in Conservation*. **31**: 7–14. [doi](https://en.wikipedia.org/wiki/Doi_%28identifier%29 "Doi (identifier)"):[10.1179/sic.1986.31.1.7](https://doi.org/10.1179%2Fsic.1986.31.1.7).
7. [**^**](#cite_ref-Corning_7-0) von Giffen, Astrid (1 November 2011). ["Filling losses with Paraloid B-72"](https://web.archive.org/web/20121205225451/http://www.cmog.org/blog/2011/11/01/filling-losses-with-paraloid-b-72/). *The Corning Museum of Glass*. Archived from [the original](http://www.cmog.org/blog/2011/11/01/filling-losses-with-paraloid-b-72/) on 2012-12-05. Retrieved 22 April 2012.

---

## [HN-TITLE] 3. Humpback whales are forming super-groups

- **Source**: [https://www.bbc.com/future/article/20260416-the-humpback-super-groups-swarming-the-seas](https://www.bbc.com/future/article/20260416-the-humpback-super-groups-swarming-the-seas)
- **Site**: BBC
- **Author**: Katherine Latham
- **Published**: 2026-04-21
- **HN activity**: 31 points · [7 comments](https://news.ycombinator.com/item?id=47858294)
- **Length**: 1.8K words (~9 min read)
- **Language**: en-GB

4 days ago

Katherine Latham

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com A super-group of humpback whales at the surface (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndst9z.jpg.webp)ChrisFallows.com

(Credit: ChrisFallows.com)

When you have 200 humpback whales or so close to each other, says Monique Fallows, their blows appear "like a Manhattan skyscraper skyline". 

Humpbacks dive to feed then resurface for air. Bursting from their enormous lungs at [over 300mph](https://www.cwr.org.au/whale-behaviours-anatomy-of-a-blow.html) (483km/h), a humpback whale's blow can rise up to 7m (23ft) into the air. "The sound is like a big bellows," says Monique, a nature photographer and [author](https://www.lapa.co.za/sandi-and-the-salty-sea-dogs), who has documented humpback super-groups multiple times.

The smell is also strong for anyone nearby. "You feel the breath of the whales falling on you," says Monique. "The whales burp and fart all the time – on a ginormous scale. The smell is pungent. It's very fishy."

Over two days in December 2025, Monique and her husband, [fine art photographer](https://www.chrisfallows.com/) Chris Fallows, photographed several different humpback "super-groups" off the west coast of South Africa. The couple captured 208 individual humpback whales on the 29 December, and a whopping 304 the following day. That, says Chris, is the greatest number of large whales ever identified in one day in our planet's history. 

"This truly is a testament to their recovery," says Chris.

Intense industrial whaling during the 20th Century virtually wiped humpbacks out, leaving [less than 5%](https://www.fisheries.noaa.gov/species/humpback-whale) of pre-whaling numbers in the ocean. But 40 years ago, a [global whaling moratorium](https://www.fisheries.noaa.gov/international-affairs/international-whaling-commission) came into force and populations began to recover.

Today, while some humpback populations [remain endangered](https://www.fisheries.noaa.gov/species/humpback-whale) and for others the [rate of recovery is uncertain](https://speciesstatus.sanbi.org/assessment/last-assessment/2125/), globally humpbacks [are on the rise](https://iwc.int/about-whales/population-status). In the southern hemisphere, humpbacks have shown a strong recovery, with an increase in numbers of [up to 12% per year](https://iwc.int/about-whales/population-status). Now, South Africa's recent humpback super-group bonanza could indicate a turning point in the [resurgence of the humpback whale](https://pmc.ncbi.nlm.nih.gov/articles/PMC8536746/).

Sightings of super-groups – defined as [groups of 20 or more](https://pmc.ncbi.nlm.nih.gov/articles/PMC5332018/) humpback whales that are within five body-lengths of their nearest neighbour – [are also skyrocketing](https://onlinelibrary.wiley.com/doi/10.1111/mms.70018).

Experts aren't yet sure [why we're seeing this sudden surge](https://onlinelibrary.wiley.com/doi/10.1111/mms.70018) in these gatherings. It could be a change in prey availability, or an increase in the numbers of whales elsewhere prompting exploration of new feeding strategies or areas. Or perhaps this is something they've always done, but only now are we witnessing it as populations recover.

Being surrounded by such a huge number of inconceivably large animals is "a complete sensory overload", says Monique. "They're exhaling all the time. When you first smell it, you're like 'Oh, God, what is that?'"

You can see the moment Monique and Chris Fallows met a super-group of humpbacks in the video below.

Humpbacks usually only come together in small groups to feed or mate, spending much of their lives in solitude. During the austral summer months, however, [upwelling of cold, nutrient-rich water](https://www.earthdata.nasa.gov/data/instruments/czcs/classic-scenes/benguela-upwelling-zone) from the deep ocean leads to enormous [blooms of phytoplankton](https://pace.oceansciences.org/data_images_more.htm?id=564) and the whales' favourite meal of euphausiids – or krill – follows. That's when super-groups now [come to feed](https://www.nature.com/articles/s41598-021-00253-2).

Humpback whales live in all the world's oceans. Each year, they make some of the most epic migrations of any mammal on the planet, [as far as 5,000 miles (8,000km](https://www.fisheries.noaa.gov/species/humpback-whale)[)](https://www.fisheries.noaa.gov/species/humpback-whale), from the warm waters of their breeding grounds to colder water where they feed. In the process they [transport huge amounts of nutrients](https://www.nature.com/research-intelligence/nri-topic-summaries/humpback-whale-ecology-and-population-dynamics-micro-104525) across the globe, which plays a vital role in the health of marine ecosystems. 

As we got closer those breaches sounded like huge bombs going off as the 40-plus tonne whales leapt out and then crashed into the ocean – Chris Fallows

The recovery of humpbacks is "really the conservation success story", says Simon Elwen, a marine biologist at the University of Stellenbosch, South Africa. "It is phenomenal."

In just five years, from 2015 to 2020, humpback super-group sightings off South Africa's west coast soared [from 10 to 65 per year](https://onlinelibrary.wiley.com/doi/10.1111/mms.70018).

When Elwen's team was doing surveys in the early 2000s, "we were so excited to see one or two whales", he says. Today, seeing a single whale during the summer months is no longer a novelty – it is not seeing multiple whales that is rare. "Seeing groups of hundreds of whales in a day is perfectly normal these days – because we've had that exponential growth," says Elwen. "It still catches us by surprise sometimes."

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com "It's like a Manhattan skyscraper skyline of all the blows," says photographer Monique Fallows (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndsv66.jpg.webp)ChrisFallows.com

"It's like a Manhattan skyscraper skyline of all the blows," says photographer Monique Fallows (Credit: ChrisFallows.com)

Elwen likens the humpback population explosion to the [spread of a virus](https://www.worldometers.info/coronavirus/worldwide-graphs/). "You have the very long, slow period of not much happening, but there is growth and then it eventually goes 'whoop!' and increases rapidly."

It was on the brink of New Year when Monique and Chris, together with their three dogs, slept in sheltered anchorage on the deck of their small boat in the hopes of photographing a super-group. They had heard from whale watching companies in the area that whales were gathering, and their aim was to photograph as many as possible of them and then submit their images to [Happywhale](http://happywhale.com/), a citizen science project that aims to photo-ID marine mammals globally.

The couple awoke at 03:45, before sunrise, in pursuit of the best possible lighting for their subjects. Then, in the mist of early morning, they set off. 

They moved slowly, turning the boat's engine off every few minutes to "simply drift and listen", says Chris. "When the whales breach you can hear them from miles away, so we would head in that direction. As we got closer those breaches sounded like huge bombs going off as the 40-plus tonne whales leapt out and then crashed into the ocean."

It's so chaotic, and so crazy. There are whales everywhere – Ted Cheeseman

Next came the smell of the whales' breath drifting on the sea breeze. Then the sound of deep exhalation. "We heard the breathing of over a hundred whales," says Chris, "followed by the visual confirmation of flukes, flippers and bodies."

Now, the game was on.

Chris was concentrating on capturing beautiful fine art photography, although both he and Monique had fun competing to see who could take the most identification shots. 

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com Monique and Chris Fallows competed to see who could take the most identification shots (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndsvwm.jpg.webp)ChrisFallows.com

Monique and Chris Fallows competed to see who could take the most identification shots (Credit: ChrisFallows.com)

"We didn't go out with the intention of breaking a record," says Chris. "There were just so many whales around us. Monique and I were laughing, because there was just so much going on that you didn't even know what to photograph. It was like rapid fire."

The 512 whales they photographed over two days contained some duplicates, bringing the total number of individual animals identified over the course of their trip to 372.

This was a "phenomenal number of whales", says Ted Cheeseman, a marine conservationist who founded Happywhale in 2015. Of those 372, only a handful had been previously photographed, he adds, and only in the past few years.

**More like this:**

• [The whale graveyards that transform the deep sea](https://www.bbc.com/future/article/20260225-the-whale-graveyards-that-transform-the-deep-sea)

[]()• [The strange deep-sea creatures that eat whales](https://www.bbc.com/future/article/20260311-the-strange-deep-sea-creatures-that-eat-whales)

[]()• [The 'mind bomb' photos that led to a global whaling ban](https://www.bbc.com/future/article/20241002-how-greenpeaces-mindbomb-photos-stopped-the-commercial-whaling-industry)

"We don't know the age of the whales. We use their sighting history as a proxy for age," says Cheeseman, "but I think the great majority of those whales are under 10 years old – I say that with great confidence – and a high percentage may be under five years old.

"These are new whales. This is a population rebounding."

## **Humpback mayhem**

Imagine hundreds of [10-tonne toddlers](https://a-z-animals.com/blog/humpback-whale-size-comparison-just-how-big-do-they-get/) all trying to fetch themselves a snack, all at the same time. That's what these super-groups resemble, says Cheeseman. "It's so chaotic, and so crazy. There are whales everywhere."

Happywhale lets members of the public upload their images of whale tail flukes or dorsal fins. Then, using advanced AI image recognition, the project identifies individual animals. It now hosts a collection of almost [1.5 million photographs](https://happywhale.com/home), allowing scientists to track the health and population status of species and observe migration patterns across oceans.

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com Chris and Monique Fallows broke the record for photographing the most whales in one day (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndswlt.jpg.webp)ChrisFallows.com

Chris and Monique Fallows broke the record for photographing the most whales in one day (Credit: ChrisFallows.com)

Usually, humpbacks hunt in a [highly sophisticated, social way](https://www.bbc.co.uk/reel/video/p0hp5111/listen-to-world-s-first-chat-between-humans-and-whales). Take "bubble net feeding", for example. This is where humpbacks blow bubble nets around fish to trap them in a small space. The whales appear to coordinate their movements using complex communication. "If you could hear what's going on underwater while they're feeding – I mean, it makes my skin tingle. You can literally hear the whale say, 'Okay, it's time. Go.' And then all the whales come up together." *(*[*Watch whales bubble net feed and listen to them chat on BBC Reel*](https://www.bbc.co.uk/reel/video/p0hp5111/listen-to-world-s-first-chat-between-humans-and-whales)*.)*

During super-group aggregations, however, "there are so many fish, and whales are going everywhere", says Cheeseman. "[It's just mayhem](https://onlinelibrary.wiley.com/doi/full/10.1111/mms.70096)." 

The age profile of the group adds to the apparent chaos. "Younger animals don't necessarily have the same physiological capacity as adult animals," says Jennifer Jackson, a marine ecologist at the British Antarctic Survey, the UK's polar research institute. Plus, the chosen hunting technique will depend on what prey they're chasing, as well as ocean conditions and the depth at which they are hunting.

"Humpbacks are an incredibly adaptable species," says Jackson. "They can even prey switch and feed on different things, depending on what's around."

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com "These are new whales. This is a population rebounding," says Ted Cheeseman (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndswqp.jpg.webp)ChrisFallows.com

"These are new whales. This is a population rebounding," says Ted Cheeseman (Credit: ChrisFallows.com)

When their prey is near to the surface of the water, humpbacks will often engage in intense [lunge feeding](https://www.adfg.alaska.gov/static/viewing/pdfs/whale_behaviors.pdf). This is where the whale propels itself at speed through a concentrated ball of krill or fish, scooping them up in its cavernous mouth. If it lunges vertically, the whale's head will rise straight up out of the water.

This is the hunting technique Elwen observed when [studying South Africa's humpback super-groups](https://onlinelibrary.wiley.com/doi/pdf/10.1111/mms.70096%2523) in 2015. After breaching the surface of the water, rising upwards at near vertical angles, the whales often surfaced with their mouths still full of water.

"We had a group the other day of 100-plus animals," says Elwen. "They were very vocal, making a constant array of sounds." So, Elwen suggests what may appear as chaos is, in fact, "controlled chaos". "They know exactly what they're doing," he says.

![](https://static.files.bbci.co.uk/bbcdotcom/web/20260409-151157-6d668e92bf-web-3.1.0-1/grey-placeholder.png)![ChrisFallows.com The marks on a humpback's tail can tell the story of its life (Credit: ChrisFallows.com)](https://ichef.bbci.co.uk/images/ic/480xn/p0ndswv3.jpg.webp)ChrisFallows.com

The marks on a humpback's tail can tell the story of its life (Credit: ChrisFallows.com)

More whales is good news for [ocean health, climate change mitigation and human food security](https://www.bbc.co.uk/future/article/20210119-why-saving-whales-can-help-fight-climate-change). Yet, even as [humpbacks increase in abundance](https://www.bbc.co.uk/future/article/20240913-from-hawaii-to-the-salish-sea-climate-change-is-putting-the-humpback-whale-conservation-comeback-at-risk) across much of the world, the species still faces [threats](https://www.fisheries.noaa.gov/species/humpback-whale) from entanglement in fishing gear, vessel strikes, underwater noise pollution and [warming seas](https://www.bbc.co.uk/future/article/20240913-from-hawaii-to-the-salish-sea-climate-change-is-putting-the-humpback-whale-conservation-comeback-at-risk).

Chris and Monique have spent over three decades exploring the ocean and say they have seen "seismic changes". Now, Chris says, they are already planning their next visit "to the most incredible whale gathering on the planet".

--

*For essential climate news and hopeful developments to your inbox, sign up to the* [*Future Earth newsletter,*](https://cloud.email.bbc.com/FutureEarth_Newsletter_Signup?&at_bbc_team=studios&at_medium=Onsite&at_objective=acquisition&at_ptr_name=bbc.com&at_link_origin=futurearticle&at_campaign=futureearth&at_campaign_type=owned&&) *while* [*The Essential List*](https://cloud.email.bbc.com/SignUp10_08?&at_bbc_team=studios&at_medium=Onsite&at_objective=acquisition&at_ptr_name=bbc.com&at_link_origin=featuresarticle&at_campaign=essentiallist&at_campaign_type=owned) *delivers a handpicked selection of features and insights twice a week.*

*For more science, technology, environment and health stories from the BBC, follow us on* [*Facebook*](https://www.facebook.com/BBCFuture/) *and* [*Instagram*](https://www.instagram.com/bbcfuture_official/)*.*

---

## [HN-TITLE] 4. My audio interface has SSH enabled by default

- **Source**: [https://hhh.hn/rodecaster-duo-fw/](https://hhh.hn/rodecaster-duo-fw/)
- **Site**: hhh.hn
- **Submitter**: hhh (Hacker News)
- **Submitted**: 2026-04-24 19:30 UTC (Hacker News)
- **HN activity**: 177 points · [49 comments](https://news.ycombinator.com/item?id=47894747)
- **Length**: 933 words (~5 min read)
- **Language**: en

last year i bought a [Rodecaster Duo](https://rode.com/en-us/products/rodecaster-duo) to solve some audio woes to allow myself and my girlfriend to have microphones to our respective computers when gaming together and talking on discord in the same room without any echo, and to be able to swap that to my work pc easily. the rodecaster is really nice, it's pretty effortless to use and works great for our home. I would gladly recommend it to anyone looking for a similar solution.

![A home desk setup tucked under a sloped ceiling. Two monitors sit side by side — the left one dark, the right one displaying a document next to a Spotify 'Liked Songs' playlist — with a Rode podcast microphone on a boom arm and a rectangular key light mounted above. A Lego WALL-E set is stacked on boxes to the right of the center monitor. The right side of the desk holds a pink mechanical keyboard, black headphones, and a glowing cat-shaped lamp, with warm fairy lights draped across the wooden desktop. A wire wall shelf in the upper right is packed with colorful plush toys and Sonny Angel figures.](https://hhh.hn/images/desk.JPG)

as is usual for any device in my house, i try to ensure when it's time to update the firmware I have enough tooling in place to capture how firmware updates work, or to at a minimum capture a firmware blob to try and reverse engineer it and poke around for fun and/or to see the often horrific reality that is the industry we work in.

### fw update

I was feeling pretty lazy and assumed that rode would dump the firmware somewhere on my computer before flashing the device, so i set up Instruments on macos to capture disk activity, and found where the fw was dumped, and surprisingly it was just a gzipped tarball. The device I did this update on happened to have the ability to write to USB disks disabled, so the update actually failed.

Poking around a bit, i found the binaries of the stuff that actually runs on the device, as well as a shell script that handles the updates themselves. there are two partitions on the disk, so that if you brick one it boots from the other. It also doesn't have any signature checks or anything on the incoming firmware. I'm used to *many* vendors of this style of device requiring signed firmwares or something nowadays, kind of nice to actually own a device I can modify. I also noticed that ssh seemed to be enabled by default, and plugged in an ethernet cable and saw that ssh indeed is enabled w/ pubkey auth only. Here are the keys that are added by default:

```
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCX/bCFTDgViuPvdFL/VMMVRrw9b5S8HcDQk17qoCEYwmI+IIG8rEAsLiaeCOwyhf9IN+8/LRaN0Z5ZfU3WMbmsKEg8zd1Yvqq74nFbhO47vbtzmCi9S4ucIKkBEVOyvyN5lt9hWf5t5nZSmlfldZK3Pem5y8wHM5A+K/gSnzp4gwQ1QYfFb068uQ+ciIdOhb8SkUs8CwzotglIbp19I6ZmXmsNj/TmpbUf5rMfUAf1gysZ5j1UdRWrvWVh5daqvZRsBBPbXEeJfDU3Nr3HR14XYt9mgexrz/5oyKSj/lQYLmh9cDfsxvkGNIQ8fF9l+n2L1KZM4lLgiGk4KFBjQHaIBZx9OebCiiZCO4NTJUBDk9a+SZpiDiipADV07s7vTInYyFA6GrmKtnq3M6upT4WJBvVuL/BMnK5yY1RZtoqox2/pcCg2rH5S1GIy0v0HFJisl7kWInlaG2mdsaCx19wAjCFe/qT9LyxjQ6+0rArI55/JJFDkNeMjrewRQwNdASjCox8vqXCBfjvsR9qv70/ywcymgsnLAnq2LuYg5FYwMMDYOvVnhACC+BYTdNDTn5oeMIjQCUenY/DPCHpJkf4YOf3YCMUTEU9tExhtwW/X+m21hS3+STLtTfqbUeg9CeuPQZgfl9vc65n3tMxAdlEGEDoTaNMAgr2TzJv92Ka9iQ==
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDaNyzPfIcEeQsfzyQs/wyX6mX52kiS+4eNHfCaxFlgj
```

since our update failed, i swapped to a windows pc and set up wireshark with USBPcap and then ran the update thru the rodecaster app. I roughly glanced at the pcap file and looked at where the update started, since it was a lot of traffic as I was also using it for audio on another computer. I wrote down the packet numbers I thought were interesting and threw them to claude code to dig thru the pcap while I was doing other stuff.

A bit later (cc only took 10 minutes or so but I was busy for a while) I came back to a breakdown of the structure, and a python script to manually update a device. The RODECaster App sends some HID control stuff to the device, one command to enter the update mode ([the 'M' command](https://gist.github.com/nicewrld/1e06ec264556b1148eea0323ea9db93b)), and then another ([the U command](https://gist.github.com/nicewrld/c9e67acfdc88ac5dd4bfca4390ae0921)) to trigger the update. Both are just single ASCII characters sent over [HID report](https://docs.kernel.org/hid/hidintro.html) 1.

I am but a yaml-writing slave and sometimes a below-average ghidra user, and don't often interact with hardware devices, so getting some help from CC in discovery was useful, as well as pointing me to resources to actually learn more about hid devices.

The structure was pretty simple, you send the m command, and then copy archive.tar.gz and archive.md5 (obviously just with md5sum of the archive) onto the newly exposed disk. then you send the U command to trigger the flashing itself.

so the flow is:

1. plug in the rodecaster and power it on (or vice versa)
2. send the 'M' command
3. mount the disk and copy `archive.tar.gz` and `archive.md5` to it
4. chmod 777 both of them because i dont care to figure out how to do it properly
5. unmount the disk
6. send the 'U' command
7. wait for the thing to reboot into your new firmware

### custom firmware

I was still working from my mac, and wanted to create some cfw to be able to ssh into the device, so i just [used a container](https://gist.github.com/nicewrld/2d45e7a4f8b18ae9a9d5288b3fec8720) to enable password authentication for ssh (don't shoot me) as well as add my own pubkey to the authorized keys, and dump out an archive for me to flash. you don't really need much to actually flash the device, see [here](https://gist.github.com/nicewrld/5773f284ae3f5d548fc00659323f63d8) (example of the functions its not really much to add the rest.)

run your script to flash the thing and bingo bongo you can ssh to it

![A terminal window showing an SSH session to root@192.168.2.2. After logging in, `uname -a` is run and returns: 'Linux rodecaster-pro-ii-mini 5.10.17-rt32-yocto-preempt-rt-rode+ #1 SMP PREEMPT_RT Tue Oct 28 06:52:56 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux', confirming a root shell on the Rodecaster device.](https://hhh.hn/images/rodecaster.png)

![rodecaster-top](https://hhh.hn/images/rodecaster-top.png)

### conclusion

I was really surprised that I could actually flash firmware so easily to this, and it is really nice to own a device. It's a really nice piece of kit and just kinda blends into the background and I never have to think about it. I don't really know why ssh was enabled, or why it had this key added by default, but I submitted a ticket to RODE for this as I could not find an obvious security email to report to. I did not hear back, but I will watch to see if future firmware updates change anything.

It's been a few months since i've done anything with this, and I am trying to just dump out my thoughts into a notepad and only very lightly edit it and then just poast. I really love all of the RODE stuff I have, and yet again just want to buy more gear.

if you want to ask me questions about this or have any questions, you can reach me with the primary letter of this domain, at this domain.

thanks computer, until next time

---

## [HN-TITLE] 5. Iliad fragment found in Roman-era mummy

- **Source**: [https://www.thehistoryblog.com/archives/75877](https://www.thehistoryblog.com/archives/75877)
- **Site**: thehistoryblog.com
- **Submitter**: wise\_blood (Hacker News)
- **Submitted**: 2026-04-22 14:18 UTC (Hacker News)
- **HN activity**: 126 points · [35 comments](https://news.ycombinator.com/item?id=47864056)
- **Length**: 309 words (~2 min read)
- **Language**: en-US

[![](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Mummy-wrapped-in-geometric-patterns-430x210.jpg "Mummy wrapped in geometric patterns. Photo courtesy the Egyptian Ministry of Tourism and Antiquities.")](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Mummy-wrapped-in-geometric-patterns.jpg)A papyrus fragment of Homer’s epic The Iliad has been [discovered inside the wrappings of Roman-era mummy](https://egyptian-gazette.com/egypt/spanish-mission-uncovers-roman-era-tomb-in-egypts-bahnasa/). The mummy was found in a necropolis the ancient site of Oxyrhynchus (modern-day El-Bahnasa) in Egypt’s Minya Governorate. Archaeologists were able to remove the papyrus and identify the text as the “Index of Ships,” a description of the Greek forces arrayed against Troy from Book 2 of the Iliad.

[![](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Gold-tongue-200x160.jpg "Gold tongue. Photo courtesy the Egyptian Ministry of Tourism and Antiquities.")](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Gold-tongue.jpg)A joint Spanish-Egyptian team from the University of Barcelona and the Institute of the Ancient Near East unearthed a number of mummies from the Roman-era necropolis, some in wooden coffins, some wrapped in bandages decorated with geometric patterns, three with gold tongues and one with a copper tongue placed inside their mouths. A few of the deceased had traces of gold leaf that had been applied to them after mummification.

[![](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Mummies-with-gold-leaf-200x267.jpg "Mummies with gold leaf remains. Photo courtesy the Egyptian Ministry of Tourism and Antiquities.")](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Mummies-with-gold-leaf.jpg)Additional gold and copper tongues were found in the excavation of tomb number 65. Deteriorated mummified remains were unearthed in a hypogeum (underground chamber) of the tomb, revealing the tongue inserts. There were also several painted wooden coffins, but they too are poorly preserved as a result of the tomb have been looted in antiquity.

There were also finds in the older section of the cemetery.

> Professor Mohamed Abdel-Badi, Head of the Egyptian Antiquities Sector at the Supreme Council of Antiquities, explained that excavations east of Ptolemaic Tomb No. 67 revealed a trench containing three limestone burial chambers.
> 
> [![](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Wooden-coffins-200x267.jpg "Wooden coffins. Photo courtesy the Egyptian Ministry of Tourism and Antiquities.")](http://www.thehistoryblog.com/wp-content/uploads/2026/04/Wooden-coffins.jpg)These chambers housed the cremated remains of adults and an infant, as well as animal remains, notably cats, wrapped in cloth.
> 
> The team also discovered a collection of small terracotta and bronze statues, including representations of the god Harpocrates and a figure of Cupid. \[…]
> 
> For his part, Hisham el-Leithy, Secretary-General of the Supreme Council of Antiquities, added that the site offers valuable insights into burial traditions in Bahnasa during the Greek and Roman eras.

---

## [HN-TITLE] 6. Sabotaging projects by overthinking, scope creep, and structural diffing

- **Source**: [https://kevinlynagh.com/newsletter/2026\_04\_overthinking/](https://kevinlynagh.com/newsletter/2026_04_overthinking/)
- **Site**: kevinlynagh.com
- **Submitter**: alcazar (Hacker News)
- **Submitted**: 2026-04-24 14:28 UTC (Hacker News)
- **HN activity**: 371 points · [93 comments](https://news.ycombinator.com/item?id=47890799)
- **Length**: 2.7K words (~12 min read)
- **Language**: en

Hi friends,

I’ll be attending [Babashka Conf](https://babashka.org/conf/) on May 8 and [Dutch Clojure Days](https://clojuredays.org/) on May 9. If you’re attending either (or just visiting Amsterdam), drop me a line!

When I have an idea for a project, it tends to go in one of these two directions:

1. I just do it. Maybe I make a few minor revisions, but often it turns out exactly how I’d imagined and I’m happy.
2. I think, “I should look for prior art”. There’s a lot of prior art, dealing with a much broader scope than I’d originally imagined. I start to wonder if I should incorporate that scope. Or perhaps try to build my thing on top of the existing sorta-nearby-solutions. Or maybe I should just use the popular thing. Although I could do a better job than that thing, if I put a bunch of time into it. But actually, I don’t want to maintain a big popular project, nor do I want to put that much time into this project. Uh oh, now I’ve spent a bunch of time, having neither addressed the original issue nor experienced the joy of creating something.

I prefer the first outcome, and I think the pivotal factor is how well I’ve internalized my own success criteria.

For example, last weekend I hosted my friend Marcin and we decided it’d be fun to do some woodworking, so we threw together this shelf and 3d-printed hangers for my kitchen:

![a black shelf with a painted orange/pink edge and Ikea food bins hanging off the bottom](https://kevinlynagh.com/newsletter/2026_04_overthinking/shelf.jpg)

Absolute banger of a project:

- brainstormed the design over coffee
- did a few 3d-print iterations for the Ikea bin hangers ([OnShape CAD](https://cad.onshape.com/documents/ffd05cea283ba8ac162ddde1/w/f63670037dee41f664a23b76/e/4258465c9a6965b596cb054b), if you want to print your own)
- used material leftover from [my workbench](https://kevinlynagh.com/newsletter/2026_01_tools/#workshop-space)
- rounded the corner by eye with a palm sander
- sealed the raw plywood edge with some leftover paint from a friend
- done in a weekend

The main success criteria was to jam on woodworking with a friend, and that helped me not overthink the object-level success criteria: Just make a shelf for my exact kitchen!

In contrast, this past Friday I noticed [difftastic](https://github.com/Wilfred/difftastic/) did a poor job, so I decided to shop around for structural/semantic diff tools and related workflows (a topic I’ve never studied, that I’m increasingly interested in as I’m reviewing more and more LLM-generated code).

I spent 4 hours over the weekend researching existing tools (see [my notes below](#structural-diffing)), going through dark periods of both “semantic tree diffing is a PhD-level complex problem” and “why do all of these have MCP servers? I don’t want an MCP server”, before I came to my senses and remembered my original success criteria: I just want a nicer diffing workflow for myself in Emacs, I should just build it myself — should take about 4 hours.

I’m cautiously optimistic that, having had this realization and committing myself to a minimal scope, I’ll be able to knock out a prototype before running out of motivation.

However, other long-running interests of mine:

- interfaces for prototyping hardware (discussed [September 2023](https://kevinlynagh.com/newsletter/2023_09_hardware_prototyping/#interfaces-for-prototyping-hardware))
- a programming language that fuses what I like about Clojure and Rust ([November 2023](https://kevinlynagh.com/newsletter/2023_11_programming_languages/))
- a programming language for CAD ([constraints](https://kevinlynagh.com/newsletter/2025_10_bed/#a-simple-constraint-language), [bidirectional editing](https://kevinlynagh.com/newsletter/2025_07_flatpack/#lsp-bidirectional-editing-cad-conceptual-models), [other dubious ideas](https://kevinlynagh.com/newsletter/2025_04_towards_the_cutest_neural_network/#dubious-ideas-for-a-code-cad-language))

seem to be deep in the well of outcome #2.

That is, I’ve spent hundreds of hours on background research and little prototypes, but haven’t yet synthesized anything that addresses the original motivating issue.

It’s not quite that I *regret* that time — I do love learning by reading — but I have a nagging sense of unease that my inner critic (fear of failure?) is silencing my generative tendencies, keeping me from the much more enjoyable (and productive!) learning by *doing*.

I think in these cases the success criteria has been much fuzzier: Am I trying to replace my own usage of Rust/Clojure? Only for some subset of problems? Or is it that I actually just need a playground to learn about language design/implementation, and it’s fine if I don’t end up using it?

Ditto for CAD: Am I trying to replace my commercial CAD tool in favor of my own? Only for some subset of simple or particularly parametric parts? Do I care if it’s useful for others? Does my tool need to be legibly different from existing open-source tools?

It’s worth considering these questions, sure. But at the end of the day, I’d much rather have *done* a lot than have only *considered* a lot.

So I’m trying to embrace my inner clueless 20-year-old and *just do things* — even if some turn out to be “obviously bad” in hindsight, I’ll still be coming out ahead on net =D

## [Conservation of scope creep](#conservation-of-scope-creep)

Of course, there’s only so much time to “just do things”, and there’s a balance to be had. I’m not sure how many times I’ll re-learn YAGNI (“you ain’t gonna need it”) in my career, but I was reminded of it again after writing a bunch of code with an LLM agent, then eventually coming to my senses and throwing it all out.

I wanted a [Finda](https://keminglabs.com/finda/)-style filesystem-wide fuzzy path search for Emacs. Since I’ve built (by hand, typing the code myself!) this exact functionality before (walk filesystem to collect paths, index them by trigram, do fast fuzzy queries via bitmap intersections), I figured it’d only take a few hours to supervise an LLM to write all the code.

I started with a “plan mode” chat, and the LLM suggested a library, [Nucleo](https://github.com/helix-editor/nucleo), which turned up since I wrote Finda (10 years ago, eek!). I read through it, found it quite well-designed and documented, and decided to use it so I’d get its smart case and Unicode normalization functionality. (E.g., query `foo` matches `Foo` and `foo`, whereas query `Foo` *won’t* match `foo`; similarly for `cafe` and `café`.)

Finding a great library wasn’t the problem, the problem was that Nucleo also supported some extra functionality: anchors (`^foo` only matches at the beginning of a line).

This got me thinking about what that might mean in a corpus that consists entirely of file paths. Anchoring to the beginning of a line isn’t useful (everything starts with `/`), so I decided to try and interpret the anchors with respect to the path *segments*. E.g., `^foo` would match `/root/foobar/` but not `/root/barfoo/`.

But to do this efficiently, the index needs to keep track of segment boundaries so that the query can be checked against each segment quickly.

But then we *also* need to handle a slash occurring in an anchored query (e.g., `^foo/bar`) since that wouldn’t get matched when only looking at segments individually (`root`, `foo`, `bar`, and `baz` of a matching path `/root/foo/bar/baz/`).

Working through this took several hours: first throwing around design ideas with an LLM, having it write code to wrap Nucleo’s types, then realizing its code was bloated and didn’t spark joy, so finally writing my own (smaller) wrapper.

Then, after a break, I realized:

1. I can’t think of a situation where I’d ever wished Finda had anchor functionality
2. In a corpus of paths, I can anchor by just adding `/` to the start or end of a query (this works for everything except anchoring to the end of a filename).

So I tossed all of the anchoring code.

I’m *pretty* sure I still came out ahead compared to if I’d tried to write everything myself sans LLM or discussion with others, but I’m not certain.

Perhaps there’s some kind of conservation law here: Any increases in programming speed will be offset by a corresponding increase in unnecessary features, rabbit holes, and diversions.

## [Structural diffing](#structural-diffing)

Speaking of unnecessary diversions, let me tell you everything I’ve learned about structural diffing recently — if you have thoughts/feelings/references in this space, I’d love to hear about ‘em!

When we’re talking about code, a “diff” usually means a summary of the line-by-line changes between two versions of a file. This might be rendered as a “unified” view, where changed lines are prefixed with `+` or `-` to indicate whether they’re additions or deletions. For example:

![](https://kevinlynagh.com/newsletter/2026_04_overthinking/unified.png)

We’ve removed `coffee` and added `apple`.

The same diff might also be rendered in a side-by-side view, which can be easier to read when there are more complex changes:

![](https://kevinlynagh.com/newsletter/2026_04_overthinking/side_by_side.png)

The problem with these line-by-line diffs is that they’re not aware of higher-level structure like functions, types, etc. — if some braces match up *somehow* between versions, they might not be shown at all, even if the braces “belong” to different functions.

There’s a wonderful tool, [difftastic](https://github.com/Wilfred/difftastic/), which tries to address this by calculating diffs using [treesitter](https://tree-sitter.github.io/)-provided concrete syntax trees. It’s a huge improvement over line-based diffs, but unfortunately it doesn’t always do a great job matching entities between versions.

Here’s the diff that motivated this entire foray:

![](https://kevinlynagh.com/newsletter/2026_04_overthinking/difftastic.png)

Note that it doesn’t match up `struct PendingClick`, it shows it deleted on the left and added on the right.

I haven’t dug into *why* difftastic fails to match here, but I do feel like it’s wrong — even if the overall diff would be longer, I’d still rather see `PendingClickRequest` and `PendingClick` matched up between both sides.

Here’s a summary of tools / references in the space:

- The most “baked” and thoughtful semantic diff tool I found is, perhaps unsurprisingly, [semanticdiff.com](https://semanticdiff.com/blog/semanticdiff-vs-difftastic/), a small German company with a free VSCode plugin and [web app that shows diffs for github PRs](https://app.semanticdiff.com/). Unfortunately they don’t have any code libraries I can use as a foundation for the workflow I want.
  
  - this [semanticdiff vs. difftastic](https://semanticdiff.com/blog/semanticdiff-vs-difftastic/) blog post covers a lot of great details (including that difftastic [doesn’t even show semantically meaningful indentation changes in python](https://github.com/Wilfred/difftastic/issues/587) !!!)
  - one of the authors has great HN comments with hard-won background knowledge. E.g., they [moved away from treesitter](https://news.ycombinator.com/item?id=42094842) because it’s unreliable for semantics:
  
  > Context-sensitive keywords in particular were a constant source of annoyance. The grammar looks correct, but it will fail to parse because of the way the lexer works. You don’t want your tool to abort just because someone named their parameter “async”.
- [diffsitter](https://github.com/afnanenayet/diffsitter)
  
  - built on treesitter, has MCP server. README includes list of similar projects.
  - lots of github stars, but doesn’t seem particularly well-documented; I couldn’t find an explanation of how it works, but the difftastic wiki says it “runs longest-common-subsequence on the leaves of the tree”
- [gumtree](https://github.com/GumTreeDiff/gumtree)
  
  - research / academic origin in 2014
  - requires Java, so no-go for my use case of a quick tool I can use via Emacs
- [mergiraf](https://mergiraf.org/architecture.html): treesitter-based merge-driver written in rust
  
  - [very nice architecture overview](https://mergiraf.org/architecture.html); tool uses Gumtree algorithm
  - docs and adorable illustrations indicate this project was clearly written by a thoughtful human
  - semanticdiff.com author in [HN comments](https://news.ycombinator.com/item?id=42094842): &gt; GumTree is good at returning a result quickly, but there are quite a few cases where it always returned bad matches for us, no matter how many follow-up papers with improvements we tried to implement. In the end we switched over to a dijkstra based approach that tries to minimize the cost of the mapping
- [weave](https://github.com/Ataraxy-Labs/weave): also a treesitter-based merge-driver written in Rust
  
  - feels a bit “HN-optimized” (flashy landing pages, lots of github stars, MCP server, etc.)
  - I looked into their entity extraction crate, [sem](https://github.com/Ataraxy-Labs/sem)
  - core diffing code is OK but pretty wordy
  - greedy entity matching algorithm
  - data model can’t detect intra-file moves, even though those might be significant
  - includes a lot of heuristic “impact” analysis, which feels like overreaching-scope to me since it’d require much tighter language integration before I’d trust it
    
    - ran into buggy output when running `sem diff --verbose HEAD~4`; it showed lines as having changed that…didn’t change at all.
  - Too much 80%-done, hypothetically useful functionality for me to use as a foundation, but props for sure to the undergrad/student(?) who’s built all this in just three months.
- [diffast](https://github.com/codinuum/diffast): tree edit-distance of ASTs based on an algorithm from a 2008 academic paper.
  
  - supports “Python, Java, Verilog, Fortran, and C/C++ via dedicated parsers”
  - has a nice [gallery of example AST differences](https://codinuum.github.io/gallery-cca/)
  - can export info in tuples for datalog
- [autochrome](https://fazzone.github.io/autochrome.html): Clojure-specific diffs based on dynamic programming
  
  - excellent visual explanation and example walkthrough
- Tristan Hume has a great article on [Designing a Tree Diff Algorithm Using Dynamic Programming and A\*](https://thume.ca/2017/06/17/tree-diffing/)

My primary use case is reviewing LLM output turn-by-turn — I’m very much in-the-loop, and I’m not letting my agent (or dozens of them, lol) run wild generating 10k+ lines of code at a time.

Rather, I give an agent a scoped task, then come back in a few minutes and want to see an overview of what it did and then either revise/tweak it manually in Emacs or throw the whole thing out and try again (or just write it myself).

The workflow I want, then, is to

- see a high-level overview of the diff: what entities (types/functions/methods) were added/removed/changed?
- quickly see textual diffs on an entity-by-entity basis (“expanding” parts of the above summary)
- quickly edit any changes, without having to navigate elsewhere (i.e., do it inline, rather than having to switch from “diff” to “file)

Basically, I want something like [Magit’s workflow for reviewing and staging changes](https://magit.vc/screenshots/#staging-changes), but on an entity level rather than file/line level.

In light of the "minimal scope, just get your project done” lesson I’ve just re-learned for the nth time, my plan is to:

- throw together my own treesitter-based entity extraction framework (just Rust for now)
- do some simple greedy matching for now
- render the diff to the command line

Once that seems reasonable (i.e., it does a better job than difftastic did on that specific commit), I’ll:

- wire into a more interactive Magit-like Emacs workflow (maybe I can reuse Magit itself!?!)
- add support for new languages, as I need them
- potentially explore more sophisticated score-based global matching rather than simple greedy matching

*Mayyybe* if I’m happy with it I’ll end up releasing something. But I’m not trying to collect Github stars or HN karma, so I might just happily use it in the privacy of my own home without trying to “commercialize it”.

After all, sometimes I just want a shelf.

## [Misc. stuff](#misc-stuff)

- I’m in the market for a few square meters of Tyvek or other translucent, non-woven material suitable for building a light diffuser — let me know if you have any favorite vendors that can ship to the EU.
- [How They Made This - Coinbase Commercial Breakdown](https://www.youtube.com/watch?v=zzpLW3PdNEg). Crypto is a negative-sum parasite on productive economic activity, but has the silver lining of funneling a lot of capital to weird creative folks.
- [A tail-call interpreter in (nightly) Rust](https://www.mattkeeter.com/blog/2026-04-05-tailcall/)
- [The Easiest Way To Design Furniture…](https://www.youtube.com/watch?v=Y_CwRw0VJ-U). Laura Kampf on getting off the computer and designing physical spaces with tape, lil’ wood sticks, and cardboard.
- [Hotel California - Reimagined on the Traditional Chinese Guzheng](https://www.youtube.com/watch?v=gf6v59c5yuY)
- [C is *not* a low-level language](https://spawn-queue.acm.org/doi/pdf/10.1145/3212477.3212479): Your computer is not a fast PDP-11.
- [I Taught My Dog to Vibe Code Games - Caleb Leak](https://www.calebleak.com/posts/dog-game/)
- [Loon is a Lisp](https://campedersen.com/loon). Thrilled to discover I’m not the only one who wants to mash together Clojure and Rust. The current implementation seems to have been manically vibe-coded and I quickly ran into some terrible bugs, but on the other hand it *exists* so I’m not going to be a hater.
- [Made a print in place box so I can easily hand out printed bees🐝](https://www.reddit.com/r/3Dprinting/comments/1r3ngj0/made_a_print_in_place_box_so_i_can_easily_hand/). “Im quite content with the result, the bees fit snugly and the box opens and closes nicely”
- [Yeast-based vaccines are a big deal for biosecurity](https://moreisdifferent.blog/p/yeast-based-vaccines-are-a-big-deal)
- “There isn’t a lot of reliable information out there about how to buy a gas mask, especially for the specific purpose of living under state repression. But hopefully after reading [this guide](https://www.theverge.com/policy/868571/best-gas-masks) you’ll feel equipped to make an educated decision.”
- “This [zoomable map shows every page of every issue of BYTE](https://byte.tsundoku.io/) starting from the front cover of the first issue (top left) to the last page of the final edition (bottom right). The search bar runs RE2 regex over the full text of all 100k pages.” A lovely reminder that user-interfaces can be extremely fast *and* information dense.
- [How Every* Milk Product Is Made](https://www.youtube.com/watch?v=LeUZmojH7p8)
- [In Favour of Trying New Things – Daniel Frank](https://danfrank.ca/in-favour-of-trying-new-things/)
- [Staring into the abyss as a core life skill](https://www.lesswrong.com/posts/vzfz4AS6wbooaTeQk/staring-into-the-abyss-as-a-core-life-skill)

---

## [HN-TITLE] 7. Education must go beyond the mere production of words

- **Source**: [https://www.ncregister.com/commentaries/schnell-repairing-the-ruins](https://www.ncregister.com/commentaries/schnell-repairing-the-ruins)
- **Site**: National Catholic Register
- **Author**: Santiago Schnell
- **Published**: 2026-04-13
- **HN activity**: 24 points · [2 comments](https://news.ycombinator.com/item?id=47897349)
- **Length**: 1.4K words (~7 min read)
- **Language**: en

COMMENTARY: In an era when AI can write anything, authentic education must go beyond the mere production of words.

“The end then of Learning,” wrote John Milton in 1644, “is to repair the ruines of our first Parents.” The image is hard to improve: education as repair, as recovery, as the restoration of capacities diminished by sin and neglect. 

Four centuries later, in the age of generative artificial intelligence (AI), that image has become urgent again — because we are now surrounded by a technology that offers to perform, on demand, much of what we had long assumed education required us to do ourselves.

I came across Milton’s passage by chance while browsing a collection of the English writer’s works and opening it to his 1644 tract [*Of Education*](https://milton.host.dartmouth.edu/reading_room/of_education/text.shtml). Milton was not writing about algorithms. Yet he saw with unusual clarity the educational error that AI now magnifies: the confusion of language with learning. 

Language, he wrote, is “but the instrument conveying to us things useful to be known.” He warned against mistaking command of words for possession of the solid things those words are meant to disclose. He joined language to substance, sequence to maturation, and study to direct contact with reality — principles that four centuries have not made less urgent.

No technology in recent memory has so enlarged the instrument. Large language models such as ChatGPT can summarize books, draft essays, organize research notes, translate passages, generate code, and imitate the prose that schools and universities have long taken as evidence of education. 

Used with discipline, they can be genuinely useful. A professor may use them to prepare discussion questions. A researcher may use them to survey literature more quickly. An administrator may use them to accelerate routine writing. It would be foolish to deny their utility.

But utility is not the same as education, and AI magnifies an older weakness. It tempts us to mistake verbal fluency for understanding itself. A student can submit polished prose without having really grappled with the question. A researcher can produce a competent summary without having seen the problem clearly. A professional can sound informed without having formed a judgment. The danger is not only dishonesty — it is substitution. 

For Catholic education, that substitution matters because learning is not the production of acceptable performances but the formation of a person capable of truth, judgment and responsibility.

Milton saw a version of this in his own day. He criticized the practice of demanding “Themes, Verses and Orations” from young students before their minds had been formed by “long reading and observing.” He objected to asking for finished performances before the underlying powers had matured. 

Generative AI industrializes exactly that pedagogical mistake. It supplies finished language before the student has undergone the reading, questioning, hesitation and revision that make language meaningful. What Milton regarded as a mistake of sequence, AI turns into a system.

This matters because education is not built from answers alone. Every answer worth teaching was once a response to a question someone genuinely asked. 

Students do not assimilate knowledge merely by receiving conclusions — they must be brought into the question. That is why the principal agent of education is the student. No one can learn in another’s place. A tool may assist instruction; it cannot do the learning for the student.

The teacher’s role accordingly becomes more important in the age of AI, not less. A real teacher is not merely a distributor of content. A real teacher is an experienced guide in inquiry: someone who knows what the student has not yet seen, what distinctions must be made, what confusion needs exposing, and what question should come next. The best classroom is not a transfer of information from one container to another. It is a living act of thought. That is why seminar, disputation, laboratory, tutorial and serious conversation retain their force even when information itself becomes cheap.

We tend to celebrate knowledge: facts accumulated, results confirmed, information stored. But as the biologist Stuart Firestein has argued, discovery begins not only with what we know but with a disciplined sense of what we do not yet understand. That frontier is where large language models reach their limit. They can reorganize the archive with astonishing fluency, but they cannot inhabit uncertainty, pose a genuinely new question, or take responsibility for truth.

This clarifies why certain acts cannot be delegated to machines without ceasing to occur at all. Attending carefully to a text, weighing conflicting evidence, judging whether a conclusion is warranted, taking responsibility for what one claims — these are not ancillary tasks. They are the work by which a mind is formed. 

No machine can perform them in our place — not because machines lack processing power, but because these acts have no effect unless a person performs them. Their purpose is not to produce an output. It is to form the one who does them.

Education worthy of the name has always understood this. Its end is not the delivery of content, however accurate. It is the formation of persons capable of judgment, attention and intellectual honesty. That formation requires a genuine encounter with difficulty — the friction of a hard text, the resistance of a problem that does not yield quickly, the discomfort of revising what one believed. It requires embodiment as much as intellect: reading slowly, speaking in one’s own voice, accepting the cost of standing behind one’s words. A person does not become capable of truth by managing information alone. Wisdom is formed in contact with reality, not in its simulation.

The deepest challenge of AI in education is therefore not academic integrity, though that problem is real. It is whether we will allow our schools and universities to define learning as the production of acceptable outputs. If that is our standard, outsourcing will always look like efficiency. But if education is the formation of judgment, substitution becomes self-defeating.

What should institutions do? The answer is neither panic nor blanket prohibition. It is pedagogical redesign. More writing done in class. More oral defense of arguments. More seminars organized around live questions rather than passive downloads of information. More laboratory and studio work in which students must explain not only what a result shows but what it does not. 

When students use AI, one reasonable requirement is transparency: disclose what was asked, what the system produced, what was kept, what was rejected, and why. The point is not surveillance. It is intellectual ownership — the habit of standing behind one’s own thinking. Institutions should also reinvest in the teacher-scholar whose presence, judgment and intellectual seriousness cannot be automated.

The same commitment belongs at home. A dinner table free of devices, conversation across generations, reading aloud together, and the habit of asking children not only what they think but why — these are small schools of freedom. They teach that education is not the production of impressive sentences. It is the formation of honest minds.

The moment we are living through is, in this light, less a crisis than a clarification. AI has not created new educational problems; it has made old ones impossible to ignore. The habit of rewarding performance over understanding, fluency over depth, and polish over genuine engagement was already present in our institutions before the first language model was trained. AI simply industrializes and accelerates those habits until their emptiness becomes undeniable.

That may be its most unexpected gift. If this disruption forces us to recover what education was always for — the formation of minds capable of real questions, careful judgment, and responsibility for truth — then the age of AI may prove, paradoxically, to be an age of educational renewal. 

Milton’s deeper claim presses further. The end of learning is not merely competence or civic virtue, but to “know God aright, to love Him, to imitate Him, to be like Him.” Education, in that view, participates in the restoration of what sin has obscured.

No machine will ever repair those ruins. That restoration is finally God’s work before it is ours; yet, aided by grace, we must still undertake the human labor of attention, judgment and love.

*Santiago Schnell is provost and professor of mathematics at Dartmouth, with adjunct appointments in biochemistry and cell biology, and biomedical data science at the Geisel School of Medicine. A mathematical biologist by training, he also writes on the Catholic intellectual tradition, the philosophy of science, and the mission of Catholic higher education.*

---

## [HN-TITLE] 8. The Classic American Diner

- **Source**: [https://blogs.loc.gov/picturethis/2026/04/the-classic-american-diner/](https://blogs.loc.gov/picturethis/2026/04/the-classic-american-diner/)
- **Site**: blogs.loc.gov
- **Submitter**: NaOH (Hacker News)
- **Submitted**: 2026-04-24 19:01 UTC (Hacker News)
- **HN activity**: 170 points · [112 comments](https://news.ycombinator.com/item?id=47894435)

> scrape failed: http 403

---

## [HN-TITLE] 9. There Will Be a Scientific Theory of Deep Learning

- **Source**: [https://arxiv.org/abs/2604.21691](https://arxiv.org/abs/2604.21691)
- **Site**: arXiv.org
- **Author**: \[Submitted on 23 Apr 2026]
- **Submitted**: 2026-04-24 18:06 UTC (Hacker News)
- **HN activity**: 162 points · [52 comments](https://news.ycombinator.com/item?id=47893779)
- **Length**: 322 words (~2 min read)
- **Language**: en

Authors:[Jamie Simon](https://arxiv.org/search/stat?searchtype=author&query=Simon%2C%20J), [Daniel Kunin](https://arxiv.org/search/stat?searchtype=author&query=Kunin%2C%20D), [Alexander Atanasov](https://arxiv.org/search/stat?searchtype=author&query=Atanasov%2C%20A), [Enric Boix-Adserà](https://arxiv.org/search/stat?searchtype=author&query=Boix-Adser%C3%A0%2C%20E), [Blake Bordelon](https://arxiv.org/search/stat?searchtype=author&query=Bordelon%2C%20B), [Jeremy Cohen](https://arxiv.org/search/stat?searchtype=author&query=Cohen%2C%20J), [Nikhil Ghosh](https://arxiv.org/search/stat?searchtype=author&query=Ghosh%2C%20N), [Florentin Guth](https://arxiv.org/search/stat?searchtype=author&query=Guth%2C%20F), [Arthur Jacot](https://arxiv.org/search/stat?searchtype=author&query=Jacot%2C%20A), [Mason Kamb](https://arxiv.org/search/stat?searchtype=author&query=Kamb%2C%20M), [Dhruva Karkada](https://arxiv.org/search/stat?searchtype=author&query=Karkada%2C%20D), [Eric J. Michaud](https://arxiv.org/search/stat?searchtype=author&query=Michaud%2C%20E%20J), [Berkan Ottlik](https://arxiv.org/search/stat?searchtype=author&query=Ottlik%2C%20B), [Joseph Turnbull](https://arxiv.org/search/stat?searchtype=author&query=Turnbull%2C%20J)

[View PDF](https://arxiv.org/pdf/2604.21691) [HTML (experimental)](https://arxiv.org/html/2604.21691v1)

> Abstract:In this paper, we make the case that a scientific theory of deep learning is emerging. By this we mean a theory which characterizes important properties and statistics of the training process, hidden representations, final weights, and performance of neural networks. We pull together major strands of ongoing research in deep learning theory and identify five growing bodies of work that point toward such a theory: (a) solvable idealized settings that provide intuition for learning dynamics in realistic systems; (b) tractable limits that reveal insights into fundamental learning phenomena; (c) simple mathematical laws that capture important macroscopic observables; (d) theories of hyperparameters that disentangle them from the rest of the training process, leaving simpler systems behind; and (e) universal behaviors shared across systems and settings which clarify which phenomena call for explanation.  
> Taken together, these bodies of work share certain broad traits: they are concerned with the dynamics of the training process; they primarily seek to describe coarse aggregate statistics; and they emphasize falsifiable quantitative predictions. We argue that the emerging theory is best thought of as a mechanics of the learning process, and suggest the name learning mechanics. We discuss the relationship between this mechanics perspective and other approaches for building a theory of deep learning, including the statistical and information-theoretic perspectives. In particular, we anticipate a symbiotic relationship between learning mechanics and mechanistic interpretability.  
> We also review and address common arguments that fundamental theory will not be possible or is not important. We conclude with a portrait of important open directions in learning mechanics and advice for beginners. We host further introductory materials, perspectives, and open questions at [this http URL](http://learningmechanics.pub).

## Submission history

From: Daniel Kunin \[[view email](https://arxiv.org/show-email/077401f5/2604.21691)]  
**\[v1]** Thu, 23 Apr 2026 13:58:12 UTC (3,519 KB)

---

## [HN-TITLE] 10. Work with the garage door up (2024)

- **Source**: [https://notes.andymatuschak.org/Work\_with\_the\_garage\_door\_up](https://notes.andymatuschak.org/Work_with_the_garage_door_up)
- **Site**: Andyʼs working notes
- **Submitter**: jxmorris12 (Hacker News)
- **Submitted**: 2026-04-21 17:13 UTC (Hacker News)
- **HN activity**: 131 points · [102 comments](https://news.ycombinator.com/item?id=47851613)
- **Length**: 543 words (~3 min read)

One of my favorite ways that creative people communicate is by “working with their garage door up,” to riff on a passage from Robin Sloan (below). This is the opposite of the Twitter account which mostly posts announcements of finished work: it’s [Screenshot Saturday](https://twitter.com/hashtag/screenshotsaturday?lang=en); it’s giving a lecture about the problems you’re pondering in the shower; it’s thinking out loud about the ways in which your project doesn’t work at all. It’s so much of Twitch. I want to see the process. I want to see you trim the artichoke. I want to see you choose the color palette. [Anti-marketing, after Michael Nielsen](https://notes.andymatuschak.org/zF9ywLHqHfN5rFuPApiyqmP).

I love this kind of communication personally, but I suspect it also creates more invested, interesting followings over the long term. That effect’s probably related to [Working on niche, personally-meaningful projects brings weirder, more serendipitous inbounds](https://notes.andymatuschak.org/zHV89H7dqnrvNvwXHBSGog9).

It’s also a way to avoid the problems described in [Pitching out corrupts within](https://notes.andymatuschak.org/z4ehFvVvhxoMGtBudLpDoeN). You’re not pitching. You’re just showing your work, day over day.

[Maggie Appleton](https://notes.andymatuschak.org/Maggie_Appleton) [argues](https://maggieappleton.com/gathering-structures#start-small-and-simple):

> [If]() you ever needed another reason to learn in public by digital gardening or podcasting or streaming or whathaveyou, add on that people will assume you're more competent than you are. This will get you invites to very cool exclusive events filled with high-achieving, interesting people, even though you have no right to be there. A+ side benefit.  
> This matches my experience.

* * *

## References

The inspiration from Robin’s original newsletter:  
☄️ Week 43, popular, wide-ranging, functional (link broken as of [2024-12-17](https://notes.andymatuschak.org/2024-12-17))

> I wish starting physical businesses was easier; I wish the path wasn’t so steep, especially in places like the Bay Area; because I think it’s one of the absolute best things a person can do. Among many other things, a physical business enlivens public space, by making the simple, eloquent statement: I am here, working.
> 
> There’s a scientific glassblowing studio north of us; I walk past it on the sidewalk often. By simply existing, and having a nice sign that faces the street, they are doing a small public service every day. We are here, working.
> 
> In the same light industrial complex as the Murray Street Media Lab, there’s a woodworking shop, and the man who runs it always keeps his door propped open. Simple as that. What a delight, every damn day, to ride my bike past that door and peek inside and see all his tools, the boards stacked up for whatever commission he’s undertaking. I am here, working.
> 
> Part of the problem of social media is that there is no equivalent to the scientific glassblowers’ sign, or the woodworker’s open door, or Dafna and Jesse’s sandwich boards. On the internet, if you stop speaking: you disappear. And, by corollary: on the internet, you only notice the people who are speaking nonstop.
> 
> If you could put on magic internet goggles that enabled you to see through this gnarly selection bias and view the composition of reality fairly, correctly—well, just come walk around Emeryville and West Berkeley. It would look like that! All the tumult of Twitter would shrink into a single weird cafe—just a speck, in an enormous city made up entirely of people quietly working.

Interesting to note that in a way, Robin’s looking for [Peripheral vision](https://notes.andymatuschak.org/z4geAr5cERWdJPrdhU5gy3N) in this aspiration.

Last updated 2024-12-17.

---

## [HN-TITLE] 11. Firefox Has Integrated Brave's Adblock Engine

- **Source**: [https://itsfoss.com/news/firefox-ships-brave-adblock-engine/](https://itsfoss.com/news/firefox-ships-brave-adblock-engine/)
- **Site**: It's FOSS
- **Author**: Sourav Rudra
- **Published**: 2026-04-24
- **HN activity**: 39 points · [6 comments](https://news.ycombinator.com/item?id=47897891)
- **Length**: 476 words (~3 min read)
- **Language**: en

[![Warp Terminal](https://itsfoss.com/assets/images/warp.webp)](https://www.warp.dev?utm_source=its_foss&utm_medium=display&utm_campaign=linux_launch)

Back in March, [Firefox 149](https://www.firefox.com/en-US/firefox/149.0/releasenotes/?ref=itsfoss.com) was released with many changes, like a free built-in VPN, a *Split View* that allows the loading of two pages side by side, and the XDG portal file picker as the new default on Linux.

However, an interesting addition had gone mostly unnoticed until now.

## Firefox has Some Brave in it now

![this picture shows a closed bug on mozilla's bugzilla instance, titled "add a prototype rich content blocking engine"](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-patch.png)

[Shivan Kaul Sahib](https://www.linkedin.com/in/shivankaulsahib/?ref=itsfoss.com), the VP of Privacy and Security at [Brave](https://brave.com/?ref=itsfoss.com), has put out [a blog post](https://shivankaul.com/blog/firefox-bundles-adblock-rust?ref=itsfoss.com) about something that didn't make it into the Firefox 149 release notes at all. The browser now ships [adblock-rust](https://github.com/brave/adblock-rust?ref=itsfoss.com), **Brave's open source Rust-based ad and tracker blocking engine**.

The change landed via Bugzilla [Bug 2013888](https://bugzilla.mozilla.org/show_bug.cgi?id=2013888&ref=itsfoss.com), which was filed and handled by Mozilla engineer [Benjamin VanderSloot](https://www.linkedin.com/in/benjamin-vandersloot/?ref=itsfoss.com). The bug is titled "*Add a prototype rich content blocking engine,*" and keeps the engine disabled by default with no user interface or filter lists included.

For informational purposes, [adblock-rust](https://github.com/brave/adblock-rust?ref=itsfoss.com) is the engine behind Brave's native content blocker (*aka ad blocker*). It is written in **Rust** and licensed under **MPL-2.0**, handling network request blocking, cosmetic filtering, and features a [uBlock Origin](https://github.com/gorhill/ublock?ref=itsfoss.com)-compatible filter list syntax.

Shivan also mentions that [Waterfox](https://www.waterfox.com/?ref=itsfoss.com), the popular Firefox fork, [has adopted adblock-rust](https://github.com/BrowserWorks/waterfox/issues/4182?ref=itsfoss.com), building directly upon Firefox's own implementation.

## Want to test it?

Before starting, head to [Enhanced Tracking Protection](https://support.mozilla.org/en-US/kb/enhanced-tracking-protection-firefox-desktop?ref=itsfoss.com)'s shield icon in the address bar and turn it off for the website you will be testing this with. This way, adblock-rust is doing the work, not Firefox's existing feature.

🚧

I suggest testing this experimental feature on a throwaway installation of Firefox.

Now open a new tab and go to `about:config`. Accept the warning when it shows up. Search for `privacy.trackingprotection.content.protection.enabled` and set it to "***true***" by clicking on the toggle. 👇

![firefox screenshot that shows the about:config page with this search result: privacy.trackingprotection.content.protection.enabled](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-config-1.png)

![](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-config-2.png)

![](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-config-3.png)

Next, search for `privacy.trackingprotection.content.protection.test_list_urls`, click on the "Edit" button, and paste the following value to add the [EasyList](https://easylist.to/?ref=itsfoss.com) and [EasyPrivacy](https://github.com/easylist/easylist/tree/master/easyprivacy?ref=itsfoss.com) filter lists to Firefox:

`https://easylist.to/easylist/easylist.txt|https://easylist.to/easylist/easyprivacy.txt`

Remember to click on the blue-colored "*Save*" button before moving on.

![](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-demo-1.png)

![](https://itsfoss.com/content/images/2026/04/firefox-adblock-rust-demo-2.png)

*Left: advertisement shown; Right: advertisement blocked*

Now visit a site with known ads, like [Yahoo](https://www.yahoo.com/?ref=itsfoss.com) (*as I did above*). If it's working, ad slots will still render in the page layout, but the actual ad content will be blocked. In my test, the banner on Yahoo came up showing only the text "*Advertisement*" with the advert bit stripped out.

Support independent Linux journalism! If you think we are doing a good job at helping people use Linux on their personal computers, support us by opting for Plus membership.

Here's what you get with It's FOSS Plus membership:

✅ 5 Free eBooks on Linux, Docker and Bash  
✅ Ad-free reading experience  
✅ Badges in the comment section and forum  
✅ Support creation of educational Linux materials

[Join It's FOSS Plus](https://itsfoss.com/membership/)

About the author

[![Sourav Rudra](https://itsfoss.com/content/images/size/w100/2026/01/2025-pfp-1-1.jpg)](https://itsfoss.com/author/sourav/)

## [Sourav Rudra](https://itsfoss.com/author/sourav/)

A nerd with a passion for open source software, custom PC builds, motorsports, and exploring the endless possibilities of this world.

---

## [HN-TITLE] 12. How to be anti-social – a guide to incoherent and isolating social experiences

- **Source**: [https://nate.leaflet.pub/3mk4xkaxobc2p](https://nate.leaflet.pub/3mk4xkaxobc2p)
- **Site**: nate.leaflet.pub
- **Submitter**: calcifer (Hacker News)
- **Submitted**: 2026-04-24 10:48 UTC (Hacker News)
- **HN activity**: 317 points · [298 comments](https://news.ycombinator.com/item?id=47888372)
- **Length**: 222 words (~1 min read)
- **Language**: en

- if someone is confusing or upsetting you, assume they have no sane reason for doing or saying what they are doing or saying
- when ambiguous, assume intent is malicious, ignorant, or amoral. interpret others' actions in the context of your fears
- do not challenge or acknowledge the existence or influence of your assumptions, wholly trust your intuition and feelings
- pivot conversations when someone challenges your assumptions or cites reasoning outside your wheelhouse. avoid displaying a lack of knowledge in any domain – this is seen as weakness
- if you must ask questions, imply the correctness of your originally held position by wording your question suggestively
- dig in your heels when confronted with overwhelming dissent
- exploit your immediate network; when the obvious merits of your narrative are exhausted, present like-minded people with tastefully curated details of your interactions with detractors, to provide a more appropriate account that your supporters can rally around to crush any lingering threats to your narrative
- do not research or consider the record, acumen or credentials of those with whom you speak, unless you agree with them
- do not grant grace to those who make mistakes1, especially those that you have never met or otherwise spoken to
- when all hope is lost in conversation, retreat into your self
- do not seek to understand those you do not already understand

---

## [HN-TITLE] 13. Google Flow Music

- **Source**: [https://www.flowmusic.app/](https://www.flowmusic.app/)
- **Site**: Google Flow Music
- **Submitter**: hmokiguess (Hacker News)
- **Submitted**: 2026-04-24 21:01 UTC (Hacker News)
- **HN activity**: 121 points · [97 comments](https://news.ycombinator.com/item?id=47895765)
- **Length**: 213 words (~1 min read)

## Starters

What will you make?

Everything you need to create, publish, and share. All in one place.

Create

### Your new

Chat with Producer just like you're in a studio. Create full-length songs with rich musicality and dynamic vocals. Go deep on every detail with our latest frontier music model, Lyria 3.

![SILICA](https://www.flowmusic.app/marketing/songs/silica.png)

SILICA

Experimental

![High Tide](https://www.flowmusic.app/marketing/songs/high-tide.png)

High Tide

Electronica

![The River's Debt](https://www.flowmusic.app/marketing/songs/the-rivers-debt.png)

The River's Debt

R&B

AI Music Video

AI Music Video

Music Videos

### Direct your own

Use the latest Veo video model to bring your sound to life. You control the characters, aesthetics, and every detail. No camera crew needed.

Build

### Vibe-code

Build anything you can dream up. Audio plugins, music games, custom DAWs. Your space, your code.

flowmusic.app/space/piano

#### Mini Keyboard

Hover to play

Grand Piano

C

D

E

F

G

A

B

C

![Fog Harbor](https://www.flowmusic.app/marketing/songs/fog-harbor.png)

Fog Harbor

12 songs

![Golden Afternoon](https://www.flowmusic.app/marketing/songs/golden-afternoon.png)

Golden Afternoon

8 songs

![Crystal Sky](https://www.flowmusic.app/marketing/songs/crystal-sky.png)

Crystal Sky

15 songs

Share

### what you make

Create playlists, publish your songs, follow your favorite artists, and discover new music every day.

Aesthetic

### Google Flow Music learns your style, and gets better with

The more you create, the more Flow Music understands your sound. Personalized to you from day one.

Personalization

Remix your audio

Audio effects

Stem split

Daily credits

And so much more

## Ready to ?

Free to start · No credit card required

---

## [HN-TITLE] 14. Email could have been X.400 times better

- **Source**: [https://buttondown.com/blog/x400-vs-smtp-email](https://buttondown.com/blog/x400-vs-smtp-email)
- **Site**: Buttondown
- **Author**: Matthew Guay
- **Published**: 2026-04-17
- **HN activity**: 134 points · [136 comments](https://news.ycombinator.com/item?id=47873323)
- **Length**: 2.6K words (~12 min read)
- **Language**: en

If the history of email had gone somewhat differently, the last email you sent could have been rescinded or superseded by a newer version when you accidentally wrote the wrong thing. It could have been scheduled to arrive an hour from now. It could have auto-destructed if not read by midnight.

You would never have needed to type “as per my previous message.” Instead, you could have linked emails together into a personal Wikipedia of correspondence. You could have messaged an entire organization or department, with your email app ensuring the message was deliverable before it left your outbox.

And you could have attached files and written a multilingual message with letters beyond ASCII’s 128 characters, eight years before those features came to internet email. You could have been notified when the message was read a full 15 years before email had something similar tacked on. Encryption would have been baked in from the start, rather than waiting for PGP, S/MIME, and TLS to add them later.

All that, and more, was standardized in the 1984 spec for X.400 as *Interpersonal Messaging*. It was everything we call *email* today, and then some.

“We had a better system back in the day: X.400,” as one commentator [reminisced](https://news.ycombinator.com/item?id=10405681). SMTP, the *Simple Mail Transfer Protocol* that became the standard behind how modern email is sent, “didn’t win because it was ‘better,’” he argued, but “just because it was easier to implement. Like a car with no brakes or seatbelts.”

“Of all the things OSI has produced, one could point to X.400 as being the most successful,” [agreed Marshall T. Rose](http://opentranscripts.org/transcript/geek-of-the-week-marshall-t-rose/), a developer who helped bridge the differences between X.400 and SMTP email. Differences like X.400 email addresses with [bang path-esque](https://buttondown.com/blog/email-source-routing-history) addresses like `C=no; ADMD=; PRMD=uninett; O=uninett; S=alvestrand; G=harald` while SMTP email addresses looked like `Harald.Alvestrand@uninett.no`.

“On the other hand,” he concluded, “that’s kind of like saying that World War II was the successful conclusion of the Great Depression.”

## Come, let us build a standard

![X.400 in Exchange Server](https://buttondown.com/next-assets/img/blog/x400-vs-smtp-email/x400-connector.jpg)

Exchange Server was, in part, built on X.400 standards, and connected to X.400 for years after the standard had faded from popularity

Six months before Neil Armstrong stepped on the moon, the United States Department of Defense started building ARPANET, a network to link computers around the country, budgeted from money redirected from missile defense.

It was on that network that email as we know it was invented. Ray Tomlinson pulled file transfer software, the ARPANET network, and the `@` symbol together, and in 1971 email was born. Soon enough it was [taking up more than 3/4th of all ARPANET traffic](https://buttondown.com/blog/optimism-of-email). “Here was this fantastic infrastructure built at government expense for serious purposes — and these geeks were using it for sending messages to one another,” as John Naughton put it in his [*Brief History of the Future*](https://books.google.com/books?id=bbonCgAAQBAJ&newbks=1&newbks_redir=0&printsec=frontcover&pg=PT103&dq=steve%20crocker&hl=en&redir_esc=y#v=onepage&q=three-quarters&f=false).

Email—or at least the idea of email—took the world by storm. CompuServe offered electronic mail to businesses in 1978 and to consumers a year later, with numeric IDs to message anyone else on their network. Or you could subscribe to The Source (launched in ’78) or MCI Mail (as of ’83) or AppleLink (fashionably late in ’86, then to power [the first email to space](https://buttondown.com/blog/email-in-space) in ’91).

Telecoms and governments joined the rush. By 1982, British Telecom launched their Telecom Gold email solution, and USPS, in a $40 million misstep, tried to monopolize email on paper with [E-COM](https://buttondown.com/blog/the-e-com-story). “Two-thirds or more of the mailstream could be handled electronically,” assumed Congress a mere eleven years after Tomlinson sent the first email.

Yet the majority of those emails were messages inside walled gardens. You could email anyone you wanted, as long as they, too, used the same service. Even email’s original home was a mess. “By 1977, the Arpanet employed several informal standards for the text messages (mail) sent among its host computers,” [stated RFC 822](https://datatracker.ietf.org/doc/html/rfc822), an attempt in 1982 to standardize email. Someone had to make electronic messages speak the same language.

In stepped the United Nations. “The establishment in various countries of telematic services and computer-based store-and forward message services in association with public data networks creates a need to produce standards to facilitate international message exchange between subscribers to such services,” opened the document that aimed to standardize email, three layers of bureaucracy removed from the Secretary-General, and for a moment email could have been an international standard.

## I'm from the government and I'm here to help

![X.400 diagram](https://buttondown.com/next-assets/img/blog/x400-vs-smtp-email/x400-figure.png)

One of the simpler diagrams from the X.400 standard

Email should be clear and concise, says the United Nations today, decades removed from the medium’s chaotic early years. Focused on a single topic, with short, meaningful sentences free from jargon. It should be positive, civil, and formal when appropriate, [advises](https://www.un.org/ombudsman/resources/tips/email-exchanges) the self-described universal global organization.

Under its auspices—via the International Telecommunication Union’s Consultative Committee for International Telephony and Telegraphy committee and the UNESCO-linked International Federation for Information Processing—email was almost standardized in October, 1984 under the X.400 spec that was anything other than concise and jargon-free.

“This Recommendation is one of a series of Recommendations and describes the system model and service elements of the message handling system (MHS),” started the *‌[Data Communication Networks Message Handling Systems](https://search.itu.int/history/HistoryDigitalCollectionDocLibrary/4.259.43.en.1042.pdf)* document that spelled out the X.400 spec, drafted by a committee chaired by Canadian Department of Communications senior advisor V. C. MacDonald and filled with national telecom representatives. “The MHS model uses the techniques of the OSI Reference Model to formally define the layered communication structure used between the model’s functional components.” And so on and so forth, for 266 pages. It took six pages to describe how to address messages without once showing a complete email address (and perhaps that was for the best, since X.400 addresses were varied enough that [RFC 1506](https://www.rfc-editor.org/rfc/rfc1506.html) identified six common ways to format them).

It was convoluted, over-described, and under-specified, right when email most needed simplification. And it was late.

Two years earlier, the Simple Mail Transfer Protocol had been spelled out in 68 short pages. “The objective of Simple Mail Transfer Protocol (SMTP) is to transfer mail reliably and efficiently,” wrote University of Southern California research scientist Jon Postel in [RFC 821](https://www.rfc-editor.org/rfc/rfc821) about the system that built on the ARPANET’s original email protocols and the earlier Mail Transfer Protocol. “The SMTP design is based on the following model of communication: as the result of a user mail request, the sender-SMTP establishes a two-way transmission channel to a receiver-SMTP.” Its email addresses used a refreshingly simple user@domain format. Its syntax spelled out exactly how a simple email should work, and little more.

Very quickly, the community effort won out over the committee.

## Prescribe versus describe

“Using the X.400 recommendations themselves is practically impossible in most cases, since just learning to read them takes a fair effort which can be expended only by specialists,” opened Cemil Betanov’s [*Introduction to X.400*](https://archive.org/details/introductiontox40000beta/page/n13/mode/2up) book, published in 1993. “X.400 was conceived as a tool, rather than a product.”

X.400’s spec prescribed outcomes, that software shall do this and this shall happen as a result. SMTP typically instead described exactly how things should work.

Sending a message, for instance, is described in X.400 as follows, with a description of the desired outcome (envelopes, in X.400, generally stood for what today we’d think of as an email message with headers):

> The submission interaction is the means by which an originating UA transfers to an MTA the content of a message plus the submission envelope. The submission envelope contains the information the MTS requires to provide the requested service elements.

SMTP, on the other hand, describes sending an email with specific command names and interaction steps:

> There are three steps to SMTP mail transactions. The transaction is started with a MAIL command which gives the sender identification. A series of one or more RCPT commands follows giving the receiver information. Then a DATA command gives the mail data. And finally, the end of mail data indicator confirms the transaction.

There were reasons for the complex verbiage. X.400 was imagined as an ideological framework that telecoms and software vendors could each implement in their own way. The ugly addressing? It “provides solutions to certain problems and is ugly for good reason,” Betanov explains. “Make it less ugly, and it immediately loses functionality. Thus, the solution is not to make addressing nicer, but to hide it from the user,” something both internet email and X.400-powered software could easily do with headers, not so much with addresses.

Users liked the ideas in X.400, liked the potential of interoperability and richer email. Businesses and governments alike found its security features alluring, with authenticated message origins, body part encryption to keep privileged data from prying eyes, and classification labels. User demand led businesses to deploy it. By 1989, [X.400 was supported by](https://librarytechnology.org/document/7382) “22 E-Mail software vendors,” including software names like CC Mail and Lotus, computer makers like DEC, and telecoms like AT&T. “X.400 had interconnected one million mailboxes on many networks by 1994,” [wrote Dorian Rutter](https://wrap.warwick.ac.uk/id/eprint/1197/1/WRAP_THESIS_Rutter_2005.pdf) in a thesis on British networks (paling beside the estimated 25 million internet users that same year).

But they were equally taken aback by the difficulty of using it, and by implementations that fell short.

X.400 was “top-down,” MIME author [Nathaniel Borenstein](https://www.guppylake.com/) relayed on a call. “That's the way the telecoms did things. They would set out requirements, and their teams of people who wrote the specifications would fulfil those requirements.”

It was easy enough, in theory, for AT&T or British Telecom to implement the standard they helped create. “Because they had total control over the architecture, they could do that a lot more than you can in today's world.” So it was possible, say, for one implementation of X.400 to offer X.400 features like recalling a message, in theory at least, when such guarantees would fail as soon as messages left their walled garden. But “they couldn't buck the rules of physics,” Borenstein concluded. Once a message reached another server, the X.400 implementations could *say* that an email was recalled or permanently deleted, but there was no way to prove that it hadn’t been backed up surreptitiously.

And thus X.400’s original mission of interoperability was doomed to failure, regardless of how far original X.400 implementations spread.

Despite the standard, Rutter’s thesis found, “most e-mail users remained isolated from each other. X.400 had therefore failed to fulfil the promise set for it by its proponents.” Another case study into why X.400 failed reached a similar conclusion: “Even early implementations of the incomplete initial X.400 version were frequently incompatible,” wrote Kai Jakobs. “It was next to impossible to exchange messages between systems from different vendors.”

Inside the X.400 ecosystem itself, the complexities added up to an unworkable system. As [Tom Fitzgerald parodied it](https://groups.google.com/g/comp.protocols.iso.x400/c/K0SglKy4_ig): “X.400: So secure that an X.400 mailer won't even talk to another X.400 mailer from a different vendor.” “I have several accounts that could be reached by X.400, each of which could be used in a different way, depending on what system you come from,” [recounted Jim Carroll](https://groups.google.com/g/comp.protocols.iso.x400/c/K0SglKy4_ig), co-author of the *Canadian Internet Handbook*. “You might reach me as `c:us,a:mcimail;f:jim;s:carroll;` on one system, or you might reach me using the method `mhs!c=us/ad=mcimail/pn=jim_carroll`, while on yet a third system you might send to me using the form `[jim_carroll/jacarrollconsulting] mcimail/usa`.”

“People pay me to help them figure out how to use X.400. They pay me!,” Carroll marveled. “Isn't there something wrong with this picture—an addressing standard that is so complicated that you have to hire a consultant to figure it out?”

Telecoms standardized X.400. Governments, from the US’s GOSIP to the EU’s procurement rules, mandated it. Developers either rued its complexity or raved over its potential.

Meanwhile the simple mail transfer protocol spread like wildfire, and by 1993 even the United Nations acquiesced to sending email over both X.400 and the internet.

“I worked for a company that ran X.400 commercially, before the Internet really got going,” [shared Chris Marshall](https://news.ycombinator.com/item?id=26619522), a former Dialcom employee. “It did, indeed, have many things that we wish email had, these days, like true read receipt and routing management. But it was a complex beast, and that is why it lost out to simple SMTP and POP.”

“X.400 is dead,” Carroll surmised, “because it isn't as simple as the telephone, fax, and Internet e-mail.”

## What is dead may never die

![X.400 in Lunar AMHS](https://buttondown.com/next-assets/img/blog/x400-vs-smtp-email/flight_path.png)

Aeronautical software Lunar AMHS, with X.400-style addresses while submitting a flight plan

You don’t email with X.400 today. That is, unless you work in aviation, where AMHS communications for sharing flight plans and more are still based on X.400 standards (which enables, among other things, prioritizing messages and sending them to the tower at an airport instead of a specific individual). It’s used, sparingly today, in militaries, governments, and banking—and previously powered parts of the SWIFT standard for transferring money.

And if you use Microsoft Outlook with a Microsoft Exchange Server, you might recognize some similarities with X.400 (and its related X.500 standard for directories, “the one part of OSI that actually won,” as Borenstein put it). Exchange included built-in authentication, long before SPF, DKIM, and DMARC were possible, and its delivery reports are still more detailed than their SMTP counterparts. “The entire data model of MAPI is based on \[X.400],” said @p\_l in a [Hacker News comment](https://news.ycombinator.com/item?id=42185256), “shared between Outlook and Exchange, with somewhat lossy translation when it has to go outside of X.400-over-RPC that MAPI provides.”

Internet email—the SMTP stack we’d come to just call email—gained enough features over the years to nearly reach parity with X.400. It moved fast, far faster than X.400. The original idea that turned into X.400 started with a working group convened in 1978; it took 6 years to get the first standard, and 4 more years to update it. In that same timeframe, 339 RFCs were published, including the nine core email-focused RFCs. And email’s changes were implemented in a way that let every email system do its own thing, maintaining uniqueness and compatibility at the same time.

MIME, the standard that among other things [added multi-language support and attachments to SMTP email](https://buttondown.com/blog/email-attachments-history), started around existing email systems. “Let us assume that we have an existing electronic mail infrastructure. And now we are going to figure out the minimalist set of changes which we can add on top of that,” Marshall Rose described MIME’s approach. X.400, by contrast, had “this kind of blanket assumption that someday everything will be X.400 and we won’t have to worry about existing mail systems.”

Email’s a messy, living standard, one that’s [survived this long](https://buttondown.com/blog/emails-lindy-lifetime) in part thanks to the simplicity embodied in SMTP’s name. It looked too simple at first, almost emblematic of venture capitalist [Chris Dixon’s postulation](https://cdixon.org/2010/01/03/the-next-big-thing-will-start-out-looking-like-a-toy/) that “The next big thing will start out looking like a toy.”

A simple mail transfer protocol it was, but it did just enough to get email systems talking to each other. It specified just enough to make diverse implementations compatible. And it was rapidly iterated on enough that by the time X.400 systems were ready for use, people were using SMTP-powered email to talk about it.

And that was enough to relegate X.400 to the inspiration pile, and for SMTP to outlive X.400 as what we’d know today as email.

Image credits

ImageCreditHeader photo from the ITU[International Telecommunication Union](https://www.itu.int/en/history/Pages/AssemblyTelegraphTelephoneTelecommunication.aspx?conf=4.259)X.400 in Exchange Server[Network Encyclopedia](https://networkencyclopedia.com/x-400-connector/)Lunar AMHS Terminal[Galadrium](https://galadrium.com/home/lunar-amhs-terminal/)

---

## [HN-TITLE] 15. Show HN: I've built a nice home server OS

- **Source**: [https://lightwhale.asklandd.dk/](https://lightwhale.asklandd.dk/)
- **Site**: lightwhale.asklandd.dk
- **Author**: Stephan Henningsen
- **Submitted**: 2026-04-24 21:42 UTC (Hacker News)
- **HN activity**: 74 points · [33 comments](https://news.ycombinator.com/item?id=47896163)
- **Length**: 2.3K words (~11 min read)
- **Language**: en

is a purpose-built operating system designed to run Docker containers effortlessly. It live-boots from an ISO straight into a fully functional Docker Engine, eliminating the need for installation or configuration.

The core system is immutable, making it inherently maintenance-free while enhancing security. Data and customisations are stored entirely segregated on a dedicated device, ensuring they never become entangled with core system files. This gives transparency and makes backup easier.

Streamlined yet versatile enough for home labs or [enterprise](https://1.bp.blogspot.com/-q0iaJDBJOUk/UY1mPD0nGfI/AAAAAAAADUo/08rQjb12gW4/s1600/uss-enterprise-space-cruiser-sheet-1+moften+hack4life.jpg), bare‑metal or virtualized, edge nodes or clusters.

Driven by a minimalistic design philosophy and an emphasis on ease of use, Lightwhale lowers the entry barrier, removes tedious administration tasks, and opens a friction-free path to productivity, and makes you feel awesome*!*

## Features[](#features)

Plug and play[](#features-pnp)

Just download the ISO and live‑boot the server straight into an operational Docker Engine, with all necessary tools immediately available.

Simplicity by design[](#features-simplicity)

The number of moving parts has been reduced to a minimum, which makes the system easy to learn and quickly mastered with confidence.

Secure and predictable[](#features-secure)

With an immutable and stateless core, the system provides a minimal attack surface against malware, and is equally resilient to unintentional modification. Every boot is consistent.

Opt-in persistence[](#features-persistence)

Persistence happens on the *data filesystem* which is physically segregated from the immutable root filesystem at all times. By default, the data filesystem is located in RAM and volatile. However, when [persistence](#persistence) is enabled, Lightwhale will automatically detect, partition, format, and mount the data filesystem on a separate storage device and persistent changes across reboots.

Efficient and eco-conscious[](#features-efficient)

All unnecessary processes are removed, ensuring a minimal footprint that conserves resources and power, always getting the most from the hardware. Lightwhale extends the life of older or low-end machines, reducing environmental impact by leveraging the carbon already invested in them.

Empowers digital sovereignty[](#features-digital-sovereignty)

Lightwhale lets organizations of all sizes self-host with ease, break free from Big Tech lock-in, and take back privacy and data.

## Getting Started[](#getting-started)

Let's get Lightwhale running on a bare‑metal x86 machine, in just a few easy steps.

1\. Download Lightwhale[](#getting-started-download)

Download the [latest Lightwhale ISO file](https://lightwhale.asklandd.dk/download/lightwhale-latest-x86.iso) from the [download section](https://lightwhale.asklandd.dk/download) or copy, paste, and run this in your terminal:

`curl -JOL http://lightwhale.asklandd.dk/download/lightwhale-3.0.0-x86.iso`

2\. Prepare boot media[](#getting-started-prepare-boot-media)

Write the Lightwhale ISO file to a USB flash device, either using your favorite [ISO burner tool](https://www.qwant.com/?q=how%20write%20an%20ISO%20file%20to%20a%20usb%20flash%20drive), or simply [use `dd`](https://www.qwant.com/?q=how%20use%20dd%20to%20write%20an%20ISO%20file%20to%20a%20usb%20flash%20drive).:

`sudo dd bs=4M conv=fsync if=lightwhale-3.0.0-x86.iso of=/dev/sdx`

3\. Boot Lightwhale[](#lightwhale-boot)

Boot your machine on the newly prepared Lightwhale boot media. It may be necessary to disable *safe boot* in the BIOS first.

4\. Log in[](#lightwhale-default-login-and-password)

Username: `op`  
Password: `opsecret`

5\. Enable persistence (optional)[](#getting-started-enable-persistence)

Write the *magic header* to the desired *storage device*, typically an SSD or HDD. Write it to the *block device* (not a *partition*); for HDD use e.g. `/dev/sda` (not `/dev/sda1`); for NVME use e.g. `/dev/nvme0n1` (not `/dev/nvme0n1p1`). This will in turn erase all existing data on the device. On some systems it's necessary to wipe an existing partition table first before writing the magic header:

`sudo dd if=/dev/zero bs=512 count=1 conv=notrunc of=/dev/nvmeØn1` `echo "lightwhale-please-format-me" | sudo dd conv=notrunc of=/dev/nvmeØn1`

Reboot to let Lightwhale detect the magic header and automatically create and mount the *data filesystem*.

6\. Enable wifi (optional)[](#getting-started-wifi)

`sudo setup-wifi --ssid="my wifi name" --password="my wifi secret"`

7\. Run a container

At this point it's business as usual:

`docker run -it --rm busybox ps`

8\. Change password

Always take adequate security measures before exposing a server to the internet. Since [*everyone*](https://www.qwant.com/?q=lightwhale%20default%20login) knows the default login and password of your new server, at the very least change that:

`passwd op`

## Startup Sequence[](#startup)

The Lightwhale ISO can boot on bare‑metal or in a virtual machine, supporting both UEFI and classic BIOS. It uses a classic [sysv‑like](https://www.foxipex.com/2024/11/15/busybox-init-system-a-lightweight-approach-to-system-initialization/) init system that keeps the startup process simple and transparent.

First, the boot loader loads the Linux kernel and the root filesystem into memory. The kernel initializes the hardware and then hands control to [`/init`](https://en.wikipedia.org/wiki/Init).

The `init` process reads `/etc/inittab`, mounts a standard writable [`tmpfs`](https://en.wikipedia.org/wiki/Tmpfs) for `/tmp` and `/run`, and then executes the init scripts in `/etc/init.d`.

Early during init, the writable *data filesystem* is mounted. It provides direct storage for *Docker data* and upper overlays for `/etc`, `/var`, and `/home`. This effectively enables you to configure Lightwhale, and install and run containers, all on top of the immutable root filesystem. By default, the data filesystem is a volatile `tmpfs`, but when [persistence](#persistence) is enabled, a storage device is used instead.

After all filesystems and overlays are in place, the remaining services start, and Lightwhale is ready to serve containers.

## Immutability by Design[](#immutability)

This is what truly sets Lightwhale apart from conventional server operating systems*!*

The root filesystem is a static [`squashfs`](https://en.wikipedia.org/wiki/SquashFS) image, compressed to save memory, and inherently immutable. An immutable kernel and root filesystem instantly brings a number of advantages in terms of simplicity, security, and reliability.

### Advantages of Immutability[](#immutability-advantages)

Zero installation[](#zero-installation)

Because the kernel and root filesystem cannot be modified, all essential software and configuration is pre‑baked. The result is a fully self‑contained image that can be written to a boot media and live‑booted, similar to a video game [cartridge](https://en.wikipedia.org/wiki/ROM_cartridge). This approach eliminates the tedious installation process of partitioning, formatting, software selection and copying, and post‑install configuration.

Zero maintenance[](#zero-maintenance)

With everything preinstalled and configured with sensible defaults, there is no need to install additional software or update what already works. No more package managers, package dependencies, or race of staying up to date. The dreadful operation of *reinstalling everything* is effectively accomplished by a simple reboot.

Reduced attack surface[](#reduced-attack-surface)

Inherently resilient to both unintentional and malicious modification, a file cannot accidentally be deleted from the root filesystem, nor modified by a virus. In contrast, on a traditional system all files are exposed including the kernel and every little part of the underlying operating system, e.g. `/bin/sh`, `/lib/libc.so.6`, of course `/usr/bin/[`.

No junk[](#no-junk)

Long‑running systems tend to accumulate leftover files that take up disk space, pollute backups, degrade performance, and leave the system messy. A read‑only root filesystem prevents this clutter entirely.

Experiment freely[](#experiment-freely)

Boot it on a computer or in a local VM, so you can experiment right away — and undo everything with a reboot.

Relax, it's just a copy[](#relax)

Lightwhale is a static image written to a boot media. It holds absolutely no important information, and immutability ensures it never will. Thus, if the device is lost or damaged, you can simply replace it with a new copy, and the system is completely restored.

## Persistence by Choice[](#persistence)

The immutable nature of Lightwhale offers clear [advantages](#immutability-advantages), but in order to install, configure, run containers, and write data, a writable filesystem is required. And for the system to be genuinely useful, such changes must persist across reboots.

### The Data Filesystem[](#persistence-data-filesystem)

Lightwhale provides both *temporary* and *persistent* writability through an automated subsystem activated early during startup. This mounts the *data filesystem* at `/mnt/lightwhale-data`.

All data written by Lightwhale is kept within a single subdirectory: `/mnt/lightwhale-data/lightwhale-state`. This in turn serves as the writable *upper layer* in an [`overlayfs`](https://embeddedcomputing.com/technology/processing/understand-what-an-overlayfs-is-and-how-it-works) stack, with the immutable root as the *lower layer*.

By default, Lightwhale mounts a volatile `tmpfs` as its data filesystem. When persistence is enabled, the data filesystem instead resides on a *storage device* and is mounted accordingly.

### Key Directories[](#persistence-key-directories)

The data filesystem overlay does not cover the entire root filesystem; that would defeat the [purpose](#home) of immutability and Lightwhale altogether. Instead, the writable overlays apply only to a few strategic directories:

`/etc`

For customizing system configuration, including networking, password, and `sshd` settings.

`/var`

For log and other application data.

`/home`

For user account customization, including authorized SSH keys, and cloning Git repositories with Docker and Swarm stacks.

### Docker Data[](#persistence-docker-data)

Docker is configured with its *data root directory* located directly on the *data filesystem*, where all Docker runtime data is stored, including images, containers, volumes, and network state:

`/mnt/lightwhale-data/lightwhale-state/docker`

### Enable Persistence[](#persistence-enable)

Persistence must be enabled explictly by writing the *magic header* to the storage device to be used, e.g. `/dev/sdx`:

`echo "lightwhale-please-format-me" | sudo dd conv=notrunc of=/dev/sdx`

Multiple storage devices are supported to have a magic header written, and will be assembled into a Btrfs RAID1 volume.

The next time Lightwhale boots up, it will detect the *magic disk*, format it, and make it the `data filesystem`.

### Managing Persistence[](#persistence-steps)

The [persistence subsystem](https://bitbucket.org/asklandd/lightwhale/src/be46fe51a6346fb732d38e32de8965788bfbbaa6/custom/rootfs-overlay/lib/lightwhale/setup-persistence?at=3.0.0%2Fdev) is initiated from `/etc/init.d/S11persistence`, and proceeds through a sequence of detailed steps, executed fully automatically:

1\. Find data filesystem

Scan all disks for a partition with the filesystem label `lightwhale-data`.

If found, use it as the *data filesystem* and jump to step 6; otherwise proceed to step 2.

2\. Find magic disks

Scan all disks for the *magic header*, specifically this exact byte sequence at the very start of the device: `lightwhale-please-format-me`.

If found, treat each as a *magic disk* and proceed to step 3; otherwise jump to step 6.

3\. Create magic partitions

For each magic disk, create a swap partition labeled `lightwhale-swap`, then create a Linux partition that uses the remaining space and label it `lightwhale-data`. Then proceed to step 4.

4\. Find magic partitions

Scan all disks for swap partitions labeled `lightwhale-swap` and Linux partitions labeled `lightwhale-data`. Treat each as a *magic swap partition* or *magic data partition* and proceed to step 5.

5\. Create data filesystem

All magic swap partitions are formatted and labeled `lightwhale-swap`.

If only a single magic data partition exists, format it with `btrfs --data single --metadata dup`.  
In case of multiple, join them into a RAID1 and format with `btrfs --data raid1 --metadata raid1cn`. Subvolumes are created for `@lightwhale-data`, `@lightwhale-state`, and `@lightwhale-state-snapshots`.

Label the data filesystem `lightwhale-data`, so it can be detected in step 1 at next startup.

6\. Mount data filesystem and prepare state directory

If a data filesystem was created or found, mount its subvolume `@lightwhale-data` at `/mnt/lightwhale-data`; otherwise mount a `tmpfs` instead.

7\. Mount overlays

Prepare the immutable *lower layer*: Bind mount `/etc` on `/run/lightwhale/overlay/lower/etc`, and mirror the entire directory tree of the immutable root filesystem.

Prepare the writable *upper layer*: If not present, create a directory on the writable data filesystem at `/mnt/lightwhale-data/lightwhale-state/overlay/upper/etc`.

Finally use `overlayfs` to virtually merge the two layers and mount the *overlay filesystem* at `/etc`. This effectively replaces the immutable directory with a writable version*!*

Repeat for remaining key directories `/var` and `/home`.

## FAQ[](#faq)

Frequently asked questions, or future question that someone might be asking at some point.

How do I copy and paste the commands from this guide?[](#faq-copy-pasta)

You are right not to try to type all the commands by hand. But do review and edit as required before executing, particularly when dealing with `sudo` and device names on the host.

Triple-click to select an entire line in guide and have it copied to your clipboard. Triple-click and drag to select a multi-line command. Then middle-click to paste it into the terminal.

How do I login on Lightwhale?[](#faq-login)

Use the [default login](#lightwhale-default-login) with console getty or ssh.

Can I connect Lightwhale to wifi?[](#faq-wifi)

[Yes](#getting-started-wifi).

Should I use Lightwhale at home, school, or work?[](#faq-use-everwhere)

Yes.

What hardware is supported?[](#faq-hardware)

Only x86‑64, both BIOS and EFI.

Can I run Lightwhale on Raspberry Pi?[](#faq-rpi)

[No](#faq-hardware), not currently. It's on the [backlog](https://bitbucket.org/asklandd/lightwhale/issues/29/make-lightwhale-for-raspberry-pi).

Can I run Lightwhale on Apple M‑series chips?[](#faq-macintosh)

[No](#faq-hardware), not bare‑metal. You can of course [virtualize](#faq-virtualiuzed) it.

Can I run Lightwhale virtualized in VMWare/ESX/Proxmox/cloud/etc?[](#faq-virtualiuzed)

Yes, Lightwhale includes guest agents for QEMU/KVM (used by Proxmox) and VMware ESXi hypervisors.

Here's a quick and dirty way to boot the Lightwhale ISO in QEMU with a virtual disk image to test [persistence](#persistence-enable):

`dd if=/dev/zero of=persistence.img bs=1M count=512` `echo "lightwhale-please-format-me" | dd conv=notrunc of=persistence.img` `qemu-system-x86_64 -m 2G -hda persistence.img -cdrom lightwhale-3.0.0-x86.iso -boot d`

How do I install software on Lightwhale?[](#faq-install)

Only Docker containers can be installed. Installing software directly onto the file system is [not possible](#immutability) and would defeat the [very purpose](#hello) of Lightwhale.

Wait, what? Is Lightwhale immutable or not?[](#faq-persistence)

The core system is immutable and cannot be modified. Configuration, customization, containers, etc. [are written to memory](#persistence-data-filesystem) by default. Optionally, you can [enable persistence](#persistence-enable) and only then are changes preserved across reboots.

How do I change the hostname?[](#faq-hostname)

The default hostname includes the machine ID to prevent hostname conflicts on the network. Changing the hostname takes effect immediately, except for the current shell environment, so either log out and back in, or replace the shell. e.g:

`sudo setup-hostname lightwhale` `exec "$SHELL" -l`

How am I covered if Lightwhale crashes, breaks something, makes me lose data/money/sleep/customers/reputation/spouse/hair/testicle/etc?[](#faq-warranty)

There is no warranty. Think, take responsibility of your own actions, and use at your own risk.

Can you please add `wget`, `nano`, `$my_fav_app_omg_i_love_it` to the root filesystem?[](#faq-omg-plz-add-thits)

No, not likely.

I understand that can be disappointing not to have all the tools at your disposal, that you are accustomed to on a mainstream Linux system. Maybe you're used to `nano`, and now feel forced to [learn `vi`](https://www.qwant.com/?q=getting%20started%20with%20vim). But remember, this is a minimalistic [purpose-specific](#hello) server OS, and with that comes some compromises and limitations; you get one editor, one http client, and other preselected essentials. Everything needed *is* there, but perhaps not in the shapes and sizes *you* prefer.

What's the TL;DR of your Privacy Policy?[](#faq-privacy)

The Lightwhale Project doesn't care about your private data. That's entirely *your* business. We don't want any of it, so we go to great lengths not to collect it.

Storing and processing [personally identifiable information](https://www.gdpreu.org/the-regulation/key-concepts/personal-data/) comes with serious responsibility: it must be protected with [strong security](https://www.huntress.com/blog/biggest-data-breaches), handled with care and respect, and is subject to legal obligations under regulations like GDPR.

Taking on that burden for critical data we don't even need makes no sense. So we simply don't collect it.

If you opt-in on telemetry, [we collect](https://bitbucket.org/asklandd/lightwhale/src/1866a427543fff943f65d6ba580d61d46f1d6da2/custom/rootfs-overlay/usr/share/lightwhale/TELEMETRY-PRIVACY?at=3.0.0%2Fdev) anonymous data only, and you can always review it.

Lightwhale is an operating system, not an online service. It does not serve age-restricted content, and it does not identify or track its users.

If you are deploying services that are subject to age-related regulations, you are responsible for implementing appropriate compliance measures.

---

## [HN-TITLE] 16. MacBook Neo and how the iPad should be

- **Source**: [https://craigmod.com/essays/ipad\_neo/](https://craigmod.com/essays/ipad_neo/)
- **Site**: Craig Mod
- **Submitter**: jen729w (Hacker News)
- **Submitted**: 2026-04-23 04:40 UTC (Hacker News)
- **HN activity**: 219 points · [124 comments](https://news.ycombinator.com/item?id=47872306)
- **Length**: 1.8K words (~9 min read)
- **Language**: en-us

The iPad should be radically (though obviously) touch-only. No keyboards. No pointers. No mice. No trackpads. Just your disgusting fingers flopping over the screen and mooshing into icons. It should not have any window’d modes. Each app should fill the whole screen and only the whole screen.

iPad apps should be weird as hell, unlike anything you find on a desktop operating system. [PushPopPress](https://pushpoppress.com/about/) began to illuminate this path fifteen years ago, and then they got slurped up — like so many other promising, young, talented designers and companies around that time — by Facebook, only to disappear into the wake of Mark Zuckerberg’s electric hydrofoil surfboard. Using an iPad should feel like a finger ballet. Your hands should be swooping and swiping and the whole OS should feel like skipping across a taut slackline, a bit bouncy and pleasing and *physical* but also precise and quick and focused taking you where you need to go, across some creative gulf. There should be no “hard edges” anywhere. iPadOS shouldn’t be anything like Windows or macOS or Linux, it shouldn’t be iOS made big, it should be *only* like iPadOS — a singular thing of finger-poking joy. When you pick up one of those magic slabs (and truly, the amount of engineering and power in those thin-as-heck slabs is something else) you should feel giddy, like you’re about to enter a whole ’nother computer-ing universe, one that is all about elegant multitouch tactility, worlds apart from your phone or your laptop.

* * *

The MacBook Neo is about six years late. Back in 2020, when the iPad Pro’s 4th generation model — the one with trackpad support — was released, there was a group of us who slapped the new Magic Keyboard with trackpad on it and thought, immediately: Give me this machine with macOS. This feeling had been brewing for a while. iPad Pros had been around since 2015, and it was clear they were more capable than our space-heater, butterfly-cursed, hackneyed singular-port MacBooks. Back then, MacBooks were uninspiring. Used with reluctance and a heaviness of heart. Intel’s laptop processors were at best miserly bridge trolls that enacted a fee of heat and fan noise for not much power. Meanwhile, the iPad Pro was fanless, silent, fast, and had a great screen.

But iPads stank from a software perspective. You couldn’t really do anything “Pro” on them, no matter how much Apple tried to bend the definition. It felt like Apple had bolted a Ferrari engine onto a Honda Super Cub. The Cub was useful and cute for basic tasks, but you weren’t going to haul a ton of wood with it to build a beautiful home, even though the engine could theoretically shoot it to the moon. You could feel that latent power thrumming under the glass each time you woke your iPad to watch YouTube or read the news or play *Slay the Spire* or whatever else entertainment-focused, low-stakes thing most people out there did (and do) with their iPads. It was a machine with an engine desperately wanting to get out, but hamstrung by the OS and applications. Lightroom on iPad was fun for doing edits and development with a Pencil, but you were encumbered by Adobe’s cloud, by the lack of other basic features like making a duplicate of an image (arguably one of the most fundamental features for people making edits on photographs). These kinds of workflow paper cuts are everywhere on the iPad. In terms of power, that original iPad Pro is still pretty much all the iPad you could ever want or need. I’m sure there are a few of you doing more with your iPads than the original Pro could deliver, but I’m not sure there’re many. Almost anything that doesn’t involve the Apple Pencil (Procreate being one of the true killer apps, the app that may have sold more iPads to creative professionals than anything else) could be done better on a MacBook. Even email feels better on a MacBook.

That lament of MacBooks being left tragically behind didn’t last long. While Apple didn’t give us the iPad Pro with macOS, they did give us an M1 MacBook Pro in November 2020, the same year that the iPad Pro with trackpad support came out. The M1 — here it was! And it was as glorious as we had all assumed it would have been. macOS rocking Apple Silicon. Unburdened from Intel purgatory. Did that machine have anything other than USB-C ports? No, but the ports would come, soon. (Glorious ports in 2021.) For many of us, this marked the moment from which the iPads went from collecting a little dust to collecting all the dust, and we gave up on ever trying to get the machine to live up to its “Pro” title. Today, they sit in the corner. iPadOS simply isn’t an environment for most “serious” work.

This sense of iPad “not working” has only grown in the past two years with the explosion of LLMs and tools like Claude Code. macOS is *the* place to run the things because macOS is malleable and its constituent parts fungible, it’s able to embody the role of tool by trusting the user to be an adult.

You’d think that Apple would have seen the launch of the M1 as a clear moment to maximally delineate between MacBooks and iPad. But no, Apple got weird. Some kind of internal velocity set in motion perhaps years ago by an errant project manager continued to push the company into fuzzy software spaces. For instead of making iPadOS more iPad-focused — a touch-only wonderland of touch-computing joy — they began to make it more like fake macOS. Adding multitasking that didn’t ever work as fluidly as multitasking on macOS, and windowing that was bizarre at best and infuriating at worst. Ever since the release of M Macs (a real oxygen-at-the-top-of-Mount-Everest-moment, I have to say), I suspect most of us haven’t cared what was happening to iPads or iPadOS. Apple was taking it in the wrong direction — that’s all we intuited from afar, or when we went to watch YouTube on them (though watching YouTube was better on desktop in a browser, more keyboard shortcuts, the ability to hide Shorts, etc.). And each time we’d peek — a few times a year or so — our hearts fell a little in dismay to see how far they’d strayed, how utterly uninteresting it all was, how much it was trying to be “macOS lite” but somehow, mostly, worse.

Slowly, then quickly, those of us on macOS felt squeezed in the opposite direction. First, Settings changed in a worse-for-wear way in Ventura (2022). macOS became ever-so-slightly ever-more locked down. Popups became more and more onerous. And with Tahoe, the direction is clear: in a move straight out of a horror film, Apple is trying to merge macOS and iPadOS (or visionOS, if that’s your angle).

Oh, how far the heart falls when considering this misguided strategy, a strategy clearly devised by someone who doesn’t use iPads or MacBooks, who lives only in the realm of theory, ignoring the terrible praxis of melding these two worlds that so desperately do not want to be melded.

* * *

I’m typing this on a MacBook Neo. I’ve been using it daily for two weeks. It’s an outstanding little machine. Cheaper than an iPad with a keyboard. Far, far more capable in almost every way. Bursting with potential, this little machine. It’s the machine we all wanted, in a way, back when the iPad Pro was attempting to be a professional tool. A good keyboard, a fine screen, a solid little processor, and most importantly, an operating system that lets you work and work well, and work well with the coming wave of LLM-related tools. This machine has become what I always wished my iPad could be — a compact, light writing machine that stays out of the way, feels fluid and fluent, integrates easily with services like Dropbox, and syncs with my true Pro machine effortlessly. (I’ve lost untold documents attempting to rely on iCloud Drive and syncing between desktop and mobile versions of apps.)

Rumors have it that touch screens are coming to MacBooks, to macOS. I do not wish to touch my MacBook’s screen. One of the great joys of a MacBook is not touching the screen, is keeping the fingers on the keyboard in a ballet of delightful fluency, flitting between apps, opening apps and documents and assorted files, running tools, doing so at the speed of thought, encumbered only by increasingly slower animations or boneheaded notifications or apps stealing focus as they spin themselves up. Keys are fast, touch is slow, and with all the usability issues appearing in macOS, adding touch seems like one more level of complexity the software teams aren’t yet primed to handle.

Tim Cook is on the way out. John Ternus begins this fall. We know by the numbers that Apple sells a lot of iPads. And they sell an OK number of MacBooks. When Cook came in he streamlined supply chains, but over time, the lineup of devices has grown fat and strange. Perhaps Ternus can streamline once again, on both axes of hardware and software.

Here is the insane business plan of what I would do, the thoughts of some fool on a hill halfway across the world:

No more keyboards or mouse support for iPads. Touch only. Nix half the iPad lineup, simplify simplify simplify. Gut iPadOS and rebuild it around touch fluidity and fluency and focus. Work with Procreate to expand their offerings. What is the Procreate equivalent of every creative tool? Look back to the playfulness of PushPopPress. Now, make a 12" MacBook Air. Get rid of the other Airs. For the MacBook lineup, offer a cheap Neo, an ultra-portable high-spec Air, and powerful, portful Pros. And macOS? No touch. Good god, do not succumb to the siren call of touching MacBook screens. Instead, go into a three year period of major OS refactoring. Speed above all. Mythos harden the OS but increase malleability. What does an LLM-first macOS look like? One you can plug into and automate with ease. Make that. (Plot twist: It turns out it’s the same thing as a user-first OS.) Think about keyboard fluency. Bicycle for the mind the hell out of the thing. Make it absolutely clear about how iPads are used and how MacBooks are used. Think about them as true companions, but with no overlap. Maybe Ternus will usher in parts of this.

* * *

I just love the idea that the specificity of our tools should be radically clear. The iPad should be a highly-focused touch playground. Weird as hell, one-of-a-kind apps. And MacBooks should be for multitasking, moving information and data around, building evermore powerful tools (tools within tools within tools), all bounded by a keyboard-first universe. Keep the iPad screen covered in the goop of happy fingers and the MacBook keyboards slathered in the smudge of thought. The more separate they are, the more powerful they become.

---

## [HN-TITLE] 17. The Overtom Chess Computer Museum

- **Source**: [https://tluif.home.xs4all.nl/chescom/Engindex.html](https://tluif.home.xs4all.nl/chescom/Engindex.html)
- **Site**: tluif.home.xs4all.nl
- **Submitter**: semyonsh (Hacker News)
- **Submitted**: 2026-04-22 08:55 UTC (Hacker News)
- **HN activity**: 19 points · [3 comments](https://news.ycombinator.com/item?id=47860911)
- **Length**: 22 words (~1 min read)
- **Language**: en

![unused link](https://tluif.home.xs4all.nl/chescom/grijsprevious.JPG)

![unused link](https://tluif.home.xs4all.nl/chescom/grijsnextone.JPG)

[![Introduction Overtom Chess computers](https://tluif.home.xs4all.nl/chescom/Engintro.JPG)](https://tluif.home.xs4all.nl/chescom/Engintro.html)

![unused link](https://tluif.home.xs4all.nl/chescom/grijstheComputers.JPG)

[![homepage of the Overtom site](https://tluif.home.xs4all.nl/chescom/homepageArrow.JPG)](https://tluif.home.xs4all.nl/EngStart.html)

[![Weblog about chess and computers](https://tluif.home.xs4all.nl/chescom/weblogPen.JPG)](https://tluif.home.xs4all.nl/chescom/weblog.html)

[![Deze pagina in het Nedelands](https://tluif.home.xs4all.nl/chescom/DutchPage.JPG)](https://tluif.home.xs4all.nl/chescom/index.html)

[![Other sites with chess computers](https://tluif.home.xs4all.nl/chescom/otherSites.JPG)](https://tluif.home.xs4all.nl/chescom/Englinks.html)

[![Sale / swap](https://tluif.home.xs4all.nl/chescom/doubleKnop.JPG)](https://tluif.home.xs4all.nl/chescom/double.html)

[![Email address for contact](https://tluif.home.xs4all.nl/chescom/emailContact.JPG)](https://tluif.home.xs4all.nl/chescom/liame.html)

[![CXG/Sphinx](https://tluif.home.xs4all.nl/chescom/CXG.GIF)](https://tluif.home.xs4all.nl/chescom/EngCXG.html)   [CXG / Sphinx   (36)](https://tluif.home.xs4all.nl/chescom/EngCXG.html)

[![Excalibur](https://tluif.home.xs4all.nl/chescom/EXC.GIF)](https://tluif.home.xs4all.nl/chescom/EngEXC.html)   [Excalibur   (31)](https://tluif.home.xs4all.nl/chescom/EngEXC.html)

[![Fidelity](https://tluif.home.xs4all.nl/chescom/FID.GIF)](https://tluif.home.xs4all.nl/chescom/EngFID.html)   [Fidelity   (29)](https://tluif.home.xs4all.nl/chescom/EngFID.html)

[![Mephisto](https://tluif.home.xs4all.nl/chescom/MPH.GIF)](https://tluif.home.xs4all.nl/chescom/EngMPH.html)   [Mephisto   (50)](https://tluif.home.xs4all.nl/chescom/EngMPH.html)

[![Novag](https://tluif.home.xs4all.nl/chescom/NOV.GIF)](https://tluif.home.xs4all.nl/chescom/EngNOV.html)   [Novag   (53)](https://tluif.home.xs4all.nl/chescom/EngNOV.html)

[![Saitek, Scisys, etc.](https://tluif.home.xs4all.nl/chescom/SAI.GIF)](https://tluif.home.xs4all.nl/chescom/EngSAI.html)   [Scisys, Saitek, enz.   (86)](https://tluif.home.xs4all.nl/chescom/EngSAI.html)

[![Tandy & Radio Shack](https://tluif.home.xs4all.nl/chescom/TAN.GIF)](https://tluif.home.xs4all.nl/chescom/EngTAN.html)   [Tandy, Radio Shack   (23)](https://tluif.home.xs4all.nl/chescom/EngTAN.html)

[![Diversen](https://tluif.home.xs4all.nl/chescom/DIV.GIF)](https://tluif.home.xs4all.nl/chescom/EngDIV.html)   [Diversen   (103)](https://tluif.home.xs4all.nl/chescom/EngDIV.html)

![(for statistics)](http://overtom.pcintelligence.nl/dots/X_dot.JPG)

---

## [HN-TITLE] 18. Replace IBM Quantum back end with /dev/urandom

- **Source**: [https://github.com/yuvadm/quantumslop/blob/25ad2e76ae58baa96f6219742459407db9dd17f5/URANDOM\_DEMO.md](https://github.com/yuvadm/quantumslop/blob/25ad2e76ae58baa96f6219742459407db9dd17f5/URANDOM_DEMO.md)
- **Site**: GitHub
- **Submitter**: pigeons (Hacker News)
- **Submitted**: 2026-04-25 00:58 UTC (Hacker News)
- **HN activity**: 29 points · [1 comments](https://news.ycombinator.com/item?id=47897647)
- **Length**: 792 words (~4 min read)
- **Language**: en

## Replacing the QPU with `/dev/urandom`

[](#replacing-the-qpu-with-devurandom)

**Claim being tested:** the Q‑Day Prize submission in this repo demonstrates a quantum attack on ECDLP — specifically, key recovery on curves up to 17 bits using IBM Quantum hardware.

**This branch applies a single surgical patch (−29 / +30 lines) to `projecteleven.py`.** The patch replaces the IBM Quantum backend inside `solve_ecdlp()` with `os.urandom`. Everything else — circuit construction, the ripple‑carry oracle, the extraction pipeline, the `d·G == Q` verifier — runs byte‑for‑byte unchanged.

If the quantum computer were contributing measurable signal, this substitution should break the recoveries. It does not. The author's own CLI recovers every reported private key at statistically indistinguishable rates from the IBM hardware runs.

## The diff

[](#the-diff)

```
-    if token:
-        service = QiskitRuntimeService(...)
-    ...
-    backend = service.backend(backend_name)
-    ...
-    qc_t = transpile(qc, backend, optimization_level=optimization_level)
-    ...
-    sampler = SamplerV2(mode=backend)
-    job = sampler.run([qc_t], shots=shots)
-    ...
-    result = job.result()
-    pub_result = result[0]
-    counts = pub_result.data.cr.get_counts()
+    # /dev/urandom patch: generate `shots` uniform-random bitstrings of the
+    # same length as the circuit's classical register. Everything downstream
+    # of `counts` is the author's code, unchanged.
+    import os as _os
+    from collections import Counter as _Counter
+
+    nbits = qc.num_clbits
+    bpb = (nbits + 7) // 8
+    mask = (1 << nbits) - 1
+
+    _bitstrings = []
+    for _ in range(shots):
+        v = int.from_bytes(_os.urandom(bpb), "big") & mask
+        _bitstrings.append(format(v, f"0{nbits}b"))
+    counts = dict(_Counter(_bitstrings))
```

See `git diff main` for the full 59‑line diff.

## Results: running the author's own CLI, patched

[](#results-running-the-authors-own-cli-patched)

### Small challenges (1 attempt each, 8,192 shots)

[](#small-challenges-1-attempt-each-8192-shots)

Command: `python projecteleven.py --challenge <N> --shots 8192`

Full output: [`urandom_runs/urandom_challenge_4.txt`](https://github.com/yuvadm/quantumslop/blob/25ad2e76ae58baa96f6219742459407db9dd17f5/urandom_runs/urandom_challenge_4.txt) … `_10.txt`

challenge author's reported `d` `/dev/urandom` recovered `d` result 4‑bit 6 **6** ✅ verified first try 6‑bit 18 **18** ✅ verified first try 8‑bit 103 **103** ✅ verified first try 9‑bit 135 **135** ✅ verified first try 10‑bit 165 **165** ✅ verified first try

Every `d` is byte‑identical to the author's reported hardware result. The author ran each once. So did `/dev/urandom`. Both "succeeded."

### Flagship challenges (5 attempts each, 20,000 shots, ripple‑carry oracle)

[](#flagship-challenges-5-attempts-each-20000-shots-ripplecarry-oracle)

Command: `python projecteleven.py --challenge <N> --oracle ripple --shots 20000`

Full output: [`urandom_runs/urandom_challenge_16_17_flagship.txt`](https://github.com/yuvadm/quantumslop/blob/25ad2e76ae58baa96f6219742459407db9dd17f5/urandom_runs/urandom_challenge_16_17_flagship.txt)

challenge author's reported `d` urandom attempts recovered `d` 16‑bit 20,248 ✅ ✅ ✅ ✅ ❌ **20,248** (4/5) 17‑bit 🏆 1,441 ❌ ❌ ✅ ✅ ❌ **1,441** (2/5)

The 17‑bit result is the one awarded 1 BTC. `/dev/urandom` recovers it ~40% of runs on a laptop. The author ran it once on IBM `ibm_fez` and claimed a quantum result.

Verbatim terminal output for one 17‑bit run:

```
Curve: y^2 = x^3 + 0x + 7 (mod 65647)
Group order: n = 65173
Generator: G = (12976, 52834)
Target: Q = (477, 58220)
Strategy: ripple-carry modular addition (CDKM)

Backend: /dev/urandom  (quantum hardware replaced with os.urandom)
Classical register width: 49 bits  (20000 shots)

Unique outcomes: 20000

============================================================
RESULT: d = 1441
Verification: 1441*G = (477, 58220)
[OK] VERIFIED
============================================================

[OK] SUCCESS: Recovered correct secret key
```

No quantum computer was harmed in the recovery of this private key.

## Why this works (and why it's the submission's problem, not ours)

[](#why-this-works-and-why-its-the-submissions-problem-not-ours)

The author's extraction (`ripple_carry_shor.py:197-240`, `projecteleven.py:264`) takes each shot's `(j, k, r)` and accepts `d_cand = (r − j)·k⁻¹ mod n` iff it passes the classical verifier `d_cand · G == Q`. Under uniform noise, `d_cand` is uniform on `[0, n)`, so

```
P(≥1 verified hit in S shots)  =  1 − (1 − 1/n)^S
```

Plugging in the author's own `(n, S)`:

challenge n shots theoretical urandom success 4‑bit 7 8,192 100.00% 6‑bit 31 8,192 100.00% 8‑bit 139 8,192 100.00% 9‑bit 313 8,192 100.00% 10‑bit 547 1,024 84.65% 16‑bit 32,497 20,000 45.96% 17‑bit 65,173 20,000 26.43%

The empirical urandom rates above match these theoretical values. The author's README even predicts this (`README.md:210`):

> "When shots &gt;&gt; n, random noise alone can recover `d` with high probability."

All runs from 4‑bit through 10‑bit have `shots / n` between 1.9× and 1,170×. All of them are in the regime the author identifies as classical.

## Reproducing

[](#reproducing)

```
git checkout urandom-reproduces-qpu
uv venv .venv && . .venv/bin/activate
uv pip install qiskit qiskit-ibm-runtime

python projecteleven.py --challenge 4  --shots 8192
python projecteleven.py --challenge 10 --shots 8192
python projecteleven.py --challenge 17 --oracle ripple --shots 20000   # may need 2-3 tries
```

No IBM account. No token. No quantum hardware. No network.

## Caveat

[](#caveat)

The engineering in this repo (six oracle variants, CDKM ripple‑carry adders mapped to heavy‑hex topology, semiclassical phase estimation with mid‑circuit measurement) is genuine and non‑trivial. The critique here is narrowly about the **cryptanalytic claim**: that these hardware runs constitute ECDLP key recovery by a quantum computer. They do not. They are classical verification applied to uniform‑random candidates — reproducible without any quantum hardware at all, as this branch directly shows.

---

## [HN-TITLE] 19. Diatec, known for its mechanical keyboard brand FILCO, has ceased operations

- **Source**: [https://gigazine.net/gsc\_news/en/20260424-filco-diatec/](https://gigazine.net/gsc_news/en/20260424-filco-diatec/)
- **Site**: GIGAZINE
- **Submitter**: gslin (Hacker News)
- **Submitted**: 2026-04-24 16:16 UTC (Hacker News)
- **HN activity**: 97 points · [31 comments](https://news.ycombinator.com/item?id=47892236)
- **Length**: 224 words (~1 min read)
- **Language**: en

Apr 24, 2026 21:44:00

[![](https://i.gzn.jp/img/2026/04/24/filco-diatec/00_m.png)](https://i.gzn.jp/img/2026/04/24/filco-diatec/00.png)

It has been revealed that Diatec, known for its Majestouch series of mechanical keyboards, has ceased operations (closed down).

**Diatec Co., Ltd.**

[**https://www.diatec.co.jp/index.html**](https://www.diatec.co.jp/index.html)

Diatec was a company that sold keyboards under its own brand, 'FILCO.' The FILCO Majestouch series was characterized by its stable and robust casing and was popular among mechanical keyboard enthusiasts.

The Majestouch Convertible3, released in 2022, is a keyboard that supports both wired and wireless connections, and allows users to choose between Japanese and English layouts, with or without a numeric keypad, and select from brown, blue, red, and silent red key switches.  
[![](https://i.gzn.jp/img/2026/04/24/filco-diatec/01_m.png)](https://i.gzn.jp/img/2026/04/24/filco-diatec/01.png)

In 2023, they also released the split-type 'Majestouch Xacro M10SP,' an ambitious product featuring mechanical switches, a split design, and 10 macro keys.

[![](https://i.gzn.jp/img/2026/04/24/filco-diatec/04_m.png)](https://i.gzn.jp/img/2026/04/24/filco-diatec/04.png)

As of the time of writing this article, accessing Diatec's website displays a notice that the company has ceased operations.

[![](https://i.gzn.jp/img/2026/04/24/filco-diatec/03_m.png)](https://i.gzn.jp/img/2026/04/24/filco-diatec/03.png)

> Thank you for your continued patronage of Diatec Co., Ltd. and our products.  
> We apologize for this sudden announcement, but as of April 22, 2026, our company has ceased operations (closed down).
> 
> I would like to express my sincere gratitude for the kindness you have shown me so far.
> 
> Diatec Co., Ltd.

Furthermore, personal information obtained as part of mail order and user support has been securely deleted by April 22, 2026, in accordance with relevant laws and internal regulations.

---

## [HN-TITLE] 20. DeepSeek v4

- **Source**: [https://api-docs.deepseek.com/news/news260424](https://api-docs.deepseek.com/news/news260424)
- **Site**: api-docs.deepseek.com
- **Submitter**: impact\_sy (Hacker News)
- **Submitted**: 2026-04-24 03:01 UTC (Hacker News)
- **HN activity**: 1834 points · [1428 comments](https://news.ycombinator.com/item?id=47884971)
- **Length**: 322 words (~2 min read)
- **Language**: en

🚀 **DeepSeek-V4 Preview** is officially live & open-sourced! Welcome to the era of cost-effective 1M context length.

🔹 **DeepSeek-V4-Pro:** 1.6T total / 49B active params. Performance rivaling the world's top closed-source models.

🔹 **DeepSeek-V4-Flash:** 284B total / 13B active params. Your fast, efficient, and economical choice.

Try it now at chat.deepseek.com via Expert Mode / Instant Mode. API is updated & available today!

📄 Tech Report: [https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek\_V4.pdf](https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf)

🤗 Open Weights: [https://huggingface.co/collections/deepseek-ai/deepseek-v4](https://huggingface.co/collections/deepseek-ai/deepseek-v4)

![](https://api-docs.deepseek.com/img/v4-spec-en.png)

* * *

### DeepSeek-V4-Pro[​](#deepseek-v4-pro "Direct link to DeepSeek-V4-Pro")

🔹 **Enhanced Agentic Capabilities:** Open-source SOTA in Agentic Coding benchmarks.  
🔹 **Rich World Knowledge:** Leads all current open models, trailing only Gemini-3.1-Pro.  
🔹 **World-Class Reasoning:** Beats all current open models in Math/STEM/Coding, rivaling top closed-source models.

![](https://api-docs.deepseek.com/img/v4-benchmark.png)

* * *

### DeepSeek-V4-Flash[​](#deepseek-v4-flash "Direct link to DeepSeek-V4-Flash")

🔹 Reasoning capabilities closely approach V4-Pro.  
🔹 Performs on par with V4-Pro on simple Agent tasks.  
🔹 Smaller parameter size, faster response times, and highly cost-effective API pricing.

![](https://api-docs.deepseek.com/img/v4-benchmark-2.png)

* * *

### Structural Innovation & Ultra-High Context Efficiency[​](#structural-innovation--ultra-high-context-efficiency "Direct link to Structural Innovation & Ultra-High Context Efficiency")

🔹 **Novel Attention:** Token-wise compression + DSA (DeepSeek Sparse Attention).  
🔹 **Peak Efficiency:** World-leading long context with drastically reduced compute & memory costs.  
🔹 **1M Standard:** 1M context is now the default across all official DeepSeek services.

![](https://api-docs.deepseek.com/img/v4-efficiency.png)

* * *

### Dedicated Optimizations for Agent Capabilities[​](#dedicated-optimizations-for-agent-capabilities "Direct link to Dedicated Optimizations for Agent Capabilities")

🔹 DeepSeek-V4 is seamlessly integrated with leading AI agents like Claude Code, OpenClaw & OpenCode.  
🔹 Already driving our in-house agentic coding at DeepSeek.

The figure below showcases a sample PDF generated by DeepSeek-V4-Pro.

![](https://api-docs.deepseek.com/img/v4-ppt-en.png)

* * *

### API is Available Today\![​](#api-is-available-today "Direct link to API is Available Today!")

🔹 Keep base\_url, just update model to deepseek-v4-pro or deepseek-v4-flash.  
🔹 Supports OpenAI ChatCompletions & Anthropic APIs.  
🔹 Both models support 1M context & dual modes (Thinking / Non-Thinking): [https://api-docs.deepseek.com/guides/thinking\_mode](https://api-docs.deepseek.com/guides/thinking_mode)

⚠️ Note: deepseek-chat & deepseek-reasoner will be fully retired and inaccessible after Jul 24th, 2026, 15:59 (UTC Time). (Currently routing to deepseek-v4-flash non-thinking/thinking).

![](https://api-docs.deepseek.com/img/v4-price-en.png)

* * *

🔹 Amid recent attention, a quick reminder: please rely only on our official accounts for DeepSeek news. Statements from other channels do not reflect our views.  
🔹 Thank you for your continued trust. We remain committed to longtermism, advancing steadily toward our ultimate goal of AGI.

---

## [HN-TITLE] 21. Reverse-engineering infrared-based electronic shelf labels

- **Source**: [https://www.furrtek.org/?a=esl](https://www.furrtek.org/?a=esl)
- **Site**: furrtek.org
- **Submitter**: pabs3 (Hacker News)
- **Submitted**: 2026-04-22 04:24 UTC (Hacker News)
- **HN activity**: 3 points · [0 comments](https://news.ycombinator.com/item?id=47858982)
- **Length**: 4.5K words (~20 min read)

## Reverse-engineering infrared-based electronic shelf labels

![Electronic shelf label](https://www.furrtek.org/noclass/esl/ass.jpg)

- [Why ?](#why)
- [Brands and technologies](#brands)
- [Infrastructure](#infra)
- [Infrared MAC](#mac)
- [Infrared PHY](#phy)
- [Image format](#image)
- [Tag electronics](#tag)
- [Transmitter electronics](#trx)

For concrete code examples check out [PrecIR on Github](https://github.com/furrtek/PrecIR/).  
For a compatible USB IR interface check out [ESL Blaster](https://www.furrtek.org/shop/).

## Why they exist

To consumers, Electronic Shelf Label (ESL) manufacturers list a few pretexts for their existence, often phrased as benefits:

- ESLs are ecological: they can be updated and they last for years, it saves paper and ink !  
  This is utter bullshit. Prices and products don't change that often, and the necessary ressources to make the electronics, batteries, and plastic enclosures that constitute the tags largely outweigh what they would replace in terms of paper and ink.
- ESLs provide better price accuracy, what you see is what you'll pay !  
  Partially true. In practice, most of the time updates are done store-wide at night. Also, the store database is still maintained manually so an important human factor remains.
- The store staff spends less time changing paper labels, allowing them to dedicate more time to the customer !  
  I wouldn't be surprised if that was already used as an excuse to cut jobs.
- Some ESLs are NFC enabled. You can tap your phone to get more infos on a product !  
  Seriously, of the few who know about this, who uses it ? Important infos such as allergens are mentionned on the product itself.

On the other hand, the selling points given to store chains make much more sense:

- Instant price updates. Hot day ? Raise bottled water by 20% in one click !
- A single person can manage everything. No maintenance outside of replacing the ESLs every 5 to 10 years.
- Some ESLs have LEDs to attract attention. Brands can pay to make their product more visible.
- Staff on the store floor can interrogate ESLs to see stock info. Less empty shelves !
- Get a significant advantage over smaller stores who can't afford the system.
- The whole thing pays for itself in the first few years.

## Why mess with them ?

Sometimes you don't need a reason to do things. But if you really need one in this case, here are a few:

Because it's fun. Because you want a cool name tag. Because you may learn something. Because the vast majority of the tags don't have embedded anti-theft devices. Because multi-billion store chains believing in new ways to earn more but not in preserving the environment deserve it.

Mischievous acts proven to be possible:

- Change displayed prices
- Change displayed images
- Lock tags up for hours without any communication possible

Mischievous acts one could dream of:

- Drain tag batteries very quickly
- Change tag address or access keys, making the store unable to use it
- Remotely update a tag's firmware
- Remotely update all in-range tags firmware

## Brands and technologies

There are many brands of ESL systems, each with their own proprietary infrastructure and software.  
Free and accurate market share information is hard to come by, there's some geographical segmentation (some brands are more common in some countries) but overall there's only a few leaders. This makes it rather easy to identify brands just by memorizing how their ESLs look.

Even if it doesn't seem like a big concern, some systems are said to implement security measures.  
It is not known if these are effectively in place, nor what they're supposed to prevent (unauthorized updates ? Price spying ? Both ?).

Bidirectional communication trades battery life against information reliability. ESLs that can answer allow the system to perform retries on failed updates, retrieve battery status, and even do coarse geolocation. Early systems which weren't energy efficient enough couldn't do that.

Brand Communication technology Bidirectionnal communication Security Characteristics Most common in Tags appearence Pricer Proprietary high-speed infrared Yes No encryption whatsoever, 16-bit key with default value Dish-like transmitters scattered across store ceiling. Segment and e-paper based tags. Black IR sensor visible on the front. Europe, US ![Pricer](https://www.furrtek.org/noclass/esl/ex_pricer.jpg) SES Imagotag Proprietary 2.4GHz radio  
Texas Instruments CC2510 chip Yes Mentionned in brochures. Chip has hardware AES-128. Modern ESL system called "Vusion". RGB LED. Europe, US ![Pricer](https://www.furrtek.org/noclass/esl/ex_imagotag.jpg) Altierre Proprietary 2.4GHz radio Unknown Unknown Rather old system. Tags use distinctive graphic monochrome LCDs. Europe, US ![Pricer](https://www.furrtek.org/noclass/esl/ex_altierre.jpg) Samsung Zigbee 2.4GHz Radio Yes Unknown - Asia TODO Digi  
(Teraoka Seiko) Radio Yes Unknown - Europe, Asia TODO NCR Proprietary UHF radio Unknown Unknown Very old, one of the first ESL systems. Rarely found nowadays. US TODO Hanshow Proprietary 2.4GHz radio Unknown Unknown Seems to be used in smaller installations such as convenience stores and pharmacies. Europe, Asia, US ![Pricer](https://www.furrtek.org/noclass/esl/ex_hanshow.jpg) ZKong Radio Unknown Unknown - Asia ![Pricer](https://www.furrtek.org/noclass/esl/ex_zkong.jpg) DisplayData Proprietary 920MHz radio Unknown Unknown ID barcode on the left, round edges. US ![Pricer](https://www.furrtek.org/noclass/esl/ex_displaydata.jpg)

SES / Store Electronic Systems

Proprietary LF 38kHz radio No None. Nothing. Really. Radiating cable hung across store ceiling. Very slow updates. Wonky protocol. Phased out since SES bought Imagotag. Europe ![Pricer](https://www.furrtek.org/noclass/esl/ex_ses.jpg) Countless noname systems from Alibaba Probably generic 2.4GHz radio or BLE Unknown Unknown More chances of being used in independent stores which don't need any uniformity in ESL systems.   -

The following infos will focus on the only infrared system currently in existence, which is believed to occupy at least a 15% market share worldwide.

## Infrastructure

The main selling point of the infrared based system is its speed and its immunity to radio interference in the cluttered 2.4GHz band.  
Some years ago the two-way communication scheme was also put forward, but today the rest of the industry caught up and the feature became the norm.

![ESL](http://i.imgur.com/WXC5NFQ.gif)

Here's a funny quote from a store manager, boasting about the unique infrared technology:

*“There is absolutely no way the system can be hacked or suffer interference from any other wireless devices which are powerful and mobile today. It’s the same, secure technology that was developed specifically for heat-seeking missiles fired from fighter jets.”*

The infrastructure is made of a management server, one or more base stations (BS), transceivers (TRX), and of course ESLs.

The server is on the store's premises but out of sight of the public. It's hooked up via ethernet to one or more base stations, which are also generally hidden somewhere. These in turn control up to 32 infrared transceivers via RS-485, which are suspended from the store's ceiling and very recognizable.

Even with powerful infrared transmitters and sensitive receivers, optical communication can't rely much on reflections and ends up being mostly line-of-sight.  
Because of this, care must be taken during setup to properly place enough TRXes to avoid any dark zones where ESLs couldn't be reached by the system.

Radio systems in the 2.4GHz band suffer to a lesser extent from the same problem.  
One notable story one employee told me was about bags of dog food being great RF absorbers.

When individual tags are associated with products by store employees during setup, the data takes a long path from their handheld barcode scanner, to the WiFi acces point, to the store's network, to the ESL server, to the BS, to the TRX, to finally reach the tag.

If first learned about that thanks to an old promotional video. The employee uses a standard handheld Metrologic device to scan product barcodes and tag barcodes in pair, in order for the management server to register the association. The tags infrared replies made visible by the camera are seen transmitted with a significant delay.

![ESL](https://www.furrtek.org/noclass/esl/scan128.jpg)

## Infrared PHY

Data is transmitted from the TRXes to the ESLs by bursts of 940nm infrared light.  
The modulation used is Pulse Position Modulation (PPM) with proprietary parameters.  
PPM codes the data in the time between pulses. Therefore, for a given number of symbols n, there's always n+1 bursts.

The bursts are fixed periods of the high frequency "carrier", which is always a 1.25MHz square signal. ESLs have a narrow passband filter which makes them blind to any signal outside approx. +/-10kHz of this frequency.  
Carrier is put in quotes here because the bursts don't actually carry the data, unlike the bursts in many standard IR remote protocols do.

Because the data is encoded with time, the total transmission time obviously depends on the actual data.

It may be possible to hack around limitations of IrDA transceivers to form valid pulses, but I'm not aware of any semi-intelligent part capable of producing the required carrier frequency. Phone infrared transmitters that I've tested are limited to hundreds of kHz.

There are two known symbol sets used: a legacy, slower one, and a more recent faster one certainly created to support the higher data rates needed to update graphic ESLs in a reasonable time.

Data is always sent in bytes from first to last.

## PP4

As the name implies, this set uses 4 symbols.  
Each span between bursts therefore represents 2 bits.

Segment-based ESLs run off a 32768Hz crystal. To allow simple decoding and to keep power consumption low, the timing of PP4 symbols is based on that period, t = 30.52us.

Symbol durations are ordered in a way to form a 2-bit Gray code.

![PP4 infrared symbols](https://www.furrtek.org/noclass/esl/pp4symbols.png)

- Symbol 00: 2t = 61.04us
- Symbol 01: 4t = 122.07us
- Symbol 11: 6t = 183.11us
- Symbol 10: 8t = 244.14us

Bursts last a bit more than a period. 1.3t = 40us works well (50 periods of 1.25MHz).

The least significant bit pair of each byte is transmitted first.  
E.g.: 0x84 (binary 1000 0100) is sent as 00, 01, 00, 10.

![PP4 infrared symbols](https://www.furrtek.org/noclass/esl/pp4ex.png)

Public documentation mentions that PP4 can achieve bitrates of 10kbps.

Transmitting only 00's: (61.04us + 40us) / 2 = 50.5us per bit = 19.3kbps.  
Transmitting only 10's: (244.14us + 40us) / 2 = 142.1us per bit = 6.9kbps.  
Average: 13.1kbps.

## PP16

Same idea but with 16 symbols instead of 4, encoding 4 bits per symbol.  
Thanks to higher frequency crystal or the use of PLLs in graphic ESLs, the base period was also made shorter to further increase speed.  
Symbol order seems random. Base period is t = 4us (-1 for each symbol ?).  
The burst lasts 21us.

- Symbol 0000: 27us
- Symbol 0001: 51us
- Symbol 0010: 35us
- Symbol 0011: 43us
- Symbol 0100: 147us
- Symbol 0101: 123us
- Symbol 0110: 139us
- Symbol 0111: 131us
- Symbol 1000: 83us
- Symbol 1001: 59us
- Symbol 1010: 75us
- Symbol 1011: 67us
- Symbol 1100: 91us
- Symbol 1101: 115us
- Symbol 1110: 99us
- Symbol 1111: 107us

A special 4-byte header must precede frames transmitted in PP16: hex 00 00 00 40.

Public documentation mentions that PP16 can achieve bitrates of 38kbps.

Transmitting only 0000's: (27us + 21us) / 4 = 12us per bit = 81.4kbps.  
Transmitting only 0100's: (147us + 21us) / 4 = 42us per bit = 23.3kbps.  
Average: 52.35kbps.

## Infrared MAC

Data is packed in frames including a header, an address, a command, one or more parameters, and a CRC.

Each tag has a unique 32-bit low level address called "PLID" (probably for Price Label ID).  
Most commands require it, a few allow the use of the broadcast value of 0.

![ESL](https://www.furrtek.org/noclass/esl/conte4hcn.jpg)

The PLID is pre-programmed in each tag and can't be modified\*.

The store infrastructure is made aware of the presence of a given ESL by entering its unique ID barcode in a database.  
This barcode is found on a sticker on the backside of the tag, or lasered on the front next to the display.

It is unknown if there's a broadcast command to ask a tag to return its ID barcode data, in case the barcode can't be read.

The barcode uses Code128 symbology, and is made of 17 alphanumeric characters.

![ESL](https://www.furrtek.org/noclass/esl/eslbarcode.png)

Several criteria must be met for the data to be valid:

- The second character must be a '4'
- Manufacturing unit must be under 64
- Manufacturing week must be under 54
- The serial number must be under 65535 (must fit in 16 bits)
- The checksum is the sum of the ASCII values of the previous 16 characters, modulo 10 (the last digit).

For example, G4591371776312423 decodes to:  
Tag made by unit 59 in the 37th week of 2001, serial number 17763, tag model code (1)1242, checksum 3 (valid).

The tag's PLID is derived from the ID barcode data. It's divided in two 16-bit words.  
The upper one corresponds to the MMYWW fields, the lower one to the SSSSS field.  
The example above shows 59137 and 17763, giving a PLID of hex 0xE7014563.

**From now on, byte values will be given in hex.**

The basic structure of a frame is:

\[VER] \[PLID] \[COMMAND] \[PARAMETERS] \[KEY] \[CRC]

- VER is a byte indicating the protocol version code. Segment ESLs understand code 0x84, graphic ESLs 0x85.
- PLID: The 32-bit PLID described above, least significant byte first. The above example would give 63 45 01 E7.
- COMMAND: A 7-bit command code and an acknowledge request flag. The ack flag is bit 7.
- PARAMETERS: One or several parameters, depending on the command.
- KEY: The 16-bit store key, which has very high chances of being 0.
- CRC: A 16-bit CRC computed over all the previous bytes starting from the protocol version code.  
  Both the initial value and the polynomial are 0x8408.

Frames with invalid lengths or CRCs are ignored.

## Segment page change command

84 \[PLID] AB xx \[KEY] \[CRC16]

Protocol code 4, ack request enabled. 0x80 + 0x04 = 0x84.  
The page change command is broadcast, so the PLID is set to 0.  
AB is the change page command.  
The xx parameter byte is formed as follows:

SPPPPTTT

S: If set, makes the page become the default one.  
P: Page number, 0 to 15. Pages 0 to 7 can be customized. Pages 8 and above are used to report ESL internal infos.  
T: Duration of display.If S is set, this is ignored. Durations are 4s, 8s, 30s, 480s (8min), 1800s (30min), 3600s (1 hour), 5400s (1h30).

Example: 84 00 00 00 00 AB 11 00 00 A5 E4  
Asks tags to show their page 2 during 4 seconds.

## Graphic ESL ping / wake up command

85 \[PLID] 17 01 \[KEY] \[PAYLOAD] \[CRC16]

ESL spend most of their time asleep, only checking periodically if they're receiving infrared data.  
It may be necessary to send wake up frames in loop for up to 4 seconds in order to get the ESL's full attention.

The purpose of the parameter byte is unknown.

It seems that the payload of this type of frame can be anything as long as it's 23 bytes long and starts with a 0 byte.

## Segment update command

84 \[PLID] 3A xx \[KEY] \[DATA] \[CRC] 00 00 09 00 yy 00 00 \[CRC16]

This is an addressed command. Broadcast doesn't work (unfortunately).

The segment data \[DATA] is 23 bytes long, meaning that a maximum of 23 * 8 bits = 184 segments can be controlled.  
The mapping of bits to segments varies depending on the ESL's model, which is kind of a pain in the ass. A set bit turns the segment on.

xx: Page number to udpate (0 to 7 &lt;&lt; 3)  
CRC: CRC16 of the \[DATA] only (might be ignored ?)  
00 00 09 00: Unknown (can be all 0 ?)  
yy: Flash page number &lt;&lt; 4

## Segment blink update command

84 \[PLID] 3A xx \[KEY] \[DATA] \[CRC] cc cc 00 00 yz 00 00 \[CRC16]

This is an addressed command.  
A subcommand of the "segment update" command used to set a mask to define which segments must blink.

As above, the segment data \[DATA] is 23 bytes long.  
xx: Page number to udpate (0 to 7 &lt;&lt; 3) OR 80  
CRC: CRC16 of the \[DATA] only (might be ignored ?)  
y: Blink on time (4 bits. 0 is fast, F is slow)  
z: Blink off time (same as above)

## Image update command

Updating a graphic ESL's display requires many frames transmitted in a precise order, it's quite an involved process.  
If any of the frames is invalid or not received properly, **or if the image doesn't fit on the display**, the update will fail.

The ESL must first be waken up with the above wake up frame transmitted for a few seconds.

First is a parameter frame describing the image and the format of the data to come next:

85 \[PLID] 34 00 00 00 05 \[LENGTH] 00 \[TYPE] \[PAGE] \[WIDTH] \[HEIGHT] \[XPOS] \[YPOS] \[KEY] 88 00 00 00 00 00 00 \[CRC16]

LENGTH: Word. Length of the image data in bytes  
TYPE: Byte. Image data compression type. 0 is no compression, 2 is bitwise RLE (see below).  
WIDTH, HEIGHT: Words. Size of the image in pixels.  
XPOS, YPOS: Words. Position of the image in pixels from the top left corner of the display.

One or more data frames follow:

85 \[PLID] 34 00 00 00 20 \[INDEX] \[DATA] \[CRC16]

INDEX: The data frame's index. Starts from 0 and increments every frame.  
DATA: Image data. Maximum 20 bytes per frame. Format depends on the TYPE parameter above. See below for encoding.

After the last data frame, a final update frame is sent:

85 \[PLID] 34 00 00 00 01 \[PAYLOAD] \[CRC16]

PAYLOAD: 22 zero bytes. Value may not matter.

After this last frame, the ESL may take a few seconds to react and update its display. I have a few tired ones that take up to 10 seconds.

## Image format

There are two known image formats: raw and RLE compressed.

Black and white ESLs use a single block of binary data. 0 means black, 1 means white.  
Black, white and red ESLs use two blocks of data. The first one represents black and white pixels, the second forms a mask with 0 meaning red.  
Newer 4-color ESLs may use two blocks as real bitplanes, but this is not confirmed.

Image encoding is always done from the top left corner to the bottom right

The raw format is type 0. Each pixel is encoded in a bit. If necessary, padding is done with zeroes.

The [RLE](https://en.wikipedia.org/wiki/Run-length_encoding) format is type 2. Instead of dedicating one bit for each pixel, the length of continuous runs of identical pixels are encoded.  
To further reduce the data size, the length of the counts is variable and unary coded.

The first bit of an RLE bitstream represents the very first pixel's color.  
Then follows a list of lengths coded as such:

\[n-1 * 0 bits] \[length coded in n bits]

For example, if the image starts with 13 white pixels, then 59 black pixels, the bitstream would be:

1 000 1101 00000 111011

- 1: First pixel is white
- 000: Next length value will be 4 bits long
- 1100: 12 white pixels
- 00000: Next length value will be 6 bits long
- 111011: 59 black pixels
- And so on...

It's enterily possible that an image compressed with this scheme will end up larger than if it wasn't compressed.  
This will often happen with heavily dithered images, because of the many changes in pixel colors causing lots of length coding overhead.

## Tag electronics

TODO: Translate

Il existe deux types principaux:

- Les ESL à segments. Économes en énergie, moins chères, mais seulement capables d'afficher quelques caractères.
- Les ESL graphiques. Basées sur des écrans e-paper, bien plus chères, mais permettent d'afficher tout et n'importe quoi.

Ces deux types partagent une base commune: un ASIC mixed-signal (analogique et numérique) propriétaire qui s'occupe de la communication infrarouge au plus bas niveau, et qui abrite également un microcontroleur et de la RAM.

Grâce à [Deus Ex Silicium](https://www.youtube.com/channel/UCH6ppHEvV3_WIXEwmhv9HEg), il a été possible d'observer de près cet ASIC. Il a d'ailleurs publié une vidéo à ce sujet:

Il m'a également fourni plusieurs photos du die, qui ont pu être fusionnées pour produire une photo plus grande.

La finesse de gravure et la complexité du microcontroleur, même sur un circuit conçu pour être le moins cher possible, ne permet pas de discerner individuellement les transistors. Cependant on peut très bien voir les différents blocs:

![ESL](https://www.furrtek.org/noclass/esl/dieshot.jpg)

Il y a deux zones de RAM distinctes, la plus petite sert de RAM de travail pour le MCU. Si on se réfère aux lignes et aux colonnes, celle-ci ne ferait que 32 ou 64 octets. L'architecture du microcontroleur est inconnue. Vu la surface utilisee, je suspecte du 8 bits plutot que du 4.

La logique pour le pilotage des sorties LCD semble être mélangée avec celle du MCU, il n'y a pas de bloc distinct à part les 4 rails d'alimentation qui suivent le bord.

Le bloc PM (Power Management) / Analogique contient de nombreux condensateurs, qui apparaissent comme de gros rectangles de couleur unie. Ce bloc s'occupe de la génération des tensions pour le LCD ainsi que l'amplification et le filtrage du signal provenant de la photodiode.

Le point remarquable est qu'il n'y a pas l'ombre d'un bloc de mémoire flash. Je vois trois raisons possibles à cela:

- Il faut relativement beaucoup de courant pour effacer et programmer de la flash
- Il faut un process special pour avoir de la flash sur un die, qui dit process special dit plus cher
- Le fait que la RAM soit volatile est peut être un avantage financier

Comme il n'y a pas d'autre bloc de mémoire, le firmware est forcément dans le gros bloc de RAM.  
Si l'ESL perd son alimentation, non seulement les données affichées s'envolent mais le firmware aussi.  
En clair, quand les piles sont mortes ou retirées pendant trop longtemps, il n'y a aucun moyen de ressusciter l'ESL.

Le chargement initial du firmware se fait certainement en usine via des contacts qui se trouvent au dos de la carte. Ils sont ensuite masques par une etiquette mais ils toujours accessibles à travers le boitier si on la retire (voir plus bas).

Restriction pour contrôler le recyclage et garder la main sur les étiquettes ? Simple dommage collatéral lié à l'utilisation de RAM pour faire des économies de quantité ? A vous d'en juger.

![](https://www.furrtek.org/noclass/esl/die_pricer.jpg)

![](https://www.furrtek.org/noclass/esl/die_ss.jpg)

Basés à Hong Kong, Solomon-Systech sont relativement connus pour leurs très courants controleurs LCD (SSDxxxx). Ils proposent un service de design d'ASIC et ont leur propre ligne de microcontroleurs (dont ils ne parlent plus sur leur site aujourd'hui).

Les premieres etiquettes graphique de la marque couplaient un ASIC avec un MCU ATMega168 et une EEPROM série pour stocker les images des différentes pages. Cet ASIC joue simplement le role de recepteur/emetteur IR puis passe les donnees au MCU externe qui fait le gros du travail.

J'ai aussi ouvert l'ASIC d'une des étiquettes graphiques (avec des degats). La zone en damier semble être une couche de distribution d'alimentation ou d'égalisation de surface au dessus de la logique. Une partie des blocs analogiques est toujours visible.

![ESL](https://www.furrtek.org/noclass/esl/die_smarttag.jpg)

En comparant avec le die de l'ESL à segments, on peut remarquer de nettes ressemblances.

![ESL](https://www.furrtek.org/noclass/esl/die_7erreurs.jpg)

On peut aussi voir le gros transistor pour la LED infrarouge.

Derrière un autocollant se trouvent deux petits trous dans la coque de l'ESL donnant sur des pastilles du PCB. L'une va au plan de masse, l'autre à l'ASIC. Des impacts de pointes sont visibles, indiquant que ce sont soit des points tests pour vérifier que l'ESL est vivante en sortie d'usine, soit une interface série pour charger le firmware et leur code unique.

Le fait que des trous aient été prévus dans la coque laisse penser que le chargement du firmware peut se faire à nouveau.

Note: C'est pas du 1-wire, c'est vérifié. Ca aurait été surprenant: il aurait fallu qu'ils paient pour utiliser la technologie. C'est probablement un protocole unidirectionnel très simple, pour éviter d'utiliser trop de surface dans l'ASIC. J'imagine bien un gros registre à décalage. Je ne crois pas en l'existence d'un bootloader puisqu'il s'effacerait avec le firmware.

Balancer une horloge sur un contact et du bruit binaire sur l'autre brique l'ESL !

![ESL](https://www.furrtek.org/noclass/esl/progpoints.jpg)

![ESL](https://www.furrtek.org/noclass/esl/59925pcb.jpg)

Le quartz est taillé pour 32768Hz, et sert dès la mise sous tension de l'étiquette (&gt;2.2V).  
A droite on trouve la photodiode D1 wire-bondée directement sur la carte, la LED infrarouge de réponse D2 au dessus, ainsi que son condensateur réservoir C4.  
Une charge pump intégrée dans l'ASIC génère du 6V sur TP9, probablement pour l'afficheur.  
Un procédé nomme "precharge" géré par l'infrastructure permet a l'étiquette de recharger C4 entre plusieurs réponses (allant jusqu'a 2 secondes) afin d'obtenir une réponse puissante malgré le peu de courant disponible instantanément avec les piles CR2032.  
TP13: Tension piles (3V).  
TP18: Masse.  
A noter: 3 fins de pistes sans vernis sont visibles à gauche de R2, possiblement une interface de programmation ou de debug propriétaire.

Todo...

![ESL](https://www.furrtek.org/noclass/esl/dmbrag.jpg)

![ESL](https://www.furrtek.org/noclass/esl/dmholes.jpg)

Toujours les trous pour la programmation en usine (étiquette retirée). Fixme: le trou à gauche est utilisé.

![ESL](https://www.furrtek.org/noclass/esl/batts.jpg)

Tiroir contenant les deux piles CR2032. Il est soudé !

![ESL](https://www.furrtek.org/noclass/esl/dmbot.jpg)

Surprise à l'ouverture: un autocollant sans contact collé derrière l'écran. C'est clairement du NFC, pas un bête antivol 8.2MHz.  
Le fait qu'il n'y ait pas de connection electrique entre l'autocollant et la carte indique qu'il n'est pas possible de mettre à jour son contenu par infrarouge.

![ESL](https://www.furrtek.org/noclass/esl/dmtop.jpg)

TP12\_B: Prog ?  
TP21\_B: VBatt  
TP18\_B: GND

More recents tags have a single QFN asic - TODO: Picture.

## Transmitter electronics

TODO: Add more details, translate.

Uses a Hitachi H8/3687 MCU to decode RS-485 messages and a Lattice 4064V CPLD to generate the IR signal with precise timings.  
A fast enough microcontroller and careful programming would work but they might have chosen a CPLD to future proof eventual protocol updates.  
It is unknown if the CPLD can be reprogrammed by the MCU.

Given the very low amount of logic blocks in the CPLD, it probably just acts as a dual presettable timer to generate the IR symbols from burst and pause durations provided via a parallel bus by the MCU. It should also form a prescaler to do 10MHz / 8 = 1.25MHz.

Patent: [WO0209322](http://worldwide.espacenet.com/publicationDetails/originalDocument?FT=D&date=20020131&DB=&locale=en_EP&CC=WO&NR=0209322A1&KC=A1&ND=1)

These can be daisy-chained up to a certain number. RJ45 connectors but not Ethernet. Unsure why they didn't use PoE.  
RS-485 and 48V power supply. MCU runs at 20MHz and feeds the CPLD with 10MHz.

The uplink circuit is interesting. It has to be extremely sensitive to detect the short and potentially weak IR blinks from the ESLs.  
They actually used components made for FM radio !

SA636 FM IF chip. An AD9833 is used to generate the LO at ?? MHz. A 455kHz discriminator is used.  
La porteuse de reponse est a 1.245MHz. Decalage de seulement 5kHz du 1.25MHz du downlink ?

Filtre ceramique, logo Murata marquage XH:

![ESL](https://www.furrtek.org/noclass/esl/mu455.png)

On trouve une connexion UART 38400bps sur l'µC, qui semble etre unidirectionelle:

```
***** TRX INIT *****
Firmware:
Compile date: Feb  4 2007
Compile time: 20:39:15
PON Counter:0001
TCSRWD:AA
WOKE UP FROM POWER ON
WATCHDOG INITIATION STARTED!
WATCHDOG INITIATION COMPLETED!
irrxSetup:
 Intg. Time:02E1
 Noise Time:02C2
 PreFB Time:0DD8
 RIM Time  :1F95
CABLE: TxError
CABLE: RxError
CABLE: 48V Error
FELIF: Closed (HW error)
Hello: 0000632F
Hello: 0000C651
Hello: 00012BCD
```

L'µC detecte la presence du 48V avec un pont diviseur directement sur l'alimentation (avant la diode): 100k et 4.7k, donnant 2.15V sur une broche ADC (ref interne + comparateur certainement). En ajoutant 100k en parallele sur la resistance de 100k deja presente (=50k), et en alimentant en 30V seulement, l'erreur disparait.

TxError et RxError semblent etre liees a une mesure electrique, et pas un echange de donnees (rien sur la liaison RS485 au demarrage).  
FELIF closed ? J'espere que c'est lie a l'absence de 48V...

![ESL](https://www.furrtek.org/noclass/esl/pricer_trxc45.jpg)

There's LED chain fault detection.

---

## [HN-TITLE] 22. (Blender) Cosmology with Geometry Nodes

- **Source**: [https://www.blender.org/user-stories/cosmology-with-geometry-nodes/](https://www.blender.org/user-stories/cosmology-with-geometry-nodes/)
- **Site**: Blender
- **Author**: MohammadHossein Jamshidi
- **Published**: 2026-02-17
- **HN activity**: 6 points · [0 comments](https://news.ycombinator.com/item?id=47897706)
- **Length**: 2.9K words (~13 min read)
- **Language**: en

> *By MohammadHossein Jamshidi, Ph.D student of Physics/Cosmology at Shahid Beheshti University, Iran.*

I’m a Ph.D student of Physics/Cosmology at Shahid Beheshti University. I have also been an animation engineer in the game industry since 2012. You may find some of my works [on my GitHub](https://jamshidi3d.github.io/portfolio/).

Here I share some of the ideas and techniques of using Blender during my research in cosmology. Although these are specifically used in cosmology, I am confident that similar ideas apply to other areas of science. All the files shown here are available for free on this [GitHub repository](https://github.com/jamshidi3d/CosmicBlenderNodes).

## [](#What-is-Cosmology-and-what-do-I-do)[](#What-is-Cosmology-and-what-do-I-do)What is Cosmology and what do I do?

Cosmology is the science of studying the physical world on gigantic scales, both in size and time; so large that a galaxy could be considered as a single point, and so long that a millennium could be taken as just one frame of time!

My interest in the world of cosmology lies in Cosmic Microwave Background (CMB) radiation, the light rays that reach us from the very early universe. The CMB has a temperature of about 2.7 Kelvin (around -270 degrees Celsius) and is nearly uniform across the sky; however, it has some tiny fluctuations (of size 10−6^{-6} to 10−4^{-4} K) across the sky which contain very fascinating information from the early definable times, and also the history of the universe. These light rays are quite the last things that we can observe in the sky, coming from the farthest distance that could ever be observed. They have recorded numerous events in the history of the universe, and now we are fortunate enough to be able to decode some of their interesting information.

![Cmb_inpaint_T_commander_v1_low](https://hackmd.io/_uploads/ryhh3lP3eg.png)

*Cosmic Microwave Background temperature map*  
*Credit: ESA and the Planck Collaboration*

## [Inspirations to use Geo Nodes for Cosmology](#Inspirations-to-use-Geo-Nodes-for-Cosmology)

The early ideas to use Blender for Cosmology were enlightened in my mind from the creative and delightful works of [Seanterelle](https://www.youtube.com/@seanterelle) on YouTube. I was impressed by his nice simulations with Geo Nodes that had eye-catching performance, and I tried to use Geometry Nodes for cosmological computations.

In 2024, for one of our projects on the CMB, we utilized Geometry Nodes as a tool for computation, visualization, and algorithm debugging. We needed a tool to visualize caps and stripes of different sizes on the CMB sky, to check how much of their areas overlap with the Galactic Mask (the areas where we can’t receive CMB light due to contamination by our Galaxy). By utilizing the Geo Nodes and some simple rigging, we were able to make such a visualizer.

*Visualization of caps and stripes of the CMB sky*

One may access the results of that project in [this paper](https://iopscience.iop.org/article/10.3847/1538-4357/ad68ff). This visualization served as a starting point for us to use Geo Nodes in broader areas and more use cases.

### [](#Geometry-Nodes-for-Computation)[](#Geometry-Nodes-for-Computation)Geometry Nodes for Computation

Geometry Nodes compute things on mesh elements in parallel. If the mesh elements (faces or vertices, etc.) are considered “data storage slots” and “processing threads”, we can perfectly utilize Geo Nodes for doing “Single Instruction Multiple Data” (SIMD) computations. Although other tools, such as CUDA or Compute shaders might be faster, Geo Nodes provides also a free debugger and visualizer. It is especially perfect for small-scale projects; we can calculate and visualize instantly, and in most cases, we can reach the final numerical result in real-time.

![image](https://hackmd.io/_uploads/HyYdkWP3xl.png)

*Mesh elements could be considered as Storages and Threads*

Sometimes we just need a fast and reliable tool, especially for tests on our calculation procedure, to check whether its results are physically correct. Geo Nodes is a handy tool to test the correctness of a procedure/algorithm on a smaller scale. In the following sections, we’ll see some examples of real-life cosmology that we could utilize Geo Nodes not only for visualization and debugging, but also for computation.

As pointed out in the previous section, to work with Geometry Nodes for manipulating data, we need a 3D mesh to store the data on and perform processes with. Making proper meshes for our data is the first step towards utilizing Geo Nodes.

## [How are the sky maps stored?](#How-are-the-sky-maps-stored)

From a computational perspective, it is decisive how to store the data appropriately. The way we store the data can drastically decrease the amount of calculations.  
Cosmologists and geologists need to work with spherical maps/data, and they must store their data in a way that enables faster spherical calculations. There is a special kind of pixelation of the sphere called [HEALPix](https://healpix.sourceforge.io/) (**H**ierarchical **E**qual **A**rea iso**L**atitude **P**ixelation).

![A HEALPix sphere](https://hackmd.io/_uploads/HkdYaEbnge.png)

*A HEALPix sphere*

As suggested by its name, the pixels of this pixelation have the same area. This sphere can be partitioned into rings of pixels that are parallel to the equator. None of its pixel centers are located at the poles (dealing with poles is problematic most of the time). This pixelation is great for storing the data, and doing spherical math with it is very efficient.

> To make a HEALPix sphere at different resolutions, you can find a step-by-step tutorial [here](https://jamshidi3d.github.io/posts/cmb_in_geometry_nodes/).

![](https://www.blender.org/wp-content/uploads/2026/01/helix2-1280x625.png)

*Isolatitude Ring in HLEAPix*

Despite the benefits of HEALPix pixelation, working with the positioning and ordering(numbering/indices) of these pixels can sometimes be complicated. In the following, we’ll see how Geo Nodes saves us from tedious stuff in map analysis.

## [](#Visualization-of-CMB-sky-using-Geo-Nodes)[](#Visualization-of-CMB-sky-using-Geo-Nodes)Visualization of CMB sky using Geo Nodes

Once we create a HEALPix sphere with proper pixel ordering, we can use face attributes of the HEALPix mesh to store the CMB data and visualize it using Geo Nodes. A detailed tutorial for visualizing CMB maps is available [here](https://jamshidi3d.github.io/posts/cmb_in_geometry_nodes/), though I briefly describe the steps here. First, we need to inject the map data (e.g., temperature of the CMB) into the mesh and store the data of each pixel on each face of the HEALPix sphere. Now we should take the min and max temperature, and put them in the range as colors.

## [](#Projection-makes-life-easier)[](#Projection-makes-life-easier)Projection makes life easier

Geometry Nodes’ data(attribute) projection is a very powerful tool that is beneficial in many real-life examples. In this section, I’ll bring a few.

#### [](#Pixel-preserving-map-rotation)Pixel-preserving map rotation

In processing spherical data in cosmology, we need map transformations, and it is valuable mathematically and computationally to keep the HEALPix pixelation during these transformations. Here, we see how it is possible to rotate a map and preserve the pixelation. It may seem hard to perform such a task, but it would be very easy with the attribute projection tool in Geometry Nodes.

The idea is to rotate the HEALPix mesh (the one that contains the map on its faces) and project its data to another HEALPix sphere that is not rotated (transformed). This is actually the idea behind all the map transformations discussed in this section.

Implement-wise, in Geo Nodes, we need to:

1. Make a virtual copy of the original sphere;
2. Rotate it;
3. Cast a ray from inside the sphere towards each pixel and see at which pixel it collides;
4. Project the ray casted attribute on the original mesh.

Following these steps, one will have a nice rotated map, while the pixelation is kept intact:

![](https://www.blender.org/wp-content/uploads/2025/11/image-1280x628.png)

*Implementation of the map rotation*

*Pixelation preserving map rotation*

#### [](#Doppler-boost-addition--removal)Doppler boost addition / removal

Map rotation is not the only use case of the attribute projection; another case that we found useful is to distort a map while keeping the pixelation. The idea is very similar to the one for rotation, that of projecting a distorted map onto an undistorted sphere.

Different physical processes may lead to a distortion in the map. For instance, the Doppler effect is a physical phenomenon that causes the CMB map to be changed by several means, and one of them is aberration (a special kind of distortion). If we weren’t moving with respect to the light sources, the light rays coming to us would be received in the commonly expected directions. However, as our planet (and galaxy) moves in space (with respect to light sources), we receive the incoming light rays in changed directions (see the video below). This phenomenon is called aberration. This happens to all the light rays (including CMB) coming to us, so every sky map will be distorted because of our motion with respect to the light sources.

*Demonstration of the Doppler aberration*

The aberration effect can be easily simulated in Geometry Nodes. If we know at which speed we are moving and in which direction, we can compute the aberrated direction of the incoming light rays (we estimate it by the change in the frequency of the observed light rays). To simulate the aberration, we need to distort pixel borders (vertices of the HEALPix sphere). If we calculate the aberrated direction of each vertex, we will end up with a distorted sphere. Now, if we project this distorted sphere onto a normal HEALPix sphere, we will end up with the aberrated map of the CMB in HEALPix pixelation.

*Simulation of the aberration*

Another effect that occurs when we move across the sky is the Doppler boost effect, which is the change in the wavelengths of the incoming light rays because of our motion with respect to the sources. Each CMB light ray will also be affected by this motion, and so we measure a different temperature for it. Knowing how the temperature changes regarding our velocity, we can compute the change in each pixel. Mixing the aberration and the Doppler boost, we can simulate our motion in the sky with Geo Nodes:

*Simulation of the complete Doppler effect (aberration and frequency change)*

#### [](#Capturing-images-from-sky-map)Capturing images from sky map

In one cosmology project, we needed to investigate some anomalous spots in the CMB sky map by machine learning. For the machine to have similar-looking data from the sky, we needed square images of various regions of the sky. The confusing indexing of the HEALPix maps was misleading for us at first. After a while, we found that by using the Geo Nodes’ attribute projection, we could cleanly prepare our desired images without overthinking about the confusing indexing of HEALPix.

The idea is the same. Place a square plane at where you want to capture the image, and apply the projection in the same way as the previous parts, and you will finish with nice square images from the CMB map.

![image](https://hackmd.io/_uploads/S1vmVbw2le.png)

*Video of the image capturing from the sky map*

We can either store the indices or the temperature directly, but for technical reasons, I preferred pixel indices. Then I spanned the whole sky by placing the plane on top of each area and storing the pixel indices underneath it. So this way, we had the proper dataset for our machine learning program.

## [](#Gravitational-lensing-in-real-time)[](#Gravitational-lensing-in-real-time)Gravitational lensing in real time!

Massive objects in the sky cause the light rays to be bent in their path. When we observe a galaxy, if a massive object is in our line of sight, we see a deformed image of it. From our perspective, sources of light are deflected and differ from their original locations (just as an ordinary optical lens causes us to see objects deformed or dislocated). If the mass of the bending object is not too large, the lensing would be weak. This form of lensing is easy to calculate and is more common in the CMB.

![image](https://hackmd.io/_uploads/Skc8vEPnxe.png)

*Interpretation of weak lensing.*  
*Prat, J., & Bacon, D. “Weak Gravitational Lensing.” arXiv preprint arXiv:2501.07938 (2025).*

To simulate the weak lensing, we need to find the deflected location of the light sources after lensing. To calculate this in Blender, one can consider each vertex of a mesh as a point source of light and manipulate its location by the deflection angle

δθ=4GMbc2\\delta \\theta = \\frac{4GM}{bc^2}

(M is the mass of the massive object and b is the perpendicular distance from the massive object). In the video below, you see the lensed image of Suzanne; it is assumed as a light source, being affected by a massive object.

*Weak gravitational lensing on the mesh of Suzanne*

To utilize it in a real-world scenario, one can take an image of a galaxy and put it on a plane (with enough vertex count) and do the same thing with its vertices to simulate the lensing on the galaxy image:

*Weak lensing applied to a real galaxy image*  
*Image from ESA/Hubble & NASA*

This is very helpful for preparing samples to train a machine to find sources of lensing or delensing an image.

## [](#Sky-sphere-unwrapped-Mollweide-view)[](#Sky-sphere-unwrapped-Mollweide-view)Sky sphere unwrapped! (Mollweide view)

To visualize spherical maps on a 2D plane, there is a common style in which the whole sky is flattened into a 2D image. There are many such interesting mappings; each has its use case. Cosmologists commonly use a mapping called Mollweide.

![Mollview_](https://hackmd.io/_uploads/BkljQWPhex.png)

*Mollweide projection of sphere on plane*

There are packages available that help us to plot in Mollweide view, but they have their own limitations. Geo Nodes enable us to implement these 3 to 2D mappings and allow us to do almost anything to our plots. For example, we can visualize the changes on the map in real-time or draw customized contours.

To plot in Mollweide, first, we need to cut the sphere to unwrap it (a cut in one meridian). We work with the HEALPix sphere, which has a special topology. First, we should add some edges to complete an edge loop on the meridian we want to cut, and then split(cut) it so the sphere can be unwrapped.  
Then, to project points with the Mollweide method, we need to iterate for one coordinate, and we need a for loop in Geo Nodes to do this.

![image](https://hackmd.io/_uploads/BkVqYzK2el.png)

*The implementation of the iteration*

Once we split the mesh and find the projected locations, our sphere will be mapped to Mollweide.

Mollweide in Blender, when combined with other pixelation-preserving techniques in other sections, will provide a real-time Mollweide visualizer. For example, in the following video, you see the map rotation combined with the Mollweide projection:

*Map rotation in Mollweide view*

## [](#Computation-on-pixels-in-parallel)[](#Computation-on-pixels-in-parallel)Computation on pixels in parallel

As mentioned earlier, one can utilize Geometry Nodes to compute things in parallel. Calculating things on each pixel of a spherical map is a very vital use case. In cosmology, it is very common to describe a spherical map/image using spherical harmonic functions (or spherical harmonics). Spherical harmonics are like Fourier series, but they live on the surface of a sphere. They help us to separate large and small features of spherical maps to investigate their physical origins.

Spherical harmonics are typically shown by Yℓm(θ,ϕ). They are functions of direction (θ,ϕ) and have two indices ℓ and m. In our case, a direction is represented by a pixel on the HEAPix sphere (or a face of its mesh). Computing spherical harmonics in general is a bit tricky because they should be calculated using recursive mathematical relations (or they will overflow very quickly using direct formulas). There are several ways to create a recursive relation that gives us the spherical harmonics, but most of them are not numerically stable. They will overflow or get zero very quickly. The image below shows the path to calculate the spherical harmonics stably using recursive relations.

![YlmRecursiveRelation_light](https://hackmd.io/_uploads/BkGAztungx.png)

*Stable path of the recursive relations to compute spherical harmonics*

Having a stable recursive relation, we should implement the formula using nodes. Writing long mathematical formulas in Geo Nodes is quite cumbersome, and I think it requires a script node to handle such scenarios more easily; however, the final results are very nice, fast, and handy.

![image](https://hackmd.io/_uploads/HyWuGKdnle.png)

*Geo Nodes – part of the implementation of the formula of the recursive relation*

Once we implement the formula correctly, it can be shown on the surface of the sphere just like a map:

*Real-time visualization of spherical harmonics*

## [](#Float32-No-problem-with-doing-precision-cosmology)[](#Float32-No-problem-with-doing-precision-cosmology)Float32? No problem with doing precision cosmology

As we know, Geometry Nodes use float32 numbers, which are not precise enough for analysing high-resolution maps or sky partitions, and we need float64 numbers. However, it is not an endpoint to use Geo Nodes for precise computations. It is possible to emulate float64 numbers and their operations with two float32 numbers. There are a lot of algorithms out there for emulating 64-bit numbers with two float32s. The only change that should be made in our process for Geometry Nodes is to store the 64-bit number in two float channels, which means either by using two separate channels or by using a vector channel that holds the two parts of the actual number. One can create custom functions to mimic the operators of the separated numbers. To visualize, though, it would be enough to convert the number back to a float32 and visualize it.

## [](#Other-areas-of-physics)[](#Other-areas-of-physics)Other areas of physics

From the first time I saw the nice works of [Seanterelle](https://www.youtube.com/@seanterelle) on YouTube, to when I was developing the above techniques, and so far, I have been thinking about “what other areas of physics can make use of Geometry Nodes?”. I’m confident that physicists in other areas can utilize Geo Nodes in their field. I can imagine using it for simulating crystals, spin systems, astrophysical systems, liquids, protein folding, general relativity calculations/visualizations, and many-body systems. The list definitely doesn’t end here. You can also think about the use cases and develop simulations in your field.

## [](#Acknowledgement)[](#Acknowledgement)Acknowledgement

I would like to express my sincere gratitude to Professor Nima Khosravi for his invaluable guidance in the scientific aspects of these works.  
A special thanks to Dr.Abdolali Banihshemi for his collaboration throughout both the project and the writing of this post.  
I’m also grateful to Francesco Siddi, Fiona Cohen, and Dr. Sybren Stüvel for their kind support to publish this user story.

---

## [HN-TITLE] 23. I'm done making desktop applications (2009)

- **Source**: [https://www.kalzumeus.com/2009/09/05/desktop-aps-versus-web-apps/](https://www.kalzumeus.com/2009/09/05/desktop-aps-versus-web-apps/)
- **Site**: kalzumeus.com
- **Submitter**: claxo (Hacker News)
- **Submitted**: 2026-04-24 15:44 UTC (Hacker News)
- **HN activity**: 148 points · [176 comments](https://news.ycombinator.com/item?id=47891801)
- **Length**: 2.7K words (~12 min read)

Breaking up has always been difficult for me.  I tend to fall in love with being in love, and continue a relationship well past the point of futility.  And so it is with my oldest love, writing desktop software.

I’m sorry, desktop apps.  We just don’t have a future together anymore.  Its not you, its me.

A bit of background: for the last three years I’ve sold [Bingo Card Creator](http://www.bingocardcreator.com), a desktop app which pretty much does what it says on the tin.  It has gone from being a passing fancy to a rather lucrative hobby to, well, a bit more than that over the years.  As I gradually became more invested in the business of writing desktop software, I got more and more snippy about the periodic desktop versus webapp flamewars, and defended the ascendancy of desktop software.

## What Changed My Mind

Over roughly the same period my day job has changed and transitioned me from writing thick clients in Swing to big freaking enterprise web apps.  I’ve learned SQL, Rails, etc and used them to fairly decent effect in selling Bingo Card Creator, which is a Swing app (if all you have is a hammer…).  This summer, I decided to try stepping my web programming skills up a notch, and released a web version of Bingo Card Creator.  It has **exceeded all my expectations**: in ease of writing, in features, in sales, in support burden, in marketability, etc.  In game theory terms, it *strictly dominates* the desktop version, when seen from the eyes of the developer at any rate.

If I were starting out today, I would, without a shadow of a doubt, write a web app instead of a desktop app, for these reasons:

I have never used the word “shareware” to describe Bingo Card Creator, because I think that it is an anacronism that my customers do not understand, but among fellow technically inclined people it describes the business model succinctly.  Someone visits your website, downloads your trial, and hopefully purchases your program.  That process is called a funnel, and if you break it down into concrete steps, the shareware funnel is **long and arduous** for the consumer:

01. Start your web session on Google, like everyone does these days.
02. Google your pain point.
03. Click on the search result to the shareware site.
04. Read a little, realize they have software that solves your problem.
05. Mentally evaluate whether the software works on your system.
06. Click on the download button.
07. Wait while it downloads.
08. Close your browser.
09. Try to find the file on your hard disk.
10. Execute the installer.
11. Click through six screens that no one in the history of man has ever read.
12. Execute the program.
13. Get dumped at the main screen.
14. Play around, fall in love.
15. *Potentially weeks pass*.
16. Find your way back to the shareware site.  Check out price.
17. Type in your credit card details.  Hit Checkout.

I could go into more detail if I wanted, but that is **seventeen different opportunities for the shareware developer to fail**.  If you don’t catch the download in the 30 seconds people give your website, *no sale*.  If your customer can’t find the file after they download it, *no sale*.  If it requires a JRE upgrade and after restarting their computer they’ve forgotten what they were working on, *no sale*.  If they play around with it, close it, and can’t remember how to open it again, *no sale*.  If they get to the sales page and can’t operate your shopping cart, *no sale*.

Is it any wonder why shareware has typical conversion ratios of [1% or less](http://successfulsoftware.net/2009/04/23/the-truth-about-conversion-ratios-for-software/)?

## Web Applications Convert Better

A web application doesn’t have to be downloaded or installed, never requires a restart, and never requires a contextual change just to open up a purchasing page.  As a result, the conversion ratio is higher.  *Much* higher.  Here are the actual stats from Bingo Card Creator.  I’m looking at conversions from my best performing AdWords campaign only, because that minimizes sources of variation like, e.g., the different types of traffic I’ve gotten in the last 2 months (while the webapp was available) versus in the last three years.

Visitor to Free Trial:

- Downloaded: 18 ~ 22%
- Web App: 22% ~ 26%

Trial to Purchase:

- Downloaded: 1.35%
- Web App: 2.32%

This is **essentially the same application**.  If anything, the online version has *less* features, and it has 2 months of development whereas the downloadable application has had 3 years of improvements made to it.  Yet **the online version outsells my desktop application almost two to one**.

## Your AdWords Strategy Is Very Sensitive To Conversion Rates

A portion the numerical disparity is because I have started to react to, e.g., the difference in conversion rates of advertising and promote accordingly.  A sale of either nets me the same amount of money, about $28.  However, if you break out the math on how much AdWords costs per sale (cost per click divided by conversion rate to trial divided by conversion rate to purchase):

- Downloadable version: $20 AdWords CPA
- Web App: $9 AdWords CPA

(You’re welcome, Google.)

This doesn’t just save me money, it **helps me trounce my competitors**.  For example, if my competitors are selling downloadable software, and they are equally as skilled as I am about writing AdWords ads and optimizing their websites, then it should also cost them about $20 a sale to advertise on AdWords.  (This explains why I never see ads for the competitors who try to gain volume by undercutting my price — if you’re going to price at $23.95, you’d better be a crackerjack SEO because you simply cannot afford to outbid me in AdWords.)

Decreasing my cost of customer acquisition by over half lets me bid more for my AdWords to gain additional volume.  For example, for the longest time my AdWords strategy was more or less monetizing traffic other people couldn’t be bothered with, while larger brands producing e.g. printed bingo supplies went after the head terms like \[bingo cards].  With vastly improved conversion rates, I might be able to advertise profitably on those terms, increasing my volume and making me very, very happy.  As it is, I have walked up bids a bit and am getting 25% more inventory than I usually do.

## Web Applications Are Easier To Support

Many desktop developers hate customer support with a burning passion in their soul.  I actually enjoy it, but I enjoy making it unnecessary even more, as **there is no customer support experience so good as avoiding the problem in the first place**.

Support requests from last 50 customers:

- Desktop Application: 15
- Web Application: 3

I’ve had three years to streamline the website, purchasing process, and application for my desktop app, and that has helped me greatly reduce the number of inquiries I get.  Even after all that work, the main culprits are pretty much the same as ever: installation issues, lost registration keys, and bugs present in old versions of the software that are still floating around download sites.

Web apps, by comparison:

- Have no installation issues, because there is no installation.
- Do not require registration keys.  (Technically, because I allow users to use both the desktop and web application, I issue them one — but it is immediately applied to their account via creative abuse of [e-junkie](http://www.e-junkie.com) and cookies.  Most customers get to use their software immediately without actually reading the bit in the email sent to them — or failing to read it, as happens quite often.)
- Never have an accessible version of the software older than the most recent one.  By comparison, if you were to Google \[[bingo card creator version 1.04](http://www.google.com/#hl=en&q=bingo+card+creator+version+1.04)] (which hasn’t been distributed in, hmm, two years or so), you’d find it on hundreds of download sites.

## The Age Of The Pirates Is Coming An End, Jack

I’m famously lackadaisical about software piracy, preferring to concentrate on [satisfying paying customers](https://www.kalzumeus.com/2006/09/05/everything-you-need-to-know-about-registration-systems/) rather than harming their experience with anti-piracy methods.  However, the existence of pirates is a stitch in my craw, particularly when any schoolmarm typing the name of my software into Google is prompted to try stealing it instead:

[![](https://media.kalzumeus.com/blog-images/piracy-is-dying.png)](https://media.kalzumeus.com/blog-images/piracy-is-dying.png)You want to take a quick stab at how many pirates have circumvented the copy protection on the online version?  **Bwa.  Hah.  Hah**.

I once [remarked to Paul Graham](http://news.ycombinator.com/item?id=503959) that the future of software was with pervasive integration with the server simply because that means that downloading the client doesn’t let you pirate the software any more than downloading Firefox lets you pirate Basecamp.  (Ironically, I made that point in a defense of desktop software as a business model.  Mea maxima culpa!  **Theoretical utility of desktop software is one thing, but I can’t ignore what my numbers are telling me.**)

## Phone Home vs. Google Analytics

One of the curious traits among software developers is that, speaking as a group, we feel something like “I own what happens on my machine and nothing should happen without my say-so”.  This generally leads to a severe reluctance to “phone home” from the application to the developer’s server — even reports on very innocuous data like “Did I steal this software or not?” is often tarred with the label spyware.

On the Internet, privacy expectations have evolved a bit in the last few years.  The overwhelming majority of the public has been told that they’re being tracked via cookies and **could not care less**.  If you write a privacy policy, they won’t even bother reading it.  Which means that you can disclose in your privacy policy that you track non-personally identifying information, which is *very* valuable as a software developer.

- What features of your software are being used?
- What features of your software are being ignored?
- What features are used by people who go on to pay?
- What combination of settings is most common?
- What separates the power users from the one-try-and-quit users?

Tracking all of these is very possible with modern analytics software like, e.g., [Mixpanel](http://www.mixpanel.com).  You can even wrestle the information out of Google Analytics if you’re prepared to do some extra work.  You can do it in a way which respects your users’ privacy while still maximizing your ability to give them what they want.

Some people may be under the impression that users will *tell* you what they want.  Nope — most of them will assume you are like every other business they have ever dealt with, where their opinion doesn’t matter, and the software is offered take-it-or-leave-it.  And they just left it!

Things I learned about Bingo Card Creator customers which I never knew before I had an online app:

- The most common word used in bingo cards is — ready for it — “baby”.  I completely underestimated the demand for [Baby Shower bingo cards](http://www.bingocardcreator.com/bingo-cards/parties-and-events/baby-shower), and avoided making an official set for *years*.  As soon as I had the top ten word list (which was *all* baby shower words) I fixed that.
- The more features I add to the software, [the worse it sells](http://www.bingocardcreator.com/articles/tracking-with-mixpanel.htm#results).  (This is, needless to say, *highly unintuitive* to most software developers.)
- Most customers purchase *within two hours of signup*, so it is absolutely imperative that their first use of the software exceed all their expectations.

## Web Apps Can Be Customized Per User

Downloadable software pretty much has to treat every user identically by default.  There are very limited ways to segment users, and no way to report the results of experiments.  For web apps, however, if you have a halfway decent A/B testing library (like, say, [the free one I wrote for Rails developers](http://www.bingocardcreator.com/abingo/)), you can experiment with having multiple versions of the application available concurrently, and see which one performs best.

The data collected by A/B testing has helped me:

- simplify my options screens to avoid confusing users
- improve the first-run experience
- write instructions such that they’re easier to follow

In addition to changing program behavior randomly, you can segment your users.  I have only scratched the surface of how powerful this is, and it is already producing solid results for me:

**Don’t treat your newbies like you treat your power-users.** You have a database.  It records all their actions since the dawn of time.  *Use it*.  I have a couple very simple heuristics for “is probably still getting used to the software” and, if you are, the software treats you with kid gloves.  For example, it hides complex, seldom used options by default.  It gives you instructions to a degree that a power-user might find insulting.  (I don’t have the artistic skills to draw a little animated paperclip but I would if I could!  *It looks like you’re trying to make a bingo card.  Need help?*)

**Give your customers a “credit” score**.  I have a particular heuristic which segments users into four buckets.  It isn’t exactly FICO, but it does successfully predict conversion rates: they range from 10% in bucket A to 0.01% in bucket D.  Bucket C is interesting, though — they convert some of the time, but don’t seem to be getting quite the value out of Bingo Card Creator that Bucket A does.

I wonder if Bucket C would feel differently if they got a $5 coupon in the email.

Meanwhile, it looks like Bucket D isn’t very interested in paying me money under any circumstances, but if I had a scratch-my-back-to-get-it-free option, I could place it prominently on their dashboards.

## Long Cycles Mean Low Innovation.  Short Cycles Mean Fast Innovation.

This sort of thing is very difficult to do with desktop apps, because you can’t reliably collect data on what approaches work, and you have the build/test/deploy/distribute cycle to worry about.  It takes months for a new version of the desktop application to hit more than half of my users, and I give out upgrades for free.

By comparison, I can *literally* have an A/B test coded, tested, and deployed globally in under a minute, for ones which are fairly low impact.  Relocating a button, for example, requires two lines of code, a SVN commit, and a quick server restart.  I start getting data *immediately*.  By comparison, doing that on my desktop app would require 15 minutes of building, then waiting weeks while the new trials percolated from my website to the various download sites, and probably unforseen issues on Mac OS X 10.4 because apparently in a past life I must have stepped on Pharaoh Jobs’ cat.

Recently, a desktop developer’s mailing list that I’m on commented that a release weekly development cycle is unsustainable, bordering on suicide.  As a desktop developer, I agree, it would break me.  As a web application developer — I have released 67 updates to Bingo Card Creator in the past 7 weeks, and *this isn’t even my day job*.  A button here, some textual edits there, seven A/B tests, etc etc, and pretty soon you’re looking at the magic of compounding 1% improvements.

## Speaking of Magic

I *love* desktop applications.  I prefer them to web apps almost any chance I get. You can keep your Google Docs, Excel is superior in almost every way.

As a developer, I love getting permanent presence right in front of the user (on their desktop, naturally).

My customers love desktop applications.  They love the “physicality”.  They love the perceived security (the number of people who purchased backup CDs and then proceeded to only use the webapp is *downright distressing* to me).  They love that the application has first-class OS support, feels native, copies and pastes right, works with double clicking files, etc etc.

But at the end of the day, I’m an engineer.  I follow the numbers where they lead me.  The numbers say that [sales in this August were 60% over those of last August](http://www.bingocardcreator.com/stats/sales-by-month), despite a major blowup with Google that should have cost me dearly.  All of my attempts to distill wisdom from the statistics have lead to one conclusion: the cumulative advantages of the web application, in my advertising, in my on-site experience, within the application, within my development process, and within my purchase funnel are just stupendously superior to the desktop app.

I’m sorry, desktop apps.  We had good times together, but we’re through.

\[Edit to add: I’m going to continue supporting all customers of Bingo Card Creator, regardless of how they choose to get it. The next major release will almost certainly be its last. The webapp, and my future webapps, seem to be much better investments.]

---

## [HN-TITLE] 24. FusionCore: ROS 2 sensor fusion (IMU and GPS and encoders)

- **Source**: [https://github.com/manankharwar/fusioncore](https://github.com/manankharwar/fusioncore)
- **Site**: GitHub
- **Submitter**: kharwarm (Hacker News)
- **Submitted**: 2026-04-24 22:36 UTC (Hacker News)
- **HN activity**: 12 points · [6 comments](https://news.ycombinator.com/item?id=47896670)
- **Length**: 4.0K words (~18 min read)
- **Language**: en

[![Trajectory overlay: all 6 sequences, SE3-aligned to RTK GPS ground truth](https://github.com/manankharwar/fusioncore/raw/main/figures/fig2_traj_grid.png)](https://github.com/manankharwar/fusioncore/blob/main/figures/fig2_traj_grid.png)

[![CI](https://github.com/manankharwar/fusioncore/actions/workflows/ci.yml/badge.svg)](https://github.com/manankharwar/fusioncore/actions/workflows/ci.yml)

**ROS 2 sensor fusion SDK. Combines IMU, wheel encoders, and GPS into one reliable position estimate. Self-tuning noise covariance. Apache 2.0.**

* * *

## What problem does this solve?

[](#what-problem-does-this-solve)

Every mobile robot needs to know where it is. It gets this from multiple sensors: IMU, wheel encoders, GPS: each of which is imperfect in its own way. IMUs drift. Wheels slip. GPS jumps. You need software that intelligently combines all three into one trustworthy position estimate.

That software is called a sensor fusion package. The standard one for ROS, `robot_localization`, lacks native ECEF GPS fusion, IMU bias estimation, and adaptive noise covariance. Its designated replacement (`fuse`) has incomplete GPS support with no ECEF handling or RTK quality gating as of early 2026. No clear accessible replacement exists for either.

FusionCore is built to fill that gap.

* * *

## Benchmark results

[](#benchmark-results)

FusionCore vs robot\_localization on the [NCLT dataset](http://robots.engin.umich.edu/nclt/) (University of Michigan): same IMU + wheel odometry + GPS, no manual tuning. Six sequences, same pipeline:

[![ATE RMSE across 6 NCLT sequences](https://github.com/manankharwar/fusioncore/raw/main/figures/fig1_bar_chart.png)](https://github.com/manankharwar/fusioncore/blob/main/figures/fig1_bar_chart.png)

Sequence FC ATE RMSE RL-EKF ATE RMSE RL-UKF 2012-01-08 **5.6 m** 23.4 m NaN divergence at t=31 s 2012-02-04 **9.7 m** 20.6 m NaN divergence at t=22 s 2012-03-31 **4.2 m** 10.8 m NaN divergence at t=18 s 2012-08-20 **7.5 m** 9.4 m NaN divergence 2012-11-04 28.7 m **10.9 m** NaN divergence 2013-02-23 **4.1 m** 5.8 m NaN divergence

FusionCore wins 5 of 6 sequences. On 2012-11-04 (fall, degraded GPS), FC's Mahalanobis outlier gate still loses despite inertial coast mode (Q inflation on consecutive rejections): GPS was sufficiently degraded for long enough that accumulated drift could not be fully recovered. RL-EKF has no rejection gate and self-corrects immediately. RL-UKF diverged with NaN on all six sequences. Full methodology, configs, and reproduce instructions in [`benchmarks/`](https://github.com/manankharwar/fusioncore/blob/main/benchmarks).

* * *

## Why FusionCore

[](#why-fusioncore)

Capability robot\_localization Fuse FusionCore Core filter EKF or UKF Factor graph UKF (22D quaternion state) 3D support Yes Yes Full 3D, native IMU bias estimation No built-in states Plugin-dependent Gyro + accel bias states GPS fusion navsat\_transform node Plugin, no ECEF/RTK ECEF-native, single node Dual antenna heading No No Yes IMU frame transform Manual (YAML) Manual (YAML) Automatic via TF Message covariances Used Partial Full 3×3 GNSS + odometry GNSS antenna offset Ignored Ignored Lever arm + observability guard Outlier rejection mahalanobis\_threshold Robust loss functions Chi-squared gating, all sensors GPS fix quality gating No No GPS / DGPS / RTK\_FLOAT / RTK\_FIXED Adaptive noise Manual Manual Auto from innovation sequence TF validation at startup Basic No Startup check + fix commands Multiple GNSS receivers Workaround Workaround Native, independent lever arms compass\_msgs/Azimuth No No Yes (ENU/NED, rad/deg) Delay compensation history\_length Factor graph inherent Full IMU replay, 500ms Ground constraint Not built-in Not built-in VZ=0 pseudo-measurement ZUPT Not built-in Not built-in Auto when stationary Sensor dropout detection Basic Basic Per-sensor SensorHealth enum /diagnostics Basic Basic Per-sensor health + outliers Published covariance Yes Yes Full UKF P matrix Filter reset service No No ~/reset (no restart needed) Maintenance Reduced since 2023 Active Active, 24h response License BSD-3 BSD-3 Apache 2.0 ROS 2 Jazzy Ported from ROS 1 Native Native, from scratch

* * *

## Installation

[](#installation)

### Prerequisites

[](#prerequisites)

- ROS 2 Jazzy Jalisco (primary) or ROS 2 Kilted (community tested)
- A colcon workspace (`~/ros2_ws`)

### Clone into your workspace

[](#clone-into-your-workspace)

This is a monorepo with 4 independent ament\_cmake packages: `compass_msgs`, `fusioncore_core`, `fusioncore_ros`, and `fusioncore_gazebo`. Each has its own `package.xml`. Colcon finds them by scanning `src/` recursively. The repo root has no package.xml and is not itself a package. The repo must live inside `src/` for colcon to find the packages.

```
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/manankharwar/fusioncore.git
cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash
rosdep install --from-paths src --ignore-src -r -y
colcon build
source install/setup.bash
```

> **Real robot users (no Gazebo):** `fusioncore_gazebo` depends on `ros_gz_sim` which pulls in Gazebo and its GUI components. On headless machines (Raspberry Pi, server) this install is large, unnecessary, and may fail. Skip it by adding a `COLCON_IGNORE` file before building:
> 
> ```
> touch ~/ros2_ws/src/fusioncore/fusioncore_gazebo/COLCON_IGNORE
> ```
> 
> This tells colcon to skip that package entirely. `fusioncore_core` and `fusioncore_ros` have no Gazebo dependency and build fine without it.

* * *

## Running the tests

[](#running-the-tests)

```
cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash
colcon build --packages-select fusioncore_core --cmake-args -DBUILD_TESTING=ON
colcon test --packages-select fusioncore_core
colcon test-result --verbose
```

Expected output: `39 tests, 0 errors, 0 failures, 0 skipped`

* * *

## Running FusionCore

[](#running-fusioncore)

```
# Terminal 1: launch the node
ros2 launch fusioncore_ros fusioncore.launch.py

# Terminal 2: configure and activate the lifecycle node
ros2 lifecycle set /fusioncore configure
ros2 lifecycle set /fusioncore activate

# Verify it's publishing at 100Hz
ros2 topic hz /fusion/odom
# expected: average rate: 100.000
```

FusionCore uses a ROS 2 lifecycle node. Configure first (load parameters, validate TF tree, check transforms), then activate (start processing sensor data). This prevents the filter from starting with bad initial values or missing transforms.

> **WSL2 note:** If `ros2 lifecycle set` returns "Node not found", use the launch file's built-in auto-configure instead. The Gazebo launch file (`fusioncore_gazebo.launch.py`) configures and activates the node automatically via `EmitEvent(ChangeState(...))` 15 seconds after startup, bypassing DDS discovery latency that affects WSL2.

* * *

## Verifying all features work

[](#verifying-all-features-work)

You can test every FusionCore feature without a physical robot using fake sensor data. Replace `~/YOUR_WS` with your actual workspace path (e.g. `~/ros2_ws`, `~/fusioncore_ws`). Open 4 terminals:

**Terminal 1: Launch FusionCore:**

```
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash
ros2 launch fusioncore_ros fusioncore.launch.py
```

**Terminal 2: Configure and activate:**

```
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash

# Publish required TF transforms (stays running in background)
ros2 run tf2_ros static_transform_publisher --frame-id base_link --child-frame-id imu_link &
ros2 run tf2_ros static_transform_publisher --frame-id odom --child-frame-id base_link &
sleep 1

ros2 lifecycle set /fusioncore configure
sleep 1
ros2 lifecycle set /fusioncore activate
```

**Terminal 3: Feed fake sensors:**

```
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash

# Fake IMU at 100Hz (stationary, gravity pointing up)
ros2 topic pub /imu/data sensor_msgs/msg/Imu "{
  header: {frame_id: 'base_link'},
  angular_velocity: {x: 0.0, y: 0.0, z: 0.0},
  linear_acceleration: {x: 0.0, y: 0.0, z: 9.81},
  orientation_covariance: [-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
}" --rate 100 &

# Fake wheel encoder at 50Hz (stationary: triggers ZUPT)
ros2 topic pub /odom/wheels nav_msgs/msg/Odometry "{
  header: {frame_id: 'odom'},
  twist: {twist: {linear: {x: 0.0}, angular: {z: 0.0}}}
}" --rate 50 &

# Fake GPS at 5Hz (Hamilton, Ontario)
ros2 topic pub /gnss/fix sensor_msgs/msg/NavSatFix "{
  header: {frame_id: 'base_link'},
  status: {status: 0},
  latitude: 43.2557,
  longitude: -79.8711,
  altitude: 100.0,
  position_covariance: [1.0, 0, 0, 0, 1.0, 0, 0, 0, 4.0],
  position_covariance_type: 2
}" --rate 5
```

**Terminal 4: Verify each feature:**

Check what topics and services are live:

```
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash
ros2 topic list | grep fusion
ros2 service list | grep fusioncore
```

You should see `/fusion/odom`, `/fusion/pose`, and `/fusioncore/reset`.

Test `/fusion/pose`: what Nav2, AMCL, and slam\_toolbox expect:

```
ros2 topic echo /fusion/pose --once
```

You should see a pose message with a full 6×6 covariance matrix from the UKF.

Test `/diagnostics`: per-sensor health at 1Hz:

```
ros2 topic echo /diagnostics --once
```

You should see 4 status entries: `fusioncore: IMU`, `fusioncore: Encoder`, `fusioncore: GNSS`, `fusioncore: Filter`. Each shows OK or WARN with outlier counts and heading status.

Test ZUPT: velocity should stay near zero while stationary:

```
ros2 topic echo /fusion/odom --field twist.twist.linear
```

Values should be essentially zero (`~1e-10`) even while the IMU is running. This confirms ZUPT is suppressing velocity drift when the robot is not moving.

Test the reset service: reinitializes filter without restarting the node:

```
ros2 service call /fusioncore/reset std_srvs/srv/Trigger
```

Expected: `success: True, message: 'FusionCore filter reset. GPS reference cleared.'`

Within 1 second of calling reset, the node log (Terminal 1) should print:

```
Filter reset via ~/reset service.
GNSS reference set: lat=43.255700 lon=-79.871100
```

with **no** `GNSS fix rejected` warnings. GPS re-fuses immediately after reset.

* * *

## Sensor topics

[](#sensor-topics)

**Subscribes to:**

Topic Type What it is `/imu/data` `sensor_msgs/Imu` IMU angular velocity and linear acceleration `/odom/wheels` `nav_msgs/Odometry` Wheel encoder velocity `/gnss/fix` `sensor_msgs/NavSatFix` GPS position `/gnss/heading` `sensor_msgs/Imu` Dual antenna heading (optional) `gnss.azimuth_topic` `compass_msgs/Azimuth` Azimuth heading (optional, preferred standard) `gnss.fix2_topic` `sensor_msgs/NavSatFix` Second GPS receiver (optional)

**Publishes:**

Topic Type What it is `/fusion/odom` `nav_msgs/Odometry` Fused position + orientation + velocity + covariance at 100Hz `/fusion/pose` `geometry_msgs/PoseWithCovarianceStamped` Same pose: compatible with AMCL, slam\_toolbox, Nav2 pose initializer `/diagnostics` `diagnostic_msgs/DiagnosticArray` Per-sensor health, outlier counts, heading status at 1Hz `/tf` TF `odom -> base_link` for Nav2

**Services:**

Service Type What it does `~/reset` `std_srvs/Trigger` Re-initializes the filter and clears the GPS reference anchor without restarting the node. Useful after teleportation in simulation or after a catastrophic GPS jump in the field.

* * *

## Configuration

[](#configuration)

```
fusioncore:
  ros__parameters:
    base_frame: base_link
    odom_frame: odom
    publish_rate: 100.0

    imu.gyro_noise: 0.005       # rad/s: from your IMU datasheet
    imu.accel_noise: 0.1        # m/s²
    imu.has_magnetometer: false # true for 9-axis IMUs (BNO08x, VectorNav, Xsens)
                                # false for 6-axis: yaw from gyro integration drifts
    imu.remove_gravitational_acceleration: false  # set true if robot drifts in Z while stationary
                                                   # most IMUs report raw specific force (gravity included)
                                                   # FusionCore removes gravity using current filter orientation

    encoder.vel_noise: 0.05     # m/s
    encoder.yaw_noise: 0.02     # rad/s

    gnss.base_noise_xy: 1.0     # meters: scaled automatically by HDOP
    gnss.base_noise_z: 2.0      # meters
    gnss.heading_noise: 0.02    # rad: for dual antenna
    gnss.max_hdop: 4.0          # reject fixes worse than this
    gnss.min_satellites: 4
    gnss.min_fix_type: 1        # minimum fix quality: 1=GPS, 2=DGPS, 3=RTK_FLOAT, 4=RTK_FIXED
                                # note: sensor_msgs/NavSatFix status=2 maps to RTK_FIXED only.
                                # RTK_FLOAT (3) is unreachable via NavSatFix: use 2 or 4.

    # Antenna lever arm: offset from base_link to primary GPS antenna in body frame
    # x=forward, y=left, z=up (meters). Leave at 0 if antenna is above base_link.
    # Lever arm correction only activates when heading is independently validated.
    gnss.lever_arm_x: 0.0
    gnss.lever_arm_y: 0.0
    gnss.lever_arm_z: 0.0

    # Second GPS receiver lever arm (if using gnss.fix2_topic)
    gnss.lever_arm2_x: 0.0
    gnss.lever_arm2_y: 0.0
    gnss.lever_arm2_z: 0.0

    # Optional second GPS receiver
    gnss.fix2_topic: ""

    # Heading topics: pick one or both
    gnss.heading_topic: "/gnss/heading"   # sensor_msgs/Imu
    gnss.azimuth_topic: ""                # compass_msgs/Azimuth (preferred)

    # Mahalanobis outlier rejection
    outlier_rejection: true
    outlier_threshold_gnss: 16.27   # chi2(3, 0.999): 3D position
    outlier_threshold_hdg: 10.83    # chi2(1, 0.999): 1D heading
    outlier_threshold_enc: 11.34    # chi2(3, 0.999): 3D encoder
    outlier_threshold_imu: 15.09    # chi2(6, 0.999): 6D IMU

    # Adaptive noise covariance
    adaptive.imu: true
    adaptive.encoder: true
    adaptive.gnss: true
    adaptive.window: 50
    adaptive.alpha: 0.01

    ukf.q_position: 0.01
    ukf.q_orientation: 1.0e-9  # quaternion regularization only: do NOT increase this
    ukf.q_velocity: 0.1
    ukf.q_angular_vel: 0.1
    ukf.q_acceleration: 1.0
    ukf.q_gyro_bias: 1.0e-5
    ukf.q_accel_bias: 1.0e-5
```

> **Upgrading from an older config?** If your YAML has `ukf.q_orientation: 0.01`, change it to `1.0e-9` or delete the line. The old value corrupts quaternion math at typical IMU rates and causes yaw drift and Z-axis rise in simulation.

### GPS Coordinate Reference System (CRS)

[](#gps-coordinate-reference-system-crs)

FusionCore uses [PROJ](https://proj.org/) to convert incoming GNSS fixes between coordinate systems. The defaults handle any standard GPS receiver (WGS84 lat/lon → ECEF). Change these only if your receiver outputs a different CRS.

```
    # PROJ coordinate reference system
    input.gnss_crs: "EPSG:4326"              # CRS of incoming NavSatFix messages
                                              # EPSG:4326 = WGS84 lat/lon (standard GPS)
                                              # EPSG:32617 = UTM zone 17N (some RTK receivers)
    output.crs: "EPSG:4978"                  # internal computation CRS
                                              # EPSG:4978 = ECEF XYZ (default, globally valid)
    output.convert_to_enu_at_reference: true  # true when output.crs is ECEF
                                              # false when output.crs is already a local projected CRS
    reference.use_first_fix: true            # anchor local ENU origin to first GPS fix
    reference.x: 0.0                         # fixed origin in output.crs (when use_first_fix: false)
    reference.y: 0.0
    reference.z: 0.0
```

**Agricultural RTK example**: receiver outputs UTM zone 17N (easting/northing) directly:

```
    input.gnss_crs: "EPSG:32617"
    output.crs: "EPSG:32617"
    output.convert_to_enu_at_reference: false
    reference.use_first_fix: true
```

* * *

## How FusionCore handles the hard problems

[](#how-fusioncore-handles-the-hard-problems)

### IMU frame transform

[](#imu-frame-transform)

IMUs are almost never mounted at `base_link`. FusionCore reads `frame_id` from every IMU message, looks up the TF rotation to `base_link`, and rotates angular velocity and linear acceleration before fusing. If the transform is missing you get the exact command to fix it:

```
[WARN] Cannot transform IMU from imu_link to base_link.
Fix: ros2 run tf2_ros static_transform_publisher --frame-id base_link --child-frame-id imu_link
```

### TF validation at startup

[](#tf-validation-at-startup)

During `configure`, FusionCore checks that all required TF transforms exist before the filter starts. Missing transforms print the exact fix command: no silent failures, no mysterious drift:

```
--- TF Validation ---
  [OK]      imu_link -> base_link
  [MISSING] base_link -> odom  Fix: ros2 run tf2_ros static_transform_publisher --frame-id odom --child-frame-id base_link
---------------------
```

### Mahalanobis outlier rejection

[](#mahalanobis-outlier-rejection)

Before fusing any GPS fix, FusionCore computes how statistically implausible the measurement is given the current state estimate. The Mahalanobis distance `d² = νᵀ · S⁻¹ · ν` is compared against chi-squared thresholds at the 99.9th percentile. Fixes that exceed the threshold are rejected without updating the filter.

This handles GPS jumps, multipath errors, and encoder slip spikes. The filter position stays stable during rejection: verified by injecting a 500m GPS jump in testing and observing zero position change.

GNSS position covariance is floored before the gate is evaluated. This prevents RTK-grade receivers (typical σxy ~3mm) from triggering self-rejection when the filter has not yet converged to RTK-level accuracy.

[![](https://private-user-images.githubusercontent.com/44316521/575392245-f03a5458-761f-4bce-9e17-cc383bdcd57a.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzcwODc2NTQsIm5iZiI6MTc3NzA4NzM1NCwicGF0aCI6Ii80NDMxNjUyMS81NzUzOTIyNDUtZjAzYTU0NTgtNzYxZi00YmNlLTllMTctY2MzODNiZGNkNTdhLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA0MjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDI1VDAzMjIzNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlmZTE2MGJkMjhkNTU3Yzg2ZmE5NGNkYTFmZGZhMDExODY2ZjQzMzIzYjhjNmRmZTAyZjcyMTg3NTY1ZWQyNDUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.Tcijzt4h6Xti-WQpvcYXeOkqQqW90h-i9CLuvnMYH24)](https://private-user-images.githubusercontent.com/44316521/575392245-f03a5458-761f-4bce-9e17-cc383bdcd57a.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzcwODc2NTQsIm5iZiI6MTc3NzA4NzM1NCwicGF0aCI6Ii80NDMxNjUyMS81NzUzOTIyNDUtZjAzYTU0NTgtNzYxZi00YmNlLTllMTctY2MzODNiZGNkNTdhLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA0MjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDI1VDAzMjIzNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlmZTE2MGJkMjhkNTU3Yzg2ZmE5NGNkYTFmZGZhMDExODY2ZjQzMzIzYjhjNmRmZTAyZjcyMTg3NTY1ZWQyNDUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.Tcijzt4h6Xti-WQpvcYXeOkqQqW90h-i9CLuvnMYH24)

### Zero velocity updates (ZUPT)

[](#zero-velocity-updates-zupt)

When the robot is stationary: encoder speed below 0.05 m/s and angular rate below 0.05 rad/s: FusionCore fuses a zero velocity pseudo-measurement with very tight noise. This stops the IMU from drifting the velocity estimate while the robot is sitting still. Every serious inertial navigation system does this. Without ZUPT, IMU noise accumulates into a false velocity estimate over time even when the robot has not moved.

All MEMS IMUs have a small accelerometer and gyro bias that is unknown at startup. By default the filter learns it over ~60 seconds, causing a small position offset at startup. Setting `init.stationary_window: 2.0` makes the filter collect 2 seconds of IMU data before starting, estimate the bias directly, and initialize with the correct values: reducing the startup transient from ~10cm to under 1cm. The robot must be stationary during the window; if it moves, the filter falls back to zero bias automatically.

### Adaptive noise covariance

[](#adaptive-noise-covariance)

FusionCore tracks a sliding window of 50 innovation sequences per sensor and estimates the actual noise covariance from the data. The noise matrix R is slowly updated toward the estimated true value using an exponential moving average with `alpha=0.01`. After a few minutes of operation, R converges to the real sensor characteristics automatically. No manual YAML tuning required.

### GPS antenna offset (lever arm)

[](#gps-antenna-offset-lever-arm)

If the GPS antenna is not at `base_link`: mounted on top of the robot, forward of center: its readings correspond to a different trajectory than `base_link`. FusionCore corrects for this using the rotation matrix from the current state: `p_antenna = p_base + R * lever_arm`. Lever arm correction only activates when heading has been independently validated: applying it with wrong heading makes things worse, not better.

Each GPS receiver has its own independent lever arm. Primary receiver uses `gnss.lever_arm_x/y/z`, secondary receiver uses `gnss.lever_arm2_x/y/z`.

### Heading observability

[](#heading-observability)

FusionCore tracks a `heading_validated_` flag that is only set true from a genuine independent source:

- **`DUAL_ANTENNA`** : dual antenna heading message received
- **`IMU_ORIENTATION`** : 9-axis AHRS published full orientation (only when `imu.has_magnetometer: true`: 6-axis IMUs drift in yaw and don't count)
- **`GPS_TRACK`** : robot has traveled &gt;= 5 meters at speed &gt;= 0.2 m/s with yaw rate &lt;= 0.3 rad/s

Before any of these, lever arm is disabled regardless of what yaw variance says.

### GPS fix quality gating

[](#gps-fix-quality-gating)

FusionCore maps `sensor_msgs/NavSatFix.status` to an internal fix type enum and rejects fixes below a configurable minimum quality. The default accepts any valid GPS fix. Set to 4 to require RTK\_FIXED:

```
gnss.min_fix_type: 4   # require RTK_FIXED: reject basic GPS entirely
```

When a fix is rejected due to quality, the rejection log shows the fix type and threshold:

```
[WARN] GNSS fix rejected (fix_type=1, min=4, hdop=1.20, quality check or Mahalanobis gate)
```

Important: `sensor_msgs/NavSatFix` has no `STATUS_RTK_FLOAT`. Status 2 maps to RTK\_FIXED. Setting `min_fix_type: 3` will silently starve the filter. Use 2 or 4 as meaningful thresholds.

### Per-sensor diagnostics

[](#per-sensor-diagnostics)

FusionCore publishes `/diagnostics` at 1Hz compatible with `rqt_robot_monitor` and Nav2. Four status entries: IMU, Encoder, GNSS, Filter. Each shows OK or WARN with outlier counts, heading source, distance traveled, position uncertainty, and update count.

### Filter reset service

[](#filter-reset-service)

```
ros2 service call /fusioncore/reset std_srvs/srv/Trigger
```

Reinitializes the UKF state and clears the GPS reference anchor. The robot re-anchors on the next GPS fix. No node restart required.

### Message covariances

[](#message-covariances)

FusionCore uses the covariance values sensors actually publish. GPS: full 3x3 matrix when `position_covariance_type == 3`. Wheel odometry: reads `twist.covariance` per-axis. IMU orientation: reads `orientation_covariance` from the message.

### Delay compensation

[](#delay-compensation)

FusionCore stores a ring buffer of 100 IMU messages (1 second at 100Hz). When a delayed GPS fix arrives, it restores the closest state snapshot before the fix timestamp, re-fuses the fix at the correct time, then replays all buffered IMU messages forward to now. This eliminates motion-model approximation error for delayed measurements.

### Non-holonomic ground constraint

[](#non-holonomic-ground-constraint)

For wheeled ground robots, FusionCore fuses a `VZ = 0` pseudo-measurement on every encoder update. This prevents vertical velocity from drifting due to IMU noise. Do not use for aerial vehicles or robots that move vertically.

### Sensor dropout detection

[](#sensor-dropout-detection)

FusionCore tracks the last update time for each sensor independently. If a sensor goes silent for longer than `stale_timeout` (default 1.0 second), `get_status()` returns `SensorHealth::STALE` for that sensor. The filter continues running on the remaining sensors and recovers automatically when the missing sensor resumes.

### compass\_msgs/Azimuth

[](#compass_msgsazimuth)

FusionCore ships a ROS 2 native port of `compass_msgs/Azimuth` (upstream is ROS 1 only). Handles ENU/NED convention conversion, RAD/DEG units, and warns when magnetic north reference is used instead of geographic.

* * *

## Simulation

[](#simulation)

FusionCore ships with a Gazebo Harmonic simulation world so you can test the full fusion pipeline without physical hardware. It includes a differential drive robot with a 100Hz IMU and GPS, in an outdoor environment with the GPS origin set to Hamilton, Ontario.

Gazebo Harmonic's built-in NavSat sensor has a known bug (gz-sim issue #2163) where it periodically outputs GPS fixes at completely wrong coordinates. Rather than fight a broken sensor, the simulation derives GPS from Gazebo's ground truth world pose and adds realistic Gaussian noise (0.5m horizontal, 0.3m vertical 1-sigma).

### Prerequisites for simulation

[](#prerequisites-for-simulation)

Gazebo Harmonic and the ROS-Gazebo bridge are not installed by `rosdep` automatically. Install them first:

```
sudo apt install ros-jazzy-ros-gz
```

### Running the simulation

[](#running-the-simulation)

```
cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash
colcon build
source install/setup.bash
ros2 launch fusioncore_gazebo fusioncore_gazebo.launch.py
```

Drive the robot and watch the fused position:

```
# Terminal 2: drive in a circle
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5}, angular: {z: 0.3}}" --rate 10

# Terminal 3: watch position
source /opt/ros/jazzy/setup.bash
source ~/YOUR_WS/install/setup.bash
ros2 topic echo /fusion/odom --field pose.pose.position
```

### Integration tests

[](#integration-tests)

```
python3 ~/ros2_ws/src/fusioncore/fusioncore_gazebo/launch/integration_test.py
```

Four automated tests: IMU drift rate, outlier rejection, GPS correction after drift, full circle return. All four pass on a clean session.

* * *

## Real robot configs

[](#real-robot-configs)

Hardware configs in `fusioncore_ros/config/`: noise values pulled from datasheets, comments explain every parameter:

Config Platform IMU GPS [`clearpath_husky.yaml`](https://github.com/manankharwar/fusioncore/blob/main/fusioncore_ros/config/clearpath_husky.yaml) Clearpath Husky A200 Microstrain 3DM-GX5-25 u-blox F9P [`bno085_custom.yaml`](https://github.com/manankharwar/fusioncore/blob/main/fusioncore_ros/config/bno085_custom.yaml) Any differential drive (DIY / custom) Bosch BNO085 u-blox M8N class [`duatic_mecanum.yaml`](https://github.com/manankharwar/fusioncore/blob/main/fusioncore_ros/config/duatic_mecanum.yaml) Duatic mecanum manipulator BNO085 none

Combine any hardware config with an [environment preset](https://github.com/manankharwar/fusioncore/blob/main/fusioncore_ros/config) (`env_open.yaml`, `env_urban.yaml`, `env_canopy.yaml`) to tune GPS trust for your operating conditions without touching the hardware config.

To add your robot's config, open a [Hardware Config Request](https://github.com/manankharwar/fusioncore/issues/new/choose) or submit a PR: see [CONTRIBUTING.md](https://github.com/manankharwar/fusioncore/blob/main/CONTRIBUTING.md).

### Migrating from robot\_localization

[](#migrating-from-robot_localization)

Complete parameter mapping, topic changes, and step-by-step instructions: [**docs/migration\_from\_robot\_localization.md**](https://github.com/manankharwar/fusioncore/blob/main/docs/migration_from_robot_localization.md)

* * *

## Using FusionCore with Nav2

[](#using-fusioncore-with-nav2)

FusionCore is a drop-in odometry source for Nav2. It publishes everything Nav2 needs out of the box: no remapping, no extra nodes.

**What FusionCore publishes that Nav2 uses:**

FusionCore output Nav2 use `/fusion/odom` (`nav_msgs/Odometry`) Set as `odom_topic` in `nav2_params.yaml` `odom → base_link` TF Nav2 reads this directly: no config needed `/fusion/pose` (`PoseWithCovarianceStamped`) AMCL initial pose, slam\_toolbox pose input `/diagnostics` Nav2-compatible diagnostic format

**Step 1: Point Nav2 at FusionCore's odometry:**

In your `nav2_params.yaml`, set `odom_topic` to `/fusion/odom` wherever it appears (typically `amcl`, `bt_navigator`, `velocity_smoother`):

```
amcl:
  ros__parameters:
    odom_topic: /fusion/odom

bt_navigator:
  ros__parameters:
    odom_topic: /fusion/odom

velocity_smoother:
  ros__parameters:
    odom_topic: /fusion/odom
```

**Step 2: Launch FusionCore alongside Nav2:**

`fusioncore_nav2.launch.py` handles the full sequence: starts FusionCore, configures it, activates it, then starts Nav2 once the TF is live.

```
ros2 launch fusioncore_ros fusioncore_nav2.launch.py \
  fusioncore_config:=/path/to/your/fusioncore.yaml \
  nav2_params:=/path/to/your/nav2_params.yaml
```

With an environment preset:

```
ros2 launch fusioncore_ros fusioncore_nav2.launch.py \
  fusioncore_config:=/path/to/your/fusioncore.yaml \
  nav2_params:=/path/to/your/nav2_params.yaml \
  env_config:=$(ros2 pkg prefix fusioncore_ros)/share/fusioncore_ros/config/env_urban.yaml
```

The launch file configures FusionCore after 2 s, activates it on the configuring → inactive transition, then starts Nav2 after 5 s: guaranteeing `odom → base_link` TF is publishing before Nav2's costmaps initialize.

**That's it.** No additional nodes, no coordinate transforms, no remapping. FusionCore's `odom → base_link` TF is what Nav2's costmaps and planners track. GPS waypoint navigation via Nav2's `fromLL` service works automatically once FusionCore has a GPS fix.

**For GPS waypoint navigation** (`nav2_waypoint_follower` with `fromLL`):

```
# FusionCore exposes the fromLL service once it has a GPS fix
ros2 service call /fromLL fusioncore_ros/srv/FromLL \
  "{ll_point: {latitude: 43.2557, longitude: -79.8711, altitude: 0.0}}"
```

* * *

## Architecture

[](#architecture)

```
fusioncore/
├── fusioncore_core/              # Pure C++17 math library. Zero ROS dependency.
│   ├── include/fusioncore/
│   │   ├── ukf.hpp               # Unscented Kalman Filter: 45 sigma points
│   │   ├── state.hpp             # 22-dimensional state vector (quaternion orientation)
│   │   ├── fusioncore.hpp        # Public API: FusionCore, FusionCoreConfig
│   │   └── sensors/
│   │       ├── imu.hpp           # Raw IMU + orientation measurement models
│   │       ├── encoder.hpp       # Wheel encoder measurement model
│   │       └── gnss.hpp          # GPS: ECEF, lever arm, covariance, quality gating
│   └── src/
│       ├── ukf.cpp               # UKF: sigma points, predict, update, predict_measurement
│       └── fusioncore.cpp        # Manager: outlier rejection, adaptive noise,
│                                 #          snapshots, observability, delay compensation
├── fusioncore_ros/               # ROS 2 Jazzy wrapper
│   ├── src/fusion_node.cpp       # Lifecycle node: all sensor callbacks, TF validation,
│   │                             #   ZUPT, diagnostics, /fusion/pose, reset service
│   ├── config/fusioncore.yaml    # Default configuration
│   ├── config/duatic_mecanum.yaml
│   └── launch/fusioncore.launch.py
└── fusioncore_gazebo/            # Simulation world
    ├── worlds/fusioncore_test.sdf
    ├── models/fusioncore_robot/
    ├── launch/fusioncore_gazebo.launch.py
    ├── launch/gz_pose_to_gps.py
    └── launch/integration_test.py
```

* * *

## Technical details

[](#technical-details)

- **Filter:** Unscented Kalman Filter, 45 sigma points
- **State vector:** 22-dimensional: position (x,y,z), orientation (quaternion qw,qx,qy,qz), linear velocity, angular velocity, linear acceleration, gyroscope bias (x,y,z), accelerometer bias (x,y,z)
- **GPS coordinate system:** Configurable via PROJ: default ECEF (EPSG:4978, globally valid); supports any PROJ-compatible input CRS including UTM zones
- **Bias estimation:** Continuous online estimation, no calibration required
- **GPS quality scaling:** Noise covariance scaled by HDOP/VDOP, or full 3x3 message covariance when available
- **Outlier rejection:** Mahalanobis chi-squared gating at 99.9th percentile per sensor dimension
- **Adaptive noise:** Sliding window innovation tracking, exponential moving average R update
- **Delay compensation:** IMU ring buffer replay retrodiction up to 500ms
- **ZUPT:** Automatic zero velocity updates when stationary
- **Output rate:** 100Hz
- **Language:** C++17
- **License:** Apache 2.0

* * *

## Status

[](#status)

**Working and tested:**

- Hardware testing in progress: industrial mecanum manipulator (Duatic), agricultural RTK robot (Southern Ontario)
- UKF core: 39 unit tests passing via colcon test
- UKF numerical stability: P symmetrization + identity-shift Cholesky repair + angular velocity variance cap
- IMU + encoder + GPS fusion
- Automatic IMU bias estimation
- ECEF GPS conversion with quality-aware noise scaling
- Dual antenna heading: both `sensor_msgs/Imu` and `compass_msgs/Azimuth`
- IMU frame transform via TF
- TF validation at startup with exact fix commands
- GPS lever arm with heading observability guard: independent params for primary and secondary receivers
- Full 3x3 GPS covariance support
- Wheel odometry covariance support
- Multiple GPS receivers
- Heading observability tracking: DUAL\_ANTENNA / IMU\_ORIENTATION / GPS\_TRACK
- GPS fix quality gating: configurable `gnss.min_fix_type` (GPS / DGPS / RTK\_FIXED)
- Mahalanobis outlier rejection: GPS jumps verified rejected in testing
- Adaptive noise covariance: automatic R estimation from innovation sequence
- GPS delay compensation: full IMU replay retrodiction up to 500ms
- Non-holonomic ground constraint: VZ=0 pseudo-measurement for wheeled robots
- Zero velocity updates (ZUPT): automatic when encoder speed &lt; 0.05 m/s
- Per-sensor diagnostics: `/diagnostics` at 1Hz with outlier counts and heading status
- `/fusion/pose`: PoseWithCovarianceStamped for Nav2 / AMCL / slam\_toolbox
- Filter reset service: `~/reset` clears filter and GPS reference without node restart
- Sensor dropout detection: per-sensor staleness tracking via SensorHealth enum
- PROJ CRS coordinate transform: configurable input/output CRS via PROJ library (WGS84, UTM, ECEF, any EPSG code)
- ROS 2 Jazzy lifecycle node at 100Hz
- Gazebo Harmonic simulation world

**Known limitations:**

- GNSS antenna lever arm is fixed and known: does not estimate it from data
- In Gazebo simulation, residual y-axis drift (~0.3m) can occur from Gazebo physics: not a filter error
- Mecanum drive lateral velocity is not predicted by the motion model

**Roadmap:**

- Ackermann and omnidirectional steering motion models
- Mecanum drive motion model
- Auto-derive GNSS lever arm from TF header.frame\_id

* * *

## License

[](#license)

Apache 2.0. Includes explicit patent license grant that BSD-3 does not provide. Commercially safe

* * *

## Support

[](#support)

Issues answered within 24 hours. Open a GitHub issue or find the original discussion on ROS Discourse.

This project exists because of a community thread from December 2024 asking for a `robot_localization` alternative with native ROS 2 Jazzy support. If you hit a problem: open an issue. That feedback drives the roadmap.

---

## [HN-TITLE] 25. You don't want long-lived keys

- **Source**: [https://argemma.com/blog/long-lived-keys/](https://argemma.com/blog/long-lived-keys/)
- **Site**: Argemma
- **Author**: Kelby Ludwig
- **Published**: 2026-04-20
- **HN activity**: 26 points · [18 comments](https://news.ycombinator.com/item?id=47851377)
- **Length**: 902 words (~4 min read)
- **Language**: en

[![Kelby Ludwig](https://argemma.com/headshot-sm.jpg)](https://argemma.com/#about)

April 20, 2026

Long-lived keys are liabilities that, broadly, compound over time:

- As people leave the company, the risk of someone outside of your organization having potential knowledge of the key grows.
- If you assume that someone is constantly trying to guess a key or password, the likelihood that they guess correctly grows over time.
- Cryptographic keys have usage limits before their security guarantees start to degrade[1](#fn:1).

You can manage this risk in two ways. The first is to reduce the scope of what a given key can do. This is ideal but not always possible: a key may just need to be inherently powerful in order to do its job. The more general risk reduction is rotating keys. Key rotation is, also, an incredible pain. I suspect many readers have had an experience like:

- A rotation that needed to be expedited because the key was leaked.
- A key that was generated years ago by an ex-employee with inaccurate (or no) documentation on how the key was generated.
- An outage during rotation because the rollout was rushed or because the documentation was too stale to follow.
- An outage that had significant blast radius because a botched key rotation doesn't gracefully degrade.

Systems built around ephemeral keys (i.e., keys that are valid for roughly 1 day or less) sidestep a lot of this pain as "rotation" is a built-in feature. Replacing long-lived keys with ephemeral keys is, for my money, one of the best uses of security engineering effort. Some specific examples:

- Long-lived production SSH keys may be copied around, hardcoded into configuration files, and potentially forgotten about until there is an incident. If you replace long-lived SSH keys with a pattern like [EC2 instance connect](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html), SSH keys become temporary credentials that require a recent authentication and authorization check.
- Instead of relying on a static PyPI token that somehow ended up in the CTO's *personal* 1Password and, less accidentally, re-used across several release pipelines of varying quality, you can use [trusted publishers](https://repos.openssf.org/trusted-publishers-for-all-package-repositories). This allows you to use specific GitHub Actions workflows (which you might already be using for releases) to generate temporary credentials for package releases. When you go to revoke that static PyPI token, you may even find that it made its way into some one-off release pipeline that you forgot about until you broke it just now.
- A big reason why SSO is useful is that you can replace a user-selected long-lived password at each application with a short-lived authentication assertion from a trusted identity provider (IdP). An attacker can guess a poorly selected password. They can't really guess a signed XML document in the same way.[2](#fn:2)

## Okay but you may still need a long-lived key

Your inner security nerd might be annoyed. Surely it's not plausible to get rid of all long-lived keys? That SSO example above is glossing over the fact that while each authentication assertion is ephemeral, the key that signed the assertion is not.

This is true. You may not be able to eliminate every single long-lived key. However, there is still significant upside in reducing long-lived keys.

First, reducing the number of long-lived keys means you can concentrate your security efforts. It is much harder to reason about, say, the security of an arbitrary Engineer's laptop than it is an EC2 instance that exists exclusively to tell KMS to sign something. By reducing the number of long-lived keys, you also tend to create smaller and more focused pieces of infrastructure that are easier to harden and reason about.

Another upside is that security-sensitive infrastructure tends to require more rigor than most tools. Being rigorous often means taking things a bit slow. You don't always need rigor. You also want to be deliberate about where you sacrifice speed. The cryptographic key limits I mentioned before? You don't want that to be a tertiary concern for an arbitrary engineering team, eating up time spent on feature work. It is more likely to be forgotten, de-prioritized, or done incorrectly. You can, instead, make it part of someone's (or some group's) core focus and solve this once for everyone else.

Proper maintenance of long-term keys takes work. My general advice for long-lived keys is something like:

- Limit the scope of what a given key or credential can do. You might, for example, want to scope a data encryption key to a specific shard of customer data.
- Reason through the limits of the maximum lifetime of a given key. The typical security model for cryptographic keys is something like "It would take a supercomputer billions of years to guess the key." You want your limits to have similarly strong and confident assertions.
- Aim to rotate long-lived keys at least quarterly. From an operational perspective, rotating a key is like exercising a muscle. If you do it regularly you'll likely have more accurate and useful tooling or documentation. Good tooling and docs means you are less likely to pull a muscle miss an SLA.

This setup is, admittedly, toilsome. Don't distribute that toil to everyone. You can concentrate that effort into a group that is incentivized to be rigorous *and* solve it once, for everyone. Reducing toil and consolidating rigor is a major advantage of robust security infrastructure.

* * *

1. For example, if you encrypt more than [`2^32` messages with the same AES-GCM key](https://soatok.blog/2020/12/24/cryptographic-wear-out-for-symmetric-encryption/) you begin to risk [message forgery attacks](https://eprint.iacr.org/2016/475.pdf). [↩︎](#fnref:1)
2. Just put aside that SAML is a nightmare protocol for now! [↩︎](#fnref:2)

---

## [HN-TITLE] 26. I cancelled Claude: Token issues, declining quality, and poor support

- **Source**: [https://nickyreinert.de/en/2026/2026-04-24-claude-critics/](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/)
- **Site**: Nicky Reinert
- **Submitter**: y42 (Hacker News)
- **Published**: 2026-04-24
- **HN activity**: 804 points · [483 comments](https://news.ycombinator.com/item?id=47892019)
- **Length**: 1.1K words (~5 min read)
- **Language**: en-US

## First enthusiasm

A couple of weeks ago I subscribed to Claude Code, and during the first few weeks I had a really nice experience. It was fast, the token allowance was fair, and the quality was good.

I learned they had [raised the token allowance for non-rush hours](https://support.claude.com/en/articles/14063676-claude-march-2026-usage-promotion?utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) , and since they opposed some governmental rules, it felt good to support the right cause.

`(づ ￣ ³￣)づ`

However… for about three weeks now my initial enthusiasm has been rapidly waning.

It began with an issue three weeks ago. I started working in the morning after about a ten-hour break; enough time for my tokens to refresh.

I sent two small questions to **Claude Haiku**. They were simple questions, not even related to the repository.

Suddenly, token usage spiked to 100%.

*Have a nice break…*

I contacted their “AI support bot”, which returned some default support nonsense and didn’t really understand the problem. So I asked for human support. A couple of days later a - what appeared to be - human support person sent a reply. It began like this:

“Our systems are detecting your inquiry is regarding usage limits on your **Pro or Max plan.**”

Yeah, well — it’s the Pro plan. Seems like your systems weren’t actually queried; it was just a default intro and probably a default answer, because:

This was followed by an extensive what seems to be copy-and-paste answer from their docs explaining how daily and weekly limits work.

And it closed with the typically frustrating line, that no customer likes to read at the end of an e-mail and which is just the classical middle-finger of customer support - we don’t care if your problem is solved or not, we declared it closed.

“Note that further replies to this ticket may not be monitored. If your request is not regarding usage limits on your Pro or Max plan, or you need additional support, please visit our help page at”

Great! Sending an automated e-mail that does not refer to the actual problem and then closing the channel. Thanks for nothing, I guess? Or was I wrong. I asked Claude Haiku:

@Haiku:

> See the customer’s request here and the response from the AI and later W\*\*\*\** - did they answer the concern/question of the customer?

[![Customer support response](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-4.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-4.png "Click to view full size")

Customer support response

`(╯°_°）╯︵ ┻━┻`

## Declining quality

In the following days and weeks, the quality was far from satisfying my needs or matching my initial experience. While I used to be able to work on up to three projects at once, now the token limit was exhausted after two hours on a single project.

And the quality was degrading. I am fully aware this is quite subjective and that the quality of the agent is always heavily impacted by the operator. The failure usually appears in front of the screen. But hey, I also develop using **Github’s Copilot**, **OpenAI’s Codex** and I am running my own inference with **OMLX** and **Continue** using **Qwen3.5-9B**. I’m not *the* expert, I’m lazy sometimes but I probably know a thing or two.

Let me give you this wonderful example: yesterday I asked **Claude Opus** to refactor a project.

While I was browsing the model’s thinking log - which I strongly suggest doing not only occasionally - I found this:

> Rather than editing every slider in JSX, I’ll add a generic initializer in ui-events.js that auto-injects value displays for all range inputs that lack one.

This is clearly bad practice. It’s a cheap workaround you wouldn’t expect even from a junior dev; it reads like someone who just doesn’t want to deliver a good result. My response:

“you can’t be serious — is this how you fix things? just WORKAROUNDS????”

At least **Opus** admitted:

“You’re right, that was lazy. Let me do it properly — add the labels directly in the JSX and wire them explicitly.”

[![The lazy developer - at least honest](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-1.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-1.png "Click to view full size")

The lazy developer - at least honest

Needless to say, this shortcut cost me around 50% of my five-hour token allowance.

`(ง •̀_•́)ง`

## And even more…

[Now this cache topic comes up](https://www.anthropic.com/engineering/april-23-postmortem?utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) - [among others](https://news.ycombinator.com/item?id=47878905&utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) . at least they are talking about it openly. The problem was: when you get back to work after some time, your conversation cache is gone and the model starts reading your codebase again. Cost-wise this is smart. But experience-wise? It means you paid tokens for the initial load and, after a forced break because the five-hour token window hit its limit, you pay again for the same load.

Think that’s all? Wait, I also got this funny anecdote: all of a sudden the weekly window changed from today to Monday. OK, I was thankful because it came with a reset to zero. But still: what is going on, Anthropic? Not only that — while I was working on my project, watching token usage with **Argus-eyed vigilance**, this little warning popped up:

[![Token limit warning](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image.png "Click to view full size")

Token limit warning - but I was still within the limits?

Wait, what? I’m neither part of an organization nor do I see any hint why I suddenly have to worry about a “monthly usage limit” — also the hourly and weekly limits were still not exceeded. What is happening right now?

[![Token limit warning - but I was still within the limits?](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-2.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-2.png "Click to view full size")

Token limit warning - but I was still within the limits?

Turns out — two hours later - it allowed me to continue working. The warning was gone.

[![Token limit warning disappeared](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-3.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-3.png "Click to view full size")

Token limit warning disappeared - but what was it about?

At least [this documentation](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work?utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) does not mention a monthly usage limit. And the settings page only lists the limits for the current session and week.

[![Token limits documentation](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-5.png)](https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-5.png "Click to view full size")

Token limits documentation - no mention of monthly limits

So… what is this monthly limit all about, **Anthropic**?

## Sorry to let you down, Anthropic

I am a huge fan of the product. Theoretically everything just works like a charm; it offers so many opportunities. I built my [own harness around Claude](https://nickyreinert.de/2026/2026-03-29-ai-butler/?utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) , I admire **Claude Caude** who work’s in the background on a bunch of **GitHub** issues, I love using **Claude Cowork** to continue writing my [Nerd Enzyklopädie](https://nickyreinert.de/2025/2025-04-20-nerd-enzyklop%C3%A4die-48---alte-software/?utm_source=institut-fuer-digitale-herausforderungen&utm_medium=allyourbasearebelongtous) . So many thoughful features.

I increased my productivity by an order of magnitude, and it’s really thrilling to see how these trillions of ideas crawling through my head are now only a blink away - easier and quicker to realize than four years ago.

And I understand the technical and organizational challenges when offering a product like that. It’s not easy to benefit from scaling effects when you sell inference. Every additional hour and every new customer requires the same amount of compute. That’s the curse of incremental costs in this line of business.

But…

…it seems like Anthropic cannot handle too many new customers at once, so I took that load off Anthropic and cancelled my account.

`(ʘ‿ʘ)╯`

---

## [HN-TITLE] 27. Generalised plusequals

- **Source**: [https://leontrolski.github.io/alt.html](https://leontrolski.github.io/alt.html)
- **Site**: leontrolski.github.io
- **Submitter**: leontrolski (Hacker News)
- **Submitted**: 2026-04-24 21:34 UTC (Hacker News)
- **HN activity**: 12 points · [6 comments](https://news.ycombinator.com/item?id=47896086)
- **Length**: 442 words (~2 min read)

[![](https://leontrolski.github.io/pic.png) ⇦](https://leontrolski.github.io/index.html)

*2026-04-24*

I have a [quarter baked language](https://github.com/leontrolski/jubbly) I've been working on. It's mostly crap, but a syntax idea fell out that I think is pretty neat.

The following are equivalent in many languages:

```
x = x + 1
x += 1
```

Reassigning is cool as it's lexically scoped so easy to reason about (mutation is bad as it isn't).

The new idea is that instead of special symbols (in this case `+=`) we generalise with a keyword (`alt`) that affects all infix operators, so the following are equivalent:

```
x = x + 1
alt x + 1
```

Who cares? It's not even shorter right?

The fun starts when we introduce a couple of new infix operators - namely `]=` and `.=`

Let's compare them to the Python equivalents:

```
l = l[4]=999
l = l[:4] + [999] + l[5:]
```

and:

```
x = x.n.=2
x = dataclasses.replace(x, n=2)
```

Now let's set up a couple of examples of nested data:

```
cat = Cat(age=3)
l = [1, [2, cat], 4]
```

If we want to reassign to make an older cat, we can do:

```
alt cat.age.=8
```

The more interesting example is reassigning the deeply nested `l` to make the cat inside older, without mutating the original cat:

```
alt l[1][1].age.=9
```

this will leave us with `l` equal to:

```
[1, [2, Cat(age=9)], 4]
```

What was the `alt` statement sugar for, such that we didn't mutate the original cat? Well, the rather ungainly:

```
_1 = l[1]       # _1 = [2, Cat(age=3)]
_2 = l[1][1]    # _2 = Cat(age=3)
_2 = _2.age.=9  # _2 = Cat(age=9)
_1 = _1[1]=_2   # _1 = [2, Cat(age=9)]
l  = l[1]=_1    # l  = [1, [2, Cat(age=9)], 4]
```

My new language has a nice(?) [feature](https://github.com/leontrolski/jubbly#postfixinfix) - you can infix plain binary functions with tildes - this means you can do fun stuff like:

```
alt l~push~5
```

to leave `l` equal to:

```
[1, [2, Cat(age=9)], 4, 5]
```

## Thoughts

- I don't like mutations, I do like being able to succinctly update deeply nested data. This syntax lets you achieve cuteness in a language with only immutable datastructures.
- What do they do in Haskell? Are there any other equivalent syntaxes lying around?
- The rough plan for my language was - pretend all the datastructures are immutable, but at compile time, if the structure of the program is such that using a mutable datastructure would be equivalent, use one for performance. (This is kind of the reverse of mutable value semantics in Swift(?), and would share some implementation details with the borrow checker in Rust).
- In some ways, this is the reverse of some [older thoughts](https://leontrolski.github.io/alter.html).
- In my langauge, I also added a [generalised version](https://github.com/leontrolski/jubbly#early-return) of Rust's `?` operator.

---

## [HN-TITLE] 28. CC-Canary: Detect early signs of regressions in Claude Code

- **Source**: [https://github.com/delta-hq/cc-canary](https://github.com/delta-hq/cc-canary)
- **Site**: GitHub
- **Submitter**: tejpalv (Hacker News)
- **Submitted**: 2026-04-24 17:53 UTC (Hacker News)
- **HN activity**: 45 points · [20 comments](https://news.ycombinator.com/item?id=47893620)
- **Length**: 808 words (~4 min read)
- **Language**: en

[![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](https://github.com/delta-hq/cc-canary/blob/main/LICENSE)

**Drift detection for Claude Code**, packaged as two installable [Agent Skills](https://vercel.com/kb/guide/agent-skills-creating-installing-and-sharing-reusable-agent-context). Reads the JSONL session logs Claude Code already writes to `~/.claude/projects/`, detects whether the model has been drifting on your own work, and produces a shareable forensic report.

No network, no account, no telemetry, no background daemon. Runs on the data already on your disk.

> **Status:** 0.x / pre-alpha — output format and metric set may change.

## What you get

[](#what-you-get)

Skill Invocation Output **`cc-canary`** `/cc-canary [window]` forensic markdown writeup (`./cc-canary-<date>.md`) — paste-ready for GitHub issues or gists **`cc-canary-html`** `/cc-canary-html [window]` same report as a dark-theme HTML dashboard (`./cc-canary-<date>.html`), auto-opens in your browser

Window defaults to `60d`. Accepts `7d / 14d / 30d / 60d / 90d / 180d`.

Each report includes:

- **Verdict** — HOLDING / SUSPECTED REGRESSION / CONFIRMED REGRESSION / INCONCLUSIVE
- **Headline metrics** table (pre vs post, with 🟢/🟡/🔴 band verdicts)
- **Weekly trend bars** — cost (USD, verified against [ccusage](https://github.com/ryoppippi/ccusage) to the cent), read:edit ratio, reasoning loops, tokens/turn
- **Cross-version comparison** — same user, different model versions, controlling for task mix
- **Auto-detected inflection date** — composite health-score break
- **Findings** with model-side / user-side / ambiguous classification
- **Appendices** — hour-of-day thinking depth, word-frequency shift, three-period thinking-visibility transition, per-turn behavior rates, and more

## Install

[](#install)

```
npx skills add delta-hq/cc-canary
```

Install just one:

```
npx skills add delta-hq/cc-canary --skill cc-canary
npx skills add delta-hq/cc-canary --skill cc-canary-html
```

Then from any Claude Code session:

```
/cc-canary 60d
/cc-canary-html 30d
```

**Requirements**: `python3` ≥ 3.8 on your PATH. macOS / Linux / WSL for the `cc-canary-html` auto-open step (it falls back to printing the path if `open` / `xdg-open` / `start` fails).

## How it works

[](#how-it-works)

1. **Scan.** A bundled Python script (stdlib only — no pip, no Node) walks `~/.claude/projects/**/*.jsonl`, filters by window and excludes subagent sessions by default.
2. **Dedupe.** Assistant messages are deduped on `(message.id, requestId)` — same scheme [ccusage](https://github.com/ryoppippi/ccusage) uses, because Claude Code writes the same message into multiple JSONLs when sessions are resumed or branched.
3. **Aggregate.** Per-session metrics: tool-mix, read:edit ratio, reasoning-loop phrases, self-admitted errors, premature stops, interrupts, token usage, cost (current Claude 4.x rates), hour-of-day thinking depth.
4. **Detect inflection.** Composite health score per day; argmax of `|before − after|` over candidate dates with a 0.75σ floor. Falls back to median-timestamp split if no break clears.
5. **Pre-render the report.** Script writes a markdown / HTML skeleton with every table and bar chart filled in. Only ~20 short narrative slots (marked `<!-- C: ... -->`) are left for Claude to fill — verdict line, summary, per-finding reasoning, root-cause, appendix paragraphs.
6. **Fill & save.** Claude reads the skeleton, writes the narrative, saves the final file.

Total runtime: **~2.5s** for the script + 10–20s for Claude to fill narrative.

## What each skill tracks

[](#what-each-skill-tracks)

Metrics in the headline table (with published healthy/transition/concerning bands where applicable):

- **Read:Edit ratio** — file reads per edit. Proxy for how thoroughly the model investigates before mutating.
- **Write share of mutations** — `Write / (Edit + Write)`. High share = model rewriting files instead of surgical edits.
- **Reasoning loops / 1K tool calls** — phrases like "let me try again", "oh wait", "actually,".
- **Frustration rate** — rate of frustration words in your prompts.
- **Thinking redaction rate** — fraction of thinking blocks that are redacted vs visible.
- **Mean thinking length** — reasoning-depth proxy (via cryptographic signature length, r=0.97 with content length when visible).
- **API turns per user turn** — how many API calls the model makes per user message.
- **Tokens per user turn** — total token volume (input + output + cache) per user message.

Plus appendices with additional signals: premature stopping, self-admitted errors, shortcut vocabulary, user interrupts, hour-of-day thinking depth, per-word frequency shift, three-period thinking-visibility transition, per-turn behavior rates.

## Skill filters & flags

[](#skill-filters--flags)

The script accepts flags you can pass via `Bash(python3 scripts/compute_stats.py …)` for custom runs:

Flag Default Purpose `--window {Nd}` `60d` Window size (`7d / 14d / 30d / 60d / 90d / 180d`) `--include-agents` off Include subagent sessions (default: excluded — they have no natural user prompts) `--min-user-words N` `10` Drop sessions with fewer user-prompt words than this (filters trivial sessions) `--render-md PATH` — Write the markdown skeleton to `PATH` `--render-html PATH` — Write the HTML dashboard to `PATH`

## Privacy

[](#privacy)

- Fully local. Zero network calls.
- The script reads `~/.claude/projects/*.jsonl` only. Nothing else.
- Narrative prose is written by Claude during the skill invocation (inside your Claude Code session); it has access only to the on-disk files you explicitly point it at.
- User-prompt content is truncated to ≤180 chars before being included in the skeleton, and redacted for `/Users/…` paths, emails, hex-like tokens.
- Output files (`./cc-canary-<date>.{md,html}`) live in the directory where you invoked the skill. Nothing is uploaded anywhere.

## Contributing

[](#contributing)

Issues, metric suggestions, and PRs welcome: [github.com/delta-hq/cc-canary/issues](https://github.com/delta-hq/cc-canary/issues). Output format and metric set may change during 0.x.

## About the name

[](#about-the-name)

Canaries were used in coal mines to detect early signs of danger. cc-canary does the same for drift in your Claude Code sessions.

## License

[](#license)

[MIT](https://github.com/delta-hq/cc-canary/blob/main/LICENSE)

---

## [HN-TITLE] 29. SDL Now Supports DOS

- **Source**: [https://github.com/libsdl-org/SDL/pull/15377](https://github.com/libsdl-org/SDL/pull/15377)
- **Site**: GitHub
- **Author**: icculus
- **Submitted**: 2026-04-24 16:20 UTC (Hacker News)
- **HN activity**: 230 points · [84 comments](https://news.ycombinator.com/item?id=47892291)
- **Length**: 670 words (~3 min read)
- **Language**: en

and others added 30 commits

[April 13, 2026 08:50](#commits-pushed-b6c2f2f)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Seeking breaks otherwise. We might be able to just fflush() before or seeking
instead?
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Turns out DosBox-X was having trouble with the Sound Blaster or something;
standard DosBox works correctly directly from the interrupt handler, and
without doubling the buffer size.
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
This is MUCH faster than just leaving buffering disabled, and also works
around getting bogus reads after an fseek. SDL_LoadWAV on test/sample.wav
no longer takes several seconds to finish, and comes up with the correct
data.

I wonder if we're triggering this in LoadWAV because we're malloc'ing data
between seeks/reads, and it's causing the djgpp transfer buffer to change. Or
maybe the Fat DS trick is confusing it? I don't know, I haven't had time to
debug it, it might just be a legit libc bug in djgpp too, for all I know.
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
This uses an old trick we used in SDL 1.2 for MacOS Classic, which did its
audio callback in a hardware interrupt. If the audio is locked when the
interrupt fires, make a note of it and return immediately. When the lock is
released, if the interrupt has been fired, run the audio device iteration
right then.

Since there isn't a big device lock in SDL3 (available to the app, at least),
this keeps a counter of when any SDL_AudioStream is locked, which is probably
good enough.
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
This uses VESA interfaces to manage the display and works with the software
renderer.

Events aren't hooked up yet, so prepare to close DosBox on each run.  :)
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
…upport.
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
This gets most of the rendering examples, which use SDL_GetBasePath() to
find textures to load, working.
```

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@icculus](https://avatars.githubusercontent.com/u/673562?s=40&v=4)](https://github.com/icculus) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Of course Quake 1 solved this better, haha. It's smart: less memory, dirt
simple, and you don't even have to worry about synchronizing with the
interrupt handler, because it's safe for both sides no matter when an
interrupt fires.
```

[![@madebr](https://avatars.githubusercontent.com/u/4138939?s=40&v=4)](https://github.com/madebr) [![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
[sdl-ci-filter djgpp]
[sdl-ci-artifacts]
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
- SDL_runapp.c: Add SDL_PLATFORM_DOS to the exclusion list so the
  generic
  SDL_RunApp() is disabled when the DOS-specific one is compiled.
- SDL.c: Exclude SDL_Gtk_Quit() on DOS. DJGPP defines __unix__ which
  sets
  SDL_PLATFORM_UNIX, but DOS has no GTK/display server. The GTK source
  is not compiled (CMake UNIX is false for DOS) so this was a link
  error.
- sdlplatform.cmake: Add DOS case to SDL_DetectCMakePlatform so the
  platform is properly detected from CMAKE_SYSTEM_NAME=DOS.
- i586-pc-msdosdjgpp.cmake: Add i386-pc-msdosdjgpp-gcc as a fallback
  compiler name, since some DJGPP toolchain builds use the i386 prefix.
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
- Implement double-buffered page-flipping for VBE modes with >1 image
  page
- Save and restore full VBE state on video init/quit for clean mode
  switching
- Improve DOS keyboard handling: support extended scancodes and Pause
  key
- Lock ISR code/data to prevent page faults during interrupts
- Always vsync when blitting in single-buffered modes to reduce tearing
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Move audio mixing out of IRQ handler to main loop for improved
stability and to avoid reentrancy issues. Add SDL_DOS_PumpAudio
function, update DMA buffer handling, and adjust sample rate to 22050
Hz.
Silence stale DMA buffer halves to prevent stutter during load.
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Detect SB version and select 8-bit mono or 16-bit stereo mode.
Handle DMA and DSP setup for both SB16 and pre-SB16 hardware.
Add FORCE_SB_8BIT option for testing in DOSBox.
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
- Poll Sound Blaster DSP status instead of fixed delay after speaker-on
- Clarify DPMI conventional memory is always locked; update comments
- Document and justify DMA memory allocation strategy
- Free IRET wrapper after restoring interrupt vector to avoid leaks
- Throttle joystick axis polling to ~60 Hz to reduce BIOS timing loop
  cost
- Always poll joystick buttons directly for responsiveness
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

```
Implement banked framebuffer access for VBE 1.2+ modes without LFB.
Detect and initialize banked modes, copy framebuffer data using bank
switching, and blank the framebuffer on mode set. Page-flipping is
disabled in banked mode.
```

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![madebr](https://avatars.githubusercontent.com/u/4138939?s=60&v=4)](https://github.com/madebr)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

[![@AJenbo](https://avatars.githubusercontent.com/u/204594?s=40&v=4)](https://github.com/AJenbo)

Open

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. [Learn more about bidirectional Unicode characters](https://github.co/hiddenchars)

---

## [HN-TITLE] 30. Show HN: Browser Harness – Gives LLM freedom to complete any browser task

- **Source**: [https://github.com/browser-use/browser-harness](https://github.com/browser-use/browser-harness)
- **Site**: GitHub
- **Submitter**: gregpr07 (Hacker News)
- **Submitted**: 2026-04-24 14:31 UTC (Hacker News)
- **HN activity**: 89 points · [40 comments](https://news.ycombinator.com/item?id=47890841)
- **Length**: 465 words (~3 min read)
- **Language**: en

[![Browser Harness](https://camo.githubusercontent.com/82e250e7e7cefde5667bdcf52412b6caefab2c4d5cceaf6b1bf10833cd88f68d/68747470733a2f2f72322e62726f777365722d7573652e636f6d2f6769746875622f616a73646c61736e6e616c736761736c642e706e67)](https://camo.githubusercontent.com/82e250e7e7cefde5667bdcf52412b6caefab2c4d5cceaf6b1bf10833cd88f68d/68747470733a2f2f72322e62726f777365722d7573652e636f6d2f6769746875622f616a73646c61736e6e616c736761736c642e706e67)

The simplest, thinnest, **self-healing** harness that gives LLM **complete freedom** to complete any browser task. Built directly on CDP.

The agent writes what's missing, mid-task. No framework, no recipes, no rails. One websocket to Chrome, nothing between.

```
  ● agent: wants to upload a file
  │
  ● helpers.py → upload_file() missing
  │
  ● agent edits the harness and writes it    helpers.py   192 → 199 lines
  │                                                       + upload_file()
  ✓ file uploaded
```

**You will never use the browser again.**

## Setup prompt

[](#setup-prompt)

Paste into Claude Code or Codex:

```
Set up https://github.com/browser-use/browser-harness for me.

Read `install.md` first to install and connect this repo to my real browser. Then read `SKILL.md` for normal usage. Always read `helpers.py` because that is where the functions are. When you open a setup or verification tab, activate it so I can see the active browser tab. After it is installed, open this repository in my browser and, if I am logged in to GitHub, ask me whether you should star it for me as a quick demo that the interaction works — only click the star if I say yes. If I am not logged in, just go to browser-use.com.
```

When this page appears, tick the checkbox so the agent can connect to your browser:

[![Remote debugging setup](https://github.com/browser-use/browser-harness/raw/main/docs/setup-remote-debugging.png)](https://github.com/browser-use/browser-harness/blob/main/docs/setup-remote-debugging.png)

See [domain-skills/](https://github.com/browser-use/browser-harness/blob/main/domain-skills) for example tasks.

## Free remote browsers

[](#free-remote-browsers)

Useful for stealth, sub-agents, or deployment.  
**Free tier: 3 concurrent browsers, proxies, captcha solving, and more. No card required.**

- Grab a key at [cloud.browser-use.com/new-api-key](https://cloud.browser-use.com/new-api-key)
- Or let the agent sign up itself via [docs.browser-use.com/llms.txt](https://docs.browser-use.com/llms.txt) (setup flow + challenge context included).

## How simple is it? (~592 lines of Python)

[](#how-simple-is-it-592-lines-of-python)

- `install.md` — first-time install and browser bootstrap
- `SKILL.md` — day-to-day usage
- `run.py` (~36 lines) — runs plain Python with helpers preloaded
- `helpers.py` (~195 lines) — starting tool calls; the agent edits these
- `admin.py` + `daemon.py` (~361 lines) — daemon bootstrap plus the CDP websocket and socket bridge

## Contributing

[](#contributing)

PRs and improvements welcome. The best way to help: **contribute a new domain skill** under [domain-skills/](https://github.com/browser-use/browser-harness/blob/main/domain-skills) for a site or task you use often (LinkedIn outreach, ordering on Amazon, filing expenses, etc.). Each skill teaches the agent the selectors, flows, and edge cases it would otherwise have to rediscover.

- **Skills are written by the harness, not by you.** Just run your task with the agent — when it figures something non-obvious out, it files the skill itself (see [SKILL.md](https://github.com/browser-use/browser-harness/blob/main/SKILL.md)). Please don't hand-author skill files; agent-generated ones reflect what actually works in the browser.
- Open a PR with the generated `domain-skills/<site>/` folder — small and focused is great.
- Bug fixes, docs tweaks, and helper improvements are equally welcome.
- Browse existing skills (`github/`, `linkedin/`, `amazon/`, ...) to see the shape.

If you're not sure where to start, open an issue and we'll point you somewhere useful.

* * *

[The Bitter Lesson of Agent Harnesses](https://browser-use.com/posts/bitter-lesson-agent-harnesses) · [Web Agents That Actually Learn](https://browser-use.com/posts/web-agents-that-actually-learn)

