10 interesting stories served every morning and every evening.

Desktop apps

docs.deno.com

deno desk­top turns a Deno pro­ject (anything from a sin­gle TypeScript file to a Next.js app) into a self-con­tained desk­top ap­pli­ca­tion. The out­put is a re­dis­trib­utable bi­nary that bun­dles your code, the Deno run­time, and a web ren­der­ing en­gine into one bun­dle per plat­form.

Coming in Deno 2.9

deno desk­top ships in Deno v2.9.0 and is not in a sta­ble re­lease yet. To try it now, run deno up­grade ca­nary to in­stall the ca­nary build. The com­mand, con­fig­u­ra­tion keys, and TypeScript APIs may still change be­fore the fea­ture is sta­ble.

Why deno desk­top Jump to head­ing

Web tech­nol­ogy is the most widely-known UI toolkit in the world. Desktop apps built on web stacks (Electron, Tauri, Electrobun) take ad­van­tage of that, but each has trade­offs you have to live with: huge bi­na­ries, miss­ing plat­form sup­port, no JavaScript ecosys­tem, no built-in up­date story, no frame­work in­te­gra­tion.

deno desk­top is opin­ion­ated about those trade­offs:

Small by de­fault, full Node com­pat­i­bil­ity. The de­fault WebView back­end uses the op­er­at­ing sys­tem’s own we­b­view for small bi­na­ries, and you still have the en­tire npm ecosys­tem avail­able through Deno’s Node com­pat layer. Opt into the bun­dled Chromium (CEF) back­end when you need iden­ti­cal ren­der­ing across ma­cOS, Windows, and Linux.

Framework auto-de­tec­tion. Point deno desk­top at a Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, or Vite SSR pro­ject and it runs: the pro­duc­tion server in re­lease mode, the dev server with hot re­load un­der –hmr. No code changes are re­quired to take an ex­ist­ing web pro­ject to the desk­top.

In-process bind­ings in­stead of IPC. Backend and UI com­mu­ni­ca­tion goes through in-process chan­nels, not socket-based IPC. Values are still en­coded as they cross the call bound­ary, but there is no cross-process round-trip be­tween your Deno code and the we­b­view.

Cross-compile from one ma­chine. The same ma­chine can build for ma­cOS, Windows, and Linux. Backends are down­loaded as needed, not built lo­cally.

Built-in bi­nary-diff auto-up­date. Ship a sin­gle lat­est.json man­i­fest and bs­d­iff patches; the run­time polls, ap­plies, and rolls back au­to­mat­i­cally on failed launches.

Hello, desk­top Jump to head­ing

Create a one-file desk­top app:

main.ts

Deno.serve(() => new Response(“<h1>Hello, desk­top</​h1>”, { head­ers: { content-type”: text/html” }, }) );

>_

deno desk­top main.ts

The com­piled bi­nary opens a win­dow pointed at a lo­cal HTTP server bound to your Deno.serve() han­dler. Run it di­rectly:

>_

./main # ma­cOS / Linux .\main.exe # Windows

Deno.serve() au­to­mat­i­cally binds to the ad­dress the we­b­view nav­i­gates to, so you do not need to pass a port or host­name. See HTTP serv­ing for de­tails.

What’s in this sec­tion Jump to head­ing

Configuration: the desk­top block in deno.json.

Backends: CEF, we­b­view, raw; how to choose.

HTTP serv­ing: Deno.serve() in­te­gra­tion and the serv­ing model.

Frameworks: Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, and oth­ers.

Windows: Deno.BrowserWindow life­cy­cle, mul­ti­ple win­dows, events.

Bindings: call­ing Deno code from the we­b­view via bind­ings.<name>().

Menus: ap­pli­ca­tion and con­text menus.

Tray and dock: sys­tem sta­tus icons and the ma­cOS dock.

Dialogs: prompt(), alert(), con­firm() as na­tive pop­ups.

Notifications: na­tive OS no­ti­fi­ca­tions via the Web Notification API.

Hot mod­ule re­place­ment: –hmr for frame­work and non-frame­work apps.

DevTools: uni­fied DevTools at­tached to both the Deno run­time and the we­b­view.

Auto-update: Deno.autoUpdate(), man­i­fests, bs­d­iff, roll­back.

Error re­port­ing: cap­tur­ing un­caught ex­cep­tions and pan­ics.

Distribution: cross-com­pi­la­tion, out­put for­mats, in­stallers.

Comparison: how deno desk­top re­lates to Electron, Tauri, Electrobun, Dioxus.

deno desk­top CLI ref­er­ence: the com­mand, its flags, and the deno.json desk­top schema.

Did my old job only exist because of fraud? – It's not about code

david.newgas.net

Early in my soft­ware en­gi­neer­ing ca­reer, the UK-based startup I worked at, GenieDB, was taken over by a US Venture Capital fund, Frost VP, owned by Stuart Frost. I was func­tion­ally the only piece that came to the US. The code was re­built, the rest of the team even­tu­ally ro­tated out, even the core strat­egy was re­placed. But I was early in my ca­reer and ex­cited to be work­ing in the VC tech-startup world. I ended up mak­ing my life in the US, so this was a pretty piv­otal phase in my life.

For a while I lived the start-up life: build­ing rapidly and play­ing Foosball1. GenieDB ac­tively re­jected rev­enue op­por­tu­ni­ties (a la Silicon Valley) with the aim of get­ting ac­quired for pi­o­neer­ing tech­nol­ogy. We limped along for years with never more than 3 cus­tomers, even when big-tech and even­tu­ally open-source did what we tried to do bet­ter than us. I left with mixed feel­ings and even­tu­ally came to re­al­ize we never had the se­ri­ous foot­ing it would have taken to ac­tu­ally de­velop sig­nif­i­cant tech­nol­ogy.

A decade later I heard from a for­mer col­league that Frost was be­ing sued by the SEC for fraud. Still not sure what to make of my time at GenieDB, I was cu­ri­ous enough to skim the com­plaint, where I saw this line:

Was GenieDB in­volved in this fraud some­how? I chewed on this ques­tion and it evolved to a form that be­gan to haunt me:

Did my old job — the one that brought me to the USA and changed the course of my en­tire life — only ex­ist be­cause of fraud?

Did my old job — the one that brought me to the USA and changed the course of my en­tire life — only ex­ist be­cause of fraud?

With this burn­ing ques­tion I dove into the record of the case. The al­leged fraud was sim­ple: Frost VP op­er­ated as an in­cu­ba­tor, pro­vid­ing ser­vices to the port­fo­lio com­pa­nies, and the in­vestors say these fees charged were ex­ces­sive.

The case went to bind­ing ar­bi­tra­tion2 and the in­vestors won, af­ter which the SEC sued to bar Frost from man­ag­ing funds in the fu­ture. There’s some great el­e­ments like claim­ing a per­sonal chef and cleaner as ex­penses, telling in­vestors no salary would be paid by the fees (it was) and start­ing a mar­ket­ing com­pany just to spon­sor some­one’s visa.

But what about GenieDB? Both the ar­bi­tra­tion and SEC suit elab­o­rate on this idea of cre­at­ing com­pa­nies just to charge them fees:

This al­le­ga­tion was never lit­i­gated though, nei­ther court nor ar­bi­tra­tor were kind enough to rule on why GenieDB was in the port­fo­lio. I had to dive into the ev­i­dence and de­cide for my­self. First of all, my old CEO tes­ti­fied that GenieDB was pay­ing ex­ces­sive fees:

I was only re­ally shaken when I saw what the in­sid­ers at the VC fund say­ing to each other:

To me, this email shows that the in­vest­ments were mo­ti­vated by the fees3 and GenieDB was be­ing used to siphon in­vestor money away. I felt like an ant. My ca­reer, my fam­ily, my cit­i­zen­ship all would be com­pletely dif­fer­ent ex­cept for this fraud. This story plays out be­tween in­vestors, multi-mil­lion­aire VCs, judges, and my en­tire life is­n’t even a foot­note.

I took a deep breath. GenieDB did have a con­cept at the core, which pre-dated Frost turn­ing his eyes on us. A con­cept which turned out to be quite use­ful when more se­ri­ous play­ers took it on. My col­leagues and I were re­ally try­ing to build it, even if the fund man­ager was eat­ing up our run­way with spu­ri­ous fees for his per­sonal gain. I was­n’t just some in­stru­ment of fraud work­ing on noth­ing.

Besides, tur­bu­lence from chance events, light­hearted de­ci­sions and even crime al­ters the course of every life. Stories of chance meet­ings of a spouse are dime-a-dozen. I just looked in my wake and was sur­prised by a dark cur­rent.

Amusingly Foosball abil­ity re­flected the cor­po­rate hi­er­ar­chy. Stuart Frost was hands down the best. His sec­ond in com­mand a close sec­ond, and GenieDB’s CEO could beat any of us pro­gram­mers. ↩︎

In fact Frost ac­tu­ally ini­ti­ated the ar­bi­tra­tion be­cause he al­leged the in­vestors were con­spir­ing against him. The coun­ter­claim laid out the fraud. ↩︎

With Genie com­ing out” here is re­fer­ring to GenieDB dis­solv­ing and not pay­ing fees to the in­cu­ba­tor any more. GenieDB is­n’t one of the com­pa­nies they are propos­ing here. ↩︎

Never Give Them Your Face

nevergivethemyourface.com

They want your face. It will be called safety. Verification. Age as­sur­ance. A small step to pro­tect chil­dren. But strip the lan­guage away and the de­mand is plain: be­fore you may speak, post, or read, you must first prove who you are. And the only way they’ve fig­ured out how to do it is with your gov­ern­ment ID, or with your face held up to a cam­era that de­cides whether you are old enough to be trusted. This is the deal now be­ing writ­ten into law on three con­ti­nents, and you are meant to ac­cept it qui­etly. Don’t.

It’s al­ways won’t some­one think of the chil­dren?!”. But this af­fects every­one.

No one dis­putes that the in­ter­net can hurt kids. That grief is real, but it’s be­ing ex­ploited. Here is the trick: to con­firm that a child is not pre­sent, a ser­vice has to check every­body. Every adult passes through the check­point. A law writ­ten about six­teen-year-olds qui­etly be­comes an iden­tity re­quire­ment for the en­tire in­ter­net. You are not carded be­cause you are sus­pected of any­thing. You are carded be­cause card­ing has be­come the price of ad­mis­sion to life on the web.

We run back­ground checks on peo­ple who want to buy a gun, but we do not back­ground check every­one at all times just in case. Yet that is ex­actly the de­sign here. It’s a per­mit check at the door of every con­ver­sa­tion, ap­plied to all, jus­ti­fied by the few.

It is not age ver­i­fi­ca­tion. It is iden­tity ver­i­fi­ca­tion.

Watch the words drift. This whole sys­tem was sold as age as­sur­ance, which is a yes-or-no ques­tion, are you over eigh­teen? But al­most none of these sys­tems are built to an­swer only that. They are built to know who you are: your name, your date of birth, your doc­u­ment num­ber, your face. This is not age ver­i­fi­ca­tion at all. It is forced iden­tity track­ing. Your real-world iden­tity cap­tured by not only Meta, Facebook, Twitter, Instagram, etc, but shared broadly with every creepy agency you al­ready worry about having all your data”.

Name the places now de­mand­ing age ver­i­fi­ca­tion,” and see how many will ac­cept a plain gov­ern­ment doc­u­ment that says only that you are over eigh­teen — and noth­ing else. Almost none will. Because age was never the point.

Name the places now de­mand­ing age ver­i­fi­ca­tion,” and see how many will ac­cept a plain gov­ern­ment doc­u­ment that says only that you are over eigh­teen — and noth­ing else. Almost none will. Because age was never the point.

We spent a gen­er­a­tion teach­ing peo­ple the first rule of the in­ter­net: never give out your real iden­tity to strangers. We have a word, doxxing, for in­flict­ing that ex­po­sure on some­one against their will. And now the same gov­ern­ments and plat­forms are ask­ing every cit­i­zen to do it to them­selves, vol­un­tar­ily, as a con­di­tion of log­ging in.

You can change a pass­word. You can­not change your face.

A leaked pass­word is an in­con­ve­nience. You re­set it and move on. Your face, your dri­ver’s li­cense, the unique geom­e­try a scan­ner re­duces to a num­ber can­not be re­set. A face scan is not a pho­to­graph. It is a three-di­men­sional map of you, a bio­met­ric tem­plate pre­cise enough to be matched later against a sur­veil­lance cam­era on a street cor­ner. When you hand over and it lives on some­one else’s server, of­ten a third-party ven­dor you never chose, can­not name, and can­not hold to ac­count.

Every one of those data­bases is a hon­ey­pot. The ver­i­fier promises your doc­u­ments are deleted the mo­ment they are checked. They are not al­ways deleted, and the promise is worth­less the day the com­pany is breached. Remember the last twenty years of worth­les $17.99 Equifax IDentityGuard+ cred­its from all those data breaches? It has hap­pened, it will hap­pen again, ex­cept this time it’s not your email, hashed pass­word, or even your SSN. It’s your face and pass­port that’s for sale on the dark web.

It does not work — and it makes the dan­ger worse.

Here is the in­sult be­neath the in­jury: it fails at the one thing it promises. Determined teenagers route around age gates like breath­ing — a bor­rowed lo­gin, a VPN, a check­box, a ver­i­fied ac­count bought for the price of a cof­fee. Within hours of one plat­form rolling out age brack­ets, pre-ver­i­fied ac­counts for every age were for sale on eBay. Teenagers ma­chete their way through tech­nolo­gies de­signed to protect” or limit them in the same way that wa­ter finds the cracks in the wall. They have all the time in the world, all of the in­cen­tives, and all of the so­cial struc­ture and ob­fus­cated chat chan­nels to do it.

Worse, the ar­chi­tec­ture built to protect” chil­dren can en­dan­ger them. Sort users into age-la­beled pens and you have not only failed to stop a preda­tor, you have cre­ated a chil­dren in­dex, a phone­book, a way to fil­ter di­rectly for chil­dren. Teenagers pushed off main­stream plat­forms do not stop go­ing on­line (see point about wa­ter above). They move to smaller, darker, un­mod­er­ated cor­ners, away from the very over­sight that was sup­posed to keep them safe. The chil­dren are not saved. The sur­veil­lance is the only thing that sur­vives in­tact.

Safe now, ??? later

The data­base you are help­ing build for a trust­wor­thy gov­ern­ment does not stay in trust­wor­thy hands. Administrations change. A reg­istry that merely cat­a­logs who you are to­day be­comes, un­der a fu­ture gov­ern­ment, a map of who to find. We al­ready know that US fed­eral agen­cies spy on cit­i­zens whole­sale: who at­tended which protest, who read which fo­rum, who be­longs to which group. People are right to be afraid of what a hos­tile regime would do with a ready-made list. The data does not for­get, and it does not take sides. It sim­ply waits for who­ever holds it next.

The whole in­ter­net starts to feel like the of­fice: every­one too fright­ened to say any­thing but the safe thing, lest a real name at­tached to a real opin­ion cost them a real job.

The whole in­ter­net starts to feel like the of­fice: every­one too fright­ened to say any­thing but the safe thing, lest a real name at­tached to a real opin­ion cost them a real job.

A prin­ci­pled stance

Most peo­ple are fine with this, based on the same de­bunked nothing to hide” fal­lac­ies that are al­ways trot­ted out in these con­ver­sa­tions. Surveys find over­whelm­ing ma­jori­ties want chil­dren pro­tected on­line, and large ma­jori­ties say they sup­port age ver­i­fi­ca­tion in the ab­stract.

This is not a pop­u­lar­ity con­test, and re­fusal is not a vote you are try­ing to win. A ver­i­fi­ca­tion regime does not need your ap­proval — it needs your par­tic­i­pa­tion. It only works if nearly every­one com­plies. The point of re­fusal is not to per­suade a ma­jor­ity be­fore act­ing; it is to deny the sys­tem the uni­ver­sal co­op­er­a­tion it re­quires to func­tion at all. You do not need to win the poll. Just don’t up­load the photo. Never give them your face.

If Starbucks asked to scan your ID and put it in a na­tional data­base to sell you a latte, would you give it to them? No, be­cause you value your iden­tity more than your latte. Do you not value your iden­tity more than your abil­ity to see some ran­dom cousin post about their re­pug­nant po­lit­i­cal opin­ions or a pic­ture of some­one’s dog?

I am but one

In the­ory, us nor­mal in­ter­net users can stop this whole sys­tem by opt-ing out, by boy­cotting the process. Imagine a National Month of Identity Choice”, where no one used any plat­form de­mand­ing your face, no one logged on, no one saw any ads, no one bought any spon­sored pro­jects. The plat­forms would see mas­sive rev­enue drops, and there would be in­tense lob­by­ing to re­verse these aw­ful laws. We can do it.

The only word they can­not route around is no.

These sys­tems run on com­pli­ance. They as­sume you will sigh, up­load the photo, and move on. Their en­tire busi­ness model de­pends on it. Which is also their weak­ness. A ver­i­fi­ca­tion wall that no one ver­i­fies for is a wall with no one stand­ing at it.

So refuse. Refuse the scan. Refuse the up­load. Close the ac­counts that de­mand it and tell them, in writ­ing, ex­actly why you are leav­ing. The plat­forms need you far more than you need them. \You can live with­out the feed, they can­not live with­out the crowd. Do not com­ply in ad­vance. The face on your ID is the most per­ma­nent thing you own.

Never give them your face.

Pledging Another $400,000 to the Zig Software Foundation

mitchellh.com

My fam­ily is pledg­ing an­other $400,0001 to the Zig Software Foundation (ZSF). This brings our to­tal pledged sup­port for ZSF to $700,000, af­ter our ini­tial do­na­tion in 2024.

Zig con­tin­ues to earn my re­spect as a tech­ni­cal pro­ject and as a com­mu­nity. The 2026 de­vlog shows steady progress on the hard prob­lems of build­ing an ex­cel­lent lan­guage and com­piler. I also deeply re­spect the pro­jec­t’s ap­proach to main­tain­er­ship and com­mu­nity, re­flected in ini­tia­tives like Loris Cro’s Contributor Poker and Zig’s AI Ban. That phi­los­o­phy con­tin­ues to at­tract and de­velop some of the most tal­ented peo­ple in open source.

Recently, Zig’s strict no-LLM con­tri­bu­tion pol­icy be­came a pub­lic topic of dis­cus­sion again, es­pe­cially in the con­text of Bun’s Zig fork and Rust rewrite. I have no prob­lem with what Bun did, I think Bun is a great pro­ject, and I’m not in­ter­ested in turn­ing this into a Bun post. Instead, what stood out to me was how quickly peo­ple vil­lainized one an­other. Too much of the con­ver­sa­tion lacked em­pa­thy and re­spect for view­points dif­fer­ent from our own.

I use AI heav­ily. I’ve writ­ten about my AI adop­tion jour­ney and ship­ping real fea­tures with AI as­sis­tance. I’m also quite vo­cal about re­main­ing ra­tio­nal about its ca­pa­bil­i­ties and frus­trated with its neg­a­tive im­pacts on open source.

The point is that I have opin­ions. Those opin­ions don’t fully align with ZSFs ap­proach. And yet, I have noth­ing but re­spect for ZSF: the peo­ple, the poli­cies, and the pro­ject. Part of what makes the in­ter­net and open source great is that pro­jects can be weird and dif­fer­ent. They can set un­usual bound­aries, build their own cul­ture, and pur­sue qual­ity in ways that won’t make sense to every­one.

Zig is ex­cep­tional soft­ware: am­bi­tious, prac­ti­cal, in­de­pen­dent, and un­usu­ally se­ri­ous about qual­ity. Ghostty ex­ists in large part be­cause Zig made it pos­si­ble for me to build the kind of soft­ware I wanted to build. This is why I sup­port Zig.

I’m proud to sup­port Zig and the Zig Software Foundation again. Please con­sider do­nat­ing if you can.

Footnotes

$200,000 per year split over two years, the same struc­ture as our 2024 do­na­tion. ↩

$200,000 per year split over two years, the same struc­ture as our 2024 do­na­tion. ↩

Fully Open Foundation Model for Sovereign AI

apertvs.ai

Developed by the Swiss AI Initiative as a col­lab­o­ra­tive ef­fort be­tween EPFL, ETH Zurich, and CSCS. Open weights, open data, open sci­ence.

Fully Open

Training data, code, weights, meth­ods, and align­ment prin­ci­ples — all doc­u­mented and re­pro­ducible. Apertus is to AI as Open is to Source.

Compliant at Scale

Built to meet EU AI Act re­quire­ments: the model re­spects opt-outs, re­moves PII, pre­vents mem­o­riza­tion. A global foun­da­tion to build on.

Run for Performance

Competitive with top open mod­els at an equiv­a­lent scale of 8B and 70B pa­ra­me­ters. Multilingual from day one — trained on 1000+ lan­guages.

Swisscom is a Strategic Partner of the Swiss AI Initiative.

Stay Updated

Our newslet­ter will keep you on top of Apertus re­leases, our team’s re­search, and com­mu­nity news.

help i accidentally a wigglegram

lmao.center

Do you know what a wig­gle­gram is?

It is a kind of stereo im­age you make by loop­ing frames to­gether, like as a GIF.

The ef­fect is quite con­vinc­ing.

I am some­thing of an in­de­ci­sive pho­tog­ra­pher and when I like an an­gle I will take a lot of frames, from slightly dif­fer­ent an­gles etc., look­ing for the shot”. And since I am also a bit of a hoarder I never clear out my cam­era roll.

Same shot from dif­fer­ent an­gles”? You know what that sounds a bit fa­mil­iar.

Sure enough my phone is full of wig­gle­grams that I took by ac­ci­dent. Years’ worth, wait­ing for me to sit down and stitch them to­gether.

Or, per­haps, for some­thing to stitch them to­gether. It oc­curred to me last week­end that I can use per­cep­tual hash­ing - what TinEye (et al.) uses for re­verse im­age search - to try and find runs of sim­i­lar im­ages and pull them out from my li­brary au­to­mat­i­cally. So I wrote a lit­tle script to hash all my pic­tures:

Hashing is quick but down­load­ing pho­tos from iCloud is not.

The re­sult is a hash that - un­like a cryp­to­graphic func­tion like sha1 - will share more bits with hashes of sim­i­lar-look­ing im­ages than with dis­sim­i­lar ones. We can use that to cal­cu­late the ham­ming dis­tance be­tween pairs of im­ages and find a thresh­old:

And ex­tract pairs:

And hun­dreds of wig­gle­grams spew forth.

A few of them I am guilty of tak­ing in­ten­tion­ally. But most are true ac­ci­dents. As such many of them come out as less stereoscopic” and more kinescopic” - like lit­tle un­in­ten­tional movies.

Animals are a nat­ural fit for the con­cept, un­pre­dictable as they are:

Design-work also. (I am al­ways in­de­ci­sive.)

And sculp­ture:

What fun. I have the script up on Github if you want to play with it - it’ll work on your iCloud pho­tos li­brary if you’re on a Mac, or you can point it at a di­rec­tory of pic­tures oth­er­wise.

Cheers~

home ~ posted june 04 2026

GLM-5.2 vs Claude Opus

techstackups.com

GLM-5.2 just came out, and it’s an­other step for­ward for what open mod­els can do. The in­ter­net promptly freaked out, and it’s hard to tell what’s real and what’s hype.

So we ran it head-to-head against Claude Opus 4.8: same one-shot prompt, build a 3D plat­former in raw WebGL from scratch. Here’s our take af­ter run­ning the test and dig­ging through the bench­marks and the buzz.

We’re not switch­ing our main off Opus. In our test Opus was faster and shipped a cleaner, more cor­rect game, and it can check its own vi­sual out­put, which the text-only GLM-5.2 can’t. But GLM-5.2 earns a per­ma­nent spot in the ar­se­nal: it’s a gen­uinely ca­pa­ble model at a frac­tion of the price, and be­cause it’s open weights, it’ll al­ways be avail­able. A closed model can be re­tired or re­stricted with lit­tle warn­ing (Fable was a re­cent re­minder); weights you can down­load can’t be taken away.

You can play both games right now, or grab the source:

GLM-5.2′s game: 3dgame-glm.d.ritzademo.com

Opus’s game: 3dgame-opus.d.ritzademo.com

Source for both: github.com/​james­daniel­whit­ford/​glm-5.2-vs-opus-plat­form­ers

Both are browser games writ­ten from scratch, with no game en­gine or 3D ren­der­ing li­brary like Three.js. The 3D mod­els are free CC0 as­sets from Kenney.

Here’s how the two runs com­pared:

GLM-5.2 cost a frac­tion as much. Opus fin­ished in half the time and shipped a cleaner game.

On pa­per, the bench­marks put GLM-5.2 just be­hind the top closed mod­els, and the on­line buzz is a mix of gen­uine sig­nal and as­tro­turf. We get into both be­low, af­ter the game.

What is GLM-5.2​

GLM-5.2 is Z.ai’s lat­est flag­ship model. It’s open weights un­der an MIT li­cense, so you can down­load it, run it your­self, or call it through Z.ai’s API.

It’s built for long-hori­zon tasks, the kind of long, multi-step cod­ing-agent work that runs for hours. It ships with a 1M-token con­text win­dow and two think­ing ef­fort lev­els, High and Max, that trade speed for ca­pa­bil­ity.

note

GLM-5.2 is text-only, not mul­ti­modal. It can’t read im­ages, so work­flows built around screen­shots or di­a­grams still need a model like Claude Opus.

Z.ai po­si­tions it roughly be­tween Claude Opus 4.7 and 4.8 at sim­i­lar to­ken us­age. Here’s their an­nounce­ment, if you want to read more:

@Zai_org on X

Pricing and ac­cess​

Because it’s open weights, GLM-5.2 is cheap. Through an API it costs a frac­tion of Opus, and you can run it your­self for free if you have the hard­ware.

Pricing, per 1M to­kens (vendor docs):

On out­put to­kens, GLM-5.2 is less than a fifth the price of Opus.

The weights are on Hugging Face and ModelScope un­der an MIT li­cense, with no re­gional re­stric­tions. You can serve it lo­cally with frame­works like vLLM, SGLang, or Transformers.

Our vibe test: a 3D game from scratch​

To cut through the vibes, we gave Opus 4.8 and GLM-5.2 the same one-shot prompt: build a 3D plat­former game from scratch, in raw WebGL, with no game en­gine or 3D li­brary.

Why this task​

A model can zero-shot a good-look­ing land­ing page, and the com­mu­nity al­ready dis­counts that as a test of much. A 3D plat­former in raw WebGL can’t be faked in one pretty file. It has real struc­ture: a GLB model parser, ma­trix and vec­tor math, GLSL shaders, skinned skele­tal an­i­ma­tion, a fixed-timestep loop, col­li­sion, a fol­low cam­era.

That struc­ture tests both things peo­ple ar­gue about at once. Holding a lay­ered, multi-file build to­gether over many steps is the agen­tic part, where GLM-5.2 is meant to be strong. Getting the en­gine in­ter­nals right, the parts that look fine but qui­etly break, is the rea­son­ing-and-taste part, where Opus is meant to pull ahead.

We bun­dled the 3D as­sets lo­cally, so the test is the en­gine and the ren­der­ing, not whether the har­ness can fetch a model file. The art it­self is a hu­man-made as­set pack, Kenney’s CC0 Platformer Kit, and both agents were handed the iden­ti­cal files.

What each model had to build​

To fin­ish, each model had to build:

A 3D en­gine and ren­derer in raw WebGL, no Three.js or any li­brary.

A loader for the sup­plied 3D char­ac­ter and world mod­els.

A char­ac­ter that runs and jumps around an arena, with grav­ity and col­li­sion.

A fol­low cam­era and key­board con­trols.

The whole thing runnable in the browser with one com­mand.

Both did most of it by hand (by tool? by claw?): a GLB bi­nary parser, the ma­trix and quater­nion math, a WebGL2 ren­derer with GLSL skin­ning shaders, and sub­stepped AABB col­li­sion to keep the char­ac­ter from tun­nel­ing through plat­forms.

Both got the same prompt, the same as­sets, and one at­tempt with no hints. We ran Opus 4.8 with ex­tended think­ing on high, and GLM-5.2 with think­ing set to high (GLM-5.2 also has a higher Max tier we did­n’t use). You can dig into both runs your­self:

Play GLM-5.2′s game: 3dgame-glm.d.ritzademo.com

Play Opus’s game: 3dgame-opus.d.ritzademo.com

Source for both: github.com/​james­daniel­whit­ford/​glm-5.2-vs-opus-plat­form­ers

Opus build tran­script: full ses­sion

GLM-5.2 build tran­script: full ses­sion

How long it took, and what it cost​

Opus 4.8 built in Claude Code; GLM-5.2 built in Pi over OpenRouter.

Side-by-side time­lapse. Opus fin­ishes at 34:00, GLM-5.2 at 1:11.

The time­lapse shows the whole build com­pressed: Opus work­ing through it in roughly half the wall-clock time, GLM-5.2 grind­ing longer but for far less money. The full num­bers are in the re­sults table at the top.

Playtesting both games​

We played both games start to fin­ish. Here’s how each one held up.

Both built the same kind of game: a third-per­son 3D plat­former with the same con­trols. You move with WASD or the ar­row keys, jump with space, sprint with shift, and or­bit the cam­era by drag­ging the mouse, with the wheel to zoom. The goal is the same too: col­lect the coins across the plat­forms and reach the flag, avoid­ing a spike haz­ard, with a fall off the world send­ing you back to the start.

GLM-5.2​

GLM-5.2′s game looks kind of rough. From the playthrough:

It does­n’t look great over­all.

The char­ac­ter is miss­ing some of its ma­te­ri­als.

The spike haz­ard does­n’t kill the char­ac­ter.

Reaching the flag does noth­ing. There’s no win con­di­tion.

So it’s not that great. It did nail one thing, though: the spring.

GLM-5.2 spring launch.

You can jump on the spring and launch up to the next plat­form.

Opus​

Opus’s game is cleaner, and plays well. From the playthrough:

The cam­era and con­troller work.

The spike haz­ard kills the player, so that logic is cor­rect. But it sits off to the side of the level, not on the path, so you’d have to go out of your way to hit it.

It looks good over­all, and you can reach the flag and win. There’s a real win con­di­tion.

The an­i­ma­tions look good and run smoothly, with tex­tures ap­plied prop­erly.

Opus: an­i­ma­tions, tex­tures, con­troller work­ing.

How each model checked its own work​

Both mod­els were told to ver­ify their work be­fore stop­ping. One com­mon way an agent does this is to take a screen­shot of the fin­ished prod­uct and look at it, to check that noth­ing is bro­ken or miss­ing. That is ex­actly what Opus did in its ses­sion.

GLM-5.2 hit a prob­lem here, be­cause it can’t read im­ages. It is­n’t mul­ti­modal. So in­stead of look­ing at a screen­shot, it fell back on a hacky workaround: it wrote scripts to read the raw pixel data and check whether the col­ors came out roughly as ex­pected.

Why GLM-5.2′s self-check missed the bugs​

Because it could­n’t see the screen­shot it had saved, GLM-5.2 tried to ver­ify the frame by read­ing its pix­els in­stead. Here’s an ex­cerpt from its fi­nal re­port, where it analyzed” the saved im­age by sam­pling col­ors:

fi­nal_s­tart/​overview/​flag.png an­a­lyzed for color: grass green, dirt brown, coin gold, flag red, char­ac­ter bluish, half-Lam­bert lit, no black

fi­nal_s­tart/​overview/​flag.png an­a­lyzed for color: grass green, dirt brown, coin gold, flag red, char­ac­ter bluish, half-Lam­bert lit, no black

The col­ors it ex­pected were there, so it con­firmed the game was fin­ished and stopped. But as you can see in its own fi­nal screen­shot be­low, the char­ac­ter is a flat gray with its tex­tures miss­ing, and the de­bug over­lay is still sit­ting over the scene. An agent that could ac­tu­ally look at the screen­shot would likely have caught both, and gone back to fix them.

GLM-5.2′s fi­nal screen­shot: tex­tures miss­ing on the char­ac­ter, de­bug over­lay still on. It never saw the frame.

On a task with a vi­sual re­sult, be­ing able to un­der­stand an im­age gives a model a real edge over one that can’t.

How Opus checked its work​

Opus is mul­ti­modal, so it could read a screen­shot di­rectly. Its har­ness ren­dered the game and cap­tured a frame, and Opus in­spected that im­age as part of its ver­i­fi­ca­tion. Here’s an ex­cerpt from its ses­sion, de­scrib­ing what it saw:

The fi­nal scene ren­ders cor­rectly: grass-topped blocks with brown dirt sides, the stair­case climb­ing up, gold/​sil­ver coins and a jewel, the blue spike-block haz­ard on the right is­land, the red flag at the top goal, the char­ac­ter […] stand­ing on the start plaza, and the score HUD. Lighting and shad­ing are cor­rect, geom­e­try is clean.

The fi­nal scene ren­ders cor­rectly: grass-topped blocks with brown dirt sides, the stair­case climb­ing up, gold/​sil­ver coins and a jewel, the blue spike-block haz­ard on the right is­land, the red flag at the top goal, the char­ac­ter […] stand­ing on the start plaza, and the score HUD. Lighting and shad­ing are cor­rect, geom­e­try is clean.

Opus’s screen­shot: clean HUD, de­bug read­outs re­moved.

Because it could see the frame, Opus no­ticed the de­bug read­outs it had left on screen and cleared them be­fore fin­ish­ing.

The bugs​

Both games had bugs. Here’s what broke in each.

GLM-5.2​

GLM-5.2′s bugs were fre­quent and vis­i­ble, and sev­eral were fun­da­men­tals.

The char­ac­ter faces the wrong way. It walks in the right di­rec­tion, but the model is turned back­wards the whole time.

Missing tex­tures and a dis­ap­pear­ing head. The char­ac­ter ren­ders flat gray in­stead of tex­tured, and its head van­ishes when­ever the cam­era moves. The Kenney mod­els point to a shared color palette in a sep­a­rate file rather than em­bed­ding it, and GLM-5.2′s ren­derer never loaded that file, so it fell back to flat col­ors. Opus loaded the palette, so its char­ac­ter came out tex­tured.

The death spike does­n’t kill. The char­ac­ter lands right on a spike haz­ard and noth­ing hap­pens. No death, no re­set.

Opus​

Opus’s were fewer and sub­tler, edge cases rather than bro­ken ba­sics.

Standing on thin air. The char­ac­ter can sit be­side a plat­form, in mid-air, with­out falling. This is its coy­ote-time grace pe­riod, the brief win­dow where you can still jump just af­ter step­ping off an edge, tuned a lit­tle too gen­er­ously. A pol­ish fea­ture slightly over­done, not a bro­ken fun­da­men­tal.

Winning from too far away. The win trig­gers while the char­ac­ter is still well short of the flag.

What the test showed​

Both mod­els built a com­plete, run­ning 3D plat­former from scratch, no en­gine and no 3D li­brary, in a sin­gle pass. That is a high bar, and not long ago nei­ther would have cleared it. Here is how they split.

GLM-5.2: slower, rougher, cheaper​

GLM-5.2 took over twice as long and shipped a rough game: a gray un­tex­tured char­ac­ter, a spike that does­n’t kill, no work­ing win con­di­tion, and a de­bug over­lay still on screen at the end. Most of its bugs were fun­da­men­tals. It cost a fifth as much.

Opus: faster, cleaner, pricier​

Opus fin­ished in half the time and shipped the cleaner, more cor­rect game. Its bugs were edge cases, not bro­ken ba­sics. It cost roughly four times as much.

The mul­ti­modal ad­van­tage​

cancel_claude

www.marble.onl

There is min­i­mal down­side to switch­ing to open mod­els

Andrew Marblemarble.onlandrew@willows.aiJune 21, 2026

There was a time not too long ago when us­ing Linux en­tailed some pro­fes­sional risk1. First there was com­pat­i­bil­ity: you may not have been able to ren­der a Word doc­u­ment or PowerPoint cor­rectly, and you might have had to trust Open Office’s ex­port ca­pa­bil­ity to ren­der docs the way you wanted. There might have been spe­cialty file for­mats you could­n’t eas­ily view and so could­n’t col­lab­o­rate. And sec­ond, the soft­ware ecosys­tem was just worse gen­er­ally. There were lots of half-build open-source pro­jects try­ing to achieve the func­tion­al­ity of main­stream soft­ware, but they al­ways had rough edges. I, em­bar­rass­ingly, stayed on Windows un­til I left acad­e­mia over Matlab.

Nowadays I think this is­sue has largely dis­ap­peared. Most pro­duc­tiv­ity soft­ware has a web-app, Linux is more ma­ture, open-source soft­ware is bet­ter. I’m sure that there are all sorts of ap­pli­ca­tion spe­cific soft­ware (CAD?) that still re­quire a Windows ma­chine, but the gap is much nar­rower and Linux + open source gen­er­ally aren’t the sacrifice” they once were gen­er­ally.

There re­mains a clear penalty for be­ing an open2 LLM user. Every leader­board con­sis­tently gets topped by pro­pri­etary mod­els served over API. Today on June 21, 2026, Claude and GPT are at the top of the Artificial Analysis in­tel­li­gence leader­board. That’s from the per­for­mance side. The com­pat­i­bil­ity side is worse too. Claude code just works, and more gen­er­ally, the big two pro­vide nice APIs that make them easy to use, and, even if it’s a low bar, are trustworthy” in the sense that we’ve largely all agreed we don’t mind send­ing them our LLM queries and trust them to han­dle them ap­pro­pri­ately.

Open mod­els are served via var­i­ous means, some by the com­pa­nies that re­leased them and some by third par­ties like OpenRouter. Unfortunately, both of these routes are dodgier in terms of pri­vacy and data shar­ing, and I would not feel the same com­fort send­ing API calls con­tain­ing client or con­fi­den­tial data to them3.

The other op­tion or course is to run them your­self. This solves the pri­vacy is­sue but is at least two of ex­pen­sive, com­pli­cated, and com­par­a­tively slow.

Up un­til re­cently, open mod­els had mostly been a hobby for me. I’ve tin­kered with them since the orig­i­nal Llama leak, and oc­ca­sion­ally used them when I has a niche use case, but for most pro­fes­sional work, I stuck with the Big 2. This ap­pears to be chang­ing, with Claude’s ID ver­i­fi­ca­tion roll­out4. It was in­evitable that things would get worse for users, and the writ­ing was on the wall any­way re­cently with all the new safeguards” on re­cent mod­els and the whole Mythos thing. I’m not go­ing to spend time talk­ing about why I’m not go­ing to in­dulge ID ver­i­fi­ca­tion (or the LARPing that sur­rounds it) but what is im­me­di­ately con­cern­ing is what kind of pro­fes­sional penalty it will in­cur to stop us­ing the top mod­els.

I’m hop­ing it’s go­ing to be min­i­mal. I’m al­ready set up to run a range of open mod­els ei­ther lo­cally or in the cloud, there are good cod­ing har­nesses for open mod­els, and most im­por­tantly the open mod­els are now very close to the lead­ers and typ­i­cally trail only by a few months. This does­n’t feel like 2008 Linux vs Windows, it’s much closer. I ex­pect pro­duc­tiv­ity will take a short-term hit, but don’t think it’s a deal breaker the way switch­ing from Matlab to GNU Octave would have been when I was do­ing re­search.

I’m as­sum­ing a tech­ni­cal job that in­cludes gen­eral pur­pose work that re­quires pro­duc­tiv­ity soft­ware like MS Office etc.↩︎

I’m as­sum­ing a tech­ni­cal job that in­cludes gen­eral pur­pose work that re­quires pro­duc­tiv­ity soft­ware like MS Office etc.↩︎

I use open” here to mean the weights are avail­able, I have writ­ten ex­ten­sively on why I don’t con­sider this au­to­mat­i­cally open source, but I’m us­ing open” as short­hand. And un­like when I ad­dressed this pre­vi­ously, the cur­rent lead­ing open mod­els gen­er­ally are MIT li­censed which I do con­sider open source, though many don’t.↩︎

I use open” here to mean the weights are avail­able, I have writ­ten ex­ten­sively on why I don’t con­sider this au­to­mat­i­cally open source, but I’m us­ing open” as short­hand. And un­like when I ad­dressed this pre­vi­ously, the cur­rent lead­ing open mod­els gen­er­ally are MIT li­censed which I do con­sider open source, though many don’t.↩︎

I won’t dwell on this, happy to be cor­rected, but in my ex­pe­ri­ence un­der nor­mal cir­cum­stances no­body balks if you tell them you’re us­ing OpenAI or Anthropic. If you’re send­ing re­quests to Deepseek or OpenRouter etc. there are likely to be more con­cerns, re­gard­less of the un­der­ly­ing truth.↩︎

I won’t dwell on this, happy to be cor­rected, but in my ex­pe­ri­ence un­der nor­mal cir­cum­stances no­body balks if you tell them you’re us­ing OpenAI or Anthropic. If you’re send­ing re­quests to Deepseek or OpenRouter etc. there are likely to be more con­cerns, re­gard­less of the un­der­ly­ing truth.↩︎

https://​sup­port.claude.com/​en/​ar­ti­cles/​14328960-iden­tity-ver­i­fi­ca­tion-on-claude↩︎

https://​sup­port.claude.com/​en/​ar­ti­cles/​14328960-iden­tity-ver­i­fi­ca­tion-on-claude↩︎

Codex SQLite feedback logs can write ~640 TB/year and rapidly consume SSD endurance

github.com

Issue

Codex is con­tin­u­ously writ­ing a large amount of data to the lo­cal SQLite feed­back log data­base:

~/.codex/logs_2.sqlite

~/.codex/logs_2.sqlite-wal

~/.codex/logs_2.sqlite-shm

On my ma­chine, af­ter about 21 days of up­time, the main SSD has writ­ten about 37 TB. Process/file-level checks show Codex SQLite logs are the main con­tin­u­ous writer.

That ex­trap­o­lates to roughly 640 TB/year. On a 1 TB SSD, that is about 640 full-drive writes per year. Some con­sumer SSDs are rated around 600 TBW, so this could con­sume roughly a full dri­ve’s war­ranted write en­durance in less than a year.

Evidence

Current re­tained rows in logs_2.sqlite:

Level dis­tri­b­u­tion:

Largest tar­get+level pairs:

The top sources are mostly global TRACE logs, mir­rored teleme­try logs, and raw web­socket/​SSE pay­load log­ging. TRACE alone is about 70.7% of re­tained bytes. codex_o­tel.log_only + codex_o­tel.trace_safe add an­other 25.3%. Filtering these cat­e­gories should re­move roughly 96% of re­tained log bytes in this sam­ple with­out fully dis­abling feed­back logs.

These are high-fre­quency re­tained sam­ples. Raw web­socket/​SSE pay­load bod­ies are in­ten­tion­ally not in­cluded be­cause they may con­tain pri­vate con­ver­sa­tion con­tent.

128,764x TRACE log: in­o­tify event: … mask: OPEN, name: Some(“ld.so.cache”) 37,982x TRACE log: in­o­tify event: … mask: OPEN, name: Some(“locale.alias”) 23,843x TRACE log: in­o­tify event: … mask: OPEN, name: Some(“passwd”) 3,639x TRACE log: <tokio-tungstenite check­out>/​src/​com­pat.rs:131 AllowStd.with_context 3,505x TRACE log: <tokio-tungstenite check­out>/​src/​lib.rs:245 WebSocketStream.with_context 3,362x TRACE log: <tokio-tungstenite check­out>/​src/​com­pat.rs:154 Read.read 3,356x TRACE log: <tokio-tungstenite check­out>/​src/​com­pat.rs:157 Read.with_context read -> pol­l_read 3,230x TRACE log: <tokio-tungstenite check­out>/​src/​lib.rs:294 Stream.poll_next 3,227x TRACE log: <tokio-tungstenite check­out>/​src/​lib.rs:304 Stream.with_context pol­l_next -> read() 3,213x TRACE log: in­o­tify event: … mask: OPEN, name: Some(“nsswitch.conf”) 2,001x TRACE log: WouldBlock 1,217x TRACE log: Masked: false 1,169x TRACE log: Opcode: Data(Text) 1,169x TRACE log: First: 11000001

The dom­i­nant INFO sources are mostly re­peated OpenTelemetry mir­ror events. IDs are redacted.

843x INFO codex_­client::cus­tom_ca: us­ing sys­tem root cer­tifi­cates be­cause no CA over­ride en­vi­ron­ment vari­able was se­lected …

334x INFO codex_o­tel.trace_safe: ses­sion_loop{thread­_id=<redacted>}:sub­mis­sion_dis­patch{otel.name=“op.dis­patch.user_in­put” sub­mis­sion.id=<redacted> codex.op=“user_in­put”}:turn{otel.name=“ses­sion_­task.turn” thread.id=<redacted> …}

333x INFO codex_o­tel.log_only: ses­sion_loop{thread­_id=<redacted>}:sub­mis­sion_dis­patch{otel.name=“op.dis­patch.user_in­put” sub­mis­sion.id=<redacted> codex.op=“user_in­put”}:turn{otel.name=“ses­sion_­task.turn” thread.id=<redacted> …}

332x INFO codex_o­tel.log_only: ses­sion_loop{thread­_id=<redacted>}:sub­mis­sion_dis­patch{otel.name=“op.dis­patch.user_in­put_with­_­turn_­con­text” sub­mis­sion.id=<redacted> codex.op=“user_in­put_with­_­turn_­con­text”}:turn{otel.name=“ses­sion_­task.turn” thread.id=<redacted> …}

332x INFO codex_o­tel.trace_safe: ses­sion_loop{thread­_id=<redacted>}:sub­mis­sion_dis­patch{otel.name=“op.dis­patch.user_in­put_with­_­turn_­con­text” sub­mis­sion.id=<redacted> codex.op=“user_in­put_with­_­turn_­con­text”}:turn{otel.name=“ses­sion_­task.turn” thread.id=<redacted> …}

Additional ev­i­dence: re­tained size hides to­tal churn

A later snap­shot makes the write am­pli­fi­ca­tion eas­ier to see:

So the data­base cur­rently re­tains only ~0.5M rows, while the SQLite AUTOINCREMENT counter has al­ready ad­vanced past 5.5B ids.

That is roughly a 10,000x gap be­tween re­tained rows and his­tor­i­cal in­serted row ids. Even us­ing the cur­rent ~1.2 GiB data­base size as a rough base­line, this points to 10TB+ scale his­tor­i­cal log churn, be­fore ac­count­ing for WAL, in­dexes, prun­ing, check­points, page rewrites, and filesys­tem/​de­vice-level write am­pli­fi­ca­tion.

Write am­pli­fi­ca­tion

The re­tained DB size hides the real write vol­ume. In a 15-second sam­ple:

About 36,211 rows were in­serted in 15 sec­onds, while re­tained row count stayed flat. This sug­gests con­tin­u­ous in­sert-and-prune write am­pli­fi­ca­tion: rows are in­serted, in­dexed, writ­ten to WAL, then pruned.

Likely cause

The SQLite feed­back log sink is in­stalled with a global TRACE de­fault:

Targets::new().with_default(Level::TRACE)

This per­sists all tar­gets at TRACE level by de­fault, in­clud­ing de­pen­dency/​in­ter­nal logs and large raw pro­to­col pay­loads.

Proposed fix

Keep feed­back logs en­abled, but nar­row what is per­sisted by de­fault:

Do not use global TRACE for the SQLite feed­back log sink.

Drop or raise thresh­olds for low-value de­pen­dency noise, es­pe­cially tar­get=log, hy­per­_u­til, tokio-tung­sten­ite in­ter­nals, in­o­tify spam, and low-level OpenTelemetry SDK logs.

Avoid per­sist­ing full raw web­socket/​SSE pay­loads by de­fault. Store sum­maries in­stead: event kind, du­ra­tion, suc­cess/​er­ror, to­ken us­age, and pay­load byte length.

Avoid per­sist­ing mir­rored codex_o­tel.log_only / codex_o­tel.trace_safe events un­less they are ex­plic­itly use­ful for feed­back de­bug­ging.

Add a global logs DB size/​write cap. Per-thread caps are not enough when many threads/​processes ex­ist.

An op­tional es­cape hatch such as sqlite_logs_en­abled = false would still be use­ful, but the main fix should be bet­ter de­fault fil­ter­ing.

Related is­sues and dis­cus­sions

Excessive SQLite WAL writes dur­ing stream­ing due to TRACE logs ig­nor­ing RUST_LOG #17320

Codex Desktop rapidly grows logs_2.sqlite / WAL dur­ing nor­mal ac­tive use #24275

app-server: feed­back log sqlite (logs_N.sqlite) grows un­bounded — ~0.75 GB/day, no re­ten­tion/​ro­ta­tion #26374

logs_2.sqlite-wal grows in­def­i­nitely and re­mains al­lo­cated af­ter dele­tion be­cause stale/​sus­pended Codex TUI processes keep the deleted WAL open #22444

Heavy I/O ac­tiv­ity from idle codex processes. #20563

Severe disk I/O / 100% disk ac­tive time on Windows WSL2 when us­ing Codex ex­ten­sion / CLI #27020

goal­s_1.sqlite write am­pli­fi­ca­tion: ~11 MB/s sus­tained writes (11 GB life­time) on a 4 KB data­base #27911

Codex Desktop be­comes un­us­able on long ac­tive threads due to app-server/​ren­derer mem­ory and TRACE log churn #21134

app-server: source /feedback logs from sqlite at trace level #12969

Everything Is Logarithms

alexkritchevsky.com

Some con­nec­tions be­tween things, which I have not seen else­where. Maybe they mean some­thing?

1. The Baseless Logarithm

Normally one writes a log­a­rithm with a base, \(\log_b (x)\), to mean

\[y = \log_b (x) \Lra b^y = x\]

And then you can change the base of the log­a­rithm with

\[\log_b (x) = \frac{\log_a (x)}{\log_a(b)}\]

Which fol­lows from re­ar­rang­ing \(\log_a (x) = \log_a (b^{\log_b x}) = \log_b (x) \times \log_a (b)\).

One way of think­ing about what this for­mula does is that it is a change of units, akin to writ­ing \(2 \text{ km} = 2000 \text{ m} / \frac{1000 \text{ m}}{1 \text{ km}}\) or \(5 \text{ bytes} = 40 \text{ bits}/\​frac{8 \text{ bits}}{1\text{ byte}}\). It says: how many copies of \(b\) are in \(x\)? It’s the num­ber of copies of \(a\) in \(x\), di­vided by the num­ber of copies of \(a\) that are in \(b\).

This is per­fectly sim­ple, but for some rea­son it’s hard to think about log­a­rithms that way. The no­ta­tion kind of… ob­fus­cates things? Specifically it is hard to read \(\log_b x\) as how many copies of \(b\) are in \(x\)”, be­cause that English ex­pres­sion should cor­re­spond to the no­ta­tion \(x/b\), not \(\log_b x\). How many fac­tors of \(b\) are in \(x\)” is a bit bet­ter, but it still feels off.

I found a way of think­ing about log­a­rithms which I think makes this clearer, but you have to al­low a sort of odd ob­ject that I am call the base­less log­a­rithm. It is sim­ply a log­a­rithm with­out a base:

\[\log N\]

which we re­gard as an ab­stract ob­ject, not a num­ber. Then we write our nor­mal based” log­a­rithm as a ra­tio of two of these base­less log­a­rithms:

\[\log_2 N = \frac{\log N}{\log 2}\]

Note, this is al­ready a thing peo­ple do col­lo­qui­ally, e.g. leav­ing out the base of log­a­rithms in as­ymp­totic for­mu­las. But I do not mean it as a short­hand; it is more use­ful to re­gard it as an ac­tual al­ge­braic ob­ject.

We in­ter­pret \(\log 2\) as be­ing the unit bits”. To write \(\log N\) in bits is to fac­tor it as a mul­ti­ple of \(\log 2\):

\[\log N = \frac{\log N}{\log 2} \log 2 = \log_2 (N) \log 2 = \log_2 (N) \text{ bits}\]

Then the change-of-base for log­a­rithms fol­lows from just writ­ing the same geo­met­ric quan­tity in dif­fer­ent units. For ex­am­ple \(\log e\) as a unit is some­times called nats”:

\[\begin{aligned} \log N = \frac{\log N}{\log 2} \log 2 = \log_2 (N) \text{ bits} = \frac{\log N}{\log e} \log e = \ln (N) \text{ nats} \end{aligned}\]

The base­less \(\log N\) is sort of the mul­ti­plica­tive ver­sion of an ob­ject that might be fa­mil­iar from dis­cus­sions of vec­tors. It is com­mon with vec­tors to dis­tin­guish be­tween points and dis­place­ments: a dis­place­ment vec­tor \(\b{v}\) is given by the dif­fer­ence of two points \(\v = (b) - (a)\). When we write think of points as hav­ing co­or­di­nates, this in­volves an ex­plicit choice of ori­gin \(\O\), such that \(\b{a} \equiv (a) - \O\) and \(\b{b} \equiv (b) - \O\). Then a dis­place­ment vec­tor is con­structed by sub­tract­ing off the fac­tors of \(\O\), \(\b{v} = \b{b} - \b{a} = ((b) - \O) - ((a) - \O) = (b) - (a)\). The base­less log­a­rithm im­ple­ments the same thing but with mul­ti­pli­ca­tion: the value \(\log N\) may be thought of as \(\log N / \log \O\) for an un­spec­i­fied choice of ori­gin; turn­ing it into an ac­tual nu­meric value in­volves di­vid­ing two such log­a­rithms to can­cel out the ori­gin, \(\log_M N = \log N / \log M = (\log N / \log \O) / (\log M / \log O)\). I think of \(\log N\) as the point cor­re­spond­ing to \(N\) and \(\log N / \log \O\) as its cor­re­spond­ing dis­place­ment vec­tor once you pick a co­or­di­nate sys­tem. The point ver­sion is more fun­da­men­tal.

You might ask: if we have a base­less log­a­rithm \(\log N\), do we also have a baseless ex­po­nen­tial”? Normally \(b^{\log_b N}\) can be writ­ten as some­thing like \(b^{\log_b N} = b^{\ln N / \ln b} = e^{\ln N} = N\); is there any way to do this with­out ac­tu­ally choos­ing a base, like \((\ast)^{\log N}\) or some­thing? I think the an­swer has to be no”, be­cause I can’t think of a way to make it mean any­thing. All we can say is that we have split the one ob­ject, a log­a­rithm \(\log_b N\) which is the so­lu­tion of \(b^y = N\), into two ob­jects, \(\log N\) and \(\log b\), each of which on their own are with­out units” and so have no nu­mer­i­cal mean­ing.

So log­a­rithms act kinda like mul­ti­plica­tive vec­tors, in the sense that they have have to de­fined rel­a­tive to an origin’, a choice of base. In fact there are many sur­pris­ing sim­i­lar­i­ties be­tween log­a­rithms and vec­tors, which I had fun ex­posit­ing about:

2. Logarithms are Vectors

When do­ing vec­tor al­ge­bra and dif­fer­en­tial geom­e­try in a prop­erly co­vari­ant way, we dis­tin­guish be­tween ab­stract vec­tors and vec­tors in a par­tic­u­lar co­or­di­nate sys­tem.

My per­sonal con­ven­tion for this is to re­fer to the ab­stract vec­tors as geometric” vec­tors and al­ways write them in bold, \(\v\), whereas coordinate” vec­tors, tu­ples of their val­ues in co­or­di­nates, are writ­ten with an ar­row over them like \(\vec{v} = (v_x, v_y, v_z)\). Boldface geo­met­ric vec­tors are al­ways co­or­di­nate-free, whereas co­or­di­nate vec­tors are just col­lec­tions of num­bers or other ob­jects. The geo­met­ric vec­tor \(\b{v}\) can be writ­ten as a dot prod­uct of its co­or­di­nates with a frame’ \(X = (\x, \y, \z)\) of ba­sis vec­tors

\[\b{v} = \vec{v} \cdot X = (v_x, v_y, v_z) \cdot (\x, \y, \z) = v_x \x + v_y \y + v_z \z\]

The pro­jec­tion of \(\v\) onto a ba­sis vec­tor \(\x\) is then given by measuring’ the vec­tor against the ba­sis vec­tor (which does not have to be of unit length). I like to write this as di­vi­sion be­cause it acts a lot like di­vi­sion (although it’s tech­ni­cally pseu­do­di­vi­sion in­stead):

\[\frac{\v}{\x} = v_x\]

That’s in my own very non­stan­dard no­ta­tion1 for vec­tor di­vi­sion here. The more com­mon way to write this is to pro­ject a com­po­nent of a dif­fer­en­tial \(df = f_x dx + f_y dy + f_z dz\) with a par­tial de­riv­a­tive, which is also the pseu­do­di­vi­sion op­er­a­tion (which is in­ci­den­tally the sense in which par­tial de­riv­a­tives kinda work like di­vi­sion but not re­ally):

\[\frac{\p f}{\p x} = f_x\]

I will write things in both forms to make it easy to trans­late be­tween them; I do pre­fer my vec­tor-di­vi­sion ver­sion be­cause it avoids bring­ing in the ir­rel­e­vant no­ta­tions of dif­fer­en­tial cal­cu­lus, but since the lat­ter is ac­tu­ally stan­dard I ought to in­clude it for com­par­i­son.

Suppose \(\b{v}\) is one-di­men­sional, \(\b{v} = v_x \x\). Then the pro­jec­tion onto a measuring stick’ \(\b{m} = m \x\) mea­sures its length in terms of mul­ti­ples of \(m\):

\[\frac{\v}{\b{m}} = \frac{v_x \x}{m \x} = \frac{v_x}{m}\]

Multiplying by \(\b{m}\) again is what we mean by writing \(\b{v}\) in units of \(\b{m}\)”:

\[\frac{\b{v}}{\b{m}} \b{m} = (\frac{v_x}{m}) (m \x)\]

Here \(m\) is the unit meters” and \(v_x/m\) is the value of \(v_x\) writ­ten in me­ters. Of course to ac­tu­ally com­pute \(v_x/m\) you have to have it in units in the first place—but clearly it’s the same kind of thing as in the log­a­rithm case, where you can think of \(\b{v}\) and \(\b{m}\) as unitless” con­cepts that are com­pared geo­met­ri­cally, and then \(v_x/m\) as their pro­jec­tions into an arib­trary co­or­di­nate sys­tem.2

The base­less log­a­rithm is per­form­ing the same op­er­a­tion on log­a­rithms, where \(\log N\) is fill­ing the role of the geo­met­ric vec­tor \(\v\) and \(\log 2 = \text{bits}\) is the unit vec­tor or mea­sur­ing stick, which takes the role of \(\x\).

\[\begin{aligned} \frac{\log N}{\log 2} &= \log_2 N \\ \frac{\log N}{\log 2} \log 2 &= \log_2 N \text{ bits} \end{aligned}\]

In this sense base­less log­a­rithms write num­bers in co­or­di­nates in ex­actly the same way that mea­sur­ing sticks write vec­tors in co­or­di­nates.

The equiv­a­lence of log­a­rithms in dif­fer­ent units

\[\begin{aligned} \log N &= \frac{\log N}{\log 2} \log 2 = \log_2 (N) \text{ bits} \\ &= \frac{\log N}{\log e} \log e = \ln (N) \text{ nats} \end{aligned}\]

is the same as the equiv­a­lence of geo­met­ric vec­tors in dif­fer­ent units

\[\begin{aligned} \v &= \frac{\v}{\x} \x = v_x \x \\[1em] &= \frac{\v}{\x’} \x’ = v_{\x’} \x’ \\ \end{aligned}\]

or

\[\begin{aligned} df &= \frac{\p f}{\p x} dx = f_x dx \\ &= \frac{\p f}{\p x’} dx’ = f_{x’} dx’ \end{aligned}\]

And the change of base for­mula that com­putes a ra­tio of log­a­rithms in dif­fer­ent bases

\[\begin{aligned} \log_2 N \text{ bits}&= \ln N \text{ nats} \\ \log_2 N &= \frac{\text{nats}}{\text{bits}} \ln N\\ &= \frac{\log e}{\log 2} \ln N \\ &= \log_2 (e) \ln N \end{aligned}\]

is ex­actly like the change of co­or­di­nates for a vec­tor, where \(\x\) and \(\x\) are two units for the same quan­tity.

\[\begin{aligned} v_x \x &= v_{x’} \x’ \\ v_x &= \frac{\x’}{\x} v_{\x’} \\ \end{aligned}\]

or3

\[\begin{aligned} f_x dx &= f_{x’} dx’ \\ f_x &= \frac{dx’}{dx} f_{x’} \end{aligned}\]

What log­a­rithms don’t al­low that vec­tor di­vi­sion and dif­fer­en­tial no­ta­tions eas­ily do is to talk about a par­tial pro­jec­tion op­er­a­tion or a par­tial de­riv­a­tive in iso­la­tion. For ex­am­ple, if \(N = 2^a 3^b\), you can only talk about the total” log­a­rithm, the ra­tio with re­spect to a sin­gle unit \(\log 2\)

\[\frac{\log N}{\log 2} = a \frac{\log 2}{\log 2} + b \frac{\log 3}{\log 2} = a + b \log_2 3\]

which is equiv­a­lent to writ­ing a vec­tor as a mul­ti­ple of a sin­gle ba­sis vec­tor (like in Clifford/geometric al­ge­bra)

\[\frac{\v}{\x} = v_x + v_y \frac{\y}{\x}\]

or to a to­tal de­riv­a­tive

\[\frac{df}{dx} = f_x + f_y \frac{dy}{dx}\]

But there is no equiv­a­lent of the op­er­a­tion of par­tial dif­fer­en­ti­a­tion, a partial log­a­rithm”, which would let you fac­tor a num­ber like

\[N \? (\log_{\p 2} N) \log 2 + (\log_{\p 3} N) \log 3\]

However, I keep find­ing that peo­ple have gone and in­vented the pro­jec­tion / par­tial de­riv­a­tive op­er­a­tion on log­a­rithms any­way. For ex­am­ple, the p-adic val­u­a­tion in num­ber the­ory

\[\nu_p (n) = \max \{ k \in \bb{N} \mid p^k \mid n \}\]

cor­re­sponds to ex­tract­ing the co­ef­fi­cient of \(\log p\) of an nat­ural num­ber in a log­a­rith­mic ba­sis

\[\begin{aligned} \log n &= \log 2^{n_2} 3^{n_3} 5^{n_5} \cdots \\ &= n_2 \log 2 + n_3 \log 3 + n_5 \log 5 + \ldots \\ \nu_p (n) &= n_p \end{aligned}\]

Each co­ef­fi­cient is a pos­i­tive in­te­ger, and \(\nu_p\) just takes the com­po­nent cor­re­spond­ing to \(\log p\). Clearly \(\log n\) acts like a vec­tor (although since the co­ef­fi­cients are in \(\bb{N}\) it is tech­ni­cally a com­mu­ta­tive monoid in­stead of a vec­tor space… nev­er­the­less, it has the fa­mil­iar struc­ture of a vec­tor). Since \(\nu_p\) is a projection’ out of this log­a­rithm, it still obeys log­a­rith­mic iden­ti­ties like \(\nu_p(m/n) = \nu_p(m) - \nu_p(n)\). But there is not re­ally a good no­ta­tion for ac­tu­ally ex­press­ing it as a pro­jec­tion, so sadly it gets a whole sep­a­rate nomen­cla­ture that you have to learn.4

The same thing also works for ra­tio­nal \(n\) or rad­i­cal \(n\) (meaning it is the prod­uct of rad­i­cals of prime fac­tors), in which case the co­ef­fi­cients be­come in­te­gers or ra­tio­nals. (As a bonus the re­sult­ing ob­jects live in an ac­tual vec­tor space.)

Another ex­am­ple of these log­a­rith­mic pro­jec­tions: in com­plex analy­sis the order of van­ish­ing” \(\text{ord}_a f(z)\) of a mero­mor­phic func­tion \(f(z)\) at a point \(z=a\) is the or­der of the pole or zero at a point (where ze­roes are like neg­a­tive poles). That is, it is the de­gree \(n\) of the low­est-de­gree term in the Laurent se­ries of the func­tion around the point \(z=a\),

\[f(z) = f_{-n} (z-a)^{-n} + f_{-n+1} (z-a)^{-n+1} + \cdots + f_{-1} (z-a)^{-1} + f_0 + f_1 (z-a) + \cdots\]

(that is, the value of \(n\) such that \((z-a)^n f(z)\) is holo­mor­phic around \(a\)). This is ex­tracted with a log­a­rithm:

\[\text{ord}_a f(z) = \lim_{z \ra a} \frac{\log f(z)}{\log (z-a)} = -n\]

since for \(z \approx a\), \(f(z) \sim f_{-n} (z-a)^{-n}\) which dom­i­nates the other terms that blow up less quickly. If we write \(g(z)\) for the rest of \(f(z)\) which has \(\text{ord}_a (g(z)) > -n\):

\[\begin{aligned} \lim_{z \ra a} \frac{\log f(z)}{\log (z-a)} &= \lim_{z \ra a} \frac{\log (f_{-n} (z-a)^{-n} + g(z))}{\log (z-a)}\\ &= \lim_{z \ra a} \frac{\log f_{-n} (z-a)^{-n} (1 + \frac{g(z)}{f_{-n}} (z-a)^n)}{\log (z-a)} \\ &= \lim_{z \ra a} \frac{\log f_{-n}}{\log (z-a)} -n \frac{\log (z-a)}{\log (z-a)} + \frac{\log (1 + c (z-a))}{\log (z-a)} \\ &= -n \end{aligned}\]

So this is a very sim­i­lar op­er­a­tion: the limit \(\lim_{z \ra a} \log (z-b)/\log(z-a) = 1_{a=b}\) serves to can­cel out the rest of the terms, like how \(\p_j dx^i \sim (\p x^i)/(\​p x^j) = 1_{i=j}\) serves to can­cel out the terms in a par­tial de­riv­a­tive, ex­tract­ing the \(dx\) com­po­nent of \(df = f_x dx + f_y dy + \ldots\).

(I’m not very good at com­plex analy­sis so that’s all I’m go­ing to say about that. Still, it seems clear that this is ba­si­cally the same op­er­a­tion.)

We see that the base­less log­a­rithm \(\log n\) works a lot like a vec­tor \(\v\) or dif­fer­en­tial \(df\), and then ex­press­ing a log­a­rithm in a base like \(\log_2 n = \log n / \log 2\) is a lot like a to­tal de­riv­a­tive \(df/dx\) or Clifford di­vi­sion \(\v \ast \b{x}^{-1}\). What is miss­ing is some equiv­a­lent of the par­tial de­riv­a­tive / pro­jec­tion op­er­a­tor that pro­jects only onto that com­po­nent… but var­i­ous fields have gone and Found a way to in­vent that any­way, ei­ther in the form of a par­tial de­riv­a­tive \(\p f/\​p x\), or just by mak­ing up the \(p\)-adic val­u­a­tion \(\nu_p\), or by the lim­its \(\lim_{z\ra a} \log f(z) / \log (z-a)\) in com­plex analy­sis. The si­m­il­iar­i­ties are all sus­pi­cious, though, and I can’t help but think there is some uni­fy­ing the­ory here that ties all this to­gether… but I can’t see what it is yet.

One thing that we might try in or­der to in­vent a \(\log_2 N\) that acts like \(\p_x f\) or \(\b{v}/\x\) is to some­how re­strict the val­ues of the log­a­rithms to cer­tain spaces, e.g. in­te­gers or ra­tio­nals. Since the \(\{\log p_i\}\) are lin­early in­de­pe­dent (which is es­sen­tially equiv­a­lent to prime fac­tor­iza­tions be­ing unique), you would end up with ob­jects like \(\log_2 3 = \log_3/\log_2\) which have no value in \(\bb{Q}\); zeroing” those out then gives some­thing that acts like a par­tial de­riv­a­tive. But I don’t know if that’s use­ful. Certainly it does­n’t help in any nu­meric con­text.

Anyway, onto more things that are log­a­rithms.

3. Vectors are also Logarithms?

In dif­fer­en­tial geom­e­try one in­ter­prets vec­tors like \(\v = v_x \x + v_y \y\) be­ing writ­ten in a ba­sis of par­tial de­riv­a­tive op­er­a­tors, \(\v = v_x \p_x + v_y \p_y\). These can then be used to cre­ate dis­crete trans­la­tions which move around in the var­i­ous co­or­di­nates,

\[T^{\v} = e^{\v} = e^{v_x \p_x + v_y \p_y }\]

The par­tial de­riv­a­tives are here in or­der to make it op­er­ate on func­tions

\[e^{v_x \p_x + v_y \p_y} f(x,y) = f(x + v_x, y + v_y)\]

which is true at the level Taylor ex­pan­sions as well. I of­ten find it eas­ier to dis­pense with the par­tial de­riv­a­tives and just think of these as trans­la­tion op­er­a­tors on the space \((x,y)\) di­rectly

\[e^{v_x \p_x + v_y \p_y} (x, y) = (x + v_x, y + v_y)\]

(You can think of this act­ing on the func­tion \(f(x,y) = (x,y)\) also, but that feels like overkill.)

In any case, all this is re­ally do­ing (in flat space, at least) is rewrit­ing the ad­di­tive vec­tor \(\b{v}\) into a mul­ti­plica­tive form \(T^{\b{v}}\) which cor­re­sponds to the same op­er­a­tion. Things are just be­ing writ­ten dif­fer­ently: its terms are mul­ti­plied in­stead of added, and scalar co­ef­fi­cients are ap­plied via ex­po­nen­ti­a­tion in­stead of mul­ti­pli­ca­tion. A ba­sis for the vec­tor space now con­sists of trans­la­tion op­er­a­tors in each co­or­di­nate:5

\[T^{\v} = e^{v_x \p_x} e^{v_y \p_y} = T_x^{v_x} T_y^{v_y}\]

(In non-flat space this is not so sim­ple be­cause the trans­la­tions in dif­fer­ent co­or­di­nates may not com­mute; you can still write it in this form but it’s a lot more com­pli­cated.)

What this means for us is: look, vec­tors are log­a­rithms too!

\[\begin{aligned} \ln T^{\v} &= \ln T_x^{v_x} T_y^{v_y} \\ &= v_x \ln T_x + v_y \ln T_y \\ &= v_x \p_x + v_y \p_y \end{aligned}\]

I can’t ex­actly say why, but it seems prefer­able to have this writ­ten in terms of base­less log­a­rithms also. We do this by re­al­iz­ing that \(T_x = e^{\p_x} = T^{\p_x}\) and think­ing of this sym­bol \(T\) as a sort of generic’ base for trans­la­tions, ab­sent the nu­meric mean­ing of the sym­bol \(e\), which has \(\log T_x = \log T^{\p_x} = \p_x \log T\). Then

\[\log T^{\v} = \v \log T = v_x \p_x \log T + v_y \p_y \log T\]

And then we can write \(\v = \log_T T^{\v} = \log T^{\v} / \log T\). This is equiv­a­lent to the nat­ural log ver­sion but it avoids ex­plic­itly de­pend­ing on the nu­meric value of \(e\): any choice of base for the log­a­rithm \(T\) gives the same con­cept of a vec­tor, writ­ten in terms of the ex­po­nen­ti­a­tion of \(T\), but now we make ex­plicit that the units’ on \(\v\) come in part from the units on \(\log T\) it­self.

So vec­tors in dif­fer­en­tial geom­e­try may also be thought of as log­a­rithms, specif­i­cally, the log­a­rithms of trans­la­tion op­er­a­tors.

Regular mul­ti­pli­ca­tion can even be viewed as an ex­am­ple of this. A prod­uct like \(xa\) can be rewrit­ten as translation” in the \(\ln a\) co­or­di­nate:

\[xa = e^{\ln x} e^{\ln a} = e^{(\ln x) \p_{\, \ln a}} a = x^{\p_{\, \ln a}} a\]

I men­tion this be­cause it’s cute, but I can’t imag­ine how it would ever be use­ful.

4. Logarithms are Derivatives?

This part does­n’t re­ally con­nect to the rest; I just thought I would men­tion it so that this ar­ti­cle con­tains every fun fact about log­a­rithms that I know.

One way of defin­ing the nat­ural log­a­rithm is

\[\ln x = \lim_{a \ra 0} \frac{x^a - 1}{a}\]

Which can be found by rewrit­ing \(x^a = e^{a \ln x}\) and then Taylor ex­pand­ing:

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.