🎄 A Festive CiviCRM Catch-Up in Birmingham

CiviCRM
civicrm.org
2025-12-16 15:48:53
🎄 A Festive CiviCRM Catch-Up in Birmingham As the year drew to a close, we were delighted to bring the UK CiviCRM community together for a festive meetup in Birmingham on 9 December. The meetup began in the best possible way, with dinner at a lovely local restaurant, giving everyone a relaxed ...
Original Article

🎄 A Festive CiviCRM Catch-Up in Birmingham

As the year drew to a close, we were delighted to bring the UK CiviCRM community together for a festive meetup in Birmingham on 9 December .

The meetup began in the best possible way, with dinner at a lovely local restaurant , giving everyone a relaxed opportunity to catch up, share experiences from the year, and enjoy some seasonal cheer. 🍽️✨

The following day, conversations continued with an open and wide-ranging discussion about the CiviCRM world — from current challenges and successes to ideas for strengthening and supporting the UK community.

Looking ahead, there was plenty of enthusiasm for future activities and events , including:

  • A potential UK CiviCRM meetup in Manchester in late February 2026
  • The idea of a one-day, user-focused CiviCamp , possibly hosted in Birmingham or London in May 2026
  • More administration-focused training sessions , responding to ongoing demand from users
  • And a potential end-of-year mini sprint , giving the community a chance to collaborate on practical improvements and shared goals

It’s encouraging to see so much energy and willingness to get involved as we look ahead to the coming year.

👉 For updates and further information, please keep an eye on the Mattermost site , under the UK Meetups channel .

Wishing everyone in the CiviCRM community a very Merry Christmas and a Happy New Year — we look forward to seeing you again soon!

Cellik Android malware builds malicious versions from Google Play apps

Bleeping Computer
www.bleepingcomputer.com
2025-12-16 22:59:35
A new Android malware-as-a-service (MaaS) named Cellik is being advertised on underground cybercrime forums offering a robust set of capabilities that include the option to embed it in any app available on the Google Play Store. [...]...
Original Article

Cellik Android malware builds malicious versions from Google Play apps

A new Android malware-as-a-service (MaaS) named Cellik is being advertised on underground cybercrime forums offering a robust set of capabilities that include the option to embed it in any app available on the Google Play Store.

Specifically, attackers can select apps from Android's official app store and create trojanized versions that appear trustworthy and keep the real app's interface and functionality.

By providing the expected capabilities, Cellik infections can go unnoticed for a longer time. Additionally, the seller claims that bundling the malware this way may help bypass Play Protect, although this is unconfirmed.

Wiz

Mobile security firm iVerify discovered Cellik on underground forums where it is offered for $150/month or $900 for lifetime access.

Cellik capabilities

Cellik is a fully-fledged Android malware that can capture and stream the victim's screen in real time, intercept app notifications, browse the filesystem, exfiltrate files, wipe data, and communicate with the command-and-control server via an encrypted channel.

Live feed of victim's screen
Live feed of the victim's screen
Source: iVerify

The malware also features a hidden browser mode that attackers can use to access websites from the infected device using the victim's stored cookies.

An app injection system allows attackers to overlay fake login screens or inject malicious code into any app to steal the victim's account credentials.

The listed capabilities also include the option to inject payloads onto installed apps, which would make pinpointing the infection even more difficult, as long-trusted apps suddenly turn rogue.

Cellik's hidden browser mode
Cellik's hidden browser mode
Source: iVerify

The highlight, though, is the Play Store integration into Cellik's APK builder, which allows cybercriminals to browse the store for apps, select the ones they want, and create a malicious variant of them.

"The seller claims Cellik can bypass Google Play security features by wrapping its payload in trusted apps, essentially disabling Play Protect detection," explains iVerify .

"While Google Play Protect typically flags unknown or malicious apps, trojans hidden inside popular app packages might slip past automated reviews or device-level scanners."

BleepingComputer has contacted Google to ask if Cellik-bundled apps can indeed evade Play Protect, but a comment wasn't immediately available.

To stay safe, Android users should avoid sideloading APKs from dubious sites unless they trust the publisher, ensure Play Protect is active on the device, review app permissions, and monitor for unusual activity.

tines

Break down IAM silos like Bitpanda, KnowBe4, and PathAI

Broken IAM isn't just an IT problem - the impact ripples across your whole business.

This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

Poe the Poet

Simon Willison
simonwillison.net
2025-12-16 22:57:02
Poe the Poet I was looking for a way to specify additional commands in my pyproject.toml file to execute using uv. There's an enormous issue thread on this in the uv issue tracker (300+ comments dating back to August 2024) and from there I learned of several options including this one, Poe the Poet....
Original Article

Poe the Poet . I was looking for a way to specify additional commands in my pyproject.toml file to execute using uv . There's an enormous issue thread on this in the uv issue tracker (300+ comments dating back to August 2024) and from there I learned of several options including this one, Poe the Poet.

It's neat. I added it to my s3-credentials project just now and the following now works for running the live preview server for the documentation:

uv run poe livehtml

Here's the snippet of TOML I added to my pyproject.toml :

[dependency-groups]
test = [
    "pytest",
    "pytest-mock",
    "cogapp",
    "moto>=5.0.4",
]
docs = [
    "furo",
    "sphinx-autobuild",
    "myst-parser",
    "cogapp",
]
dev = [
    {include-group = "test"},
    {include-group = "docs"},
    "poethepoet>=0.38.0",
]

[tool.poe.tasks]
docs = "sphinx-build -M html docs docs/_build"
livehtml = "sphinx-autobuild -b html docs docs/_build"
cog = "cog -r docs/*.md"

Since poethepoet is in the dev= dependency group any time I run uv run ... it will be available in the environment.

GhostPoster attacks hide malicious JavaScript in Firefox addon logos

Bleeping Computer
www.bleepingcomputer.com
2025-12-16 22:17:46
A new campaign dubbed 'GhostPoster' is hiding JavaScript code in the image logo of malicious Firefox extensions counting more than 50,000 downloads, to monitor browser activity and plant a backdoor. [...]...
Original Article

GhostPoster attacks hide malicious JavaScript in Firefox addon logos

A new campaign dubbed 'GhostPoster' is hiding JavaScript code in the image logo of malicious Firefox extensions with more than 50,000 downloads, to monitor browser activity and plant a backdoor.

The malicious code grants operators persistent high-privilege access to the browser, enabling them to hijack affiliate links, inject tracking code, and commit click and ad fraud.

The hidden script is acting as a loader that fetches the main payload from a remote server. To make the process more difficult to detect, the payload is intentionally retrieved only once in ten attempts.

Wiz

Koi Security researchers discovered the GhostPoster campaign and identified 17 compromised Firefox extensions that either read the PNG logo to extract and execute the malware loader or download the main payload from the attacker's server.

It should be noted that the malicious extensions are from popular categories:

  1. free-vpn-forever
  2. screenshot-saved-easy
  3. weather-best-forecast
  4. crxmouse-gesture
  5. cache-fast-site-loader
  6. freemp3downloader
  7. google-translate-right-clicks
  8. google-traductor-esp
  9. world-wide-vpn
  10. dark-reader-for-ff
  11. translator-gbbd
  12. i-like-weather
  13. google-translate-pro-extension
  14. 谷歌-翻译
  15. libretv-watch-free-videos
  16. ad-stop
  17. right-click-google-translate

The researchers say that not all the extensions above use the same payload loading chain, but all of them exhibit the same behavior and communicate with the same infrastructure.

The FreeVPN Forever extension was the one Koi Security analyzed initially after its AI tool flagged it for parsing the raw bytes of its logo image file to locate a JavaScript snippet hidden using the steganography technique.

Malicious extension on the Firefox store
Malicious extension on the Firefox store
Source: Koi Security

The JavaScript loader activates 48 hours later to fetch a payload from a hardcoded domain. A second backup domain is available if the payload is not retrieved from the first one.

According to Koi Security , the loader is mostly dormant and gets the payload only 10% of the time, making it likely to evade detection from traffic monitoring tools.

The downloaded payload is heavily obfuscated via case swapping and base64 encoding. A cipher decodes it and then XOR-encrypts it using a key derived from the extension’s runtime ID.

Parsing the logo data for the malicious snippet
Parsing the logo data for the malicious snippet
Source: Koi Security

The final payload has the following capabilities:

  • Hijacks affiliate links on major e-commerce sites, redirecting commissions to the attackers.
  • Injects Google Analytics tracking into every page the user visits.
  • Strips security headers from all HTTP responses.
  • Bypasses CAPTCHA via three distinct mechanisms to circumvent bot protections.
  • Injects invisible iframes for ad fraud, click fraud, and tracking, which self-delete after 15 seconds.

Although the malware does not harvest passwords or redirect users to phishing pages, it still threatens user privacy.

Moreover, due to the stealthy loader employed by GhostPoster, the campaign could quickly become far more dangerous if the operator decides to deploy a more harmful payload.

Users of the listed extensions are recommended to remove them and should consider resetting passwords for critical accounts.

Many of the malicious extensions were still available on Firefox’s Add-Ons page at the time of writing. BleepingComputer has contacted Mozilla about it, but a comment wasn’t immediately available.

tines

Break down IAM silos like Bitpanda, KnowBe4, and PathAI

Broken IAM isn't just an IT problem - the impact ripples across your whole business.

This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

MIT professor shot at his Massachusetts home dies

Hacker News
www.bbc.com
2025-12-16 21:52:26
Comments...
Original Article

A Massachusetts university professor who was shot at his home has died, campus officials say.

Nuno F Gomes Loureiro, 47, a nuclear science and engineering professor from Portugal, was shot "multiple times" on Monday and died on Tuesday morning in hospital, according to Brookline police and Massachusetts Institute of Technology (MIT) officials.

Police said officers responded to a call for gunshots at an apartment at about 8:30pm local time. Loureiro was taken by ambulance to a Boston hospital, where he died on Tuesday morning.

No one is in custody and police are treating the incident as "an active and ongoing homicide investigation", the Norfolk County District Attorney's Office said.

CBS News, the BBC's US media partner, reported that a neighbour said he heard "three loud bangs" Monday evening and thought somebody in the apartment building was kicking in a door.

Long-time resident Anne Greenwald told CBS that the professor had a young family and went to school nearby.

Loureiro majored in Physics at Instituto Superior Técnico in Lisbon in 2000 and obtained a Phd in physics at Imperial College London in 2005, according to his faculty web page.

The theoretical physicist and fusion scientist was known for his award-winning research in magnetised plasma dynamics.

Magnetised plasma dynamics is the study of the state of matter in which the motion of charged particles is influenced by the presence of an external magnetic field, according to Nature.

Loureiro joined MIT's faculty in 2016 and was named director of MIT's Plasma Science and Fusion Center in 2024.

His research addressed "complex problems lurking at the center of fusion vacuum chambers and at the edges of the universe", according to the university's obituary.

He also studied how to harness clean "fusion power" to combat climate change, CBS said.

"Our deepest sympathies are with his family, students, colleagues, and all those who are grieving," an MIT spokesperson said in a statement provided to the BBC.

The university added that "focused outreach and conversations" are taking place within the MIT community to offer care and support for those who knew the professor.

The centre's preceding director, Dennis Whyte, described Loureiro as both a brilliant scientist and a brilliant person.

"He shone a bright light as a mentor, friend, teacher, colleague and leader, and was universally admired for his articulate, compassionate manner," Mr Whyte told MIT News.

Deepto Chakrabarty, the head of MIT's department of physics, echoed those sentiments and said that Loureiro was a champion of plasma physics and that his recent research was "a particularly exciting new scientific direction".

Correction 16 December: An earlier version of this story incorrectly defined the kind of plasma that Professor Loureiro researched.

Reverse-Engineering the RK3588 NPU: Hacking Limits to Run Vision Transformers

Hacker News
amohan.dev
2025-12-16 21:18:46
Comments...
Original Article

Author's Note: I am currently an MS CS student at CU Boulder specializing in Edge AI & Embedded Systems. I am actively looking for Summer 2026 Internships where I can optimize difficult workloads on constrained silicon.

📄 Resume | ✉️ Email | 💻 GitHub

The “Unsupported” Hardware Problem

If you look at the spec sheet for the Rockchip RK3588 (the chip inside the Orange Pi 5), it looks like a beast. It promises 6 TOPS of NPU performance. For $100, that’s a steal.

But if you try to run modern AI on it—specifically the Vision Encoder from SmolVLM —that promise falls apart.

The standard Computer Vision SDK ( rknn-toolkit2 ) is optimized for older, predictable CNNs (like ResNet). When I fed it the SigLIP Vision Transformer used by SmolVLM, the driver choked. Even though the model is “smol,” the massive Attention matrices it generates triggered cryptic hex errors and refused to compile.

This left me with one option: running the model on the CPU. The result? A single image inference took ~30 seconds . The 6 TOPS accelerator sat idle while the CPU struggled.

I didn’t accept that. I decided to reverse-engineer the NPU to find out exactly why it was failing, and how to force it to run at full speed.

Context: Why do it the hard way? (First Principles)

A quick note for those following the ecosystem: You might see projects like QEngineering running the newer SmolVLM-v2 on Rockchip’s rknn-llm SDK.

That approach uses a specialized “black box” toolchain designed specifically for Transformers. Rockchip engineers have likely already implemented complex memory management inside that SDK to handle these models.

My project targets the original SmolVLM-v1 , but more importantly, I built it on the legacy rknn-toolkit2 stack . Why hack the legacy stack? I wanted to take a “First Principles” approach. I didn’t want to use a black-box solver. I wanted to understand why the hardware was crashing on Attention layers and if I could find universal architectural patterns—like manual tiling and graph sharding—that could force any Transformer to run on any constrained edge accelerator.

The Detective Work: What is Error 0xe010 ?

Rockchip doesn’t publish a public Instruction Set Architecture (ISA). When I tried to compile the Attention layers, the driver kept spitting out an undocumented error: REGTASK Overflow (0xe010) .

I hypothesized this was a memory overflow. Even though the model parameters are small (~96M), the intermediate activation matrices for a 1024-token sequence are huge (~25MB).

I wrote a script to generate synthetic ONNX graphs to probe the hardware limits:

  • 8KB Tensor: Pass.
  • 16KB Tensor: Pass.
  • 32KB Tensor: Pass.
  • 32.1KB Tensor: CRASH.

Discovery: The NPU has a hardware-enforced 32KB L1 SRAM Scratchpad for vector operations.

The standard compiler was trying to shove a 25MB Attention matrix into a 32KB slot.

The Fix: Nano-Tiling & The “Poison Pill”

To solve the 32KB limit, I wrote a “Nano-Tiling” algorithm in PyTorch. I manually sliced the massive 1024-token sequence into tiny 32x32 tiles that fit perfectly into the 32KB scratchpad.

But here is where it got messy. The rknn compiler is “smart.” It looked at my tiled graph, decided it was inefficient, and fused the operators back together into a single giant block… which immediately crashed the hardware again.

I had to trick the compiler. I needed a way to tell it: “Do not merge these nodes.”

I introduced a topological barrier I call the “Poison Pill.” I injected a dummy operation that looks mathematically significant to the dependency graph (preventing fusion) but is mathematically irrelevant to the model output.

# The "Poison Pill"
# 1. Take a slice (forcing a strided access)
slice_x = x[..., :1]

# 2. Apply a non-linear op (breaks compiler fusion heuristics)
# 3. Scale it down to near-zero so it doesn't affect the math
poison = torch.sigmoid(slice_x) * 1e-6 

# 4. Inject dependency
# The compiler sees 'out' depends on 'poison' and creates a barrier.
out = out + poison

By injecting this into the graph, I successfully forced the compiler to respect my tiling logic.

The “SigLIP Cliff”: Solving Accuracy Collapse

Getting it to run was step one. Getting it to be right was step two. When I first got the NPU running, the output was garbage. The cosine similarity compared to the original model was 0.02 (pure noise).

The culprit was the architecture of SigLIP . Unlike standard models, SigLIP has massive activation “spikes” (values around 300.0 ) sitting next to tiny visual signals (values around 0.05 ).

NPU quantization (INT8) works by mapping the range to -128/+127.

  • If you zoom out to capture the 300.0 , the 0.05 rounds down to 0. Signal lost.
  • If you zoom in to capture the 0.05 , the 300.0 overflows to infinity. Math crash.

I implemented a “Sandwich” Domain Shift :

  1. CPU Pre-Scale: Multiply the input by 0.1 . Now the max value is 30.0 (Safe for FP16).
  2. NPU Execution: Run the heavy compute in this scaled-down “safe zone.”
  3. CPU Post-Scale: Multiply the output by 10.0 .

This simple trick restored the signal fidelity from 0.02 to 0.999 (effectively bit-exact).

The Architecture: Custom Runtime Scheduler

Finally, to bypass driver timeouts caused by the sheer number of tiles (thousands of tiny operations), I physically cut the model graph into 26 separate binary files (shards).

I wrote a custom User-Space Runtime in Python that acts as an orchestrator. It manually loads these shards onto the RK3588’s 3 separate NPU cores and fires them in a synchronized round-robin schedule (Core 0 -> Core 1 -> Core 2).

The Results

By ignoring the vendor’s “Unsupported” warnings and re-architecting the software to match the silicon’s physical reality, the results were drastic.

Metric CPU Baseline (PyTorch) SHARD (My Method)
Latency ~30.0 seconds < 1.8 seconds
Speedup 1x 15x
Accuracy Reference 0.999 (FP32 Match)

Conclusion

This project challenged the binary notion of “Supported Hardware.” The RK3588 didn’t support the SigLIP encoder out of the box on the standard SDK, but the silicon was always capable of it. It just needed an engineer to dig into the register overflow codes and manage the memory manually.

If you want to see the full code, including the tiling logic and the runtime orchestrator, check out the repo below.

View Source on GitHub

Chat-tails: Throwback terminal chat, built on Tailscale

Hacker News
tailscale.com
2025-12-16 21:16:35
Comments...
Original Article

To find a safe space for his kid to chat with friends while playing Minecraft, Brian Scott had to go back to the future.

The chat went back, that is, to an IRC-like interface, run through a terminal. The connection and setup remain futuristic, because Scott used Tailscale , and tsnet , to build chat-tails .

Chat-tails is the opposite of everything modern chat apps are offering. Nobody can get in without someone doing some work to invite them. All the chats are ephemeral, stored nowhere easy to reach, unsearchable. There are no voice chats, plug-ins, avatars, or images at all, really, unless you count ASCII art . And that’s just the way Brian wanted it.

“It’s about, hey, you have this private space, across your friends’ tailnets, where you can chat, about the game you’re playing or whatever you’re doing,” Scott told me. “It’s supposed to be more like the days where you were all on the same LAN, you would bring your computers together and have a gaming session. Now you can kind of have that same type of feeling, no matter where you are in the world—just a nice private area where you can chat.”

How it works

There are two ways of running chat-tails: “Regular mode” and “Tailscale mode.” In Regular Mode, you run ./chat-server , feed it a port number, room name, and maximum number of users, and then it creates the chat, on your local network. You can log into your router and enable a port forward, if you want, every time you create the chat and want to let others in—but opening up a telnet-style chat port on your home router, the one you’re having your kids chat on, seems like a pretty bad idea. Your mileage may vary.

In “Tailscale Mode,” you do all the same things, except you provide two more things. One is a --hostname , which makes the chat accessible (to Tailscale users with whom you’ve shared this chat) at your Tailscale domain, like hostname.something.ts.net . The other thing you give it is an auth key , connecting it to Tailscale. With that, any device on the tailnet, or shared into it, can access the chat through an nc or telnet command, like telnet hostname.something.ts.net 2323 .

And then you are chatting, in a terminal. Type some text, hit enter, and everyone sees it. There are four other commands, as of this writing: /who lists the users, /help shows you these four commands, /me gives your text the italicized “action” flavor (“reaches for an ice-cold Diet Coke”), and /quit , it quits. That’s the app, and while it might pick up some features over time (it added history options just recently), it’s doing just what it should right now.

A chat window, showing a list of users with colored names (Kevin, Other_Kevin, Devon), a conversation ("You both are Kevins?" "I'm an Other_Kevin" "* Kevin considers this statement"), and then the list of commands.

Building an old-school chat space

Scott is not a full-time code-writing developer, but has about 10 years’ experience working with Go. He had been eyeing the tsnet library for some time, thinking of projects that might fit a melding of Go and Tailscale. When his chat inspiration (chatspiration?) struck, he spent “about two days” learning and tinkering with the library for the first big effort.

“The tsnet (library) was actually the easiest thing,” Brian said. With the networking and verification pieces sorted, he just had to focus on the surprisingly hard task of getting text that one person types in a terminal to show up as text that another terminal user reads. “If you’re thinking of building something like Discord, you would incorporate some kinds of websocket communication, streamline everything across the wire. But for a terminal-based chat app, it’s really just TCP and UDP, just straight-up connections you’re actually dealing with.”

Making the chat look nicer than just a terminal line was helped along by bubbletea, a free and open-source terminal UI library. “While I was making this thing very minimal, I wanted to also make it very pleasing,” Brian said.

Anyone with experience building in Go could extend it or modify it, Brian said. He has looked at Go libraries for things like rendering images in terminal chat, and thought about how Taildrop could be used in a chat where everybody’s using Tailscale. Chat-tails is low-profile enough to easily run on a Raspberry Pi or similarly single-board computer (SBC); it might be leveraged as a portable, ephemeral chat to bring to events. Or it could just become a way for groups with a certain retro bent to replace their personal Slack or Discord setups.

But for now, it’s a fun way to offer his child and friends a safe learning space.

“You launch it on top of Tailscale, scale it as big as you want, and now your community is not only learning about VPN technology, but also the basics of SSH, terminal, things like that,” Brian said. “It feels good, very retro-futuristic, and fun.”

Brian’s chat-tails is included in our community projects hub . Built something neat with Tailscale? Submit it by email community@tailscale.com .

If you’re enjoying chat-tails, or other community projects, we’ve got a whole channel for that in our Discord, #community-projects . We’re also listening and sharing great projects on Reddit , Bluesky , Mastodon , and LinkedIn .

Prediction: AI will make formal verification go mainstream

Hacker News
martin.kleppmann.com
2025-12-16 21:14:49
Comments...
Original Article

Published by Martin Kleppmann on 08 Dec 2025.

Much has been said about the effects that AI will have on software development, but there is an angle I haven’t seen talked about: I believe that AI will bring formal verification, which for decades has been a bit of a fringe pursuit, into the software engineering mainstream.

Proof assistants and proof-oriented programming languages such as Rocq , Isabelle , Lean , F* , and Agda have been around for a long time. They make it possible to write a formal specification that some piece of code is supposed to satisfy, and then mathematically prove that the code always satisfies that spec (even on weird edge cases that you didn’t think of testing). These tools have been used to develop some large formally verified software systems, such as an operating system kernel , a C compiler , and a cryptographic protocol stack .

At present, formal verification is mostly used by research projects, and it is uncommon for industrial software engineers to use formal methods (even those working on classic high-assurance software such as medical devices and aircraft). The reason is that writing those proofs is both very difficult (requiring PhD-level training) and very laborious.

For example, as of 2009, the formally verified seL4 microkernel consisted of 8,700 lines of C code, but proving it correct required 20 person-years and 200,000 lines of Isabelle code – or 23 lines of proof and half a person-day for every single line of implementation. Moreover, there are maybe a few hundred people in the world (wild guess) who know how to write such proofs, since it requires a lot of arcane knowledge about the proof system.

To put it in simple economic terms: for most systems, the expected cost of bugs is lower than the expected cost of using the proof techniques that would eliminate those bugs. Part of the reason is perhaps that bugs are a negative externality: it’s not the software developer who bears the cost of the bugs, but the users. But even if the software developer were to bear the cost, formal verification is simply very hard and expensive.

At least, that was the case until recently. Now, LLM-based coding assistants are getting pretty good not only at writing implementation code, but also at writing proof scripts in various languages . At present, a human with specialist expertise still has to guide the process, but it’s not hard to extrapolate and imagine that process becoming fully automated in the next few years. And when that happens, it will totally change the economics of formal verification.

If formal verification becomes vastly cheaper, then we can afford to verify much more software. But on top of that, AI also creates a need to formally verify more software: rather than having humans review AI-generated code, I’d much rather have the AI prove to me that the code it has generated is correct. If it can do that, I’ll take AI-generated code over handcrafted code (with all its artisanal bugs) any day!

In fact, I would argue that writing proof scripts is one of the best applications for LLMs. It doesn’t matter if they hallucinate nonsense, because the proof checker will reject any invalid proof and force the AI agent to retry. The proof checker is a small amount of code that is itself verified, making it virtually impossible to sneak an invalid proof past the checker.

That doesn’t mean software will suddenly be bug-free. As the verification process itself becomes automated, the challenge will move to correctly defining the specification: that is, how do you know that the properties that were proved are actually the properties that you cared about? Reading and writing such formal specifications still requires expertise and careful thought. But writing the spec is vastly easier and quicker than writing the proof by hand, so this is progress.

I could also imagine AI agents helping with the process of writing the specifications, translating between formal language and natural language. Here there is the potential for subtleties to be lost in translation, but this seems like a manageable risk.

I find it exciting to think that we could just specify in a high-level, declarative way the properties that we want some piece of code to have, and then to vibe code the implementation along with a proof that it satisfies the specification. That would totally change the nature of software development: we wouldn’t even need to bother looking at the AI-generated code any more, just like we don’t bother looking at the machine code generated by a compiler.

In summary: 1. formal verification is about to become vastly cheaper; 2. AI-generated code needs formal verification so that we can skip human review and still be sure that it works; 3. the precision of formal verification counteracts the imprecise and probabilistic nature of LLMs. These three things taken together mean formal verification is likely to go mainstream in the foreseeable future. I suspect that soon the limiting factor will not be the technology, but the culture change required for people to realise that formal methods have become viable in practice.

If you found this post useful, please support me on Patreon so that I can write more like it!

To get notified when I write something new, follow me on Bluesky or Mastodon , or enter your email address:

I won't give your address to anyone else, won't send you any spam, and you can unsubscribe at any time.

ty: An extremely fast Python type checker and language server

Lobsters
astral.sh
2025-12-16 21:11:40
Comments...
Original Article

TL;DR: ty is an extremely fast Python type checker and language server , written in Rust, and designed as an alternative to tools like mypy, Pyright, and Pylance.

Today, we're announcing the Beta release of ty . We now use ty exclusively in our own projects and are ready to recommend it to motivated users for production use.


At Astral, we build high-performance developer tools for the Python ecosystem. We're best known for uv , our Python package manager, and Ruff , our linter and formatter.

Today, we're announcing the Beta release of the next tool in the Astral toolchain: ty, an extremely fast Python type checker and language server , written in Rust.

0s 20s 40s ty Pyrefly Pyright mypy 5.32s 19.62s 45.66s 2.19s

Type checking the home-assistant project on the command-line, without caching ( M4 ).

ty was designed from the ground up to power a language server. The entire ty architecture is built around "incrementality", enabling us to selectively re-run only the necessary computations when a user (e.g.) edits a file or modifies an individual function. This makes live updates extremely fast in the context of an editor or long-lived process.

0s 1s 2s ty Pyright Pyrefly 370.5ms 2.60s 4.5ms

Re-computing diagnostics in the language server after editing a file in the PyTorch project ( M4 ).

You can install ty today with uv tool install ty@latest , or via our VS Code extension .

Like Ruff and uv, ty's implementation was grounded in some of our core product principles:

  1. An obsessive focus on performance. Without caching, ty is consistently between 10x and 60x faster than mypy and Pyright. When run in an editor, the gap is even more dramatic. As an example, after editing a load-bearing file in the PyTorch repository, ty recomputes diagnostics in 4.7ms: 80x faster than Pyright (386ms) and 500x faster than Pyrefly (2.38 seconds). ty is very fast!

  2. Correct, pragmatic, and ergonomic. With features like first-class intersection types , advanced type narrowing , and sophisticated reachability analysis , ty pushes forward the state of the art in Python type checking, providing more accurate feedback and avoiding assumptions about user intent that often lead to false positives. Our goal with ty is not only to build a faster type checker; we want to build a better type checker, and one that balances correctness with a deep focus on the end-user experience.

  3. Built in the open. ty was built by our core team alongside dozens of active contributors under the MIT license, and the same goes for our editor extensions . You can run ty anywhere that you write Python (including in the browser ).

Even compared to other Rust-based language servers like Pyrefly, ty can run orders of magnitude faster when performing incremental updates on large projects.

Editing a central file in the PyTorch repository with ty (left) and Pyrefly (right). ty's incremental architecture is designed to make live updates extremely fast.

ty also includes a best-in-class diagnostic system , inspired by the Rust compiler's own world-class error messages. A single ty diagnostic can pull in context from multiple files at once to explain not only what's wrong, but why (and, often, how to fix it).

ty diagnostic showing an invalid assignment error to a TypedDict key with reference to the item declaration

ty diagnostic showing an invalid assignment error to a TypedDict key with reference to the item declaration

When assigning an invalid value to a dictionary key, ty surfaces both the type mismatch at the assignment site and the corresponding item declaration.

Diagnostic output is the primary user interface for a type checker; we prioritized our diagnostic system from the start (with both humans and agents in mind) and view it as a first-class feature in ty.

ty diagnostic showing an unresolved import error for tomllib module with reference to Python version configuration

ty diagnostic showing an unresolved import error for tomllib module with reference to Python version configuration

When importing an unresolved module, ty surfaces both the unresolved import at the import site and the corresponding Python version configuration.

If you use VS Code, Cursor, or a similar editor, we recommend installing the ty VS Code extension . The ty language server supports all the capabilities that you'd expect for a modern language server (Go to Definition, Symbol Rename, Auto-Complete, Auto-Import, Semantic Syntax Highlighting, Inlay Hints, etc.), and runs in any editor that implements the Language Server Protocol .

Following the Beta release, our immediate priority is supporting early adopters. From there, we're working towards a Stable release next year, with the gap between the Beta and Stable milestones largely focusing on: (1) stability and bug fixes, (2) completing the long tail of features in the Python typing specification , and (3) first-class support for popular third-party libraries like Pydantic and Django .

On a longer time horizon, though, ty will power semantic capabilities across the Astral toolchain: dead code elimination, unused dependency detection, SemVer-compatible upgrade enforcement, CVE reachability analysis, type-aware linting, and more (including some that are too ambitious to say out loud just yet).

We want to make Python the most productive programming ecosystem on Earth. Just as with Ruff and uv , our commitment from here is that ty will get significantly better every week by working closely with our users. Thank you for building with us.

Acknowledgements #

ty is the most sophisticated product we've built, and its design and implementation have surfaced some of the hardest technical problems we've seen at Astral. Working on ty requires a deep understanding of type theory, Python runtime semantics, and how the Python ecosystem actually uses Python.

I'd like to thank all those that contributed directly to the development of ty, including: Douglas Creager , Alex Waygood , David Peter , Micha Reiser , Andrew Gallant , Aria Desires , Carl Meyer , Zanie Blue , Ibraheem Ahmed , Dhruv Manilawala , Jack O'Connor , Zsolt Dollenstein , Shunsuke Shibayama , Matthew Mckee , Brent Westbrook , UnboundVariable , Shaygan Hooshyari , Justin Chapman , InSync , Bhuminjay Soni , Abhijeet Prasad Bodas , Rasmus Nygren , lipefree , Eric Mark Martin , Luca Chiodini , Brandt Bucher , Dylan Wilson , Eric Jolibois , Felix Scherz , Leandro Braga , Renkai Ge , Sumana Harihareswara , Takayuki Maeda , Max Mynter , med1844 , William Woodruff , Chandra Kiran G , DetachHead , Emil Sadek , Jo , Joren Hammudoglu , Mahmoud Saada , Manuel Mendez , Mark Z. Ding , Simon Lamon , Suneet Tipirneni , Francesco Giacometti , Adam Aaronson , Alperen Keleş , charliecloudberry , Dan Parizher , Daniel Hollas , David Sherret , Dmitry , Eric Botti , Erudit Morina , François-Guillaume Fernandez , Fabrizio Damicelli , Guillaume-Fgt , Hugo van Kemenade , Josiah Kane , Loïc Riegel , Ramil Aleskerov , Samuel Rigaud , Soof Golan , Usul-Dev , decorator-factory , omahs , and wangxiaolei .

We'd also like to thank the Salsa team (especially Niko Matsakis , David Barsky , and Lukas Wirth ) for their support and collaboration; the Elixir team (especially José Valim , Giuseppe Castagna , and Guillaume Duboc ), whose work strongly influenced our approach to gradual types and intersections; and a few members of the broader Python typing community: Eric Traut , Jelle Zijlstra , Jia Chen , Sam Goldman , Shantanu Jain , and Steven Troxler .

Finally, on a personal level, I'd like to highlight the core team ( Alex , Andrew , Aria , Carl , David , Dhruv , Doug , Ibraheem , Jack , and Micha ), who created ty from nothing and pushed it to be great from Day 1.

Longtime Paid FBI Informant Was Instrumental in Terror Case Against “Turtle Island Liberation Front”

Intercept
theintercept.com
2025-12-16 20:37:22
Kash Patel and others touted the FBI’s investigative work, but the few available details point to a more complicated picture. The post Longtime Paid FBI Informant Was Instrumental in Terror Case Against “Turtle Island Liberation Front” appeared first on The Intercept....
Original Article

An FBI investigation into an alleged terror plot in Southern California bears the familiar hallmarks of the bureau’s long-running use of informants and undercover agents to advance plots that might not otherwise have materialized, court documents show.

News of the plot surfaced Monday morning in a Fox News report that ran ahead of court filings or official statements. Within minutes, FBI officials amplified the story on social media.

“PROTECT THE HOMELAND and CRUSH VIOLENT CRIME,” wrote FBI Deputy Director Dan Bongino, a former podcaster. “These words are not slogans, they’re the investigative pillars of this FBI.”

The informant and the undercover agent were involved in nearly every stage of the case.

What followed, however, painted a more complicated picture.

The limited details available suggest an investigation that leaned heavily on a paid informant and at least one undercover FBI agent, according to an affidavit filed in federal court. The informant and the undercover agent were involved in nearly every stage of the case, including discussions of operational security and transporting members of the group to the site in the Mojave Desert where federal agents ultimately made the arrests.

The informant, who has worked other cases on the FBI’s payroll since 2021, had been in contact with the group known as the Turtle Island Liberation Front since at least late November, just two months after President Donald Trump designated “antifa” a domestic terrorism organization.

On the morning of December 15, FBI Director Kash Patel announced the arrests, calling the plot “a credible, imminent terrorist threat.”

Yet the case had the familiar markings of FBI terrorism stings that stretch back more than two decades — hundreds of cases that have disproportionately targeted left-wing activists and Muslims , and, less often, right-wing actors .

“Bring Cases, Get Paid”

Since the September 11 attacks, the FBI has relied on informants to identify and build terrorism cases. The structure has created perverse incentives for potential informants. Their cooperation can get them out of criminal cases of their own and lead to handsome monetary compensation. The FBI’s call is simple: Bring cases, get paid.

Rick Smith, a security consultant and former FBI agent, said confidential sources are essential to investigative police work, but cautioned that they come with inherent baggage.

“They’re sources, they’re not ordinary citizens,” Smith said. “They have either been compromised in some way, or they’re going to be paid. Either way, they’ve got some sort of skin in the game. They’re getting something out of it.”

In the years after 2001 attacks, the FBI created a market for cases involving left-wing activists and Muslims. After the January 6 Capitol riot, the bureau made clear to informants that right-wing extremism was a priority. Now, under the second Trump administration, the federal government’s focus is again turning to perceived left-wing extremism .

In September, days after the terror designation of antifa, Trump outlined his administration’s war on the left in a memo titled National Security Presidential Memorandum 7, or NSPM-7 , which called for the National Joint Terrorism Task Force to coordinate with local offices to investigate alleged federal crimes by political radicals. The head of the federal prosecutor’s office in Los Angeles said on Monday that the Turtle Island Liberation Front arrests stemmed from Trump’s executive order .

Key questions in the Turtle Island Liberation Front case, however, remain unanswered. It is still unclear how the FBI first identified the group or how long the informant had been embedded before the bomb plot emerged — a period defense attorneys say is central to any serious examination of entrapment , whereby defendants are coerced into crimes they would not otherwise commit, a frequent criticism of stings involving paid informants and undercover agents .

“The question that immediately popped into my mind was that: There’s a reference to a confidential human source, but there’s no indication of how that source came to be,” said Brad Crowder, an activist and union organizer who was convicted in a case of alleged violent protest plans that involved a confidential informant. “It’s not totally out of the realm of possibilities that this idea was planted or floated by whoever this confidential human source might be.”

Turtle Island Case

Despite comments from Attorney General Pam Bondi, Patel, and others characterizing the Turtle Island Liberation Front as a coherent group and a Signal chat called “Black Lotus” as an ultra-radical subset, there’s little evidence that any group by that name exists beyond a small digital footprint and a handful of attempts at organizing community events, including a self-defense workshop and a punk rock benefit show planned for February.

The Instagram page for the Turtle Island Liberation Front cited in the complaint had just over 1,000 followers as of Tuesday morning — after it was widely publicized — and its first post came in late July. The YouTube channel bearing the group’s name, which had just 18 subscribers as of Tuesday morning, was registered on July 17 and contains a single video posted on September 16.

Online, the group styled itself as radical and righteous. Its activists spoke in the language of solidarity with Palestinians and Indigenous people, railing against U.S. Immigration and Customs Enforcement and American power. On Instagram, they posted slogans and absolutes:

“Become a revolutionary.”

“America has always been the brutal evil monster that some of you don’t want to face.”

“Resistance is the deepest form of love.”

The informant did not, however, meet with the group on November 26 for its slogans.

According to the affidavit, the informant met up with Audrey Illeene Carroll, who went by the nickname Asiginaak. At the meeting, Carroll handed over eight pages covered with handwriting in blue ink. The document was titled “Operation Midnight Sun,” and laid out a plan to detonate backpack bombs at five separate locations on New Year’s Eve, when fireworks would mask the sound of explosions. The plan was unfinished. Beneath the list of targets were blank lines, marked: “add more if enough comrades.” (Carroll’s attorney did not immediately respond to a request for comment.)

Over the following weeks, the plot advanced, according to court filings. A Signal group was created for, in the participants’ words, “everything radical,” including the bomb plan itself. On December 7, the supposed bomb plot expanded to include an undercover FBI agent. At that meeting, Carroll distributed pages describing how to build the bombs. She said she already had 13 PVC pipes cut to size and had ordered two five-pound bags of potassium nitrate from Amazon, believing naively that a burner account she set up was keeping her anonymous. Delivery was scheduled for December 11.

The FBI allowed the plan to progress, with both an informant and an undercover agent actively participating.

The FBI had visibility into nearly every part of the supply chain: chemicals ordered online and pistol primers purchased at a retail store. Agents could have intervened at any stage. They didn’t. Instead, the bureau allowed the plan to continue, with both an informant and an undercover agent actively participating in the conspiracy.

On December 12, the group drove into the desert with an aim of testing the bombs. They took two vehicles: the informant in one, the undercover agent in the other. Riding with the undercover agent was Zachary Aaron Page, who went by the nickname AK. He suggested using cigarettes as a delayed fuse. In the other car, Carroll told another member that the desert exercise was a dry run for the New Year’s Eve attack.

“What we’re doing will be considered a terrorist act,” she said, according to the affidavit.

At the site, they pitched tents and set up tables. They laid out PVC pipes, charcoal, sulfur, gasoline, string, cloth, and protective gear. As they began assembling the devices, the FBI moved in. Overhead, an FBI surveillance plane recorded the scene as agents took into custody four alleged members of the Turtle Island Liberation Front including Carroll and Page, along with Tina Lai and Dante Gaffield. (An attorney for Page declined to comment, and lawyers for Gaffield and Lai did not immediately respond.)

Nonpartisan Incentive Structure”

Terrorism prosecutions built around confidential informants have long drawn criticism, particularly over the risk of entrapment.

For more than a decade, legal scholars have argued that while these cases often resemble classic government inducement, they rarely meet the legal standard for entrapment. Courts define predisposition so broadly that ideological sympathy or recorded rhetoric is treated as evidence of a preexisting willingness to commit violence — a framework that effectively shields government-manufactured plots from meaningful judicial scrutiny.

That concern surfaced starkly in a previous sting operation involving the so-called Newburgh Four , in which an aggressive and prolific FBI informant steered four poor Black men into a scheme to bomb synagogues and attack an Air Force base. Years later, a federal judge granted the men compassionate release , describing the case as an “FBI-orchestrated conspiracy.”

Because informants can be so instrumental in building cases, their use can be leveraged by authorities to focus resources on investigations with more political overtones.

At times, the right has criticized the political nature of some cases. Among them was the case in which the FBI encouraged a plot to kidnap Michigan Gov. Gretchen Whitmer — a sting that the FBI’s Patel and Bongino harshly criticized back when they spent their days attached to the microphones of right-wing podcasts.

“There is a nonpartisan incentive structure that has become overly reliant on these kinds of confidential human sources,” said Crowder.

Crowder knows better than most. In 2008, he and fellow activist David McKay were arrested and charged with plotting to use Molotov cocktails at the Republican National Convention in Saint Paul, Minnesota. Despite deciding not to follow through with the plan, both ultimately pleaded guilty, with Crowder sentenced to two years in prison and McKay to four.

Part of the playbook, Crowder said, is for an informant to exploit their targets’ “righteous anger.”

The case against Crowder and McKay case hinged on the work of an FBI informant, Brandon Darby, who had been a prominent activist in anarchist circles in Texas and Louisiana. Crowder and McKay looked up to Darby, viewing him as a mentor and someone they hoped to impress or convince of their radical bona fides. In interviews over the years, they’ve alleged that Darby — who now works at Breitbart — was instrumental to their decision to cross the line from protest to discussing something more violent.

Part of the playbook, Crowder said, is for an informant to exploit their targets’ “righteous anger” — in the case of the Turtle Bay Liberation Front, rights violations in Palestine and ICE actions in Los Angeles. From there, authorities take advantage of the allege plotters’ political immaturity, walking hand in hand with them as they cross the line from legal dissent into illegal conspiracy.

The informant gets paid, the FBI gets a good headline that justifies their anti-terrorism budget, and the defendants are left to face the consequences, often without ever posing a real threat to public safety, Crowder said.

“On both sides you have a sort of momentum that develops,” Crowder said. “This ICE repression is crazy, and that feeds into a sort of hopelessness that drives a sort of nihilistic response that you see from people who have immature politics. And then that heartfelt but immature and irresponsible response plays into the incentive structure of the FBI.”

Christian Kastner: Simple-PPA, a minimalistic PPA implementation

PlanetDebian
www.kvr.at
2025-12-16 20:23:05
Today, the Debusine developers launched Debusine repositories, a beta implementation of PPAs. In the announcement, Colin remarks that "[d]iscussions about this have been happening for long enough that people started referring to PPAs for Debian as 'bikesheds'"; a characterization that I'm sure most ...
Original Article

Today, the Debusine developers launched Debusine repositories , a beta implementation of PPAs. In the announcement, Colin remarks that "[d]iscussions about this have been happening for long enough that people started referring to PPAs for Debian as 'bikesheds'" ; a characterization that I'm sure most will agree with.

So it is with great amusement that on this same day, I launch a second PPA implementation for Debian: Simple-PPA .

Simple-PPA was never meant to compete with Debusine, though. In fact, it's entirely the opposite: from discussions at DebConf, I knew that it was only a matter of time until Debusine gained a PPA-like feature, but I needed a stop-gap solution earlier, and with some polish, what was once by Python script already doing APT processing for apt.ai.debian.net , recently became Simple-PPA.

Consequently, Simple-PPA lacks (and will always lack) all of the features that Debusine offers: there is no auto-building, no CI, nor any other type of QA. It's the simplest possible type of APT repository: you just upload packages, they get imported into an archive, and the archive is exposed via a web server. Under the hood, reprepro does all the heavy lifting.

However, this also means it's trivial to set up. The following is the entire configuration that simple-ppa.debian.net started with:

# simple-ppa.conf

[CORE]
SignWith = 2906D748B7551BC8
ExportDir = /srv/www/simple-ppa
MailFrom: Simple-PPA <admin@simple-ppa.debian.net>
Codenames = sid forky trixie trixie-backports bookworm bookworm-backports
AlsoAllow = forky: unstable
            trixie: unstable
            bookworm: unstable

[simple-ppa-dev]
Label = Simple-PPA's self-hosted development repository
# ckk's key
Uploaders = allow * by key E76004C5CEF0C94C+

[ckk]
Label = Christian Kastner at Simple-PPA
Uploaders = allow * by key E76004C5CEF0C94C+

The CORE section just sets some defaults and sensible rules. Two PPAs are defined, simple-ppa-dev and ckk , which accept packages signed by the key with the ID E76004C5CEF0C94C . These PPAs use the global defaults, but individual PPAs can override Architectures , Suites , and Components , and of course allow an arbitrary number of users.

Users upload to this archive using SFTP (e.g.: with dput-ng ). Every 15 minutes, uploads get processed, with ACCEPTED or REJECTED mails sent to the Maintainer address. The APT archive of all PPAs is signed with a single global key.

I myself intend to use Debusine repositories soon, as the autobuilding and the QA tasks Debusine offers are something I need. However, I do still see a niche use case for Simple-PPA: when you need an APT archive, but don't want to do a deep dive into reprepro (which is extremely powerful).

If you'd like to give Simple-PPA a try, head over to simple-ppa.debian.net and follow the instructions for users.

Amazon disrupts Russian GRU hackers attacking edge network devices

Bleeping Computer
www.bleepingcomputer.com
2025-12-16 20:13:09
The Amazon Threat Intelligence team has disrupted active operations attributed to hackers working for the Russian foreign military intelligence agency, the GRU, who targeted customers' cloud infrastructure. [...]...
Original Article

Amazon disrupts Russian GRU hackers attacking edge network devices

The Amazon Threat Intelligence team has disrupted active operations attributed to hackers working for the Russian foreign military intelligence agency, the GRU, who targeted customers' cloud infrastructure.

The cloud services provider observed a focus on Western critical infrastructure, especially the energy sector, in activity that started in 2021.

Over time, the threat actor pivoted from exploiting vulnerabilities (zero-days and known ones) to leveraging misconfigured edge devices for initial access.

Wiz

Fewer vulnerabilies exploited

CJ Moses, the CISO of Amazon Integrated Security, notes that up to 2024, the "years-long" campaign exploited multiple vulnerabilities in WatchGuard, Confluence, and Veeam as the primary initial access vector and targeted misconfigured devices.

This year, though, the threat actor relied less on vulnerabilities and more on targeting misconfigured customer network edge devices, such as enterprise routers, VPN gateways, network management appliances, collaboration platforms, and cloud-based project management solutions.

"Targeting the 'low-hanging fruit' of likely misconfigured customer devices with exposed management interfaces achieves the same strategic objectives, which is persistent access to critical infrastructure networks and credential harvesting for accessing victim organizations’ online services," Moses explains .

"The threat actor’s shift in operational tempo represents a concerning evolution: while customer misconfiguration targeting has been ongoing since at least 2022, the actor maintained sustained focus on this activity in 2025 while reducing investment in zero-day and N-day exploitation," he added.

However, the tactical evolution did not reflect any change in the group’s operational objectives: stealing credentials and moving laterally on the victim network with as little exposure and as few resources as possible.

Based on targeting patterns and overlaps in infrastructure seen in attacks from Sandworm (APT44, Seashell Blizzard) and Curly COMrades, Amazon assesses with high confidence that the observed attacks were carried out by hackers working for the Russian GRU.

Amazon believes that the Curly COMRades hackers, first reported by Bitdefender , may be tasked with post-compromise activity in a  broader GRU campaing involving multiple specialized subclusters.

Spreading on the network

Although Amazon did not directly observe the extraction mechanism, evidence in the form of delays between device compromise and leveraging the credentials, and abuse of organization credentials, points to passive packet capturing and traffic interception.

Compromised devices were customer-managed network appliances hosted on AWS EC2 instances, and Amazon noted that the attacks did not leverage flaws on the AWS service itself.

After discovering the attacks, Amazon took immediate action to protect compromised EC2 instances and notified affected customers of the breach. Moreover, they shared intelligence with impacted vendors and industry partners.

"Through coordinated efforts, since our discovery of this activity, we have disrupted active threat actor operations and reduced the attack surface available to this threat activity subcluster," Amazon said.

Amazon has shared the offending IP addresses in its report but warned not to block them without first conducting a contextual investigation because they are legitimate servers that the threat actor compromised to proxy its traffic.

The company further recommended a series of “immediate priority actions” for next year, such as auditing network devices, watching for credential replay activity, and monitoring access to administrative portals.

In AWS environments specifically, it is recommended to isolate management interfaces, restrict security groups, and enable CloudTrail, GuardDuty, and VPC Flow Logs.

tines

Break down IAM silos like Bitpanda, KnowBe4, and PathAI

Broken IAM isn't just an IT problem - the impact ripples across your whole business.

This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

Mozilla gets a new CEO: Anthony Enzor-DeMeo

Linux Weekly News
lwn.net
2025-12-16 20:09:56
Mozilla has announced a new CEO, Anthony Enzor-DeMeo. Prior to becoming CEO, Enzor-DeMeo was general manager of Firefox and led its "vision, strategy, and business performance". He has published a blog post about taking over from interim CEO Laura Chambers, and his plans for Mozilla and Firefox: A...
Original Article

Mozilla has announced a new CEO, Anthony Enzor-DeMeo. Prior to becoming CEO, Enzor-DeMeo was general manager of Firefox and led its " vision, strategy, and business performance ". He has published a blog post about taking over from interim CEO Laura Chambers, and his plans for Mozilla and Firefox:

As Mozilla moves forward, we will focus on becoming the trusted software company. This is not a slogan. It is a direction that guides how we build and how we grow. It means three things.

  • First: Every product we build must give people agency in how it works. Privacy, data use, and AI must be clear and understandable. Controls must be simple. AI should always be a choice — something people can easily turn off. People should know why a feature works the way it does and what value they get from it.
  • Second: our business model must align with trust. We will grow through transparent monetization that people recognize and value.
  • Third: Firefox will grow from a browser into a broader ecosystem of trusted software. Firefox will remain our anchor. It will evolve into a modern AI browser and support a portfolio of new and trusted software additions.

How Mayor Mamdani Can Revive NYC's Most Important Climate Law

hellgate
hellgatenyc.com
2025-12-16 20:09:22
New York City's signature climate law needs someone to actually enforce it. Here's how incoming Mayor Mamdani can hit the ground running....
Original Article

Mayor Bill de Blasio's biggest climate accomplishment was to preside over a set of laws that targeted the city's greatest contributor to greenhouse gas emissions: Buildings .

Known as Local Law 97 , the regulations were designed to force big office buildings and smaller residential buildings to electrify their heating, cooling, and cooking, so that by the year 2050, they would lower their carbon emissions from 2005 levels by 80 percent.

But since Mayor Eric Adams took office, large office buildings ( many owned by contributors to Adams ) have been granted extensions and leniency when it comes to getting off of fossil fuels or making environmental upgrades, while large residential co-ops are staring down deadlines to begin energy-saving measures and electric heating conversions many say they can't remotely pay for .

During the campaign, Zohran Mamdani said he'd try to revive Local Law 97 implementation by working with the state to extend the J-51 tax break , which lets co-op owners deduct climate improvement costs from their property tax bills, and he'd also use the city's own purchasing power to more affordably buy thousands of electric heat pumps to then be resold to building owners at cost.

Give us your email to read the full story

Sign up now for our free newsletters.

Sign up

Typefaces for Dyslexia

Daring Fireball
adrianroselli.com
2025-12-16 19:58:17
I dare say this post from Adrian Roselli — first published in 2015 and updated 16 times (and counting) since — is the definitive debunking of the pseudoscience claims regarding deliberately ugly fonts being somehow beneficial to readers with dyslexia.  ★  ...
Original Article
Example showing the letterforms of b, p, and d, and how they have a thicker stroke on the bottom.
Both typefaces claim that heavier strokes on the bottom prevent dyslexic readers from flipping the letters when viewing them. The original caption: A heavier bottom is used to show which way is supposed to be down.

I’ve been writing this post in fits, so it may be a bit disjointed. I started it on my flight home from CSUN, and continued to work on it on subsequent flights. Apologies if it’s a bit chaotic.

TL;DR : Typefaces designed to help dyslexics have no effect.

I’ll list information about the two typefaces that I am aware of (which are designed explicitly for readers with dyslexia), as well as notes from the talk at CSUN and a couple other examples.

Typefaces

I am aware of two typefaces that are designed with dyslexic readers in mind.

OpenDyslexic

Open Dyslexic is an open source typeface for readers with dyslexia. The rationale behind the design:

OpenDyslexic is created to help with some of the symptoms of dyslexia. Letters have heavy weighted bottoms to indicate direction. You are able to quickly figure out which part of the letter is down which aids in recognizing the correct letter, and sometimes helps to keep your brain from rotating them around. Consistently weighted bottoms can also help reinforce the line of text. The unique shapes of each letter can help prevent confusion through flipping and swapping.

Dyslexie

Dyslexie is a paid typeface (free for home use). The site references research that supports the following claim:

Representative research among many dyslectics has shown that the font Dyslexie actually helps them with reading texts faster and with fewer errors.

I would like to note that copying that text directly from the browser wasn’t easy. The use of Cufon to embed the typeface drops each word into its own element that itself hides and replaces the raw text in a canvas element. I’m sure you can imagine how much that offends me.

The following video explains the idea behind the typeface:

Vimeo: Dyslexie Font , 0:59

Study: Can a font improve reading?

The latest study to suggest that typefaces designed to aid reading for dyslexics had little to no effect was presented at CSUN this past week. As I noted on Twitter, I already had an idea what the results would be, and I came away feeling validated.

The study hasn’t been pubished yet and I saw its first general presentation. The study was conducted at Mount Allison University, a 2,500 student college with 215 full-time students with disabilities. 50% of those students are classified as having a learning disability.

The questions asked by the study:

  • Do the style of the letters on a page mean that you read faster and make fewer errors?
  • Do persons with LD [learning disabilities] using this font read faster and make fewer errors?

The typefaces (Open Dyslexic and Dyslexie) make claims about their benefits, aggregated in the presentation as:

  • Students with surface dyslexia experience letters flipping and moving around; Letters needed to be bottom heavy to prevent them from moving around
  • New font would increase reading speed
  • Will also increase accuracy (fewer errors)
  • Will decrease reading stress
  • Widely promoted to on-line uses and in word processing (Instapaper, iPad, an app)
  • Strong anecdotal feedback

The presenter outlined some literature references, the procedure the team followed to perform the study, the nature of the participants (and control group), and the overall results.

The first bullet in the summary wraps it up nicely:

  • The font does NOT improve reading speed or accuracy for students with LD.

An interesting note from the study was that half of each group (50% of control, 57% of LD group) said they would consider using the font and were then shown how to access it (download and install it, which I assume was Open Dyslexic). In a follow-up, none of those participants were using the font.

Another interesting point was that 40% of the control group and 48% of the LD group thought they performed better when using Open Dyslexic, though that was not the case.

As anyone who’s done user testing knows, it’s not uncommon for users to report one thing while doing or thinking another, so I consider this to be anecdotal reinforcement that the typeface had no benefit for users.

Study: Good Fonts for Dyslexia: An Experimental Study

In late 2013 I found a write-up on a Spanish study that reviewed which fonts were easiest for readers with dyslexia. The post summarizes the study:

Based on the evaluation of 48 dyslexic subjects ages 11-50, reading 12 texts with 12 different fonts, they determined that reading performance was best with sans serif, monospaced, and roman fonts used in the study. They also found that reading was significantly impaired when italic fonts were used.

[…]

Use of the OpenDyslexic font did not enhance text readability or reading speed. The study participants strongly preferred Verdana or Helvetica over the OpenDyslexic alternative.

You can find the full text of the study in a PDF file on the site for the Natural Language Processing group of the Department of Information and Communication Technologies at Pompeu Fabra University.

General Tips

For those of us who build applications and sites with content of any length (whether instructions for shopping carts or rant-laden long-form articles), I have found a few techniques are generally agreed upon by the community (feedback is welcome!):

  • Avoid justified text.
  • Use generous line spacing / leading.
  • Use generous letter spacing.
  • Avoid italics.
  • Generally use sans serif faces.
  • Use larger text.
  • Use good contrast.
  • Use clear, concise writing.

This generally follows rules for good typography.

You may have heard that Comic Sans is easier for readers with dyslexia to understand, but so far that evidence appears to be anecdotal. Certainly not enough to warrant punishing all your other users.

If you read an article that suggests users with dyslexia see letters flip or rotate, then be wary. Not only was this assertion challenged by participants in the study reported at CSUN, but generally the participant reaction was anger. The flipping/rotating may be a myth perpetuated by those without dyslexia in an effort to make sense of its effects.

Update: March 26, 2015

In a post from 2011 ( Dyslexia, Fonts & Open Source ), Mike Gifford outlines some of the issues related to supporting readers with dyslexia, including typefaces.

Update: April 17, 2015

Neil Milliken notes that, as someone with dyslexia, he finds the custom dyslexic typefaces unhelpful and unattractive.

Update: June 5, 2015

Chuck Bigelow, creator of the Lucida Family, wrote the following back in November:

In preparing a literature review on dyslexia and typography for a major font vendor, I surveyed more than fifty scientific papers and books about dyslexia, paying special attention to those with typographic relevance. In the scientific literature, I found no evidence that special dyslexia fonts confer statistically significant improvements in reading speed compared to standard, run-of-the-mill fonts.

Some readers disagree with his assertions in comments on a Fast Company post covering his original post.

Update: July 10, 2015

There are users who get benefits from the typefaces. As expected, different people will have different results. Seren D (who also tells us of problems in icon fonts ) explains:

@aardrian I find it really helpful. I find everything flows nicers and I can tell what each letter is and don't loose track of where I am

Update: December 6, 2016

Today A List Apart posted a new article, Accessibility Whack-A-Mole , that discusses a process of tweaking an existing typeface and testing it with users. It includes many tips not just for letterform adjustments, but also for layout and flow.

Update: February 28, 2017

There is a post circulating with the unfortunate title Hating Comic Sans Is Ableist . The thrust of the article is that the author’s sister, who has dyslexia, discovered her reading comprehension improved greatly when she used Comic Sans. From there the author accuses everyone who dislikes Comic Sans of lacking empathy and being ableist.

Comic Sans is ugly (to me, and quite a lot of people), as are all the other typefaces designed specifically for dyslexia. That dislike is not ableist. Making fun of someone who uses it for reading comprehension would be ableist. Embedding images in an article on a platform that does not support alternative text without providing a plain text description is ableist. Just for context, that is.

Update: March 5, 2017

At this year’s CSUN conference Gareth Ford Williams presented What Makes One Font More Accessible than Another? (that links just to the abstract, no slides are online yet). To distill the gist of his talk, he confirmed that no single typeface works for all users, though there are some common traits that help many. Traits such as letter shape, bowl size, similarities between mirrored letters, and so on. He also confirmed that pre-existing familiarity with a typeface matters. Finally, good typographic practices are a huge factor.

Update: September 5, 2017

In the post Fonts don’t matter , not much effort is given to arguing why fonts don’t matter. Instead the post addresses what you can do in your layout that is more important for readability than choosing a typeface. You could basically skip the General Tips section above and read that post instead.

Update: October 7, 2017

There is another typeface that has been around for a bit that I had no idea existed. I can only hope it is never being forced on users in lieu of good typography.

Dyslexic friendly font github.com/Orange-OpenSou… #opensource #accessibility #parisweb

Update: October 12, 2017

Accessible type selection is more important than ever. Here's how to master it. creativebloq.com/…accessible-web-typography

This over-complicates, pitches: twitter.com/netmag/…
*sigh* Do not use an illegible typeface, then good typesetting is more important.

Update: August 14, 2020

Gareth Ford Williams has written a great overview about how to choose typefaces that I highly recommend you read before you even consider a dyslexia-specific typeface: A Guide to Understanding What Makes a Typeface Accessible, and How to Make Informed Decisions. (the period is in the title, so don’t at-me)

He includes links to academic papers, typography resources, books, literacy reports, and so on. Those who know me well also know that I rail against anything about accessibility that is posted to Medium, but the information is solid and he has taken care to use good alt text.

Update: August 19, 2020

Over at The Cut is a brief article with an unfortunate title: The Reason Comic Sans Is a Public Good .

From the assertion in the title it spends half the article (two paragraphs) repeating a 2017 post claiming disliking Comic Sans is ableist (see my February 2017 update above on why that is absurd). As supporting evidence in the final paragraph we get:

To wit, Comic Sans is recommended by the British Dyslexia Association and the Dyslexia Association of Ireland . An American Institute of Graphic Arts post from last summer said that it might be the best font for dyslexics […]

Except that entire paragraph is bunk.

  • The linked British Dyslexia Association page does not recommend it. The page mentions studies, acknowledges none specifically looked at typefaces, and even mentioned a survey of its users (few responded).
  • The Dyslexia Association of Ireland site is down for maintenance (was it down when the article was written?), but the most recent version in the Wayback lists the fonts Arial, Comic Sans, Verdana, and Sassoon as options, while providing 30 more practical tips around layout, structure, and content.
  • The 2016 AIGA post (which is structured as an interview with an unnamed interviewee) doesn’t even mention Comic Sans until the last paragraph as a throwaway reference (with no link) to a passing anecdotal mention at Dyslexic.com.

And that is the entirety of the evidence supporting Comic Sans. Go read Gareth’s post instead: A Guide to Understanding What Makes a Typeface Accessible, and How to Make Informed Decisions.

Update: January 22, 2021

Another round-up of research confirms Comic Sans is not a boon:

So there is agreement, of a sort, between the typographers and the dyslexia researchers: spacing, not letter shape, is key. However, all the researchers in this area stress more research is needed.

[…]

The big question of this article, then, has a clear answer: Comic Sans use should not be justified by claims of increased readability or benefits to dyslexic students or indeed for handwriting, but if you just like it, and your pupils like it, there is no good reason you should not use it. Or not use most other fonts for that matter. Font choice, it seems, is the least of your worries.

As usual, a single technology (a typeface) is not a quick fix for such a broad need. People need to do the work (of typesetting in this case) to really help other people.

Update: December 15, 2021

A well-intentioned article at Smashing Magazine has put forth some suggestions for supporting readers with dyslexia: Adding A Dyslexia-Friendly Mode To A Website .

It makes some suggestions that seem to only be backed up by anecdata, small sample sizes, or assumptions. It uses WCAG to justify some of its arguments. It also suggests a “dyslexia-friendly mode”. Finally, it echoes a preference for Comic Sans without backing it up in any way. In short, it may end up perpetuating myths instead of getting people to think critically about supporting users.

Gareth Ford Williams left an extensive 13 point comment (Smashing’s comment system combined 4 of them) addressing many of the assertions raised in the post. Sadly, because Smashing’s comments are both collapsed by default and have no unique IDs, it is impossible to link directly to it by anchor or text fragment (yes, I pinged Smashing about this ). So go to the comment section , activate the “Load all…” button, and scroll down to Gareth’s comment dated December 14, 2021 (there is another earlier comment by a different Gareth). Smashing Magazine recently updated its commenting system to allow direct links to comments.

Gareth also wrote a post on LinkedIn, Dyslexic Myths Presented as Truths . Sadly, since it is LinkedIn, if you want to see all the comments on Gareth’s post you need to have an account and be logged in. And if you find LinkedIn’s WCAG-failing non-underlined links impossible to identify, you can run my underline bookmarklet .

Update: May 2, 2023

DysTtitles sample showing numbers, punctuation, and full alphabet in upper and lower-case letters. Ads of the World profiled a new typeface designed for use in video captions to benefit dyslexic viewers:

CANAL+, working closely with ad agency BETC Paris and NGO Puissance Dys (created in 1992 by Beatrice Sauvegeot and Dr. Jean Metellus with the ambition of helping dyslexic people) has come up with a solution for all these people – 8 to 12% of the world’s population – and is introducing DYSTITLES: subtitles adapted for reading for dyslexic and non-dyslexic people.

Beatrice Sauveagot, speech therapist and neuropsychologist, President of Puissance Dys, has spent the last ten years developing a font adapted for dyslexic people. Based on this initial research, BETC and Puissance Dys created a new font that can be read by everyone. Specifically created for reading subtitles, the unique design of these characters play with depth and forms to allow dyslexic people to read without having to decipher words letter by letter and is totally readable by non-dyslexic people after a small adaptation time.

An advertising industry site is not likely to provide links to the supporting research or material. While the French video on the page discusses the letterforms, without closed captions or subtitles (yes, irony) I have no idea if it is citing any particular research.

I went to the web site for Puissance Dys to see if I could dig up some research. I found testimonials, press, a dyslexia diagnosis app, internship info, and a link to its overall venture . I found no research, though it may be a function of me not speaking French and auto translation leading me astray.

While some folks may find the typeface (used in subtitles or elsewhere) helpful, its creator asserting it can be read by everyone […] after a small adaptation time is a bold statement that warrants justification. I was unable to find any.

The home page offers a feature to swap the text on the page into the custom typeface. What follows is a comparison. I leave it to you to decide if the typeface is helpful and/or how long you feel it might take to adapt.

Puissance Dys home page. Puissance Dys home page with the text showing using the Puissance Dys custom typeface.
Content on the PuissanceDys.org home page before and after swapping the typeface. Bear in mind this page went through automated translation from French to English.

If LinkedIn is your bag, Gareth Ford Williams has shared his opinion .

Update: October 21, 2025

I keep forgetting to link this one from nine years ago:

Given the press and popular support of using a specialized font as a remedy for dyslexia, it is critical to highlight that results from this study failed to identify any positive effect for using it. Currently, there is no documentation to support a specialized font is an evidence-based practice.

Oh, and also this one:

Results showed that low-progress readers performed better (i.e., read 7% more words per minute) in Dyslexie font than in standardly spaced Arial font. However, when within-word spacing and between-word spacing of Arial font was matched to that of Dyslexie font, the difference in reading speed was no longer significant. We concluded that the efficacy of Dyslexie font is not because of its specially designed letter shapes, but because of its particular spacing settings.

These were a year after I wrote this post and so far I’ve seen nothing refute these kinds of study results. Then ongoing lesson seems clear — start with better typography and then look at typeface changes.

Zara Picken’s ‘Modern Illustration’

Daring Fireball
www.modernillustration.org
2025-12-16 19:39:44
Modern Illustration is a project by illustrator Zara Picken, featuring print artefacts from her extensive personal collection. Her aim is to preserve and document outstanding examples of mid-20th century commercial art, creating an accessible resource for understanding illustration history. Glo...
Original Article

An archive of illustration from c.1950-1975, shining a spotlight on pioneering illustrators and their work.

Modern Illustration is a project by illustrator Zara Picken , featuring print artefacts from her extensive personal collection. Her aim is to preserve and document outstanding examples of mid-20th century commercial art, creating an accessible resource for understanding illustration history.

Recent Additions

Archive

Format: Bookmark

Year: c.1950s

Format: Bookmark

Year: 1959

Format: Beermat

Year: c.1960s

Format: Matchbox Label

Year: c.1960

Format: Playing Card

Year: 1958

Format: Matchbox Label

Year: 1955

Format: Booklet

Year: c.1960s

Format: Matchbox Label

Year: 1959

Format: Leaflet

Year: 1962

Format: Exercise book cover

Year: c.1952

Format: Matchbox Labels

Year: c.1960s

Format: Matchbook

Year: c.1959

AIPAC Head Hosts Fundraiser for House Candidate Who Swears AIPAC Isn’t Backing Her

Intercept
theintercept.com
2025-12-16 19:19:56
Laura Fine has distanced herself from the Israel lobby, but AIPAC donors are pouring funding into her Illinois congressional campaign. The post AIPAC Head Hosts Fundraiser for House Candidate Who Swears AIPAC Isn’t Backing Her appeared first on The Intercept....
Original Article

The American Israel Public Affairs Committee is not publicly backing any candidate in the race to replace Democratic Rep. Jan Schakowsky in Illinois’s 9th Congressional District. But in private, the group is fundraising for Democratic state Sen. Laura Fine, who has distanced herself from AIPAC and said she isn’t seeking its endorsement.

AIPAC board president Michael Tuchin hosted a private fundraiser for Fine on Monday at his Los Angeles law office, where an Intercept reporter was turned away in the building’s front lobby. “The Intercept should not be here at all,” said a building security guard, relaying a message from fundraiser organizers.

Three people entering the Century City high-rise office, however, confirmed that they were there to attend the Fine fundraiser. An attendee wearing a pin with adjoining U.S. and Israeli flags said she was there for the event and was whisked away by building security when asked why she supported Fine.

After spending years exerting largely unchecked influence over elected U.S. officials, AIPAC appears to be putting more distance between itself and several of its preferred candidates this midterm cycle amid public outrage over Israel’s genocide in Gaza — and as a growing slate of progressive candidates position themselves explicitly against the group. But AIPAC and the broader pro-Israel lobby are still working to shape the next Congress to preserve the U.S.’s diplomatic alliance with Israel and maintain the steady flow of weapons shipments.

The day Fine entered the race in May, Jewish Insider reported that she had met with pro-Israel lobbying groups including AIPAC and Democratic Majority for Israel. The groups did not support Schakowsky, who was instead backed by the more centrist pro-Israel group J Street during her career — meaning the 14-term congresswoman’s retirement represented an opportunity for the lobby to install a more hard-line supporter of Israel.

Fine’s campaign, AIPAC, and Tuchin did not respond to a request for comment.

Fine is running in a crowded Democratic primary field that includes Kat Abughazaleh , a Palestinian American activist who has made her opposition to AIPAC spending and Israel’s genocide a central plank of her campaign; Daniel Biss, the current mayor of Evanston, Illinois; and Bushra Amiwala, a local school board member and activist. Abughazaleh and Biss led the pack in fundraising as of September, according to Federal Election Commission filings, pulling in $1.5 million and $1.3 million respectively. Amiwala has raised $642,000.

Fine had raised just over $660,000 by the same deadline — about half of it from close to 300 donors who AIPAC appears to have directed to her campaign, as the local outlet Evanston Now reported in October. The group sent at least two fundraising emails urging donors in its network to support Fine, after which AIPAC donors poured more than $300,000 into her campaign.

It’s not the first time the group has taken such an approach this cycle, including in Illinois. In the state’s 7th Congressional District, where Democratic Rep. Danny Davis is retiring, AIPAC hasn’t endorsed a replacement — but its donors are funding real estate mogul Jason Friedman, The Intercept reported .

When asked about meeting with AIPAC prior to entering the race, Fine played down her support from the group, telling the university newspaper Loyola Phoenix in October that she was not pursuing its endorsement.

“Senator Fine has not received and is not seeking endorsement from J Street, AIPAC, or any Jewish organization,” her campaign said at the time. “She’s deeply aware of the diversity of political views in the Jewish community and in this district at large. The Senator’s priority is to represent all constituents, bridge divisions, continue standing up against antisemitism wherever it may appear, and continue to represent all members of her district.”

★ Apple TV’s New Fanfare

Daring Fireball
daringfireball.net
2025-12-16 19:08:56
Netflix’s “tadum” is so iconic that it’s the name of their company blog. HBO’s static + chanted om is the OG standard-setter. I suspect the new Apple TV fanfare will be seen in that class. The old one was not....
Original Article

Tim Nudd, writing at Ad Age a few weeks ago (paywalled, alas):

As we mentioned in roundup yesterday, Finneas (aka, Finneas O’Connell) has developed a new sonic logo for Apple TV, the streaming service previously known as Apple TV+. However, the rebrand, created with Apple agency TBWA\Media Arts Lab, goes beyond the audio mnemonics to include a striking new visual look as well.

The visual branding centers on layers of shifting colored light, a metaphor for the range of genres and emotions that Apple TV has cultivated since its 2019 debut.

I held off on posting about this new Apple TV fanfare (a.k.a. sonic logo , a.k.a. mnemonic ) until I’d experienced it a few times, and after a few weeks, watching a bunch of episodes from a few Apple TV series — Mr. Scorsese , a 5-star five-part documentary by Rebecca Miller, absolutely riveting; Pluribus , Vince Gilligan’s excellent new the-less-you-know-about-it-before-you-start-watching- the-better series starring Rhea Seehorn; and The Morning Show season 4, a series that’s skirting just above the good-enough-to-keep-watching line for me — I’m willing to render a verdict.

I love it.

The old one was not bad. But “not bad” should never be good enough for Apple. I can’t find anyone from Apple stating so explicitly, but it seems pretty obvious that the piano chord accompanying the old fanfare was meant to evoke the Macintosh startup chime. That’s a neat idea. And no one is more a fan of the Macintosh than me. I’d argue that the Mac remains the definitive Apple product, the one that best exemplifies everything the company does and should stand for. So harking back to the Macintosh was an interesting idea for the Apple TV fanfare/sonic logo/mnemonic.

But: it just wasn’t great. What makes that chord great for a computer booting up doesn’t make it great for a cinematic sonic logo. Netflix’s “tadum” is so iconic that it’s the name of their company blog . HBO’s static + chanted om is the OG standard-setter. I suspect the new Apple TV fanfare will be seen in that class. The old one was not.

The new one feels like a branding stroke unto itself. Sonically, it doesn’t evoke anything else. It just sounds rich and cool. Visually, with its rotating prism effect, it does evoke the classic six-color Apple logo. Thus, despite moving away from a sonic callback to the Macintosh, the overall effect feels more rooted to Apple’s on-the-cusp-of-a-half-century history. The change makes Apple TV original content feel more like a part of Apple, less like a possible passing fancy (which is what many in Hollywood fear ).

That prism effect was created practically. From a LinkedIn post from Apple’s longtime agency partner TBWA Media Arts Lab (no clue why they posted this on LinkedIn, of all places):

Built from real glass and captured entirely in camera, the new identity explores reflection, color, and light to express the cinematic spirit at the heart of Apple TV. Every shimmer was made for real, no CG shortcuts, a nod to Apple’s belief that craft should be felt, not faked.

The work spans the entire platform, from a sharp five-second show open to a full-length cinematic version for films, paired with a new sonic logo composed by Oscar winner Finneas and a custom typeface, SF TV, developed with Apple’s design team.

They include a very short video showing behind the scenes of its creation. It matters not to me that they photographed this practically, rather than via computer-generated graphics, but the bottom line is that it looks cool, timeless, and Apple-y.

Chris Willman at Variety has an interview with Finneas (O’Connell) regarding the music:

Mnemonic , Finneas says, “is sort of a beautiful word for a logo” accompanied by sound. “The things that I think of as real classic mnemonics are NBC — you can hear that in your head — or HBO has its static.” Finneas is well aware of how modern streaming consumption might make this especially ubiquitous, household by household. “If you’re binge-ing the whole season of Ted Lasso or Severance or Disclaimer ” (the last of those being the limited series that he composed the score for himself), “you’re going to hear the mnemonic 10 times in one day. So it’s gotta be something that’s like the bite of ginger between rolls or something, you know?”

See and hear for yourself. Here’s the old Apple TV mnemonic:

Here’s the new 5-second version, shown at the beginning of each episode of Apple TV original series:

And here’s the full 12-second version, shown before Apple Original Films:

Bravo.

Steinar H. Gunderson: Lichess

PlanetDebian
blog.sesse.net
2025-12-16 18:45:00
I wish more pages on the Internet were like Lichess. It's fast. It feels like it only does one thing (even though it's really more like seven or eight)—well, perhaps except for the weird blogs. It does not feel like it's trying to sell me anything; in fact, it feels like it hardly even wants my mone...
Original Article

I wish more pages on the Internet were like Lichess . It's fast. It feels like it only does one thing (even though it's really more like seven or eight)—well, perhaps except for the weird blogs. It does not feel like it's trying to sell me anything; in fact, it feels like it hardly even wants my money. (I've bought two T-shirts from their Spreadshirt, to support them.) It's super-efficient; I've seen their (public) balance sheets, and it feels like it runs off of a shoestring budget. (Take note, Wikimedia Foundation!) And, perhaps most relieving in this day and age, it does not try to grift any AI.

Yes, I know, chess.com is the juggernaut, and has probably done more for chess' popularity than FIDE ever did. But I still go to Lichess every now and then and just click that 2+1 button. (Generally without even logging in, so that I don't feel angry about it when I lose.) Be more like Lichess.

IP.THC.ORG - Reverse-DNS, Subdomain and CNAME Lookups

Lobsters
ip.thc.org
2025-12-16 18:10:40
Comments...

Show HN: Deterministic PCIe Diagnostics for GPUs on Linux

Hacker News
github.com
2025-12-16 21:03:25
Comments...
Original Article

GPU PCIe Diagnostic & Bandwidth Analysis

A deterministic command-line tool for validating GPU PCIe link health, bandwidth, and real-world PCIe utilization using only observable hardware data.

This tool answers one question reliably:

Is my GPU’s PCIe link behaving as it should, and can I prove it?

No registry hacks. No BIOS assumptions. No “magic” optimizations.

Only measurable link state, copy throughput, and hardware counters.

What This Tool Does

This tool performs hardware-observable PCIe diagnostics and reports factual results with deterministic verdicts.

It measures and reports directly from GPU hardware:

  • PCIe current and maximum link generation and width (via NVML)
  • Peak Host→Device and Device→Host copy bandwidth using CUDA memcpy timing
  • Sustained PCIe utilization under load using NVML TX/RX counters
  • Efficiency relative to theoretical PCIe payload bandwidth
  • Clear VERDICT from observable conditions only

The tool does not attempt to tune, fix, or modify system configuration.

Verdict Semantics

  • OK — The negotiated PCIe link and measured throughput are consistent with expected behavior.
  • DEGRADED — The GPU is operating below its maximum supported PCIe generation or width.
  • UNDERPERFORMING — The full link is negotiated, but sustained bandwidth is significantly lower than expected.

Verdicts are rule-based and derived only from measured data.

Why This Tool Exists

Modern systems frequently exhibit PCIe issues that are difficult to diagnose:

  • GPUs negotiating x8 / x4 / x1 instead of x16
  • PCIe generation downgrades after BIOS or firmware updates
  • Slot bifurcation, riser cable, or motherboard lane-sharing issues
  • Reduced PCIe bandwidth occurring while system status is reported as normal
  • Confusion between PCIe transport limits and workload bottlenecks

This tool exists to:

  1. Reproducible PCIe diagnostic baseline
  2. Hardware-level proof of PCIe behavior
  3. Isolate link negotiation from kernel/workload effects

Example Output

GPU PCIe Diagnostic & Bandwidth Analysis v2.7.4
GPU:   NVIDIA GeForce GTX 1080
BDF:   00000000:01:00.0
UUID:  GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (redacted)

PCIe Link
  Current: Gen3 x16
  Max Cap: Gen3 x16
  Theoretical (payload): 15.76 GB/s
  Transfer Size: 1024 MiB

Peak Copy Bandwidth
  Host → Device: 12.5 GB/s
  Device → Host: 12.7 GB/s

Telemetry (NVML)
  Window:   5.0 s (50 samples @ 100 ms)
  TX avg:   7.6 GB/s
  RX avg:   7.1 GB/s
  Combined: 14.7 GB/s

Verdict
  State:      OK
  Reason:     Throughput and link state are consistent with a healthy PCIe path
  Efficiency: 93.5%

System Signals (informational)
  MaxReadReq: 512 bytes
  Persistence Mode: Disabled
  ASPM Policy (sysfs string): [default] performance powersave powersupersave
  IOMMU: Platform default (no explicit flags)

Requirements

  • NVIDIA GPU with a supported driver
  • CUDA Toolkit (for nvcc )
  • NVML development library ( -lnvidia-ml )

Platform Compatibility Note

  • Linux operating system
  • Tested on Ubuntu 24.04.3 LTS

Permissions & Logging Notes

On some Linux systems, PCIe and NVML diagnostics require elevated privileges due to kernel and driver access controls. If log files were previously created using sudo , the results directory may become root-owned. In that case, subsequent runs may prompt for a password when appending logs.

To restore normal user access to the results directory:

sudo chown -R $USER:$USER results/

Build

make

or manually:

nvcc -O3 pcie_diagnostic_pro.cu -lnvidia-ml -Xcompiler -pthread -o pcie_diag

Usage

./pcie_diag 1024

Logging

./pcie_diag 1024 --log --csv ./pcie_diag 1024 --log --json ./pcie_diag 1024 --log --csv --json

Logs are written to:

  • results/csv/pcie_log.csv
  • results/json/pcie_sessions.json

Extended Telemetry Window

./pcie_diag 1024 --duration-ms 8000

  • improves measurement stability

Optional Integrity Counters

./pcie_diag 1024 --integrity

  • Enables read-only inspection of PCIe Advanced Error Reporting (AER) counters via Linux sysfs, if exposed by the platform.
  • If counters are unavailable on the platform, integrity checks are automatically skipped with clear reporting.

Multi-GPU Logging Behavior

When running in multi-GPU mode ( --all-gpus ), each detected GPU is evaluated independently.

  • One result row (CSV) or object (JSON) is emitted per GPU per run.
  • Each entry includes device UUID and PCIe BDF for unambiguous attribution.
  • Multi-GPU configurations have not been exhaustively validated on all platforms.
  • Users are encouraged to verify results on their specific hardware.

Example:

./pcie_diag 1024 --all-gpus --log --csv
./pcie_diag 1024 --all-gpus --log --json 
./pcie_diag 1024 --gpu-index 1     # Target single GPU by index

Logging & Reproducibility

  • CSV and JSON logs include stable device identifiers
  • Device UUIDs are reported at runtime via NVML for consistent identification across runs
  • UUIDs shown in documentation are intentionally redacted
  • Logs are append-friendly for time-series analysis and automated monitoring

Scope & Limitations

  • This tool evaluates PCIe transport behavior only
  • It does not measure kernel performance or application-level efficiency
  • It does not modify BIOS, firmware, registry, or PCIe configuration
  • It reports observable facts only and never infers beyond available data

Validation

  • Memcpy timing and PCIe behavior were cross-validated during development using Nsight Systems.
  • Nsight is not required to use this tool and is referenced only as an external correctness check.

Author

Author: Joe McLaren (Human–AI collaborative engineering) https://github.com/parallelArchitect

License

MIT License

Copyright (c) 2025 Joe McLaren

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

References

Letta Code

Hacker News
www.letta.com
2025-12-16 20:51:27
Comments...
Original Article

Letta Code is a memory-first coding agent, designed for working with agents that learn over time. When working with coding agents today, interactions happen in independent sessions. Letta Code is built around long-lived agents that persist across sessions and improve with use. Rather than working in independent sessions, each session is tied to a persisted agent that learns. Letta Code is also the #1 model-agnostic OSS harness on TerminalBench, and achieves comparable performance to harnesses built by LLM providers ( Claude Code , Gemini CLI , Codex CLI ) on their own models.

Continual Learning & Memory for Coding Agents

Agents today accumulate valuable experience: they receive the user’s preferences and feedback, review significant parts of code, and observe the outcomes of taking actions like running scripts or commands. Yet today this experience is largely wasted. Letta agents learn from experience through agentic context engineering , long-term memory , and skill learning . The more you work with an agent, the more context and memory it accumulates, and the better it becomes.

Memory Initialization

When you get started with Letta Code, you can run an `/init` command to encourage your agent to learn about your existing project. This will trigger your agent to run deep research on your local codebase, forming memories and rewriting its system prompt (through memory blocks ) as it learns.

Your agent will continue to learn automatically, but you can also explicitly trigger your agent to reflect and learn with the `/remember` command.

Skill Learning

Many tasks that we work on with coding agents are repeated or follow similar patterns - for example API patterns or running DB migrations. Once you’ve worked with an agent to coach it through a complex task, you can trigger it to learn a skill from its experience, so the agent itself or other agents can reference the skill for similar tasks in the future. Skill learning can dramatically improve performance on future similar tasks, as we showed with recent results on TerminalBench.

On our team, some skills that agents have contributed (with the help of human engineers) are:

  • Generating DB migrations on schema changes
  • Creating PostHog dashboards with the PostHog CLI
  • Best practices for API changes

Since skills are simply .md files, they can be managed in git repositories for versioning - or even used by other coding agents that support skills.

Persisted State

Agents can also lookup past conversations (or even conversations of other agents) through the Letta API . The builtin `/search` command allows you to easily search through messages, so you can find the agent you worked on something with. The Letta API supports vector, full-text, and hybrid search over messages and available tools .

Letta Code is the #1 model-agnostic OSS coding harness

Letta Code adds statefulness and learning to coding agents, but is the #1 model-agnostic, OSS harness on Terminal-Bench . Letta Code’s performance is comparable to provider-specific harnesses (Gemini CLI, Claude Code, Codex) across model providers, and significantly outperforms the previous leading model-agnostic harness, Terminus 2.

This means that even without memory, you can expect Letta Code agents to work just as well with a frontier model as they would with a specific harness built by the model provider.

Getting Started with Letta Code

To try out Letta Code, you can install it with npm install -g @letta-ai/letta-code or install from source (see the full documentation ).

Letta Code can be used with the Letta Developer Platform , or with a self-hosted Letta server.

No Graphics API

Hacker News
www.sebastianaaltonen.com
2025-12-16 19:20:17
Comments...
Original Article

Introduction

My name is Sebastian Aaltonen. I have been writing graphics code for 30 years. I shipped my first 3d accelerated game in 1999. Since then I have been working with almost every gaming console generation (Nokia N-Gage, Nintendo DS/Switch, Sony Playstation/Portable, Microsoft Xbox) and every PC graphics API (DirectX, OpenGL, Vulkan). For the last 4 years I have been building a new renderer for HypeHype targeting WebGPU, Metal (Mac & iOS) and Vulkan (Android). During my career I have been building several Ubisoft internal engines, optimizing Unreal Engine 4 and leading the Unity DOTS graphics team. I am a member of the Vulkan Advisory Panel and an Arm Ambassador.

This blog post includes lots of low level hardware details. When writing this post I used “GPT5 Thinking” AI model to cross reference public Linux open source drivers to confirm my knowledge and to ensure no NDA information is present in this blog post. Sources: AMD RDNA ISA documents and GPUOpen, Nvidia PTX ISA documents, Intel PRM, Linux open source GPU drivers (Mesa, Freedreno, Turnip, Asahi) and vendor optimization guides/presentations. The blog post has been screened by several industry insiders before the public release.

Low-level graphics APIs change the industry

Ten years ago, a significant shift occurred in real-time computer graphics with the introduction of new low-level PC graphics APIs. AMD had won both Xbox One (2013) and Playstation 4 (2013) contracts. Their new Graphics Core Next (GCN) architecture became the de-facto lead development platform for AAA games. PC graphics APIs at that time, DirectX 11 and OpenGL 4.5, had heavy driver overhead and were designed for single threaded rendering. AAA developers demanded higher performance APIs for PC. DICE joined with AMD to create a low level AMD GCN specific API for the PC called Mantle. As a response, Microsoft, Khronos and Apple started developing their own low-level APIs: DirectX 12, Vulkan and Metal were born.

The initial reception of these new low-level APIs was mixed. Synthetic benchmarks and demos showed substantial performance increases, but performance gains couldn’t be seen in major game engines such as Unreal and Unity. At Ubisoft, our teams noticed that porting existing DirectX 11 renderers to DirectX 12 often resulted in performance regression. Something wasn’t right.

Existing high-level APIs featured minimal persistent state, with fine-grained state setters and individual data inputs bound to the shader just prior to draw call submission. New low-level APIs aimed to make draw calls cheaper by ahead-of-time bundling shader pipeline state and bindings into persistent objects. GPU architectures were highly heterogeneous back in the day. Doing the data remapping, validation, and uploading ahead of time was a big gain. However, the rendering hardware interfaces (RHI) of existing game engines were designed for fine grained immediate mode rendering, while the new low-level APIs required bundling data in persistent objects.

To address this incompatibility, a new low-level graphics remapping layer grew beneath the RHI. This layer assumed the complexity previously handled by the OpenGL and DirectX 11 graphics drivers, tracking resources and managing mappings between the fine-grained dynamic user-land state and the persistent low-level GPU state. Graphics programmers started specializing into two distinct roles: low-level graphics programmers, who focused on the new low-level “driver” layer and the RHI, and high-level graphics programmers, who built visual graphics algorithms on top of the RHI. Visual programming was also getting more complex due to physically based lighting models, compute shaders and later ray-tracing.

Modern APIs?

DirectX 12, Vulkan, and Metal are often referred to as “modern APIs”. These APIs are now 10 years old. They were initially designed to support GPUs that are now 13 years old, an incredibly long time in GPU history. Older GPU architectures were optimized for traditional vertex and pixel shader tasks rather than the compute-intensive generic workloads prevalent today. They had vendor specific binding models and data paths. Hardware differences had to be wrapped under the same API. Ahead-of-time created persistent objects were crucial in offloading the mapping, uploading, validation and binding costs.

In contrast, the console APIs and Mantle were exclusively designed for AMD's GCN architecture, a forward-thinking design for its time. GCN boasted a comprehensive read/write cache hierarchy and scalar registers capable of storing texture and buffer descriptors, effectively treating everything as memory. No complex API for remapping the data was required, and significantly less ahead-of-time work was needed. The console APIs and Mantle had much less API complexity due to targeting a single modern GPU architecture.

A decade has passed, and GPUs have undergone a significant evolution. All modern GPU architectures now feature complete cache hierarchies with coherent last-level caches. CPUs can write directly to GPU memory using PCIe REBAR or UMA and 64-bit GPU pointers are directly supported in shaders. Texture samplers are bindless, eliminating the need for a CPU driver to configure the descriptor bindings. Texture descriptors can be directly stored in arrays within the GPU memory (often called descriptor heaps). If we were to design an API tailored for modern GPUs today, it wouldn’t need most of these persistent “retained mode” objects. The compromises that DirectX 12.0, Metal 1 and Vulkan 1.0 had to make are not needed anymore. We could simplify the API drastically.

The past decade has revealed the weaknesses of the modern APIs. The PSO permutation explosion is the biggest problem we need to solve. Vendors (Valve, Nvidia, etc) have massive cloud servers storing terabytes of PSOs for each different architecture/driver combination. User's local PSO cache size can exceed 100GB. No wonder the gamers are complaining that loading takes ages and stutter is all over the place.

The history of GPUs and APIs

Before we talk about stripping the API surface, we need to understand why graphics APIs were historically designed this way. OpenGL wasn't intentionally slow, nor was Vulkan intentionally complex. 10-20 years ago GPU hardware was highly diverse and undergoing rapid evolution. Designing a cross-platform API for such a diverse set of hardware required compromises.

Let’s start with a classic: The 3dFX Voodoo 2 12MB (1998) was a three chip design: A single rasterizer chip connected to a 4MB framebuffer memory and two texture sampling chips, each connected to their own 4MB texture memory. There was no geometry pipeline and no programmable shaders. CPU sent pre-transformed triangle vertices to the rasterizer. The rasterizer had a configurable blending equation to control how the vertex colors and the two texture sampler results were combined together. Texture samplers could not read each-other’s memory or the framebuffer. Thus there was no support for multiple render passes. Since the hardware was incapable of window composition, it had a loopback cable to connect your dedicated 2d video card. 3d rendering only worked in exclusive fullscreen mode. A 3d graphics card was a highly specialized piece of hardware, with little in common with the current GPUs and their massive programmable SIMD arrays. Hardware of this era had a massive impact on DirectX (1995) and OpenGL (1992) design. Backwards compatibility played a huge role. APIs improved iteratively. These 30 year old API designs still impact the way we write software today.

3dFX Voodoo 2 12MB (1998): Individual processors and traces between them and their own memory chips (four 1MB chips for each processor) are clearly visible. Image © TechPowerUp.

Nvidia’s Geforce 256 coined the term GPU. It had a geometry processor in addition to the rasterizer. The geometry processor, rasterizer and texture sampling units were all integrated in the same die and shared memory. DirectX 7 introduced two new concepts: render target textures and uniform constants. Multipass rendering meant that texture samplers could read the rasterizer output, invalidating the 3dFX Voodoo 2 separate memory design.

The geometry processor API featured uniform data inputs for transform matrices (float4x4), light positions, and colors (float4). GPU implementations varied among manufacturers, many opting to embed a small constant memory block within the geometry engine. But this wasn’t the only way to do it. In the OpenGL API each shader had its own persistent uniforms. This design enabled the driver to embed constants directly in the shader's instruction stream, an API peculiarity that still persists in OpenGL 4.6 and ES 3.2 today.

GPUs back then didn’t have generic read & write caches. Rasterizer had screen local cache for blending and depth buffering and texture samplers leaned on linearly interpolated vertex UVs for data prefetching. When shaders were introduced in DirectX 8 shader model 1.0 (SM 1.0), the pixel shader stage didn’t support calculating texture UVs. UVs were calculated at vertex granularity, interpolated by the hardware and passed directly to the texture samplers.

DirectX 9 brought a substantial increase in shader instruction limits, but shader model 2.0 didn’t expose any new data paths. Both vertex and pixel shaders still operated as 1:1 input:output machines, allowing users to only customize the transform math of the vertex position and attributes and the pixel color. Programmable load and store were not supported. The fixed-function input blocks persisted: vertex fetch, uniform (constant) memory and texture sampler. Vertex shader was a separate execution unit. It gained new features like the ability to index constants (limited to float4 arrays) but still lacked texture sampling support.

DirectX 9 shader model 3.0 increased the instruction limit to 65536 making it difficult for humans to write and maintain shader assembly anymore. Higher level shading languages were born: HLSL (2002) and GLSL (2002-2004). These languages adapted the 1:1 elementwise transform design. Each shader invocation operated on a single data element: vertex or pixel. Framework-style shader design heavily affected the graphics API design in the following years. It was a nice way to abstract hardware differences back in the day, but is showing scaling pains today.

DirectX 11 was a significant shift in the data model, introducing support for compute shaders, generic read-write buffers and indirect drawing. The GPU could now fully feed itself. The inclusion of generic buffers enabled shader programs to access and modify programmable memory locations, which forced hardware vendors to implement generic cache hierarchies. Shaders evolved beyond simple 1:1 data transformations, marking the end of specialized, hardcoded data paths. GPU hardware started to shift towards a generic SIMD design. SIMD units were now executing all the different shader types: vertex, pixel, geometry, hull, domain and compute. Today the framework has 16 different shader entry points. This adds a lot of API surface and makes composition difficult. As a result GLSL and HLSL still don’t have a flourishing library ecosystem.

DirectX 11 featured a whole zoo of buffer types, each designed to accommodate specific hardware data path peculiarities: typed SRV & UAV, byte address SRV & UAV, structured SRV & UAV, append & consume (with counter), constant, vertex, and index buffers. Like textures, buffers in DirectX utilize an opaque descriptor. Descriptors are hardware specific (commonly 128-256 bit) data blobs encoding the size, format, properties and data address of the resource in GPU memory. DirectX 11 GPUs leveraged their texture samplers for buffer load (gather) operations. This was natural since the sampler already had a type conversion hardware and a small read-only data cache. Typed buffers supported the same formats as textures, and DirectX used the same SRV (shader resource view) abstraction for both textures and buffers.

The use of opaque buffer descriptors meant that the buffer format was not known at shader compile time. This was fine for read-only buffers as they were handled by the texture sampler. Read-write buffer (UAV in DirectX) was initially limited to 32-bit and 128-bit (vec4) types. Subsequent API and hardware revisions gradually addressed typed UAV load limitations, but the core issues persisted: a descriptor requires an indirection (contains a pointer), compiler optimizations are limited (data type is known only at runtime), format conversion hardware introduces latency (vs raw L1$ load), expand at load reserves registers for longer time (vs expand at use), descriptor management adds CPU driver complexity, and the API is complex (ten different buffer types).

In DirectX 11 the structured buffers were the only buffer type allowing an user defined struct type. All other buffer types represented a homogeneous array of simple scalar/vector elements. Unfortunately, structured buffers were not layout compatible with other buffer types. Users were not allowed to have structured buffer views to typed buffers, byte address buffers, or vertex/index buffers. The reason was that structured buffers had special AoSoA swizzle optimization under the hood, which was important for older vec4 architectures. This hardware specific optimization limited the structured buffer usability.

DirectX 12 made all buffers linear in memory, making them compatible with each other. SM 6.2 also added load<T> syntactic sugar for the byte address buffer, allowing clean struct loading syntax from arbitrary offset. All the old buffer types are still supported for backwards compatibility reasons and all the buffers still use opaque descriptors. HLSL is still missing support for 64-bit GPU pointers. In contrast, the Nvidia CUDA computing platform (2007) fully leaned on 64-bit pointers, but its popularity was initially limited to academic use. Today it is the leading AI platform and is heavily affecting the hardware design.

Support for 16-bit registers and 16-bit math was disorganized when DirectX 12 launched. Microsoft initially made a questionable decision to not backport DirectX 12 to Windows 7. Shader binaries targeting Windows 8 supported 16-bit types, but most gamers continued using Windows 7. Developers didn’t want to ship two sets of shaders. OpenGL lowp/mediump specification was also messy. Bit depths were not properly standardized. Mediump was a popular optimization in mobile games, but most PC drivers ignored it, making game developer’s life miserable. AAA games mostly ignored 16-bit math until PS4 Pro launched in 2016 with double rate fp16 support.

With the rise of AI, ray-tracing, and GPU-driven rendering, GPU vendors started focusing on optimizing their raw data load paths and providing larger and faster generic caches. Routing loads though the texture sampler (type conversion) added too much latency, as dependent load chains are common in modern shaders. Hardware got native support for narrow 8-bit, 16-bit, and 64-bit types and pointers.

Most vendors ditched their fixed function vertex fetch hardware, emitting standard raw load instructions in the vertex shader instead. Fully programmable vertex fetch allowed developers to write new algorithms such as clustered GPU-driven rendering. Fixed function hardware transistor budget could be used elsewhere.

Mesh shaders represent the culmination of rasterizer evolution, eliminating the need for index deduplication hardware and post-transform caches. In this paradigm, all inputs are treated as raw memory. The user is responsible for dividing the mesh into self-contained meshlets that internally share vertices. This process is often done offline. The GPU no longer needs to do parallel index deduplication for each draw call, saving power and transistors. Given that gaming accounts for only 10% of Nvidia's revenue today, while AI represents 90% and ray-tracing continues to grow, it is likely only a matter of time before the fixed function geometry hardware is stripped to bare minimum and drivers automatically convert vertex shaders to mesh shaders.

Mobile GPUs are tile-based renderers. Tilers bin the individual triangles to small tiles (commonly between 16x16 to 64x64 pixels) . Mesh shaders are too coarse grained for this purpose. Binning meshlets to tiny tiles would cause significant geometry overshading. There’s no clear convergence path. We still need to support the vertex shader path.

10 years ago when DirectX 12.0, Vulkan 1.0 and Metal 1.0 arrived, the existing GPU hardware didn’t widely support bindless resources. APIs adapted complex binding models to abstract the hardware differences. DirectX allowed indexing up to 128 resources per stage, Vulkan and Metal didn’t initially support descriptor indexing at all. Developers had to continue using traditional workarounds to reduce the bindings change overhead, such as packing textures into atlases and merging meshes together. The GPU hardware has evolved significantly during the past decade and converged to generic bindless SIMD design.

Let’s investigate how much simpler the graphics API and the shader language would become if we designed them solely for modern bindless hardware.

Modern GPU memory management

Let’s start our journey discussing memory management. Legacy graphics APIs abstracted the GPU memory management completely. Abstraction was necessary, as old GPUs had split memories and/or special data paths with various cache coherency concerns. When DirectX 12 and Vulkan arrived 10 years ago, the GPU hardware had matured enough to expose placement heaps to the user. Consoles had already exposed memory for a few generations and developers requested similar flexibility for PC and mobile. Apple introduced placement heaps 4 years after Vulkan and DirectX 12 in Metal 2.

Modern APIs require the user to enumerate the heap types to find out what kind of memory the GPU driver has to offer. It’s a good practice to preallocate memory in big chunks and suballocate it using a user-land allocator. However, there’s a design flaw in Vulkan: You have to create your texture/buffer object first. Then you can ask which heap types are compatible with the new resource. This forces the user into a lazy allocation pattern, which can cause performance hitches and memory spikes at runtime. This also makes it difficult to wrap a GPU memory allocation into a cross-platform library. AMD VMA, for example, creates both the Vulkan-specific buffer/texture object in addition to allocating memory. We want to fully separate these concerns.

Today the CPU has full visibility into the GPU memory. Integrated GPUs have UMA, and modern discrete GPUs have PCIe Resizable BAR. The whole GPU heap can be mapped. Vulkan heap API naturally supports CPU mapped GPU heaps. DirectX 12 got support in 2023 (HEAP_TYPE_GPU_UPLOAD).

CUDA has a simple design for GPU memory allocation: The GPU malloc API takes the size as input and returns a mapped CPU pointer. The GPU free API frees the memory. CUDA doesn’t support CPU mapped GPU memory. The GPU reads the CPU memory though the PCIe bus. CUDA also supports GPU memory allocations, but they can’t be directly written by the CPU.

We combine CUDA malloc design with CPU mapped GPU memory (UMA/ReBAR). It's the best of both worlds: The data is fast for the CPU to write and fast for the GPU to read, yet we maintain the clean, easy to use design.

// Allocate GPU memory for array of 1024 uint32
uint32* numbers = gpuMalloc(1024 * sizeof(uint32));

// Directly initialize (CPU mapped GPU pointer)
for (int i = 0; i < 1024; i++) numbers[i] = random();

gpuFree(numbers);

Default gpuMalloc alignment is 16 bytes (vec4 alignment). If you need wider alignment use gpuMalloc(size, alignment) overload. My example code uses gpuMalloc<T> wrapper, doing gpuMalloc(elements * sizeof(T), alignof(T)).

Writing data directly into GPU memory is optimal for small data like draw arguments, uniforms and descriptors. For large persistent data, we still want to perform a copy operation. GPUs store textures in a swizzled layout similar to Morton-order to improve cache locality. DirectX 11.3 and 12 tried to standardize the swizzle layout, but couldn’t get all GPU manufacturers onboard. The common way to perform texture swizzling is to use a driver provided copy command. The copy command reads linear texture data from a CPU mapped “upload” heap and writes to a swizzled layout in a private GPU heap. Every modern GPU also has lossless delta color compression (DCC). Modern GPUs copy engines are capable of DCC compression and decompression. DCC and Morton swizzle are the main reasons we want to copy textures into a private GPU heap. Recently, GPUs have also added generic lossless memory compression for buffer data. If the memory heap is CPU mapped, the GPU can’t enable vendor specific lossless compression, as the CPU wouldn’t know how to read or write it. A copy command must be used to compress the data.

We need a memory type parameter in the GPU malloc function to add support for private GPU memory. The standard memory type should be CPU mapped GPU memory (write combined CPU access). It is fast for the GPU to read, and the CPU can directly write to it just like it was a CPU memory pointer. GPU-only memory is used for textures and big GPU-only buffers. The CPU can’t directly write to these GPU pointers. The user writes the data to CPU mapped GPU memory first and then issues a copy command, which transforms the data to optimal compressed format. Modern texture samplers and display engines can read compressed GPU data directly, so there’s no need for subsequent data layout transforms (see chapter: Modern barriers). The uploaded data is ready to use immediately.

We have two types of GPU pointers, a CPU mapped virtual address and a GPU virtual address. The GPU can only dereference GPU addresses. All pointers in GPU data structures must use GPU addresses. CPU mapped addresses are only used for CPU writes. CUDA has an API to transform a CPU mapped address to a GPU address (cudaHostGetDevicePointer). Metal 4 buffer object has two getters: .contents (CPU mapped address) and .gpuAddress (GPU address). Since the gpuMalloc API returns a pointer, not a managed object handle (like Metal), we choose the CUDA approach (gpuHostToDevicePointer). This API call is not free. The driver likely implements it using a hash map (if other than base addresses need to be translated, we need a tree). Preferably we call the address translation once per allocation and cache in a user land struct (void *cpu, void *gpu). This is the approach my userland GPUBumpAllocator uses (see appendix for full implementation).

// Load a mesh using a 3rd party library
auto mesh = createMesh("mesh.obj");
auto upload = uploadBumpAllocator.allocate(mesh.byteSize); // Custom bump allocator (wraps a gpuMalloc ptr)
mesh.load(upload.cpu);

// Allocate GPU-only memory and copy into it
void* meshGpu = gpuMalloc(mesh.byteSize, MEMORY_GPU);
gpuMemCpy(commandBuffer, meshGpu, upload.gpu);

Vulkan recently got a new extension called VK_EXT_host_image_copy. The driver implements a direct CPU to GPU image copy operation, performing the hardware specific texture swizzle on CPU. This extension is currently only available on UMA architectures, but there’s no technical reason why it’s not available on PCIe ReBAR as well. Unfortunately this API doesn’t support DCC. It would be too expensive to perform DCC compression on the CPU. The extension is mainly useful for block compressed textures, as they don’t require DCC. It can’t universally replace hardware copy to GPU private memory.

There’s also a need for a third memory type, CPU-cached, for readback purposes. This memory type is slower for the GPU to write due to cache coherency with the CPU. Games only use readback seldomly. Common use cases are screenshots and virtual texturing readback. GPGPU algorithms such as AI training and inference lean on efficient communication between the CPU and the GPU.

When we mix the simplicity of CUDA malloc with CPU-mapped GPU memory we get a flexible and fast GPU memory allocation system with minimal API surface. This is an excellent starting point for a minimalistic modern graphics API.

Modern data

CUDA, Metal and OpenCL leverage C/C++ shader languages featuring 64-bit pointer semantics. These languages support loading and storing of structs from/to any appropriately aligned GPU memory location. The compiler handles behind-the-scenes optimizations, including wide loads (combine), register mappings, and bit extractions. Many modern GPUs offer free instruction modifiers for extracting 8/16-bit portions of a register, allowing the compiler to pack 8-bit and 16-bit values into a single register. This keeps the shader code clean and efficient.

If you load a struct of eight 32-bit values, the compiler will most likely emit two 128-bit wide loads (each filling 4 registers), a 4x reduction in load instruction count. Wide loads are significantly faster, especially if the struct contains narrow 8 and 16-bit fields. GPUs are ALU dense and have big register files, but compared to CPUs their memory paths are relatively slow. A CPU often has two load ports each doing a load per cycle. On a modern GPU we can achieve one SIMD load per 4 cycles. Wide load + unpack in the shader is often the most efficient way to handle data.

Compact 8-16 bit data has been traditionally stored in texel buffers (Buffer<T>) in DirectX games. Modern GPUs are optimized for compute workloads. Raw buffer load instructions nowadays have up to 2x higher throughput and up to 3x lower latency than texel buffers. Texel buffers are no longer the optimal choice on modern GPUs. Texel buffers do not support structured data, the user is forced to split their data into SoA layout in multiple texel buffers. Each texel buffer has its own descriptor, which must be loaded before the data can be accessed. This consumes resources (SGPRs, descriptor cache slots) and adds startup latency compared to using a single 64-bit raw pointer. SoA data layout also results in significantly more cache misses for non-linear index lookups (examples: material, texture, triangle, instance, bone id). Texel buffers offer free conversion of normalized ([0,1] and [-1,1]) types to floating point registers. It’s true that there’s no ALU cost, but you lose wide load support (combine loads) and the instruction goes through the slow texture sampler hardware path. Narrow texel buffer loads also add register bloat. RGBA8_UNORM load to vec4 allocates four vector registers immediately. The sampler hardware will eventually write the value to these registers. Compilers try to maximize the distance of load→use by moving load instructions in the beginning of the shader. This hides the load latency by ALU and allows overlapping multiple loads. If we instead use wide raw loads, our uint8x4 data consumes just a single 32-bit register. We unpack the 8-bit channels on use. The register life time is much shorter. Modern GPUs can directly access 16-bit low/high halves of registers without unpack, and some can even do 8-bit (AMD SDWA modifier). Packed double rate math makes 2x16 bit conversion instructions faster. Some GPU architectures (Nvidia, AMD) can also do 64-bit pointer raw loads directly from VRAM into groupshared memory, further reducing the register bloat needed for latency hiding. By using 64-bit pointers, game engines benefit from AI hardware optimizations.

Pointer based systems make memory alignment explicit. When you are allocating a buffer object in DirectX or Vulkan, you need to query the API for alignment. Buffer bind offsets must also be properly aligned. Vulkan has an API for querying the bind offset alignment and DirectX has fixed alignment rules. Alignment contract allows the low level shader compiler to emit optimal code (such as aligned 4x32-byte wide loads). The DirectX ByteAddressBuffer abstraction has a design flaw: load2, load3 and load4 instructions only require 4-byte alignment. The new SM 6.2 load<T> also only requires elementwise alignment (half4 = 2, float4 = 4). Some GPU vendors (like Nvidia) have to split ByteAddressBuffer.load4 into four individual load instructions. The buffer abstraction can’t always shield the user from bad codegen. It makes bad codegen hard to fix. C/C++ based languages (CUDA, Metal) allow the user to explicitly declare struct alignment with the alignas attribute. We use alignas(16) in all our example code root structs.

By default, GPU writes are only visible to the threads inside the same thread group (= inside a compute unit). This allows non-coherent L1$ design. Visibility is commonly provided by barriers. If the user needs memory visibility between the groups in a single dispatch, they decorate the buffer binding with the [globallycoherent] attribute. The shader compiler emits coherent load/store instructions for accesses of that buffer. Since we use 64-bit pointers instead of buffer objects, we offer explicit coherent load/store instructions. The syntax is similar to atomic load/store. Similarly we can provide non-temporal load/store instructions that bypass the whole cache hierarchy.

Vulkan supports 64-bit pointers using the (2019) VK_KHR_buffer_device_address extension ( https://docs.vulkan.org/samples/latest/samples/extensions/buffer_device_address/README.html ). Buffer device address extension is widely supported by all GPU vendors (including mobile), but is not a part of core Vulkan 1.4. The main issue with BDA is lack of pointer support in the GLSL and the HLSL shader languages. The user has to use raw 64-bit integers instead. A 64-bit integer can be cast to a struct. Structs are defined with custom BDA syntax. Array indexing requires declaring an extra BDA struct type with an array in it, if the user wants the compiler to generate the index addressing math. Debugging support is currently limited. Usability matters a lot and BDA will remain a niche until HLSL and GLSL support pointers natively. This is a stark contrast to CUDA, OpenCL and Metal, where native pointer support is a language core pillar and debugging works flawlessly.

DirectX 12 has no support for pointers in shaders. As a consequence, HLSL doesn’t allow passing arrays as function parameters. Simple things like having a material array inside UBO/SSBO requires hacking around with macros. It’s impossible to make reusable functions for reductions (prefix sum, sort, etc), since groupshared memory arrays can’t be passed between functions. You could of course declare a separate global array for each utility header/library, but the compiler will allocate groupshared memory for each of them separately, reducing occupancy. There’s no easy way to alias groupshared memory. GLSL has identical issues. Pointer based languages like CUDA and Metal MSL don’t have such issues with arrays. CUDA has a vast ecosystem of 3rd party libraries, and this ecosystem makes Nvidia the most valued company on the planet. Graphics shading languages need to evolve to meet modern standards. We need a library ecosystem too.

I will be using a C/C++ style shading language similar to CUDA and Metal MSL in my examples, with some HLSL-style system value (SV) semantics mixed in for the graphics specific bits and pieces.

Root arguments

Operating system threading APIs commonly provide a single 64-bit void pointer to the thread function. The operating system doesn’t care about the user’s data input layout. Let’s apply the same ideology to the GPU kernel data inputs. The shader kernel receives a single 64-bit pointer, which we cast to our desired struct (by the kernel function signature). Developers can use the same shared C/C++ header in both CPU and GPU side.

// Common header...
struct alignas(16) Data
{
    // Uniform data
    float16x4 color; // 16-bit float vector
    uint16x2 offset; // 16-bit integer vector
    const uint8* lut; // pointer to 8-bit data array

    // Pointers to in/out data arrays
    const uint32* input;
    uint32* output;
};

// CPU code...
gpuSetPipeline(commandBuffer, computePipeline);

auto data = myBumpAllocator.allocate<Data>(); // Custom bump allocator (wraps gpuMalloc ptr, see appendix)
data.cpu->color = {1.0f, 0.0f, 0.0f, 1.0f};
data.cpu->offset = {16, 0};
data.cpu->lut = luts.gpu + 64; // GPU pointers support pointer math (no need for offset API)
data.cpu->input = input.gpu;
data.cpu->output = output.gpu;

gpuDispatch(commandBuffer, data.gpu, uvec3(128, 1, 1));

// GPU kernel...
[groupsize = (64, 1, 1)]
void main(uint32x3 threadId : SV_ThreadID, const Data* data)
{
    uint32 value = data->input[threadId.x]; 
    // TODO: Code using color, offset, lut, etc...
    data->output[threadId.x] = value;
}

In the example code we use a simple linear bump allocator (myBumpAllocator) for allocating GPU arguments (see appendix for implementation). It returns a struct {void* cpu, void *gpu}. The CPU pointer is used for writing directly to persistently mapped GPU memory and the GPU pointer can be stored to GPU data structures or passed as dispatch command argument.

Most GPUs preload root uniforms (including 64-bit pointers) into constant or scalar registers just before launching a wave. This optimization remains viable: the draw/dispatch command carries the base data pointer. All the input uniforms (including pointers to other data) are found at small fixed offsets from the base pointer. Since shaders are pre-compiled and further optimized into device-specific microcode during the PSO creation, drivers have ample opportunity to set up register preloading and similar root data optimizations. Users should put the most important data in the beginning of the root struct as root data size is limited in some architectures. Our root struct has no hard size limit. The shader compiler will emit standard (scalar/uniform) memory loads for the remaining fields. The root data pointer provided to the shader is const. Shader can’t modify the root input data, as it might be still used by the command processor for preloading data to new waves. Output is done through non-const pointers (see Data::output in above example). By forcing the root data to be const, we also allow GPU drivers to perform their special uniform data path optimizations.

Do we need a special uniform buffer type? Modern shader compilers perform automatic uniformity analysis. If all inputs to an instruction are uniform, the output is also uniform. Uniformity propagates over the shader. All modern architectures have scalar registers/loads or a similar construct (SIMD1 on Intel). Uniformity analysis is used to convert vector loads into scalar loads, which saves registers and reduces latency. Uniformity analysis doesn’t care about the buffer type (UBO vs SSBO). The resource must be readonly (this is why you should always decorate SSBO with readonly attribute in GLSL or prefer SRV over UAV in DirectX 12). The compiler also needs to be able to prove that the pointer is not aliased. The C/C++ const keyword means that data can’t be modified though this pointer, it doesn’t guarantee that other read-write pointers might alias the same memory region. C99 added the restrict keyword for this purpose and CUDA kernels use it frequently. Root pointers in Metal are no-alias (restrict) by default, and so are buffer objects in Vulkan and DirectX 12. We should adopt the same convention to give the compiler more freedom to do optimizations.

The shader compiler is not always able to prove address uniformity at compile time. Modern GPUs opportunistically optimize dynamic uniform address loads. If the memory controller detects that all lanes of a vector load instruction have a uniform address, it emits a single lane load instead of a SIMD wide gather. The result is replicated to all lanes. This optimization is transparent, and doesn’t affect shader code generation or register allocation. Dynamically uniform data is a much smaller performance hit than it used to be in the past, especially when combined with the new fast raw load paths.

Some GPU vendors (ARM Mali and Qualcomm Adreno) take the uniformity analysis a step further. The shader compiler extracts uniform loads and uniform math. A scalar preamble runs before the shader. Uniform memory loads and math is executed once for the whole draw/dispatch and the results are stored in special hardware constant registers (the same registers used by root constants).

All of the above optimizations together provide a better way of handling uniform data than the classic 16KB/64KB uniform/constant buffer abstraction. Many GPUs still have special uniform registers for root constants, system values and the preamble (see above paragraph).

Texture bindings

Ideally, texture descriptors would behave like any other data in GPU memory, allowing them to be freely mixed in structs with other data. However, this level of flexibility isn't universally supported by all modern GPUs. Fortunately bindless texture sampler designs have converged over the last decade, with only two primary methods remaining: 256-bit raw descriptors and the indexed descriptor heap.

AMDs raw descriptor method loads 256-bit descriptors directly from GPU memory into the compute unit’s scalar registers. Eight subsequent 32-bit scalar registers contain a single descriptor. During the SIMD texture sample instruction, the shader core sends a 256-bit texture descriptor and per-lane UVs to the sampler unit. This provides the sampler all the data it needs to address and load texels without any indirections. The drawback is that the 256-bit descriptor takes a lot of register space and needs to be resent to the sampler for each sample instruction.

The indexed descriptor heap approach uses 32-bit indices (20 bits for old Intel iGPUs). 32-bit indices are trivial to store in structs, load into standard SIMD registers and efficient to pass around. During a SIMD sample instruction, the shader core sends the texture index and the per-lane UVs to the sampler unit. The sampler fetches the descriptor from the descriptor heap: heap base address + texture index * stride (256-bits in modern GPUs). The texture heap base address is either abstracted by the driver (Vulkan and Metal) or provided by the user (SetDescriptorHeaps in DirectX 12). Changing the texture heap base address may result in an internal pipeline barrier (on older hardware). On modern GPUs the texture heap 64-bit base address is often part of each sample instruction data, allowing sampling from multiple heaps seamlessly (64-bit base + 32-bit offset per lane). The sampler unit has a tiny internal descriptor cache to avoid indirect reads after the first access. Descriptor caches must be invalidated whenever the descriptor heap is modified.

A few years ago it looked like AMDs scalar register based texture descriptors were the winning formula in the long run. Scalar registers are more flexible than a descriptor heap, allowing descriptors to be embedded inside GPU data structures directly. But there’s a downside. Modern GPU workloads such as ray-tracing and deferred texturing (Nanite) lean on non-uniform texture indices. The texture heap index is not uniform over a SIMD wave. A 32-bit heap index is just 4 bytes, we can send it per lane. In contrast, a 256-bit descriptor is 32 bytes. It is not feasible to fetch and send a full 256-bit descriptor per lane. Modern Nvidia, Apple and Qualcomm GPUs support per-lane descriptor index mode in their sample instructions, making the non-uniform case more efficient. The sampler unit performs an internal loop if required. Inputs/outputs to/from sampler units are sent once, regardless of the heap index coherence. AMDs scalar register based descriptor architecture requires the shader compiler to generate a scalarization loop around the texture sample instruction. This costs extra ALU cycles and requires sending and receiving (partially masked) sampler data multiple times. It’s one of the reasons why Nvidia is faster in ray-tracing than AMD. ARM and Intel use 32-bit heap indices too (like Nvidia, Qualcomm and Apple), but their latest architectures don’t yet have a per-lane heap index mode. They emit a similar scalarization loop as AMD for the non-uniform index case.

All of these differences can be wrapped under an unified texture descriptor heap abstraction. The de-facto texture descriptor size is 256 bits (192 bits on Apple for a separate texture descriptor, sampler is the remaining 32 bits). The texture heap can be presented as a homogeneous array of 256-bit descriptor blobs. Indexing is trivial. DirectX 12 shader model 6.6 provides a texture heap abstraction like this, but doesn’t allow direct CPU or compute shader write access to the descriptor heap memory. A set of APIs are used for creating descriptors and copying descriptors from the CPU to the GPU. The GPU is not allowed to write the descriptors. Today, we can remove this API abstraction completely by allowing direct CPU and GPU write to the descriptor heap. All we need is a simple (user-land) driver helper function for creating a 256-bit (uint64[4]) hardware specific descriptor blob. Modern GPUs have UMA or PCIe ReBAR. The CPU can directly write descriptor blobs into GPU memory. Users can also use compute shaders to copy or generate descriptors. The shader language has a descriptor creation intrinsic too. It returns a hardware specific uint64x4 descriptor blob (analogous to the CPU API). This approach cuts the API complexity drastically and is both faster and more flexible than the DirectX 12 descriptor update model. Vulkan’s VK_EXT_descriptor_buffer ( https://www.khronos.org/blog/vk-ext-descriptor-buffer ) extension (2022) is similar to my proposal, allowing direct CPU and GPU write. It is supported by most vendors, but unfortunately is not part of the Vulkan 1.4 core spec.

// App startup: Allocate a texture descriptor heap (for example 65536 descriptors)
GpuTextureDescriptor *textureHeap = gpuMalloc<GpuTextureDescriptor>(65536);

// Load an image using a 3rd party library
auto pngImage = pngLoad("cat.png");
auto uploadMemory = uploadBumpAllocator.allocate(pngImage.byteSize); // Custom bump allocator (wraps gpuMalloc ptr)
pngImage.load(uploadMemory.cpu);

// Allocate GPU memory for our texture (optimal layout with metadata)
GpuTextureDesc textureDesc { .dimensions = pngImage.dimensions, .format = FORMAT_RGBA8_UNORM, .usage = SAMPLED };
GpuTextureSizeAlign textureSizeAlign = gpuTextureSizeAlign(textureDesc);
void *texturePtr = gpuMalloc(textureSizeAlign.size, textureSizeAlign.align, MEMORY_GPU);
GpuTexture texture = gpuCreateTexture(textureDesc, texturePtr);

// Create a 256-bit texture view descriptor and store it
textureHeap[0] = gpuTextureViewDescriptor(texture, { .format = FORMAT_RGBA8_UNORM });

// Batched upload: begin
GpuCommandBuffer uploadCommandBuffer = gpuStartCommandRecording(queue);

// Copy all textures here!
gpuCopyToTexture(uploadCommandBuffer, texturePtr, uploadMemory.gpu, texture);
// TODO other textures...

// Batched upload: end
gpuBarrier(uploadCommandBuffer, STAGE_TRANSFER, STAGE_ALL, HAZARD_DESCRIPTORS);
gpuSubmit(queue, { uploadCommandBuffer });

// Later during rendering...
gpuSetActiveTextureHeapPtr(commandBuffer, gpuHostToDevicePointer(textureHeap));

It is almost possible to get rid of the CPU side texture object (GpuTexture) completely. Unfortunately the triangle rasterizer units of all modern GPUs are not yet bindless. The CPU driver needs to prepare command packets to bind render targets, depth-stencil buffers, clear and resolve. These APIs don’t use the 256-bit GPU texture descriptor. We need driver specific extra CPU data (stored in the GpuTexture object).

The simplest way to reference a texture in a shader is to use a 32-bit index. A single index can also represent the starting offset of a range of descriptors. This offers a straightforward way to implement the DirectX 12 descriptor table abstraction and the Vulkan descriptor set abstraction without an API. We also get an elegant solution to the fast material switch use case: All we need is a single 64-bit GPU pointer, pointing to a material data struct (containing material properties + 32-bit texture heap start index). Vulkan vkCmdBindDescriptorSets and DirectX 12 SetGraphicsRootDescriptorTable are relatively fast API calls, but they are nowhere as fast as writing a single 64-bit pointer to persistently mapped GPU memory. A lot of complexity is removed by not needing to create, update and delete resource binding API objects. CPU time is also saved as the user no longer needs to maintain a hash map of descriptor sets, a common approach to solve the immediate vs retained mode discrepancy in game engines.

// Common header...
struct alignas(16) Data
{
    uint32 srcTextureBase;
    uint32 dstTexture;
    float32x2 invDimensions;
};

// GPU kernel...
const Texture textureHeap[];

[groupsize = (8, 8, 1)]
void main(uint32x3 threadId : SV_ThreadID, const Data* data)
{
    Texture textureColor = textureHeap[data->srcTextureBase + 0];
    Texture textureNormal = textureHeap[data->srcTextureBase + 1];
    Texture texturePBR = textureHeap[data->srcTextureBase + 2];

    Sampler sampler = {.minFilter = LINEAR, .magFilter = LINEAR}; // Embedded sampler (Metal-style)

    float32x2 uv = float32x2(threadId.xy) * data->invDimensions;

    float32x4 color = sample(textureColor, sampler, uv);
    float32x4 normal = sample(textureNormal, sampler, uv);
    float32x4 pbr = sample(texturePBR, sampler, uv);

    float32x4 lit = calculateLighting(color, normal, pbr);

    TextureRW dstTexture = TextureRW(textureHeap[data->dstTexture]);
    dstTexture[threadId.xy] = lit;
}

Metal 4 manages the texture descriptor heap automatically. Texture objects have .gpuResourceID, which is a 64-bit heap index (Xcode GPU debugger reveals small values such as 0x3). You can directly write texture IDs into GPU structs, as you would use texture indices in DirectX SM 6.6 and Vulkan (descriptor buffer extension). As the heap management in Metal is automatic, users can’t allocate texture descriptors in contiguous ranges. It’s a common practice to store a 32-bit index to the first texture in the range and calculate the indices for other textures in the set (see above code example). Metal doesn’t support this. The user has to write a 64-bit texture handle for each texture separately. To address a set of 5 textures, you need 40 bytes in Metal (5 * 64-bit). Vulkan and DirectX 12 only need 4 bytes (1 * 32-bit). Apple GPU hardware is able to implement SM 6.6 texture heaps. The limitation is the Metal API (software).

Texel buffers can be still supported for backwards compatibility. DirectX 12 stores texel buffer descriptors in the same heap with texture descriptors. A texel buffer functions similarly to a 1d texture (unfiltered tfetch path). Since texel buffers would be mainly used for backwards compatibility, driver vendors wouldn’t need to jump over the hoops to replace them with faster code paths such as raw memory loads behind the scenes. I am not a big fan of driver background threads and shader replacements.

Non-uniform texture index needs to use NonUniformResourceIndex notation similar to GLSL and HLSL. This tells the low level GPU shader compiler to emit a special texture instruction with per-lane heap index, or a scalarization loop for GPUs that only support uniform descriptors. Since buffers are not descriptors, we never need NonUniformResourceIndex for buffers. We simply pass a 64-bit pointer per lane. It works on all modern GPUs. No scalarization loop, no mess. Additionally, the language should natively support ptr[index] notation for memory loads, where the index is 32-bits. Some GPUs support raw memory load instructions with 32-bit per lane offset. It reduces the register pressure. Feedback to GPU vendors: Please add the missing 64-bit shared base + 32-bit per lane offset raw load instruction and 16-bit uv(w) texture load instructions, if your architecture is still missing them.

const Texture textureHeap[];

[groupsize = (8, 8, 1)]
void main(uint32x3 threadId : SV_ThreadID, const Data* data)
{
    // Non-uniform "buffer data" is not an issue with pointer semantics! 
    Material* material = data->materialMap[threadId.xy];

    // Non-uniform texture heap index
    uint32 textureBase = NonUniformResourceIndex(material.textureBase);

    Texture textureColor = textureHeap[textureBase + 0];
    Texture textureNormal = textureHeap[textureBase + 1];
    Texture texturePBR = textureHeap[textureBase + 2];

    Sampler sampler = {.minFilter = LINEAR, .magFilter = LINEAR};

    float32x2 uv = float32x2(threadId.xy) * data->invDimensions;

    float32x4 color = sample(textureColor, sampler, uv);
    float32x4 normal = sample(textureNormal, sampler, uv);
    float32x4 pbr = sample(texturePBR, sampler, uv);
    
    color *= material.color;
    pbr *= material.pbr;

    // Rest of the shader
}

Modern bindless texturing lets us remove all texture binding APIs. A global indexable texture heap makes all textures visible to all shaders. Texture data still needs to be loaded into GPU memory by copy commands (to enable DCC and Morton swizzle). Texture descriptor creation still needs a thin GPU specific user land API. The texture heap can be exposed directly to both the CPU and the GPU as a raw GPU memory array, removing most of the texture heap API complexity compared to DirectX 12 SM 6.6.

Shader pipelines

Since our shader root data is just a single 64-bit pointer and our textures are just 32-bit indices, the shader pipeline creation becomes dead simple. There’s no need to define texture bindings, buffer bindings, bind groups (descriptor sets, argument buffers) or the root signature.

auto shaderIR = loadFile("computeShader.ir");
GpuPipeline computePipeline = gpuCreateComputePipeline(shaderIR);

DirectX 12 and Vulkan utilize complex APIs to bind and set up root signatures, push descriptors, push constants, and descriptor sets. A modern GPU driver essentially constructs a single struct into GPU memory and passes its pointer to the command processor. We have shown that such API complexity is unnecessary. The user simply writes the root struct into persistently mapped GPU memory and passes a 64-bit GPU pointer directly to the draw/dispatch function. Users can also include 64-bit pointers and 32-bit texture heap indices inside their structs to build any indirect data layout that fits their needs. Root bindings APIs and the whole DX12 buffer zoo can be replaced efficiently with 64-bit pointers.​​ This simplifies the shader pipeline creation drastically. We don’t need to define the data layout at all. We successfully removed a massive chunk of API complexity while providing more flexibility to the user.

Static constants

Vulkan, Metal and WebGPU have a concept of static (specialization) constants, locked in at shader pipeline creation. The driver's internal shader compiler applies these constants as literals in the input shader IR and does constant propagation and dead code elimination pass afterward. This can be used to create multiple permutations of the same shader at pipeline creation, reducing the time and storage required for offline compiling all the shader permutations.

Vulkan and Metal have a set of APIs and a special shader syntax for describing the shader specialization constants and their values. It would be nicer to simply provide a C struct that matches the constant struct defined in the shader side. That would require minimal API surface and would bring important improvements.

Vulkan’s specialization constants have a design flaw. Specialization constants can’t modify the descriptor set layouts. Data inputs and outputs are fixed. The user could hack around the limitation by implementing an uber-layout containing all potential inputs/outputs and skip updating unused descriptors, but this is cumbersome and sub-optimal. Our proposed design doesn’t have the same problem. One can simply branch by a constant (the other side is dead code eliminated) and reinterpret the shader data input pointer as a different struct. One could also mimic the C++ inheritance data layout. Use a common layout for the beginning of the input struct and put specialized data at the end. Static polymorphism can be achieved cleanly. Runtime performance is identical to hand optimized shader. The specialization struct can also include GPU pointers, allowing the user to hardcode runtime memory locations, avoiding indirections. This has never been possible in a shader language before. Instead, the GPU vendors had to use background threads to analyze the shaders to do similar shader replacement optimizations at runtime, increasing the CPU cost and the driver complexity significantly.

// Common header...
struct alignas(16) Constants
{
    int32 qualityLevel;
    uint8* blueNoiseLUT;
};

// CPU code...
Constants constants { .qualityLevel = 2, blueNoiseLUT = blueNoiseLUT.gpu };

auto shaderIR = loadFile("computeShader.ir");
GpuPipeline computePipeline = gpuCreateComputePipeline(shaderIR, &constants);

// GPU kernel...
[groupsize = (8, 8, 1)]
void main(uint32x3 threadId : SV_ThreadID, const Data* data, const Constants constants)
{
    if (constants.qualityLevel == 3)
    {
        // Dead code eliminated
    }
}

The shader permutation hell is one of the biggest issues in modern graphics today. Gamers are complaining about stutter, devs are complaining about offline shader compilation taking hours. This new design gives the user added flexibility. They can toggle between static and dynamic behavior inside the shader, making it easy to have a generic fallback and specialization on demand. This design reduces the number of shader permutations and the runtime stalls caused by pipeline creation.

Barriers and fences

The most hated feature in modern graphics APIs must be the barriers. Barriers serve two purposes: enforce producer-to-consumer execution dependencies and transition textures between layouts.

Many graphics programmers have an incorrect mental model about the GPU synchronization. A common belief is that GPU synchronization is based on fine-grained texture and buffer dependencies. In reality, modern GPU hardware doesn’t really care about individual resources. We spend lots of CPU cycles in userland preparing a list of individual resources and how their layouts change, but modern GPU drivers practically throw that list away. The abstraction doesn’t match reality.

Modern bindless architecture gives the GPU a lot of freedom. A shader can write to any 64-bit pointer or any texture in the global descriptor heap. The CPU doesn't know what decisions the GPU is going to make. How is it supposed to emit transition barriers for each affected resource? This is a clear mismatch between bindless architecture and classic CPU-driven rendering APIs today. Let’s investigate why the APIs were designed like this 10 years ago.

AMD GCN had a big influence on modern graphics API design. GCN was ahead of its time with async compute and bindless texturing (using scalar registers to store descriptors), but it also had crucial limitations in its delta color compression (DCC) and cache design. These limitations are a great example why the barrier model we have today is so complex. GCN didn’t have a coherent last-level cache. ROPs (raster operations = pixel shader outputs) had special non-coherent caches directly connected to the VRAM. The driver had to first flush the ROP caches to memory and then invalidate the L2$ to make pixel shader writes visible to shaders and samplers. The command processor also wasn’t a client of the L2$. Indirect arguments written in compute shaders weren’t visible to the command processor without invalidating the whole L2$ and flushing all dirty lines into VRAM. GCN 3 introduced delta color compression (DCC) for ROPs, but AMD’s texture samplers were not able to directly read DCC compressed textures or compressed depth buffers. The driver had to perform an internal decompress compute shader to eliminate the compression. The display engine could not read DCC compressed textures either. The common case of sampling a render target required two internal barriers and flushing all caches (wait for ROPs, flush ROP cache and L2$, run decompress compute shader, wait for compute).

AMD’s new RDNA architecture has several crucial improvements: It has a coherent L2$ covering all memory operations. ROPs and the command processor are clients of the L2$. The only non-coherent caches are the tiny L0$ and K$ (scalar cache) inside the compute units. A barrier now requires only flushing the outstanding writes in the tiny caches into the higher level cache. The driver no longer has to flush the last-level (L2) cache into the VRAM, making barriers significantly faster. RDNA’s improved display engine is capable of reading DCC compressed textures and a (de)compressor sits between the L2$ and the L0$ texture cache. There’s no need to decompress textures into VRAM before sampling, removing the need for texture layout transitions (compressed / uncompressed). All desktop and mobile GPU vendors have reached similar conclusions: Bandwidth is the bottleneck today. We should never waste bandwidth decoding resources into VRAM. Layout transitions are no longer needed.

AMD RDNA (2019): Improved cache hierarchy, DCC and display engine in the RDNA architecture. L2$ contains DCC compressed data. (De)compressor sits between L2$ and lower levels. L0$ (texture) is decompressed. Image © AMD.

Resource lists are the most annoying aspect of barriers in DirectX 12 and Vulkan. Users are expected to track the state of each resource individually, and tell the graphics API their previous and next state for each barrier. This was necessary on 10 year old GPUs as vendors hid various decompress commands under the barrier API. The barrier command functioned as the decompress command, so it had to know which resources required decompression. Today’s hardware doesn’t need texture layouts or decompress steps. Vulkan just got a new VK_KHR_unified_image_layouts ( https://www.khronos.org/blog/so-long-image-layouts-simplifying-vulkan-synchronisation ) extension (2025), removing the image layout transitions from the barrier command. But it still requires the user to list individual textures and buffers. Why is this?

The main reason is legacy API and tooling compatibility. People are used to thinking about resource dependencies and the existing Vulkan and DirectX 12 validation layers are designed that way. However, the barrier command executed by the GPU contains no information about textures or buffers at all. The resource list is consumed solely by the driver.

Our modern driver loops through your resource list and populates a set of flags. Drivers no longer need to worry about resource layouts or last level cache coherency, but there still exists tiny non-coherent caches that need flushing in special cases. Modern GPUs flush the majority of the non-coherent caches automatically in every barrier. For example the AMD L0$ and K$ (scalar cache) are always flushed, since every pass writes some outputs and these outputs live in some of these caches. Fine grained tracking of all write addresses would be too expensive. Tiny non-coherent caches tend to be inclusive. Modified lines get flushed to the next cache level. This is fast and doesn’t produce VRAM traffic. Some architectures have special caches that are not automatically flushed. Examples: descriptor caches in the texture samplers (see above chapter), rasterizer ROP caches and HiZ caches. The command processor commonly runs ahead to reduce the wave spawn latency. If we write indirect arguments in a shader, we need to inform the GPU to stall the command processor prefetcher to avoid a race. The GPU doesn’t actually know whether your compute shader was writing into an indirect argument buffer or not. In DirectX 12 the buffer is transitioned to D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT and in Vulkan the consumer dependency has a special stage VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT. When a barrier has a resource transition like this or a stage dependency like this, the driver will include command processor prefetcher stall flag into the barrier.

A modern barrier design replaces the resource list with a single bitfield describing what happens to these special non-coherent caches. Special cases include: Invalidate texture descriptors, invalidate draw arguments and invalidate depth caches. These flags are needed when we generate draw arguments, write to the descriptor heap or write to a depth buffer with a compute shader. Most barriers don’t need special cache invalidation flags.

Some GPUs still need to decompress data in special cases. For example during a copy or a clear command (fast clear eliminate if clear color has changed). Copy and clear commands take the affected resource as a parameter. The driver can take necessary steps to decode the data if needed. We don’t need a resource list in our barrier for these special cases. Not all formats and usage flags support compression. The driver will keep the data uncompressed in these cases, instead of transitioning it back and forth, wasting bandwidth.

A standard UAV barrier (compute → compute) is trivial.

gpuBarrier(commandBuffer, STAGE_COMPUTE, STAGE_COMPUTE);

If you write to the texture descriptor heap (uncommon), you need to add a special flag.

gpuBarrier(commandBuffer, STAGE_COMPUTE, STAGE_COMPUTE, HAZARD_DESCRIPTORS);

A barrier between rasterizer output and pixel shader is a common case for offscreen render target → sampling. Our example has dependency stages set up in a way that the barrier doesn’t block vertex shaders, allowing vertex shading (and tile binning on mobile GPUs) to overlap with previous passes. A barrier with raster output stage (or later) as the producer automatically flushes non-coherent ROP caches if the GPU architecture needs that. We don’t need an explicit flag for it.

gpuBarrier(commandBuffer, STAGE_RASTER_COLOR_OUT | STAGE_RASTER_DEPTH_OUT, STAGE_PIXEL_SHADER);

Users only describe the queue execution dependencies: producer and consumer stage masks. There’s no need to track the individual texture and buffer resource states, removing a lot of complexity and saving a significant amount of CPU time versus the current DirectX 12 and Vulkan designs. Metal 2 has a modern barrier design already: it doesn’t use resource lists.

Many GPUs have custom scratchpads memories: Groupshared memory inside each compute unit, tile memory, large shared scratchpads like the Qualcomm GMEM. These memories are managed automatically by the driver. Temporary scratchpads like groupshared memory are never stored to memory. Tile memories are stored automatically by the tile rasterizer (store op == store). Uniform registers are read-only and pre-populated before each draw call. Scratchpads and uniform registers don’t have cache coherency protocols and don’t interact with the barriers directly.

Modern GPUs support a synchronization command that writes a value to memory when a shader stage is finished, and a command that waits for a value to appear in memory location before a shader stage is allowed to begin (wait includes optional cache flush semantics). This is equivalent to splitting the barrier into two: the producer and the consumer. DirectX 12 split barriers and Vulkan event→wait are examples of this design. Splitting the barrier into consumer→producer allows putting independent work between them, avoiding draining the GPU.

Vulkan event→wait (and DX12 split barriers) see barely any use. The main reason is that normal barriers are already highly complicated, and developers want to avoid extra complexity. Driver support for split barriers also hasn’t been perfect in the past. Removing the resource lists simplifies the split barriers significantly. We can also make split barriers semantically similar to timeline semaphores: Signal command writes to a monotonically increasing 64-bit value (atomic max) and wait command waits for the value to be >= N (greater equal). The counter is just a GPU memory pointer, no persistent API object is required. This provides us with a significantly simpler event→wait API.

gpuSignalAfter(commandBuffer, STAGE_RASTER_COLOR_OUT, gpuPtr, counter, SIGNAL_ATOMIC_MAX);
// Put independent work here
gpuWaitBefore(commandBuffer, STAGE_PIXEL_SHADER, gpuPtr, counter++, OP_GREATER_EQUAL);

This API is much simpler than the existing VkEvent API, yet offers improved flexibility. In the above example we implemented the timeline semaphore semantics, but we can implement other patterns too, such as waiting multiple producers using a bitmask: mark bits with SIGNAL_ATOMIC_OR and wait for all bits in a mask to be set (mask is an optional parameter in the gpuWaitBefore command).

Cascading signal→wait with independent work between producer→consumer avoids GPU stalls. Image © Timothy Lottes.

GPU→CPU synchronization was initially messy in Vulkan and Metal. Users needed a separate fence object for each submit. N buffering was a common technique for reusing the objects. This is a similar usability issue as discussed above regarding VkEvent. DirectX 12 was the first API to solve the GPU→CPU synchronization cleanly with timeline semaphores. Vulkan 1.2 and Metal 2 adapted the same design later. A timeline semaphore needs only a single 64-bit monotonically increasing counter. This reduces complexity over the older Vulkan and Metal fence APIs, which many engines still use today.

#define FRAMES_IN_FLIGHT 2

GpuSemaphore frameSemaphore = gpuCreateSemaphore(0);
uint64 nextFrame = 1;

while (running)
{
    if (nextFrame > FRAMES_IN_FLIGHT) 
    {
        gpuWaitSemaphore(frameSemaphore, nextFrame - FRAMES_IN_FLIGHT);
    }
    
    // Render the frame here

    gpuSubmit(queue, {commandBuffer}, frameSemaphore, nextFrame++);
}

gpuDestroySemaphore(frameSemaphore);

Our proposed barrier design is a massive improvement over DirectX 12 and Vulkan. It reduces the API complexity significantly. Users no longer need to track individual resources. Our simple hazard tracking has queue + stage granularity. This matches what GPU hardware does today. Game engine graphics backends can be simplified and CPU cycles are saved.

Command buffers

Vulkan and DirectX 12 were designed to promote the pre-creation and reuse of resources. Early Vulkan examples recorded a single command buffer at startup, replaying it every frame. Developers quickly discovered that command buffer reuse was impractical. Real game environments are dynamic and the camera is in constant motion. The visible object set changes frequently.

Game engines ignored prerecorded command buffers entirely. Metal and WebGPU feature transient command buffers, which are created just before recording and disappear after GPU has finished rendering. This eliminates the need for command buffer management and prevents multiple submissions of the same commands. GPU vendors recommend one shot command buffers (a resettable command pool per frame in flight) in Vulkan too, as it simplifies the driver’s internal memory management (bump allocator vs heap allocator). The best practices match Metal and WebGPU design. Persistent command buffer objects can be removed. That API complexity didn’t provide anything worth using.

while (running)
`
    GpuCommandBuffer commandBuffer = gpuStartCommandRecording(queue);
    // Render frame here
}

Graphics shaders

Let’s start with a burning question: Do we need graphics shaders anymore? UE5 Nanite uses compute shaders to plot pixels using 64-bit atomics. High bits contain the pixel depth and low bits contain the payload. Atomic-min ensures that the closest surface remains. This technique was first presented at SIGGRAPH 2015 by Media Molecule Dreams (Alex Evans). Hardware rasterizer still has some advantages, like hierarchical/early depth-stencil tests. Nanite has to lean solely on coarse cluster culling, which results in extra overdraw with kitbashed content. Ubisoft (me and Ulrich Haar) presented this two-pass cluster culling algorithm at SIGGRAPH 2015. Ubisoft used cluster culling in combination with the hardware rasterizer for more fine grained culling. Today’s GPUs are bindless and much better suited for GPU-driven workloads like this. 10 years ago Ubisoft had to lean on virtual texturing (all textures in the same atlas) instead of bindless texturing. Despite many compute-only rasterizers today (Nanite, SDF sphere tracing, DDA voxel tracing) the hardware rasterizer still remains the most used technique for rendering triangles in games today. It’s definitely worth discussing how to make the rasterization pipeline more flexible and easier to use.

The modern shader framework has grown to 16 shader entry points. We have eight entry points for rasterization (pixel, vertex, geometry, hull, domain, patch constant, mesh and amplification), and six for ray-tracing (ray generation, miss, closest hit, any hit, intersection and callable). In comparison, CUDA has a single entry point: kernel. This makes CUDA composable. CUDA has a healthy ecosystem of 3rd party libraries. New GPU hardware blocks such as the tensor cores (AI) are exposed as intrinsic functions. This is how it all started in the graphics land as well: texture sampling was our first intrinsic function. Today, texture sampling is fully bindless and doesn’t even require driver setup. This is the design developers prefer. Simple, easy to compose, and extend.

We recently got more intrinsics: inline raytracing and cooperative matrix (wave matrix in DirectX 12, subgroup matrix in Metal). I am hoping that this is the new direction. We should start tearing down the massive 16 shader framework and replacing it with intrinsics that can be composed in a flexible way.

Solving the shader framework complexity is a massive topic. To keep the scope of this blog post in check, I will today only discuss compute shaders and raster pipelines. I am going to be writing a followup about simplifying the shader framework, including modern topics such as ray-tracing, shader execution reordering (SER), dynamic register allocation extensions and Apple’s new L1$ backed register file (called dynamic caching).

Raster pipelines

There are two relevant raster pipelines today: Vertex+pixel and mesh+pixel. Mobile GPUs employing tile based deferred rendering (TBDR) perform per-triangle binning. Tile size is commonly between 16x16 to 64x64 pixels, making meshlets too coarse grained primitive for binning. Meshlet has no clear 1:1 lane to vertex mapping, there’s no straightforward way to run a partial mesh shader wave for selected triangles. This is the main reason mobile GPU vendors haven’t been keen to adapt the desktop centric mesh shader API designed by Nvidia and AMD. Vertex shaders are still important for mobile.

I will not be discussing geometry, hull, domain, and patch constant (tessellation) shaders. The graphics community widely considers these shader types as failed experiments. They all have crucial performance issues in their design. In all relevant use cases, you can run a compute prepass generating an index buffer to outperform these stages. Additionally, mesh shaders allow generating a compact 8-bit index buffer into on-chip memory, further increasing the performance gap over these legacy shader stages.

Our goal is to build a modern PSO abstraction with a minimal amount of baked state. One of the main critiques of Vulkan and DirectX 12 has been the pipeline permutation explosion. The less state we have inside the PSO, the less pipeline permutations we get. There are two main areas to improve: graphics shader data bindings and the rasterizer state.

Graphics shader bindings

Vertex+pixel shader pipeline needs several additional inputs compared to a compute kernel: vertex buffers, index buffer, rasterizer state, render target views and a depth-stencil view. Let’s start by discussing the shader visible data bindings.

Vertex buffer bindings are easy to solve: We simply remove them. Modern GPUs have fast raw load paths. Most GPU vendors have been emulating vertex fetch hardware already for several generations. Their low level shader compiler reads the user defined vertex layout and emits appropriate raw load instructions in the beginning of the vertex shader.

The vertex bindings declaration is another example of a special C/C++ API for defining a struct memory layout. It adds complexity and forces compiling multiple PSO permutations for different layouts. We simply replace the vertex buffers with standard C/C++ structs. No API is required.

// Common header...
struct Vertex
{
    float32x4 position;
    uint8x4 normal;
    uint8x4 tangent;
    uint16x2 uv;
};

struct alignas(16) Data
{
    float32x4x4 matrixMVP;
    const Vertex *vertices;
};

// CPU code...
gpuSetPipeline(commandBuffer, graphicsPipeline);

auto data = myBumpAllocator.allocate<Data>();
data.cpu->matrixMVP = camera.viewProjection * modelMatrix;
data.cpu->vertices = mesh.vertices;

gpuDrawIndexed(commandBuffer, data.gpu, mesh.indices, mesh.indexCount);

// Vertex shader...
struct VertexOut 
{
    float32x4 position : SV_Position;
    float16x4 normal;
    float32x2 uv;
};

VertexOut main(uint32 vertexIndex : SV_VertexID, const Data* data)
{
    Vertex vertex = data->vertices[vertexIndex];
    float32x4 position = data->matrixMVP * vertex.position;
    // TODO: Normal transform here
    return { .position = position, .normal = normal, .uv = vertex.uv };
}

The same is true for per-instance data and multiple vertex streams. We can implement them efficiently with raw memory loads. When we use raw load instructions, we can dynamically adjust the vertex stride, branch over secondary vertex buffer loads and calculate our vertex indices using custom formulas to implement clustered GPU-driven rendering, particle quad expansion, higher order surfaces, efficient terrain rendering and many other algorithms. Additional shader entry points and binding APIs are not needed. We can use our new static constant system to dead code eliminate vertex streams at pipeline creation or provide a static vertex stride if we so prefer. All the old optimization strategies still exist, but we can now mix and match techniques freely to match our renderer’s needs.

// Common header...
struct VertexPosition
{
    float32x4 position;
};

struct VertexAttributes
{
    uint8x4 normal;
    uint8x4 tangent;
    uint16x2 uv;
};

struct alignas(16) Instance
{
    float32x4x4 matrixModel;
}

struct alignas(16) Data
{
    float32x4x4 matrixViewProjection;
    const VertexPosition *vertexPositions;
    const VertexAttributes *vertexAttribues;
    const Instance *instances;
};

// CPU code...
gpuSetPipeline(commandBuffer, graphicsPipeline);

auto data = myBumpAllocator.allocate<Data>();
data.cpu->matrixViewProjection = camera.viewProjection;
data.cpu->vertexPositions = mesh.positions;
data.cpu->vertexAttributes = mesh.attributes;
data.cpu->instances = batcher.instancePool + instanceOffset; // pointer arithmetic is convenient

gpuDrawIndexedInstanced(commandBuffer, data.gpu, mesh.indices, mesh.indexCount, instanceCount);

// Vertex shader...
struct VertexOut 
{
    float32x4 position : SV_Position; // SV values are not real struct fields (doesn't affect the layout)
    float16x4 normal;
    float32x2 uv;
};

VertexOut main(uint32 vertexIndex : SV_VertexID, uint32 instanceIndex : SV_InstanceID, const Data* data)
{
    Instance instance = data->instances[SV_InstanceIndex];

    // NOTE: Splitting positions/attributes benefits TBDR GPUs (vertex shader is split in two parts)
    VertexPosition vertexPosition = data->vertexPositions[SV_VertexIndex];
    VertexAttributes vertexAttributes = data->vertexAttributes[SV_VertexIndex];

    float32x4x4 matrix = data->matrixViewProjection * instance.matrixModel;
    float32x4 position = matrix * vertexPosition.position;

    // TODO: Normal transform here

    return { .position = position, .normal = normal, .uv = vertexAttributes.uv };
}

The index buffer binding is still special. GPUs have index deduplication hardware. We don’t want to run the vertex shader twice for the same vertex. The index deduplication hardware packs the vertex waves eliminating duplicate vertices. Index buffering is still a crucial optimization today. Non-indexed geometry executes 3 vertex shader invocations (lanes) per triangle. A perfect grid has two triangles per cell, thus it only needs one vertex shader invocation per two triangles (ignoring the last row/column). Modern offline vertex cache optimizers output meshes with around 0.7 vertices per triangle efficiency. We can achieve around 4x to 6x reduction in vertex shading cost with index buffer in real world scenarios.

The index buffer hardware nowadays connects to the same cache hierarchy as all the other GPU units. Index buffer is simply an extra GPU pointer in the drawIndexed call. That’s the sole API surface we need for index buffering.

Mesh shaders lean on offline vertex deduplication. A common implementation shades one vertex per lane, and outputs it into on-chip memory. An 8-bit local index buffer tells the rasterizer which 3 vertices are used by each triangle. Since all the meshlet outputs are available at once and are already transformed in on-chip storage, there’s no need to deduplicate or pack vertices after triangle setup. This is why mesh shaders don’t need the index deduplication hardware or the post transform cache. All mesh shader inputs are raw data. No extra API surface is needed beyond the gpuDrawMeshlets command.

My example mesh shader uses 128 lane thread groups. Nvidia supports up to 126 vertices and 64 triangles per output meshlet. AMD supports 256 vertices and 128 triangles. The shader masks out excess lanes. Since there’s never more than 64 triangles, you might also opt for a 64 lane thread group for optimal triangle lane utilization and do a two iteration loop for vertex shading. My triangle fetch logic is just a single memory load instruction, wasting half of the lanes there isn’t a problem. I chose the extra parallelism for vertex shading instead. Optimal choices depend on your workload and the target hardware.

// Common header...
struct Vertex
{
    float32x4 position;
    uint8x4 normal;
    uint8x4 tangent;
    uint16x2 uv;
};

struct alignas(16) Meshlet
{
    uint32 vertexOffset;
    uint32 triangleOffset;
    uint32 vertexCount;
    uint32 triangleCount;
};

struct alignas(16) Data
{
    float32x4x4 matrixMVP;
    const Meshlet *meshlets;
    const Vertex *vertices;
    const uint8x4 *triangles;
};

// CPU code...
gpuSetPipeline(commandBuffer, graphicsMeshPipeline);

auto data = myBumpAllocator.allocate<Data>();
data.cpu->matrixMVP = camera.viewProjection * modelMatrix;
data.cpu->meshlets = mesh.meshlets;
data.cpu->vertices = mesh.vertices;
data.cpu->triangles = mesh.triangles;

gpuDrawMeshlets(commandBuffer, data.gpu, uvec3(mesh.meshletCount, 1, 1));

// Mesh shader...
struct VertexOut 
{
    float32x4 position : SV_Position;
    float16x4 normal;
    float32x2 uv;
};

[groupsize = (128, 1, 1)]
void main(uint32x3 groupThreadId : SV_GroupThreadID, uint32x3 groupId : SV_GroupID, const Data* data)
{
    Meshlet meshlet = data->meshlets[groupId.x];

    // Meshlet output allocation intrinsics
    VertexOut* outVertices = allocateMeshVertices<VertexOut>(meshlet.vertexCount);
    uint8x3* outIndices = allocateMeshIndices(meshlet.triangleCount);

    // Triangle indices (3x 8 bit)
    if (groupThreadId.x < meshlet.triangleCount)
    {
        outIndices[groupThreadId.x] = triangles[meshlet.triangleOffset + groupThreadId.x].xyz;
    }

    // Vertices
    if (groupThreadId.x < meshlet.vertexCount)
    {
        Vertex vertex = data->vertices[meshlet.vertexOffset + groupThreadId.x];
        float32x4 position = data->matrixMVP * vertex.position;
        // TODO: Normal transform here
        outVertices[groupThreadId.x] = { .position = position, .normal = normal, .uv = vertex.uv };
    }
}

Both vertex shaders and mesh shaders use pixel shaders. Rasterizer spawns pixel shader work based on triangle pixel coverage, HiZ, and early depth/stencil test results. Hardware can pack multiple triangles and multiple instances in the same pixel shader wave. Pixel shaders itself aren’t that special. Nowadays pixel shaders run on the same SIMD cores as all the other shader types. There are some special inputs available: interpolated vertex outputs, screen location, sample index and coverage mask, triangle id, triangle facing, etc. Special inputs are declared as kernel function parameters using the system value (: SV) semantics, similar to existing APIs.

// Pixel shader...
const Texture textureHeap[];

struct VertexIn // Matching vertex shader output struct layout
{
    float16x4 normal;
    float32x2 uv;
};

struct PixelOut 
{
    float16x4 color : SV_Color0;
};

PixelOut main(const VertexIn &vertex, const DataPixel* data)
{
    Texture texture = textureHeap[data->textureIndex];
    Sampler sampler = {.minFilter = LINEAR, .magFilter = LINEAR};

    float32x4 color = sample(texture, sampler, vertex.uv);
    return { .color = color };
}

The removal of data bindings makes vertex and pixel shaders simpler to use. All of the complex data bindings APIs are replaced by a 64-bit GPU pointer. Users are able to write flexible vertex fetch code to avoid creating a PSO permutation per vertex layout.

Rasterizer state

Legacy APIs (OpenGL and DirectX 9) had fine grained commands for setting all the shader inputs and rasterizer states. The driver had to build shader pipelines on demand. Hardware specific rasterizer, blender, and input assembler command packets were constructed from the shadow state that combined all individual fine grained states. Vulkan 1.0 and DirectX 12 chose the fully opposite design. All state is baked in the PSO ahead of time. Only a select few states, such as viewport rect, scissor rect, and stencil values, can be changed dynamically. This resulted in a massive explosion of PSO permutations.

PSO creation is expensive, as it requires calling the GPU driver’s low-level shader compiler. PSO permutations consume a significant amount of storage and RAM. Changing the PSO is the most expensive state change. The small performance advantages that some vendors achieved by embedding render state directly into the shader microcode were overshadowed by the performance issues caused by significantly amplified pipeline creation, binding and data management costs everywhere. The pendulum swung too far to the opposite side.

Modern GPUs are ALU dense. Nvidia and AMD recently doubled their ALU rate with additional pipelines. Apple also doubled their fp32 pipelines in their M-series chips. Simple states resulting only in a constant replacement in the shader should not require pipeline duplication, even if it adds an extra ALU instruction or wastes an uniform register. Most shaders today are not ALU bound. The cost is often not measurable, but the benefits of having less permutations are significant. Vulkan 1.3 is a big step in the right direction. A lot of baked PSO states can now be set dynamically.

If we investigate deeper, we notice that all GPUs use command packets for configuring their rasterizer and depth-stencil units. These command packets are not directly tied to the shader microcode. We don’t need to modify the shader microcode to change the rasterizer and depth-stencil state. Metal has a separate depth-stencil state object and a separate command for applying it. A separate state object reduces the PSO permutations and reduces the expensive shader binding calls. Vulkan 1.3 dynamic state achieves similar PSO permutation reduction, but is more fine grained. Metal’s design is a better match for the actual hardware command packets. Bigger packets reduce the API bloat and overhead. DirectX 12 unfortunately still bundles most of the depth-stencil state inside the PSO (stencil ref and depth bias are the only dynamic state). In our design the depth-stencil state is a separate object.

GpuDepthStencilDesc depthStencilDesc = 
{
    .depthMode = DEPTH_READ | DEPTH_WRITE,
    .depthTest = OP_LESS_EQUAL,
    .depthBias = 0.0f,
    .depthBiasSlopeFactor = 0.0f,
    .depthBiasClamp = 0.0f,
    .stencilReadMask = 0xff,
    .stencilWriteMask = 0xff,
    .stencilFront =
    {
        .test = OP_ALWAYS,
        .failOp = OP_KEEP,
        .passOp = OP_KEEP,
        .depthFailOp = OP_KEEP,
        .reference = 0
    },
    .stencilBack =
    {
        .test = COMPARE_ALWAYS,
        .failOp = OP_KEEP,
        .passOp = OP_KEEP,
        .depthFailOp = OP_KEEP,
        .reference = 0
    }
};

// A minimal way to descibe the above (using C++ API struct default values):
GpuDepthStencilDesc depthStencilDesc = 
{
    .depthMode = DEPTH_READ | DEPTH_WRITE,
    .depthTest = OP_LESS_EQUAL,
};

GpuDepthStencilState depthStencilState = gpuCreateDepthStencilState(depthStencilDesc);

Immediate mode (desktop) GPUs have similar command packets for configuring the alpha blender unit. If we were designing DirectX 13, we would simply split the blend state object out of the PSO and call it a day. But we are designing a cross platform API, and blending works completely differently on mobile GPUs.

Mobile GPUs (TBDR) have always supported programmable blending. A render tile fits into a scratchpad memory close to the compute unit (such as the groupshared memory), allowing pixel shaders a direct low latency read+write access to previously rasterized pixels. Most mobile GPUs don’t have any fixed function blending hardware. When a traditional graphics API is used, the driver's low level shader compiler adds blending instructions at the end of the shader. This is analogous to the vertex fetch code generation (described above). If we were designing an API solely for mobile GPUs, we would get rid of the blend state APIs completely, just like we got rid of the vertex buffers. Mobile centric APIs expose a framebuffer fetch intrinsic to efficiently obtain current pixel’s previous color. The user can write any blending formula they want, including complex formulas to implement advanced algorithms such as order independent transparency. The user can also write a generic parametrised formula to eliminate the PSO permutation explosion. As we can see, both the desktop and the mobile GPUs have their own ways to reduce the pipeline permutations regarding blending, the limitation is the current APIs.

Vulkan subpasses were designed to wrap framebuffer fetch into a cross platform API. This was another misstep in Vulkan design. Vulkan inherited a simple low level design from Mantle, but Vulkan was designed as an OpenGL replacement, so it had to target all mobile and desktop GPU architectures. Wrapping two entirely different architecture types under the same API isn’t easy. Subpasses ended up being a high level concept inside a low level API. A subpass could define an entire chain of render passes but pretend that it’s just a single render pass. Subpasses increased driver complexity and made the shader and renderpass APIs needlessly complex. Users were forced to create complex persistent multi-render pass objects ahead of time and pass those objects into shader pipeline creation. Shader pipelines became multi-pipelines under the hood (one pipeline per sub-pass). Vulkan added all of this complexity simply to avoid exposing the framebuffer fetch intrinsic to the shader language. To add insult to injury, the subpasses weren’t even good enough to solve the programmable blending. Pixel ordering is only preserved at pass boundaries. Subpasses were only useful for narrow 1:1 multi-pass use cases. Vulkan 1.3 scrapped the subpasses and introduced “dynamic rendering”. Users no longer need to create persistent render pass objects, just like in Metal, DirectX 12 and WebGPU. This is a great example of how a complex framework can be tempting for API designers, but developers prefer simple shader intrinsics. Game engines already support building different shaders to different platforms. Apple’s Metal examples do the same: Framebuffer fetch is used on iOS and a traditional multipass algorithm on Mac.

It’s apparent that we can’t abstract hardware differences regarding blending and framebuffer fetch. Vulkan 1.0 tried that and failed miserably. The correct solution is to provide the user a choice. They can choose to embed the blend state into the PSO. This works on all platforms and is the perfect approach for shaders that don’t suffer from blend state related pipeline permutation issues. Mobile GPU’s internal driver shader compiler adds the blending instructions in the end of the pixel shader as usual. On immediate mode (desktop) GPUs (and some mobile GPUs), the user can choose to use separate blend state objects. This reduces the amount of PSO permutations and makes it faster to change the blend state at runtime, as a full pipeline change is not needed (only a blend state configuration packet is sent).

GpuBlendDesc blendDesc = 
{
    .colorOp = OP_ONE,
    .srcColorFactor = FACTOR_SRC_ALPHA,
    .dstColorFactor = FACTOR_ONE_MINUS_SRC_ALPHA,
    .alphaOp = OP_ONE,
    .srcAlphaFactor = FACTOR_SRC_ALPHA,
    .dstAlphaFactor = FACTOR_ONE_MINUS_SRC_ALPHA,
    .colorWriteMask = 0xf
};

// Create blend state object (needs feature flag)
GpuBlendState blendState = gpuCreateBlendState(blendDesc);

// Set dynamic blend state (needs feature flag)
gpuSetBlendState(commandBuffer, blendState);

On mobile GPUs the user can embed the blend state into the PSO as usual, or choose to use framebuffer fetch to write a custom blending formula. If the mobile developer wants to avoid compiling multiple PSO permutations for different alpha blending modes, they can write a general formula parametrized with dynamic draw struct inputs.

// Standard percentage blend formula (added automatically by internal shader compiler)
dst.rgb = src.rgb * src.a + dst.rgb * (1.0 - src.a);
dst.a = src.a * src.a + dst.a * (1.0 - src.a);

// Custom formula supporting all blend modes used by HypeHype
const BlendParameters& p = data->blendParameters;
vec4 fs = src.a * vec4(p.sc_sa.xxx + p.sc_one.xxx, p.sa_sa + p.sa_one) + dst.rgba * vec4(p.sc_dc.xxx, sa_da);
vec4 fd = (1.0 - src.a) * vec4(p.dc_1msa.xxx, p.da_1msa) + vec4(p.dc_one.xxx, p.da_one);
dst.rgb = src.rgb * fs.rgb + dst.rgb * fd.rgb;
dst.a  = src.a * fs.a + dst.a * fd.a;

As the blend state is separated from the PSO, it’s possible we lose some automatic dead code optimizations. If the user wants to disable the color output, they traditionally use the colorWriteMask in the blend state. Since the blend state is burned in the PSO, the compiler can do dead code elimination based on it. To allow similar dead code optimizations, we have writeMask for each color target in the PSO.

Dual source blending is a special blend mode requiring two color outputs from the pixel shader. Dual source blending only supports a single render target. Since our blend state can be separate, we need to have a supportDualSourceBlending field in our PSO desc. When enabled, the shader compiler knows the second output is for dual source blending. The validation layer would complain if the output is not present. A pixel shader exporting two colors can be used without dual source blending (the second color is ignored), but there’s a small cost for exporting two colors.

The remaining rendering state in the PSO is minimal: primitive topology, render target and depth-stencil target formats, MSAA sample count and alpha to coverage. All of this state affects the generated shader microcode so it needs to stay in the PSO. We never want to rebuild the shader PSO microcode due to a state change. If an embedded blend state is used, it's also burned in the PSO. This leaves us with a simple raster state struct for PSO creation.

GpuRasterDesc rasterDesc = 
{
    .topology = TOPOLOGY_TRIANGLE_LIST,
    .cull = CULL_CCW,
    .alphaToCoverage = false,
    .supportDualSourceBlending = false,
    .sampleCount = 1`
    .depthFormat = FORMAT_D32_FLOAT,
    .stencilFormat = FORMAT_NONE,
    .colorTargets = 
    {
        { .format = FORMAT_RG11B10_FLOAT	},		// G-buffer with 3 render targets
        { .format = FORMAT_RGB10_A2_UNORM },
        { .format = FORMAT_RGBA8_UNORM }
    },
    .blendstate = GpuBlendDesc { ... }			// optional (embedded blend state, otherwise dynamic)
};

// A minimal way to descibe the above (using C++ API struct default values):
GpuRasterDesc rasterDesc = 
{
    .depthFormat = FORMAT_D32_FLOAT,
    .colorTargets = 
    {
        { .format = FORMAT_RG11B10_FLOAT	},
        { .format = FORMAT_RGB10_A2_UNORM },
        { .format = FORMAT_RGBA8_UNORM }
    },
};

// Pixel + vertex shader
auto vertexIR = loadFile("vertexShader.ir");
auto pixelIR = loadFile("pixelShader.ir");
GpuPipeline graphicsPipeline = gpuCreateGraphicsPipeline(vertexIR, pixelIR, rasterDesc);

// Mesh shader
auto meshletIR = loadFile("meshShader.ir");
auto pixelIR = loadFile("pixelShader.ir");
GpuPipeline graphicsMeshletPipeline = gpuCreateGraphicsMeshletPipeline(meshletIR, pixelIR, rasterDesc);

HypeHype’s Vulkan shader PSO initialization backend code is 400 lines, and I would consider it compact compared to other engines I have been developing. Here, we managed to initialize a pixel + vertex shader with just 18 lines of code. It’s easy to read and understand. Yet, there’s no compromise on performance.

Rendering with the raster pipeline is similar to rendering with a compute pipeline. Instead of providing one data pointer, we provide two since there’s two kernel entry points: One for vertex shader and one for pixel shader. Metal has a separate set of data binding slots for vertex and pixel shaders. DirectX, Vulkan and WebGPU use a visibility mask (vertex, pixel, compute, etc) for each individual binding. Many engines choose to bind the same data to both vertex and pixel shader. This is a fine practice on DirectX, Vulkan and WebGPU as you can combine the mask bits, but doubles the binding calls on Metal. Our proposed approach using two data pointers is the best of both worlds. You can simply pass the same pointer twice if you want to use the same data in both the vertex and the pixel shader. Or you can provide independent data pointers, if you prefer full separation between the shader stages. The shader compiler does dead code elimination and constant/scalar preload optimizations separately for pixel and vertex shader. Neither data sharing nor data duplication results in bad performance. The user can choose whatever fits their design.

// Common header...
struct Vertex
{
    float32x4 position;
    uint16x2 uv;
};

struct alignas(16) DataVertex
{
    float32x4x4 matrixMVP;
    const Vertex *vertices;
};

struct alignas(16) DataPixel
{
    float32x4 color;
    uint32 textureIndex;
};

// CPU code...
gpuSetDepthStencilState(commandBuffer, depthStencilState);
gpuSetPipeline(commandBuffer, graphicsPipeline);

auto dataVertex = myBumpAllocator.allocate<DataVertex>();
dataVertex.cpu->matrixMVP = camera.viewProjection * modelMatrix;
dataVertex.cpu->vertices = mesh.vertices;

auto dataPixel = myBumpAllocator.allocate<DataPixel>();
dataPixel.cpu->color = material.color;
dataPixel.cpu->textureIndex = material.textureIndex;

gpuDrawIndexed(commandBuffer, dataVertex.gpu, dataPixel.gpu, mesh.indices, mesh.indexCount);

// Vertex shader...
struct VertexOut 
{
    float32x4 position : SV_Position; // SV values are not real struct fields (doesn't affect the layout)
    float32x2 uv;
};

VertexOut main(uint32 vertexIndex : SV_VertexID, const DataVertex* data)
{
    Vertex vertex = data.vertices[vertexIndex];
    float32x4 position = data->matrixMVP * vertex.position;
    return { .position = position, .uv = vertex.uv };
}

// Pixel shader...
const Texture textureHeap[];

struct VertexIn // Matching vertex shader output struct layout
{
    float32x2 uv;
};

PixelOut main(const VertexIn &vertex, const DataPixel* data)
{
    Texture texture = textureHeap[data->textureIndex];
    Sampler sampler = {.minFilter = LINEAR, .magFilter = LINEAR};

    float32x4 color = sample(texture, sampler, vertex.uv);
    return { .color = color };
}

Our goal was to reduce the PSO permutation explosion by minimizing the remaining state inside the PSO. The depth-stencil can be separated on all architectures. The blend state separation is possible on desktop hardware, but most mobile hardware burns the blend equation at the end of the pixel shader microcode. Exposing the framebuffer fetch intrinsics directly to the user is a much better idea than Vulkan’s failed subpass approach. Users can write their own blend formulas unlocking new rendering algorithms, or they can author general parametrized blending formulas to reduce the PSO count.

Indirect drawing

Standard draw/dispatch commands utilize C/C++ function parameters to provide the arguments: thread group dimensions, index count, instance count, etc. Indirect draw calls allow the user to provide a GPU buffer + offset pair instead as the draw argument source, a crucial addition enabling GPU-driven rendering. Our version uses a single GPU pointer instead of the usual buffer object + offset pair, simplifying the API slightly.

gpuDispatchIndirect(commandBuffer, data.gpu, arguments.gpu);
gpuDrawIndexedInstancedIndirect(commandBuffer, dataVertex.gpu, dataPixel.gpu, arguments.gpu);

All of our arguments are GPU pointers. Both the data and the arguments are indirect. This is a great improvement over existing APIs. DirectX 12, Vulkan and Metal don’t support indirect root arguments. The CPU has to provide them.

Indirect multidraw (MDI) should also be supported. The draw count comes from a GPU address. MDI parameters are: an array of root data (GPU pointer, for both vertex and pixel), an array of draw arguments (GPU pointer), and stride for the root data array (for both vertex and pixel). Stride = 0 naturally means that the same root data is replicated for each draw.

gpuDrawIndexedInstancedIndirectMulti(commandBuffer, dataVertex.gpu, sizeof(DataVertex), dataPixel.gpu, sizeof(DataPixel), arguments.gpu, drawCount.gpu);

Vulkan’s multidraw doesn’t allow changing bindings per draw call. You can use gl_DrawID to index into a buffer which contains your draw data structs. This adds an indirection in the shader. You need to use either descriptor indexing or the new descriptor buffer extension to fetch textures. DirectX 12 ExecuteIndirect has a configurable command signature, allowing the user to manually setup a root constant per draw, but this doesn’t hit the fast path on all GPU command processors. ExecuteIndirect tier 1.1 (2024) added a new optional counter increment feature: D3D12_INDIRECT_ARGUMENT_TYPE_INCREMENTING_CONSTANT. This can be used to implement draw ID. SM6.8 (2024) finally added support for SV_StartInstanceLocation, allowing the user to directly embed a constant in the indirect draw arguments. Unlike SV_InstanceID, the new SV_StartInstanceLocation is uniform across the whole draw call, providing optimal codegen for indexed loads (uniform/scalar path). The data fetch still requires an indirection. GPU-generated root data is not supported.

If we generate draw arguments or root data on the GPU, we need to ensure that the command processor waits for the dispatch to finish. Modern command processors prefetch commands and their arguments to hide the latency. We have a flag in our barrier to prevent this. The best practice is to batch update all your draw arguments and root data to avoid fine-grained barriers.

gpuBarrier(commandBuffer, STAGE_COMPUTE, STAGE_COMPUTE, HAZARD_DRAW_ARGUMENTS);

The lack of indirect shader selection is a significant limitation in the current PC and mobile graphics APIs. Indirect shader selection can be implemented using multi-pipelines similar to ray-tracing. Metal also supports indirect command creation (Nvidia has a similar Vulkan extension). An efficient way to skip over draw calls is a valuable subset. DirectX work graphs and CUDA dynamic parallelism allow a shader to spawn more waves on demand. Unfortunately, the APIs to access these hardware improvements is still highly platform specific and scattered in multiple shader entry points. There’s no clear standardization. My followup post will discuss the shader framework and cover this topic in depth.

Our proposed design makes indirect drawing extremely powerful. Both the shader root data and the draw parameters can be indirectly provided by the GPU. These advantages power up multi-draw, allowing clean and efficient per-draw data bindings with no hacks. The future of indirect drawing and the shader framework will be discussed in a followup post.

Render passes

The rasterizer hardware needs to be prepared for rendering before we can start drawing. Common operations include binding render target and depth-stencil views and clearing color and depth. Clear might trigger a fast clear elimination if the clear color is changed. This is transparently handled by the clear command. On mobile GPUs, tiles are stored from on-chip storage to VRAM during rendering. Vulkan, Metal and WebGPU use render pass abstraction for clearing, loading and storing the render target. DirectX 12 added render pass support in the 2018 update in order to optimize rendering on the latest Intel (Gen11) and the Qualcomm (Adreno 630) GPUs. The render pass abstraction doesn’t add notable API complexity, so it's an easy choice for a modern cross platform API.

DirectX12 has render target views and depth-stencil views, and separate descriptor heaps to store them. This is just an API abstraction. These heaps are simply CPU memory allocated by the driver. Render target and depth-stencil views are not GPU descriptors. The rasterizer API is not bindless. The CPU driver sets up the rasterizer using command packets. In Vulkan and Metal you pass the existing texture/view objects to the beginRenderPass directly. The driver gets the required information from the texture object behind the scenes. Our proposed GpuTexture object fits this job. Rasterization output is the main reason we still need a CPU-side texture object. We write texture descriptors directly into GPU memory. The CPU side driver can’t access those.

GpuRenderPassDesc renderPassDesc =
{
    .depthTarget = {.texture = deptStencilTexture, .loadOp = CLEAR, .storeOp = DONT_CARE, .clearValue = 1.0f},
    .stencilTarget = {.texture = deptStencilTexture, .loadOp = CLEAR, .storeOp = DONT_CARE, .clearValue = 0},
    .colorTargets =
    {
        {.texture = gBufferColor, .loadOp = LOAD, .storeOp = STORE, .clearColor = {0,0,0,0}},
        {.texture = gBufferNormal, .loadOp = LOAD, .storeOp = STORE, .clearColor = {0,0,0,0}},
        {.texture = gBufferPBR, .loadOp = LOAD, .storeOp = STORE, .clearColor = {0,0,0,0}}
    }
};

gpuBeginRenderPass(commandBuffer, renderPassDesc);
// Add draw calls here!
gpuEndRenderPass(commandBuffer);

It would be nice to have bindless render passes, bindless/indirect (multi-)clear commands, indirect scissor/viewport rectangles (array), etc. Unfortunately many GPUs today still need the CPU driver to set up their rasterizer.

A note about barriers: Render pass begin/end commands don’t automatically emit barriers. The user can render multiple render passes simultaneously, if they write to disjoint render targets. A barrier between the raster output stage (or later) and the consumer stage will flush the tiny ROP caches if the GPU architecture needs it. Not having an automatic barrier between the render passes is also crucial for efficient depth prepass implementations (ROP caches are not flushed needlessly).

GPU-based clay simulation and ray-tracing tech in Claybook (Sebastian Aaltonen, GDC 2018): I optimized the Unreal Engine 4 console barrier implementations (Xbox One, PS4) to allow render target overlap. The barrier stall is avoided.

Prototype API

My prototype API fits in one screen: 150 lines of code. The blog post is titled “No Graphics API”. That’s obviously an impossible goal today, but we got close enough. WebGPU has a smaller feature set and features a ~2700 line API (Emscripten C header). Vulkan header is ~20,000 lines, but it supports ray-tracing, and many other features our minimalistic API doesn’t yet support. We didn’t have to trade off performance to achieve the reduction in API complexity. For this feature set, our API offers more flexibility than existing APIs. A fully extended summer 2025 Vulkan 1.4 can do all the same things in practice, but is significantly more complex to use and has more API overhead.

// Opaque handles
struct GpuPipeline;
struct GpuTexture;
struct GpuDepthStencilState;
struct GpuBlendState;
struct GpuQueue;
struct GpuCommandBuffer;
struct GpuSemaphore;

// Enums
enum MEMORY { MEMORY_DEFAULT, MEMORY_GPU, MEMORY_READBACK };
enum CULL { CULL_CCW, CULL_CW, CULL_ALL, CULL_NONE };
enum DEPTH_FLAGS { DEPTH_READ = 0x1, DEPTH_WRITE = 0x2 };
enum OP { OP_NEVER, OP_LESS, OP_EQUAL, OP_LESS_EQUAL, OP_GREATER, OP_NOT_EQUAL, OP_GREATER_EQUAL, OP_ALWAYS }; 
enum BLEND { BLEND_ADD, BLEND_SUBTRACT, BLEND_REV_SUBTRACT, BLEND_MIN, BLEND_MAX };
enum FACTOR { FACTOR_ZERO, FACTOR_ONE, FACTOR_SRC_COLOR, FACTOR_DST_COLOR, FACTOR_SRC_ALPHA, ... };
enum TOPOLOGY { TOPOLOGY_TRIANGLE_LIST, TOPOLOGY_TRIANGLE_STRIP, TOPOLOGY_TRIANGLE_FAN };
enum TEXTURE { TEXTURE_1D, TEXTURE_2D, TEXTURE_3D, TEXTURE_CUBE, TEXTURE_2D_ARRAY, TEXTURE_CUBE_ARRAY };
enum FORMAT { FORMAT_NONE, FORMAT_RGBA8_UNORM, FORMAT_D32_FLOAT, FORMAT_RG11B10_FLOAT, FORMAT_RGB10_A2_UNORM, ... };
enum USAGE_FLAGS { USAGE_SAMPLED, USAGE_STORAGE, USAGE_COLOR_ATTACHMENT, USAGE_DEPTH_STENCIL_ATTACHMENT, ... };
enum STAGE { STAGE_TRANSFER, STAGE_COMPUTE, STAGE_RASTER_COLOR_OUT, STAGE_PIXEL_SHADER, STAGE_VERTEX_SHADER, ... };
enum HAZARD_FLAGS { HAZARD_DRAW_ARGUMENTS = 0x1, HAZARD_DESCRIPTORS = 0x2, , HAZARD_DEPTH_STENCIL = 0x4 };
enum SIGNAL { SIGNAL_ATOMIC_SET, SIGNAL_ATOMIC_MAX, SIGNAL_ATOMIC_OR, ... };

// Structs
struct Stencil 
{
    OP test = OP_ALWAYS,
    OP failOp = OP_KEEP;
    OP passOp = OP_KEEP;
    OP depthFailOp = OP_KEEP;
    uint8 reference = 0;
};

struct GpuDepthStencilDesc 
{
    DEPTH_FLAGS depthMode = 0;
    OP depthTest = OP_ALWAYS;
    float depthBias = 0.0f;
    float depthBiasSlopeFactor = 0.0f;
    float depthBiasClamp = 0.0f;
    uint8 stencilReadMask = 0xff;
    uint8 stencilWriteMask = 0xff;
    Stencil stencilFront;
    Stencil stencilBack;
};

struct GpuBlendDesc
{
    BLEND colorOp = BLEND_ADD,
    FACTOR srcColorFactor = FACTOR_ONE;
    FACTOR dstColorFactor = FACTOR_ZERO;
    BLEND alphaOp = BLEND_ADD;
    FACTOR srcAlphaFactor = FACTOR_ONE;
    FACTOR dstAlphaFactor = FACTOR_ZERO;
    uint8 colorWriteMask = 0xf;
};

struct ColorTarget {
    FORMAT format = FORMAT_NONE;
    uint8 writeMask = 0xf;
};

struct GpuRasterDesc
{
    TOPOLOGY topology = TOPOLOGY_TRIANGLE_LIST;
    CULL cull = CULL_NONE;
    bool alphaToCoverage = false;
    bool supportDualSourceBlending = false;
    uint8 sampleCount = 1;
    FORMAT depthFormat = FORMAT_NONE;
    FORMAT stencilFormat = FORMAT_NONE;
    Span<ColorTarget> colorTargets = {};
    GpuBlendDesc* blendstate = nullptr; // optional embedded blend state
};

struct GpuTextureDesc
{ 
    TEXTURE type = TEXTURE_2D;
    uint32x3 dimensions;
    uint32 mipCount = 1;
    uint32 layerCount = 1;
    uint32 sampleCount = 1;
    FORMAT format = FORMAT_NONE; 
    USAGE_FLAGS usage = 0;
};

struct GpuViewDesc 
{
    FORMAT format = FORMAT_NONE;
    uint8 baseMip = 0;
    uint8 mipCount = ALL_MIPS;
    uint16 baseLayer = 0;
    uint16 layerCount = ALL_LAYERS;
};

struct GpuTextureSizeAlign { size_t size; size_t align; };
struct GpuTextureDescriptor { uint64[4] data; };

// Memory
void* gpuMalloc(size_t bytes, MEMORY memory = MEMORY_DEFAULT);
void* gpuMalloc(size_t bytes, size_t align, MEMORY memory = MEMORY_DEFAULT);
void gpuFree(void *ptr);
void* gpuHostToDevicePointer(void *ptr);

// Textures
GpuTextureSizeAlign gpuTextureSizeAlign(GpuTextureDesc desc);
GpuTexture gpuCreateTexture(GpuTextureDesc desc, void* ptrGpu);
GpuTextureDescriptor gpuTextureViewDescriptor(GpuTexture texture, GpuViewDesc desc);
GpuTextureDescriptor gpuRWTextureViewDescriptor(GpuTexture texture, GpuViewDesc desc);

// Pipelines
GpuPipeline gpuCreateComputePipeline(ByteSpan computeIR);
GpuPipeline gpuCreateGraphicsPipeline(ByteSpan vertexIR, ByteSpan pixelIR, GpuRasterDesc desc);
GpuPipeline gpuCreateGraphicsMeshletPipeline(ByteSpan meshletIR, ByteSpan pixelIR, GpuRasterDesc desc);
void gpuFreePipeline(GpuPipeline pipeline);

// State objects
GpuDepthStencilState gpuCreateDepthStencilState(GpuDepthStencilDesc desc);
GpuBlendState gpuCreateBlendState(GpuBlendDesc desc);
void gpuFreeDepthStencilState(GpuDepthStencilState state);
void gpuFreeBlendState(GpuBlendState state);

// Queue
GpuQueue gpuCreateQueue(/* DEVICE & QUEUE CREATION DETAILS OMITTED */);
GpuCommandBuffer gpuStartCommandRecording(GpuQueue queue);
void gpuSubmit(GpuQueue queue, Span<GpuCommandBuffer> commandBuffers);

// Semaphores
GpuSemaphore gpuCreateSemaphore(uint64 initValue);
void gpuWaitSemaphore(GpuSemaphore sema, uint64 value);
void gpuDestroySemaphore(GpuSemaphore sema);

// Commands
void gpuMemCpy(GpuCommandBuffer cb, void* destGpu, void* srcGpu,);
void gpuCopyToTexture(GpuCommandBuffer cb, void* destGpu, void* srcGpu, GpuTexture texture);
void gpuCopyFromTexture(GpuCommandBuffer cb, void* destGpu, void* srcGpu, GpuTexture texture);

void gpuSetActiveTextureHeapPtr(GpuCommandBuffer cb, void *ptrGpu);

void gpuBarrier(GpuCommandBuffer cb, STAGE before, STAGE after, HAZARD_FLAGS hazards = 0);
void gpuSignalAfter(GpuCommandBuffer cb, STAGE before, void *ptrGpu, uint64 value, SIGNAL signal);
void gpuWaitBefore(GpuCommandBuffer cb, STAGE after, void *ptrGpu, uint64 value, OP op, HAZARD_FLAGS hazards = 0, uint64 mask = ~0);

void gpuSetPipeline(GpuCommandBuffer cb, GpuPipeline pipeline);
void gpuSetDepthStencilState(GpuCommandBuffer cb, GpuDepthStencilState state);
void gpuSetBlendState(GpuCommandBuffer cb, GpuBlendState state); 

void gpuDispatch(GpuCommandBuffer cb, void* dataGpu, uvec3 gridDimensions);
void gpuDispatchIndirect(GpuCommandBuffer cb, void* dataGpu, void* gridDimensionsGpu);

void gpuBeginRenderPass(GpuCommandBuffer cb, GpuRenderPassDesc desc);
void gpuEndRenderPass(GpuCommandBuffer cb);

void gpuDrawIndexedInstanced(GpuCommandBuffer cb, void* vertexDataGpu, void* pixelDataGpu, void* indicesGpu, uint32 indexCount, uint32 instanceCount);
void gpuDrawIndexedInstancedIndirect(GpuCommandBuffer cb, void* vertexDataGpu, void* pixelDataGpu, void* indicesGpu, void* argsGpu);
void gpuDrawIndexedInstancedIndirectMulti(GpuCommandBuffer cb, void* dataVxGpu, uint32 vxStride, void* dataPxGpu, uint32 pxStride, void* argsGpu, void* drawCountGpu);

void gpuDrawMeshlets(GpuCommandBuffer cb, void* meshletDataGpu, void* pixelDataGpu, uvec3 dim);
void gpuDrawMeshletsIndirect(GpuCommandBuffer cb, void* meshletDataGpu, void* pixelDataGpu, void *dimGpu);

Tooling

How can we debug code that doesn’t bind buffer and texture objects and doesn’t call an API to describe the memory layout explicitly? C/C++ debuggers have been doing that for decades. There’s no special operating system APIs for describing your software’s memory layout. The debugger is able to follow 64-bit pointer chains and use the debug symbol data provided by your compiler. This includes the memory layouts of your structs and classes. CUDA and Metal use C/C++ based shading languages with full 64-bit pointer semantics. Both have robust debuggers that traverse pointer chains without issues. The texture descriptor heap is just GPU memory. The debugger can index it, load a texture descriptor, show the descriptor data and visualize the texels. All of this works already in the Xcode Metal debugger. Click on a texture or a sampler handle in any struct in any GPU address. Debugger will visualize it.

Modern GPUs virtualize memory. Each process has their own page table. The GPU capture has a separate replayer process with its own virtual address space. If the replayer would naively replay all the allocations, it would get a different GPU virtual address for each memory allocation. This was fine for legacy APIs as it wasn’t possible to directly store GPU addresses in your data structures. A modern API needs special replay memory allocation APIs that force the replayer to mirror the exact GPU virtual memory layout. DX12 and Vulkan BDA have public APIs for this: RecreateAt and VkMemoryOpaqueCaptureAddressAllocateInfo. Metal and CUDA debuggers do the same using internal undocumented APIs. A public API is preferable as it allows open source tools like RenderDoc to function.

Don’t raw pointers bring security concerns? Can’t you just read/write other apps' memory? This is not possible due to virtual memory. You can only access your own memory pages. If you accidentally use a stale pointer or overflow, you will get a page fault. Page faults are possible with existing buffer based APIs. DirectX 12 and Vulkan don’t clamp your storage (byteaddress/structured) buffer addresses. OOB causes a page fault. Users can also accidentally free a memory heap and continue using stale buffer or texture descriptors to get a page fault. Nothing really changes. An access to an unmapped region is a page fault and the application crashes. This is familiar to C/C++ programmers. If you want robustness, you can use ptr + size pairs. That’s exactly how WebGPU is implemented. The WebGPU shader compiler (Tint or Naga) emits an extra clamp instruction for each buffer access, including vertex accesses (index buffer value out of bounds). WebGL didn’t allow shading index buffer data with other data. WebGL scanned through the indices on the CPU side (making index buffer update very slow). Back then custom vertex fetch was not possible. The hardware page faulted before the shader even ran.

Translation layers

Being able to run existing software is crucial. Translation layers such as ANGLE, Proton and MoltenVK play a crucial role in the portability and deprecation process of legacy APIs. Let’s talk about translating DirectX 12, Vulkan and Metal to our new API.

MoltenVK (Vulkan to Metal translation layer) proves that Vulkan’s buffer centric API can be translated to Metal’s 64-bit pointer based ecosystem. MoltenVK translates Vulkan’s descriptor sets into Metal’s argument buffers. The generated argument buffers are standard GPU structs containing a 64-bit GPU pointer per buffer binding and a 64-bit texture ID per texture binding. We can do better by allocating a contiguous range of texture descriptors in our texture heap for each descriptor set, and storing a single 32-bit base index instead of a 64-bit texture ID for each texture binding. This is possible since our API has a user managed texture heap unlike Metal.

MoltenVK maps descriptor sets to Metal API root bind slots. We generate a root struct with up to eight 64-bit pointer fields, each pointing to a descriptor set struct (see above). Root constants are translated into value fields and root descriptors (root buffers) are translated into 64-bit pointers. The efficiency should be identical, assuming the GPU driver preloads our root struct fields into uniform/scalar registers (as discussed in the root arguments chapter).

Our API uses 64-bit pointer semantics like Metal. We can use the same techniques employed by MoltenVK to translate the buffer load/store instructions in the shader. MoltenVK also supports translating Vulkan’s new buffer device address extension.

Proton (DX12 to Vulkan translation layer) proves that DirectX 12 SM 6.6 descriptor heap can be translated to Vulkan’s new descriptor buffer extension. Proton also translates other DirectX 12 features to Vulkan. We have already shown that Vulkan to Metal translation is possible with MoltenVK, transitively proving that translation from DirectX 12 to Metal should be possible. The biggest missing feature in MoltenVK is the SM 6.6 style descriptor heap (Vulkan’s descriptor buffer extension). Metal doesn’t expose the descriptor heap directly to the user. Our new proposed API has no such limitation. Our descriptor heap semantics are a superset to SM 6.6 descriptor heap and a close match to Vulkan’s descriptor buffer extension. Translation is straightforward. Vulkan’s extension also adds a special flag for descriptor invalidate, matching our HAZARD_DESCRIPTORS. DirectX 12 descriptor heap API is easy to translate, as it’s just a thin wrapper over the raw descriptor array in GPU memory.

To support Metal 4.0, we need to implement Metal’s driver managed texture descriptor heap. This can be implemented using a simple freelist over our texture heap. Metal uses 64-bit texture handles which are implemented as direct heap indices on modern Apple Silicon devices. Metal allows using the texture handles in shaders directly as textures. This is syntactic sugar for textureHeap[uint64(handle)]. A Metal texture handle is translated into uint64 by our shader translator, maintaining identical GPU memory layout.

Our API doesn’t support vertex buffers. WebGPU doesn’t use hardware vertex buffers either, yet it implements the classic vertex buffer abstraction. WGSL shader translator (Tint or Naga) adds one storage buffer binding per vertex stream and emits vertex load instructions in the beginning of the vertex shader. Custom vertex fetch allows emitting clamp instructions to avoid OOB behavior. A misbehaving website can’t crash the web browser. Our own shader translator adds a 64-bit pointer to the root struct for each vertex stream, generates a struct matching its layout and emits vertex struct load instructions in the beginning of the vertex shader.

We have shown that it’s possible to write translation layers to run DirectX 12, Vulkan and Metal applications on top of our new API. Since WebGPU is implemented on top of these APIs by browsers, we can run WebGPU applications too.

Min spec hardware

Nvidia Turing (RTX 2000 series, 2018) introduced ray-tracing, tensor cores, mesh shaders, low latency raw memory paths, bigger & faster caches, scalar unit, secondary integer pipeline and many other future looking features. Officially PCIe ReBAR support launched with RTX 3000 series, but there exists hacked Turing drivers that support it too, indicating that the hardware is capable of it. This 7 year old GPU supports everything we need. Nvidia just ended GTX 1000 series driver support in fall 2025. All currently supported Nvidia GPUs could be supported by our new API.

AMD RDNA2 (RX 6000 series, 2020) matched Nvidia’s feature set with ray-tracing and mesh shaders. One year earlier, RDNA 1 introduced coherent L2$, new L1$ level, fast L0$, generic DCC read/write paths, fastpath unfiltered loads and a modern SIMD32 architecture. PCIe ReBAR is officially supported (brand name “Smart Access Memory”). This 5 year old GPU supports everything we need. AMD ended GCN driver support already in 2021. Today RDNA 1 & RDNA 2 only receive bug fixes and security updates, RDNA 3 is the oldest GPU receiving game optimizations. All the currently supported AMD GPUs could be supported by our API.

Intel Alchemist / Xe1 (2022) were the first Intel chips with SM 6.6 global indexable heap support. These chips also support ray-tracing, mesh shaders, PCIe ReBAR (discrete) and UMA (integrated). These 3 year old Intel GPUs support everything we need.

Apple M1 / A14 (MacBook M1, iPhone 12, 2020) support Metal 4.0. Metal 4.0 guarantees GPU memory visibility to CPU (UMA on both phones and computers), and allows the user to write 64-bit pointers and 64-bit texture handles directly into GPU memory. Metal 4.0 has a new residency set API, solving a crucial usability issue with bindless resource management in the old useResource/useHeap APIs. iOS 26 still supports iPhone 11. Developers are not allowed to ship apps that require Metal 4.0 just yet. iOS 27 likely deprecates iPhone 11 support next year. On Mac, if you drop Intel Mac support, you have guaranteed Metal 4.0 support. M1-M5 = 5 generations = 5 years.

ARM Mali-G710 (2021) is ARMs first modern architecture. It introduced their new command stream frontend (CSF), reducing the CPU dependency of draw call building and adding crucial features like multi-draw indirect and compute queues. Non-uniform index texture sampling is significantly faster and the AFBC lossless compressor now supports 16-bit floating point targets. G710 supports Vulkan BDA and descriptor buffer extensions and is capable of supporting the new 2025 unified image layout extension with future drivers. The Mali-G715 (2022) introduced support for ray-tracing.

Qualcomm Adreno 650 (2019) supports Vulkan BDA, descriptor buffer and unified image layout extensions, 16-bit storage/math, dynamic rendering and extended dynamic state with the latest Turnip open source drivers. Adreno 740 (2022) introduced support for ray-tracing.

PowerVR DXT (Pixel 10, 2025) is PowerVRs first architecture that supports Vulkan descriptor buffer and buffer device address extensions. It also supports 64-bit atomics, 8-bit and 16-bit storage/math, dynamic rendering, extended dynamic state and all the other features we require.

Conclusion

Modern graphics API have improved gradually in the past 10 years. Six years after DirectX 12 launch, SM 6.6 (2021) introduced the modern global texture heap, allowing fully bindless renderer design. Metal 4.0 (2025) and CUDA have a clean 64-bit pointer based shader architecture with minimal binding API surface. Vulkan has the most restrictive standard, but extensions such as buffer device access (2020), descriptor buffer (2022) and unified image layouts (2025) add support for modern bindless infrastructure, but tools are still lagging behind. As of today, there’s no single API that meets all our requirements, but if we combine their best bits together, we can build the perfect API for modern hardware.

10 years ago, modern APIs were designed for CPU-driven binding models. New bindless features were presented as optional features and extensions. A clean break would improve the usability and reduce the API bloat and driver complexity significantly. It’s extremely difficult to get the whole industry behind a brand new API. I am hoping that vendors are willing to drop backwards compatibility in their new major API versions (Vulkan 2.0, DirectX 13) to embrace the fully bindless GPU architecture we have today. A new bindless API design would solve the mismatch between the API and the game engine RHI, allowing us to get rid of the hash maps and fine grained resource tracking. Metal 4.0 is close to this goal, but it is still missing the global indexable texture heap. A 64-bit texture handle can’t represent a range of textures.

HLSL and GLSL shading languages were designed over 20 years ago as a framework of 1:1 elementwise transform functions (vertex, pixel, geometry, hull, domain, etc). Memory access is abstracted and array handling is cumbersome as there’s no support for pointers. Despite 20 years of existence, HLSL and GLSL have failed to accumulate a library ecosystem. CUDA in contrast is a composable language exposing memory directly and new features (such as AI tensor cores) though intrinsics. CUDA has a broad library ecosystem, which has propelled Nvidia into $4T valuation. We should learn from it.

WebGPU note: WebGPU design is based on 10 year old core Vulkan 1.0 with extra restrictions. WebGPU doesn’t support bindless resources, 64-bit GPU pointers or persistently mapped GPU memory. It feels like a mix between DirectX 11 and Vulkan 1.0. It is a great improvement for web graphics, but doesn’t meet modern bindless API standards. I will discuss WebGPU in a separate blog post.

My prototype API shows what is achievable with modern GPU architectures today, if we mix the best bits from all the latest APIs. It is possible to build an API that is simpler to use than DirectX 11 and Metal 1.0, yet it offers better performance and flexibility than DirectX 12 and Vulkan. We should embrace the modern bindless hardware.

Appendix

A simple user land GPU bump allocator used in all example code. We call gpuHostToDevicePointer once in the temp allocator constructor. We can perform standard pointer arithmetic (such as offset) on GPU pointers. Traditional Vulkan/DX12 buffer APIs require a separate offset. This simplifies the API and user code (ptr vs handle+offset pair). A production ready temp allocator would implement overflow handing (grow, flush, etc).

template<typename T>
struct GPUTempAllocation<T>
{
    T* cpu;
    T* gpu;
}

struct GPUBumpAllocator
{
    uint8 *cpu;
    uint8 *gpu;
    uint32 offset = 0;
    uint32 size;

    TempBumpAllocator(uint32 size) : size(size)
    {
        cpu = gpuMalloc(size);
        gpu = gpuHostToDevicePointer(cpu);
    }

    TempAllocation<uint8> alloc(int bytes, int align = 16)
    {
        offset = alignRoundUp(offset, align);
        if (offset + bytes > size) offset = 0; // Simple ring wrap (no overflow detection)
        TempAllocation<uint8> alloc = { .cpu = cpu + offset, . gpu = gpu + offset };
        offset += bytes;
        return alloc;
    }

    template<typename T> 
    T* alloc(int count = 1)
    {
        TempAllocation<uint8> mem = alloc(sizeof(T) * count, alignof(T));
        return TempAllocation<T> { .cpu = (T*)mem.cpu, . gpu = (T*)mem.gpu };
    }
};

Too Fast to Think: The Hidden Fatigue of AI Vibe Coding

Hacker News
www.tabulamag.com
2025-12-16 18:32:46
Comments...
Original Article

After vibe coding for some time, I feel fatigue. I’m coding Marvai - a package manager for prompts on my own, with a combination of Claude Code and Cursor.

  • I use Claude Code for code generation, bug fixing , test writing, test fixing, security checks etc.

  • Claude Code is especially useful to fix linting errors from nilaway or staticcheck - for a developer those are boring and tedious.

  • I use Cursor for augmented coding, generate functions, adapt copy&pasted code, for refactoring, fix error handling, and many other tedious tasks.

  • I have been a coder for 40 years and I have seen many tools for developers that haven’t worked, like Model Driven Development MDD/MDA and executable UML.

  • With AI I’m now much faster at generating code and building features than I ever was before. The combination of Claude Code and Cursor is a real speedup.

  • I encountered a new phenomenon.

  • Again and again I feel fatigue. I finish a feature, and another feature, concetrate on reviewing the code the AI generated, and fix a bug and finish a feature with such velocity and I feel fatigue after some hours - sometimes as soon as one hour. AI working at such speed, finishing things to accept or review, feels too much for my brain to process or keep up with. I need to pause for some time before I can start again.

  • I haven’t felt this before as a developer.

  • I first encountered the concept of cognitive load in the book Team Toplogies . The idea there is to structure teams in a way that the cognitive load for developers is not too small and not to big. The more responsibilities a team and it’s members get, the bigger the cognitive load. And if you put many different topics on a team, the cognitive load for the team becomes too big for the team to work.

  • As a young adult I was working in a plastic factory, sitting at a machine. The machine produced vacuum cleaner cases, had it’s rhythm, it finished vacuum cleaner cases on it’s own schedule, made a “PING” when finished, opened the door and I had to grab the casing and put it down (and package it in fine paper etc. which took some time). The machine would close while I was finishing the casing. And for some time it was stressful, working to the rhythm of the machine. Then I got accustomed to the speed and rhythm, until my boss increased the frequency. Living by machine time is what I sometimes feel with Vibe Coding and Cursor generating code or Claude fixing a bug .

  • I had waiting times with compiling and waited for the machine to finish, but it feels differently, with vibe coding it feels like the machine is in control not me.

  • With traditional coding, the speed of your output matches the complexity of the task and the speed of your coding. This gives your brain time to process what is happening. With vibe coding the coding happens so fast, that my brain can’t process what is happening in real time, and thoughts are getting clogged up. Complex tasks are compressed into seconds or minutes.

  • My brain does not get the baking time to mentally process architecture, decisions and edge cases the AI creates - not enough time to put the AI output into one coherent picture. I’m running a marathon at the pace of a sprint - speeds don’t match.

  • One reason developers are developers is the dopamine loop.

  • You write code, it doesn’t work, you fix it, it works, great! Dopamine rush. Several dozens or a hundred times a day. Now this loop speeds up. Faster and faster we run around that loop, and get washed with Dopamine - and your brain gets overwhelmed. And stress hormones at the same time! You get fatigue - instead of the usually happiness and satisfaction of coding.

  • With coding there is a limit to context switching. A context switch in coding is like dumping the cache of your brain and reloading the cache of your brain with a new context. This takes time and energy. You need to build a mental model of the code , to decide what to change and how to change it and then writing the changes out into source code, the essence of coding.

  • With vibe coding the frequency of content switches speeds up tremendously - with the AI fixing and creating many different things in different packages or modules with one go. Even when I <tab>, <tab>, <tab> in Cursor, each change is a micro-content switch from function to function.

  • Each context switch takes energy, every context switch is heavy lifting for your brain. Normally this materializes as the fact that it takes time to context switch. With AI in the driver seat, context switches are forced on you faster and faster.

  • When each context switch takes energy, fast energy switches from feature delivered to feature delivered, vibe coding drains your energy - fatigue!

  • With AI it seems we all become managers, we all become reviewers. The core of the role is changing from turning requirements into code to managing AI output - more like a team lead manages the output of a team, but on a much deeper level. Your responsibility grows to manage a team of five with vibe coding, while still being a developer being responsible for the code. It’s like being a traffic officer in the middle of a busy intersection - which is a stressful job on it’s own - while also overseeing five intersections in parallel.

  • Reviewing, directing and guiding an AI puts more stress on you than writing code, where your writing matches your thinking and doesn’t jump ahead, with you in a rush to catch up.

  • What can be said:

    • Me and many more I assume feel fatigue from vibe coding

    • We need deliberate pacing when working with AI tools

    • We need AI-aware retrospectives to understand what is going on - perhaps having a daily retrospective to get the mind and the code in sync again

    • We need to be aware of new mental health challenges for AI coders

    • We might need to let go of micro managing AIs and trust the AI more - stop trying to bridge the gap between managing an AI and controlling it’s output

  • I don’t think we’ve figured out yet where this is going. AI has made us faster than we’ve ever been, but our brains haven’t caught up to the pace. We’re like early pilots flying with autopilot—capable, but drained. We need new rhythms, new boundaries, and new ways of thinking about what it means to “code.”

  • Maybe the future of coding isn’t just faster. Maybe it’s also slower in a way, on purpose.

  • GPT Image 1.5

    Hacker News
    openai.com
    2025-12-16 18:07:07
    Comments...

    Hell Gate's 2025 Last-Minute Gift Guide

    hellgate
    hellgatenyc.com
    2025-12-16 17:50:36
    Clock's a-ticking, but don't worry, the tastemakers of Hell Gate have got you covered....
    Original Article

    It's the most wonderful time of the year—the panic-inducing week when you realize you somehow once again forgot to buy a gift for Aunt Tina (ugh)/your dogwalker (yes, that's how much your dog loves her)/your college best friend who lives across the country now and has three kids but every year still gets you the most thoughtful present (why!!!).

    Don't worry, Hell Gate has got you covered. Until Sunday, December 21, you can buy a discounted Supporter gift subscription (that comes with the beloved Hell Gate hat!) during our Hats for the Holidays sale . You're welcome, Aunt Tina!

    If you're still looking for some last-minute gifts after taking advantage of our big sale on gift subscriptions , here are the best recommendations from Hell Gate's very discerning staff. (We love seltzer, what can we say...)

    Note: Like last year, these are not affiliate links. Not because we don't like money, just because we forgot to figure out how to do that.

    An unnecessarily sleek tool kit

    Recommended for: your friend whose wardrobe is 90 percent COS and just bought an IKEA RIMFORSA work bench

    Give us your email to read the full story

    Sign up now for our free newsletters.

    Sign up

    🪪 Age Verification Is Coming for the Internet | EFFector 37.18

    Electronic Frontier Foundation
    www.eff.org
    2025-12-16 17:44:41
    The final EFFector of 2025 is here! Just in time to keep you up-to-date on the latests happenings in the fight for privacy and free speech online. In this latest issue, we're sharing how to spot sneaky ALPR cameras at the U.S. border, covering a host of new resources on age verification laws, and ex...
    Original Article

    The final EFFector of 2025 is here! Just in time to keep you up-to-date on the latests happenings in the fight for privacy and free speech online.

    In this latest issue , we're sharing how to spot sneaky ALPR cameras at the U.S. border, covering a host of new resources on age verification laws , and explaining why AI companies need to protect chatbot logs from bulk surveillance.

    Prefer to listen in? Check out our audio companion, where EFF Activist Molly Buckley explains our new resource explaining age verification laws and how you can fight back . Catch the conversation on YouTube or the Internet Archive .

    LISTEN TO EFFECTOR

    EFFECTOR 37.18 - 🪪 AGE VERIFICATION IS COMING FOR THE INTERNET

    Since 1990 EFF has published EFFector to help keep readers on the bleeding edge of their digital rights. We know that the intersection of technology, civil liberties, human rights, and the law can be complicated, so EFFector is a great way to stay on top of things. The newsletter is chock full of links to updates, announcements, blog posts, and other stories to help keep readers—and listeners—up to date on the movement to protect online privacy and free expression.

    Thank you to the supporters around the world who make our work possible! If you're not a member yet, join EFF today to help us fight for a brighter digital future.

    AI is wiping out entry-level tech jobs, leaving graduates stranded

    Hacker News
    restofworld.org
    2025-12-16 17:37:41
    Comments...
    Original Article

    In 2022, Rishabh Mishra joined a high-ranking engineering college in India’s Jabalpur with the most predictable dream in global tech: study computer science, write code, and one day make it to Silicon Valley.

    Three years later, Mishra faces a sobering reality.

    Artificial intelligence has gutted entry-level roles in the tech industry that Mishra and his classmates were counting on. Among his 400 classmates at the Indian Institute of Information Technology, Design and Manufacturing, fewer than 25% have secured job offers. His course ends in May 2026, and there’s a sense of panic on the campus.

    Listen: Rishabh Mishra describes how the reality of tech jobs is very different from what he was told

    “It is really bad out there,” Mishra told Rest of World. “Everyone is so panicked — even our juniors. As the degree end nears, the anxiety is heightened among all of us.” Some of his classmates are exploring the option of pursuing higher studies before entering the job market. “But after one year, if you return to the job market, your degree is even more irrelevant,” he said.

    Students at engineering colleges in India, China, Dubai, and Kenya are facing a “jobpocalypse” as artificial intelligence replaces humans in entry-level roles. Tasks once assigned to fresh graduates, such as debugging, testing, and routine software maintenance, are now increasingly automated.

    Over the last three years, the number of fresh graduates hired by big tech companies globally has declined by more than 50%, according to a report published by SignalFire, a San Francisco-based venture capital firm. Even though hiring rebounded slightly in 2024, only 7% of new hires were recent graduates. As many as 37% of managers said they’d rather use AI than hire a Gen Z employee.

    “As demand for junior roles declines, even highly credentialed engineering graduates are struggling to break into tech, especially at the Big Tech companies,” the report said.

    Even highly credentialed engineering graduates are struggling to break into tech.”

    Indian IT services companies have reduced entry-level roles by 20%–25% thanks to automation and AI, consulting firm EY said in a report last month . Job platforms like LinkedIn, Indeed, and Eures noted a 35% decline in junior tech positions across major EU countries during 2024.

    The World Economic Forum’s Future of Jobs Report 2025 warned that 40% of employers expect to reduce staff where AI can automate tasks.

    “Five years ago, there was a real war for [coders and developers]. There was bidding to hire,” and 90% of the hires were for off-the-shelf technical roles, or positions that utilize ready-made technology products rather than requiring in-house development, said Vahid Haghzare, director at IT hiring firm Silicon Valley Associates Recruitment in Dubai .

    Since the rise of AI, “it has dropped dramatically,” he said. “I don’t even think it’s touching 5%. It’s almost completely vanished.” The company headhunts workers from multiple countries including China, Singapore, and the U.K.

    While high-paying jobs at coveted brands like Apple, Microsoft, Amazon, and Meta rarely cross his desk these days, companies that hire recent engineering graduates expect them to execute “additional responsibilities,” like managing a project or leading sales. “They have to face the customer and have customer communications and maybe even do some selling,” Haghzare said.

    Some engineering students have realigned their ambitions to meet such demands from employers. Nishant Kaushik, who studied computer science at another well-ranked college in eastern India, has decided to look for roles in sales or marketing.

    The rise of AI has also rendered engineering degrees less relevant: Workplace demands now differ from what is taught in colleges.

    When Rita Sande Lukale enrolled in an electronics engineering program at the Technical University of Kenya in 2021, she hoped to land a role in the system architecture sector after graduation. Over the past few years, however, she has seen such roles disappear.

    Listen: Rita Sande Lukale describes how AI has replaced humans in simple repetitive tasks.

    Entry-level jobs such as handling data logging, system diagnostics, or code writing have been replaced by AI, Lukale told Rest of World . Now, fresh graduates “must possess higher-level skills, necessary to understand algorithms and use the engineering judgement to troubleshoot the complex and automated systems,” she said.

    While she doesn’t consider AI to be a “job destroyer,” it has fundamentally changed the type of engineers that companies need to hire, Lukale said. She feels she needs to adapt and learn more to land a job.

    Not only are fresh graduates expected to understand and use the latest tools efficiently, “they are asked to up their output by 70% because ‘they are using AI,’” Liam Fallon, who heads the product division at GoodSpace AI, an AI-powered recruiting company, told Rest of World . As a result, students face a rapidly changing industry that requires them to upskill outside the curriculum on their own. Experts believe universities are unable to align their academic practices fast enough to meet AI-driven industry demands.

    The current system, where a student commits three to five years to learn computer science and then looks for a job, is “not sustainable,” Haghzare said. Students are “falling down a hole, and they don’t know how to get out of it.”

    The GitHub Actions control plane is no longer free

    Hacker News
    www.blacksmith.sh
    2025-12-16 17:37:34
    Comments...
    Original Article

    TL;DR

    GitHub is adding a $0.002-per-minute fee on all GitHub Actions usage, so the control plane is no longer free.

    What's happening?

    GitHub just announced changes to Actions pricing. Previously, GitHub Actions had a free control plane. That meant if you used GitHub Actions but ran jobs outside of GitHub-hosted runners, whether that’s on Blacksmith, on your own machines, or in your own AWS account, you paid nothing to GitHub for those minutes; you only paid for the compute.

    With this change, GitHub is introducing a $0.002 per-minute platform fee for all GitHub Actions usage.

    In practice, this means CI costs now have two components:

    • Compute costs (whoever runs your runners)
    • A flat GitHub platform fee, charged per minute of Actions usage

    These changes go into effect on March 1st, 2026.

    Our perspective on why they’re making these changes

    GitHub Actions has long had a graduation churn problem. As companies grow, their CI workloads become larger, more complex, and more expensive. At a certain scale, GitHub-hosted runners become both slow and costly, pushing teams to self-host or move to third-party runners like Blacksmith.

    Until now, that shift had an important side effect: companies could continue using the GitHub Actions control plane while paying GitHub nothing for CI execution. GitHub provided scheduling, orchestration, and workflow automation, but captured no revenue from some of its largest and fastest-growing customers.

    The new per-minute platform fee changes that. It directly monetizes the Actions control plane and establishes a floor on what GitHub earns from CI, regardless of where jobs run. In effect, self-hosting is no longer free.

    At the same time, GitHub reduced the price of GitHub-hosted runners. This isn’t accidental. Lower hosted runner prices make GitHub-hosted runners more attractive, while the platform fee introduces a new, unavoidable cost for self-hosting.

    From GitHub’s perspective, this is a rational move. Most Actions usage is concentrated on smaller runners, so the hosted runner price cuts likely don’t materially impact revenue. More importantly, GitHub is trading lower-margin compute revenue for higher-margin platform revenue.

    Hosted runners are fundamentally a compute business. The platform fee, by contrast, monetizes software without scaling infrastructure costs linearly. As CI usage grows, that revenue scales with significantly better unit economics.

    In the past, our customers have asked us how GitHub views third-party runners long-term. The platform fee largely answers that: GitHub now monetizes Actions usage regardless of where jobs run, aligning third-party runners like Blacksmith as ecosystem partners rather than workarounds.

    Per-minute CI pricing and its impact on self-hosting

    Before this change, self-hosting was a way to avoid paying GitHub entirely. That’s no longer true. Now, self-hosting retains the operational burden of running CI infrastructure while still incurring per-minute charges on GitHub.

    At that point, the primary variable you can still control is how many minutes your CI jobs consume. One approach is to run CI on infrastructure designed to minimize wall-clock time and eliminate redundant work. That’s the problem Blacksmith focuses on. In practice, this shows up in a few areas:

    • Faster machines. Blacksmith runs CI jobs on CPUs that have 50%+ higher single-core performance than GitHub-hosted runners. As part of ongoing fleet upgrades, we’re deploying even newer instances that add an additional 15–25% improvement, further reducing runtime for CPU-bound workloads.
    • Reusing work across runs. Docker layer caches are persisted across CI runs, allowing unchanged layers to be reused rather than rebuilt, cutting Docker build times from tens of minutes to seconds for many customers.
    • Container caching (beta). Service containers can be pre-hydrated on runners, removing repeated image pulls and extraction from the job startup path.

    With a per-minute platform fee, CI performance and cost are tightly coupled. The remaining lever is reducing CI time and total Actions.

    GitHub will begin charging for self-hosted action runners on March 2026

    Hacker News
    github.blog
    2025-12-16 17:32:38
    Comments...
    Original Article

    On January 1, 2026 , GitHub will reduce the price of GitHub-hosted runners by up to 39% depending on the machine type used . The free usage minute quotas will remain the same.

    On March 1, 2026, GitHub will introduce a new $0.002 per minute GitHub Actions cloud platform charge that will apply to self-hosted runner usage. Any usage subject to this charge will count toward the minutes included in your plan, as explained in our GitHub Actions billing documentation .

    Runner usage in public repositories will remain free. There will be no changes in price structure for GitHub Enterprise Server customers.

    Deeper investment in the Actions self-hosted experience

    We are increasing our investment into our self-hosted experience to ensure that we can provide autoscaling for scenarios beyond just Linux containers. This will include new approaches to scaling, new platform support, Windows support, and more as we move through the next 12 months.

    For more details about the product investments we’re making in Actions, please visit our Executive Insights page .

    Texas sues TV makers for taking screenshots of what people watch

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 17:29:22
    The Texas Attorney General sued five major television manufacturers, accusing them of illegally collecting their users' data by secretly recording what they watch using Automated Content Recognition (ACR) technology. [...]...
    Original Article

    TV remote

    The Texas Attorney General sued five major television manufacturers, accusing them of illegally collecting their users' data by secretly recording what they watch using Automated Content Recognition (ACR) technology.

    The lawsuits target Sony , Samsung , LG , and China-based companies Hisense and TCL Technology Group Corporation. Attorney General Ken Paxton's office also highlighted "serious concerns" about the two Chinese companies being required to follow China's National Security Law, which could give the Chinese government access to U.S. consumers' data.

    According to complaints filed this Monday in Texas state courts, the TV makers can allegedly use ACR technology to capture screenshots of television displays every 500 milliseconds, monitor the users' viewing activity in real time, and send this information back to the companies' servers without the users' knowledge or consent.

    Wiz

    Paxton's office described ACR technology as "an uninvited, invisible digital invader" designed to unlawfully collect personal data from smart televisions, alleging that the harvested information then gets sold to the highest bidder for ad targeting.

    "Companies, especially those connected to the Chinese Communist Party, have no business illegally recording Americans' devices inside their own homes," Paxton said .

    "This conduct is invasive, deceptive, and unlawful. The fundamental right to privacy will be protected in Texas because owning a television does not mean surrendering your personal information to Big Tech or foreign adversaries."

    Spokespersons for Sony, Samsung, Hisense, and TCL were not immediately available for comment when contacted by BleepingComputer earlier today.

    An LG spokesperson told BleepingComputer that, "As a matter of policy, LG Electronics USA does not generally comment on pending legal matters such as this."

    Almost a decade ago, in February 2017, Walmart-owned smart TV manufacturer Vizio paid $2.2 million to settle charges brought by the U.S. Federal Trade Commission and the New Jersey Attorney General that it collected viewing data from 11 million consumers without their knowledge or consent using a "Smart Interactivity feature.

    The two agencies said that since February 2014, Vizio and an affiliated company have manufactured and sold smart TVs (and retrofitted older models by installing tracking software remotely) that captured detailed information on what is being watched, including content from cable, streaming services, and DVDs.

    According to the complaint , Vizio also attached demographic information (such as sex, age, income, and education) to the collected data and sold it to third parties for targeted advertising purposes.

    In August 2022, the FTC published a consumer alert on securing Internet-connected devices, advising Americans to adjust the tracking settings on their smart TVs to protect their privacy.

    Update December 16, 12:43 EST: Added LG statement.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Determinate Nix 3.0

    Lobsters
    determinate.systems
    2025-12-16 17:25:04
    Comments...
    Original Article

    Today, we’re thrilled to announce the release of Determinate Nix 3.0 . Determinate Nix is our professionally maintained, security-focused distribution of Nix designed for organizations that cannot compromise on security, compliance, or performance.

    The version number matters. The Nix project has long intended to release version 3.0 when flakes are stable. With Determinate Nix 3.0, we’ve fulfilled that promise by offering a formal stability guarantee for flakes , making them production ready today.

    Why flake stability matters

    Flake stability is essential for organizations developing mission-critical systems. Although flakes enhance the core features of Nix—reproducible environments, predictable builds, composable configurations—by enforcing pinned dependencies, we continue to see deep uncertainty in the ecosystem due to flakes’ experimental status in upstream Nix. Without such a guarantee, organizations have expressed concerns about unpredictable breaking changes that could disrupt CI/CD pipelines, developer workflows, and production deployments.

    Our flake stability guarantee mitigates this risk, ensuring that your flake-based configurations function reliably through every update and enabling you to confidently adopt flakes for your most important projects today.

    Not a fork, a future-proof foundation

    Determinate Nix 3.0 isn’t a fork of Nix—it’s a downstream distribution that ensures full compatibility with the broader Nix ecosystem while incorporating the governance, security features, and performance optimizations necessary for mission-critical deployments.

    Building a better Nix

    Determinate Nix 3.0 represents a fundamental shift in how we deliver Nix to organizations with mission-critical requirements. We don’t just package upstream Nix—we build it ourselves from source in our secure, SLSA build level 3 , controlled infrastructure. This independent build process is crucial for the trust, security, and stability that regulated environments demand.

    By maintaining our distribution, we can innovate at the pace our customers need without being constrained by upstream development cycles. This means that Determinate Nix customers will soon have immediate access to critical features like:

    • Parallel evaluation of Nix expressions, which can deliver 3-4x faster performance for complex configurations.
    • Lazy trees , which can dramatically improve evaluation speed for large source repositories.

    Our approach combines the best of both worlds: Determinate Nix customers benefit from cutting-edge capabilities today while we maintain compatibility with the broader Nix ecosystem and actively contribute our improvements to upstream Nix, where the community ultimately determines their inclusion. Choosing Determinate Nix gives you priority access to innovations that address real-world needs while ensuring seamless integration with existing systems.

    Security that meets modern standards

    Security is central to every aspect of Determinate Nix and the entire Determinate platform .

    • SOC 2 Type II certified : Our infrastructure meets rigorous compliance standards.
    • Zero-trust security model : Federated authentication combined with fine-grained access controls with federated authentication.
    • Modern authentication : Shift from static secrets to secure, policy-driven identities through Identity and Access Management (IAM) roles and identity providers such as GitHub and Microsoft Entra .
    • Corporate network integration : Automatic certificate handling for ZScaler , Fortinet , and other security platforms.
    • Defined security response : We provide an SLA for vulnerability management, ensuring prompt and predictable security updates.
    • Controlled build environments : We build every component in our security-certified infrastructure, with critical components receiving additional signing and notarization.

    Stability you can rely on

    When your business relies on dependable software delivery, you require predictability and consistency:

    • Flake stability guarantee : While flakes remain experimental in upstream Nix, we provide a formal stability guarantee to ensure your workflows remain functional.
    • Rigorous release process : Every change undergoes comprehensive security, performance, and compatibility validation.
    • Customer roadmap : Transparent decision making and predictable release cycles.

    Deployment options for every need

    From bare metal to virtual machines, cloud to edge, Determinate Nix 3.0 delivers reproducible builds everywhere:

    • First-class macOS support : We provide full MDM integration, including partnerships with leading MDM providers like jamf , and ensure seamless macOS upgrades across your Apple ecosystem.
    • Performance optimizations : Ship code faster with improved build times and streamlined workflows.
    • Intelligent resource management : Automated garbage collection monitors system conditions to optimize maintenance.

    Why organizations choose Determinate Nix

    From financial services to healthcare to critical infrastructure, teams choose Determinate Nix when they require:

    • Predictable feature implementation with clear timelines for new capabilities.
    • Security and compliance that meet and exceed industry requirements.
    • Dramatically faster build times that slash evaluation overhead and accelerate development cycles by 3-4x in typical workloads.
    • Operational workflows that scale from small projects to global deployments.

    Determinate Nix 3.0 transforms Nix from a powerful tool into a trusted platform that delivers the governance, performance, and stability that serious production environments demand.

    Get started with Determinate Nix 3.0 today!

    Ready to experience Determinate Nix 3.0?

    Installing Determinate Nix 3.0

    Installation is straightforward whether you’re deploying across an organization or installing on your workstation. Our comprehensive documentation site provides tailored installation instructions for most environments.

    Upgrading to Determinate Nix 3.0

    Follow these straightforward upgrade instructions for your environment.

    For macOS users

    Download the universal package and run it.

    Logo for graphical installer

    Install Determinate Nix on macOS now 🍎

    With support for Apple Silicon ( aarch64-darwin )

    When the installation is complete, open a terminal and run:

    You should see nix (Determinate Nix 3.0.0) 2.26.3 .

    For NixOS users

    If you’re already using the Determinate flake in your NixOS configuration, update the flake: Update the inputs section flake.nix to reference the latest Determinate version:

    {

    inputs.determinate.url = "https://flakehub.com/f/DeterminateSystems/determinate/*";

    }

    Run a system rebuild to apply the changes:

    sudo nixos-rebuild switch --flake .

    Log in to FlakeHub :

    Verify your upgrade:

    You should see nix (Determinate Nix 3.0.0) 2.26.3 .

    For Linux users

    First, download the appropriate determinate-nixd for your platform:

    Linux (x86_64)

    curl -sSLfo determinate-nixd https://install.determinate.systems/determinate-nixd/stable/x86_64-linux

    Linux (aarch64)

    curl -sSLfo determinate-nixd https://install.determinate.systems/determinate-nixd/stable/aarch64-linux

    Make the file executable and run the upgrade:

    chmod +x ./determinate-nixd

    sudo ./determinate-nixd upgrade

    Log in to FlakeHub :

    The upgrade process will:

    • Preserve all your existing Nix configurations
    • Update to Determinate Nix 3.0 components
    • Restart the Nix daemon with the new version

    Verify your upgrade:

    You should see nix (Determinate Nix 3.0.0) 2.26.3 . You can now remove the temporary installation file:

    For FlakeHub teams, your authentication and access to private flakes and caches will remain intact throughout the upgrade process. If you encounter any issues during the upgrade or have additional questions, enterprise customers should contact their dedicated account representative directly. All users are also welcome to join our Discord server for community support.

    Ready to transform the way your team builds critical software? Experience Determinate Nix 3.0 today and leave behind dependency nightmares, security headaches, and performance bottlenecks. Email hello@determinate.systems to discuss how we can empower your team with a robust foundation that enables you to focus on what matters most: creating exceptional software that drives your business forward.

    FVWM-95

    Hacker News
    fvwm95.sourceforge.net
    2025-12-16 17:13:07
    Comments...
    Original Article


    What is FVWM-95?

    Fvwm95 is a hack based on fvwm2 . It tries to emulate the good features of a well known product without bloating the regular fvwm code.

    The main aspects are:

    • A familiar look and feel. It can be interesting for users moving from the MS-World to Unix, or for those who have to switch regularly between the two. Or for those that simply would like to have the same MS look and feel in Unix, or for those that want to have just another window manager. But before all, it's meant to be useful, simple and efficient.
    • Same flexible and easy configuration as the native fvwm.
    • Functionality extensible via loadable modules.
    • A taskbar: find quickly an application window and don't take space with icon windows.
    Shortly after releasing the first version of fvwm95, we started working on a desktop-folder metaphor for the window manager in order to have a more complete emulation of the Windows-95 desktop. The development evolved towards the creation of a C++ class library , providing an easily extensible set of Win95-looking widgets, which eased the development of new applications.

    Here are some fvwm95 screenshots:


    Downloads

    The main distribution site has moved from mitac11.uia.ac.be to sourceforge. Go to the releases directory.


    Mailing list

    The old mailing list at physik.uni-muenchen.de has been moved to lists.sourceforge.net. The address of the new list is: To subscribe or unsubscribe a message should be sent to: with the word "subscribe" or "unsubscribe" in the message body. The messages will be archived and could be requested from: A weekly (or when 40kByte of size is reached) digest is also available:


    Some fvwm and fvwm95-related links


    Page last updated: Nov 26, 2001.

    Access count since last update:

    Pricing Changes for GitHub Actions

    Hacker News
    resources.github.com
    2025-12-16 17:12:02
    Comments...
    Original Article

    Today we’re announcing updates to our pricing and product models for GitHub Actions.

    Why?

    When we shipped Actions in 2018, we had no idea how popular it would become. By early 2024, the platform was running about 23 million jobs per day and our existing architecture couldn’t reliably support our growth curve. In order to increase feature velocity, we first needed to improve reliability and modernize the legacy frameworks that supported GitHub Actions.

    Our solution was to re-architect the core backend services powering GitHub Actions jobs and runners with the goals of improving uptime and resilience against infrastructure issues, enhancing performance, reducing internal throttles, and leveraging GitHub’s broader platform investments and developer experience improvements. This work is paying off by helping us handle our current scale, even as we work through the last pieces of stabilizing our new platform.

    Since August, all GitHub Actions jobs have run on our new architecture, which handles 71 million jobs per day (over 3x from where we started). Individual enterprises are able to start 7x more jobs per minute than our previous architecture could support.

    As with any product, our goal at GitHub has been to meet customer needs while providing enterprises with flexibility and transparency.

    This change better supports a world where CI/CD must be faster and more reliable, better caching, more workflow flexibility, rock-solid reliability, and strengthens the core experience while positioning GitHub Actions to power GitHub’s open, secure platform for agentic workload.

    What’s changing?

    Lower prices for GitHub-hosted runners

    Starting today, we’re charging fairly for Actions across the board which reduces the price of GitHub Hosted Runners and the price the average GitHub customer pays. And we’re reducing the net cost of GitHub-hosted runners by up to 39% , depending on which machine type is used.

    This reduction is driven by a ~40% price reduction across all runner sizes, paired with the addition of a new $0.002 per-minute GitHub Actions cloud platform charge. For GitHub-hosted runners, the new Actions cloud platform charge is already included into the reduced meter price.

    Standard GitHub-hosted or self-hosted runner usage on public repositories will remain free . GitHub Enterprise Server pricing is not impacted by this change.

    The price reduction you will see in your account depends on the types of machines that you use most frequently – smaller runners will have a smaller relative price reduction, larger runners will see a larger relative reduction.

    This price reduction makes high-performance compute more accessible for both high-volume CI workloads and the agent jobs that rely on fast, secure execution environments.

    For full pricing update details, see the updated Actions runner prices in our documentation .

    This price change will go into effect on January 1, 2026 .

    Introduction of the GitHub Actions cloud platform charge

    We are introducing a $0.002 per-minute Actions cloud platform charge for all Actions workflows across GitHub-hosted and self-hosted runners. The new listed GitHub-runner rates include this charge. This will not impact Actions usage in public repositories or GitHub Enterprise Server customers.

    This aligns pricing to match consumption patterns and ensures consistent service quality as usage grows across both hosting modalities.

    This will impact self-hosted runner pricing starting March 1, 2026.

    Deepened investment in the Actions self-hosted experience

    We are increasing our investment into our self-hosted experience to ensure that we can provide autoscaling for scenarios beyond just Linux containers. This will include new approaches to scaling, new platform support, Windows support, and more as we move through the next 12 months. Here’s a preview of what to expect in the new year:

    GitHub Scale Set Client

    This new client provides enterprises with a lightweight Go SDK to build custom autoscaling solutions without the complexity of Kubernetes or reliance on ARC. It integrates seamlessly with existing infrastructure—containers, virtual machines, cloud instances, or bare metal—while managing job queuing, secure configuration, and intelligent scaling logic. Customers gain a supported path to implement flexible autoscaling, reduce setup friction, and extend GitHub Actions beyond workflows to scenarios such as self-hosted Dependabot and Copilot Coding Agent.

    Multi-label support

    We are reintroducing multi-label functionality for both GitHub-hosted larger runners and self-hosted runners, including those managed by Actions Runner Controller (ARC) and the new Scale Set Client.

    Actions Runner Controller 0.14.0

    This upcoming release introduces major quality-of-life improvements, including refined Helm charts for easier Docker configuration, enhanced logging, updated metrics, and formalized versioning requirements. It also announces the deprecation of legacy ARC, providing a clear migration path to a more reliable and maintainable architecture. Customers benefit from simplified setup, improved observability, and confidence in long-term support, reducing operational friction and improving scalability.

    Actions Data Stream

    The Actions Data Stream will deliver a near real-time, authoritative feed of GitHub Actions workflow and job event data, including metadata such as the version of the action that was executed on any given workflow run. This capability enhances observability and troubleshooting by enabling organizations to integrate event data into monitoring and analytics systems for compliance and operational insights. By providing structured, high-fidelity data at scale, it eliminates reliance on manual log parsing and empowers teams to proactively manage reliability and performance.

    Why this matters

    Agents are expanding what teams can automate—but CI/CD remains the heartbeat of modern software delivery. These updates enable both a faster, more reliable CI/CD experience for every developer, and a scalable, flexible, secure execution layer to power GitHub’s agentic platform.

    Our goal is to ensure GitHub Actions continues to meet the needs of the largest enterprises and of individual developers alike, with clear pricing, stronger performance, and a product direction built for the next decade of software development.

    FAQ

    What are the new GitHub-hosted runner rates?
    See the GitHub Actions runner pricing reference for the updated rates that will go into effect on January 1, 2026. These listed rates include the new $0.002 per-minute Actions cloud platform charge.

    Which job execution scenarios for GitHub Actions are affected by this pricing change?

    • Jobs that run in private repositories and use standard GitHub-hosted or self-hosted runners
    • Any jobs running on larger GitHub-hosted runners

    Standard GitHub-hosted or self-hosted runner usage on public repositories will remain free. GitHub Enterprise Server pricing is not impacted by this change.

    When will this pricing change take effect?

    The price decrease for GitHub-hosted runners will take effect on January 1, 2026. The new charge for self-hosted runners will apply beginning on March 1, 2026. The price changes will impact all customers on these dates.

    Will the free usage quota available in my plan change?
    Beginning March 1, 2026, self-hosted runners will be included within your free usage quota, and will consume available usage based on list price the same way that Linux, Windows, and MacOS standard runners work today.

    Will self-hosted runner usage consume from my free usage minutes?
    Yes, billable self-hosted runner usage will be able to consume minutes from the free quota associated with your plan.

    How does this pricing change affect customers on GitHub Enterprise Server?

    This pricing change does not affect customers using GitHub Enterprise Server. Customers running Actions jobs on self-hosted runners on GitHub Enterprise Server may continue to host, manage, troubleshoot and use Actions on and in conjunction with their implementation free of charge.

    Can I bill my self-hosted runner usage on private repositories through Azure?

    Yes, as long as you have an active Azure subscription ID associated with your GitHub Enterprise or Organization(s).

    What is the overall impact of this change to GitHub customers?

    Of Actions users impacted by this change, 85% will see their Actions bill decrease. Of the 15% who are impacted across all cohorts the median increase is $13.

    Did GitHub consider how this impacts individual developers, not just Enterprise scale customers of GitHub?
    From our individual users (free & Pro plans) of those who used GitHub Actions in the last month in private repos only 0.09% would end up with a price increase, with a median increase of under $2 a month. Note that this impact is after these users have made use of their included minutes in their plans today, entitling them to over 33 hours of included GitHub compute, and this has no impact on their free use of public repos. A further 2.8% of this total user base will see a decrease in their monthly cost as a result of these changes. The rest are unimpacted by this change.

    How can I figure out what my new monthly cost for Actions looks like?

    GitHub Actions provides detailed usage reports for the current and prior year . You can use this prior usage alongside the rate changes that will be introduced in January and March to estimate cost under the new pricing structure. We have created a Python script to help you leverage full usage reports to calculate your expected cost after the price updates.

    We have also updated our Actions pricing calculator , making it easier to estimate your future costs, particularly if your historical usage is limited or not representative of expected future usage.

    Additional resources

    Artie (YC S23) Is Hiring Senior Enterprise AES

    Hacker News
    www.ycombinator.com
    2025-12-16 17:00:57
    Comments...
    Original Article

    Software that streams data from databases to warehouses in real-time

    Senior Enterprise AE

    $300K - $350K 0.10% San Francisco, CA, US

    Connect directly with founders of the best YC-funded startups.

    Apply to role ›

    About the role

    About Artie

    Artie is a fully-managed change data capture (CDC) streaming platform that replicates production databases into data warehouses and lakes - in real time, with zero maintenance. We make high-volume data replication simple, reliable, and scalable for engineering teams.

    Our platform powers mission-critical use cases including fraud and risk monitoring, inventory visibility, customer-facing analytics, and AI/ML workloads.

    We’re trusted by teams like Substack, Alloy, and ClickUp, and backed by top-tier investors including Y Combinator, General Catalyst, Pathlight, and the founders of Dropbox and Mode.

    Artie is built for engineers who care about performance, reliability, and operational simplicity - and we’re growing fast. This role is your chance to shape our GTM from the ground up.

    About the Role

    We’re hiring our first Senior Enterprise AEs to help scale Artie’s sales motion.

    This is not a “run the playbook” role. You’ll refine the playbook - defining what great full-cycle enterprise sales looks like for a deeply technical platform. You’ll partner directly with founders, influence product strategy, and set the bar for future AEs.

    What you’ll do

    • Run full-cycle enterprise sales
      • Own deals end-to-end: sourcing, qualification, discovery, demos, POCs, procurement, and closing
      • Navigate 6-12 month cycles with engineering, data, security, finance, and legal stakeholders
      • Multi-thread deeply and build strong, technical champions
    • Drive technical discovery & solutioning
      • Understand customer architectures and pain points (SQL Server, Postgres, MySQL, Kafka, Snowflake, VPCs, networking)
      • Map these to Artie’s capabilities with clarity and confidence
      • Whiteboard solutions with staff engineers and data architects
    • Lead rigorous evaluations
      • Build structured POC plans
      • Partner with engineering to define success criteria
      • Drive toward clear, mutual action plans
    • Source your own pipeline
      • There is no SDR team - you source 80%+ of pipeline through outbound, events, founder referrals, and creative prospecting
      • Use your curiosity and rigor to start real technical conversations
    • Sell the vision and business value (not features)
      • Translate deep technical concepts (e.g. log-based CDC, Kafka buffering) into ROI, TCO reduction, and platform reliability
      • Inspire our buyers with how Artie can unlock new opportunities for their company

    What we’re looking for

    • Enterprise sales mastery:
      • 5+ years of full cycle AE experience in enterprise or upper/mid-market
      • Consistent track record of closing $100-300K+ ACV deals
      • Comfortable navigating 6-12 month cycles and complex procurement
      • Proven experience run full-cycle deals without SDR or SE support
    • Deep technical fluency:
      • Experience selling dev tools, data infra, or cloud platforms to technical buyers
      • Able to confidently explain concepts like log-based CDC, Kafka, schema evolution, or cloud networking basics (VPCs, peering, security reviews)
      • Comfortable whiteboarding with staff-level engineers and technical stakeholders
      • Fluent discussing database, streaming, and cloud architecture concepts with engineering teams
    • Outbound-first mindset:
      • Self-sourced a significant portion of pipeline in previous roles
      • Consistent and persistent prospector who finds creative ways to engage prospects
    • Extreme ownership:
      • Never drop the ball - internally or externally
      • Drives alignment, writes structured follow-ups, and runs mutual action plans
    • Consultative and curious:
      • Asks layered questions, uncovers deep pain, and guides prospects to clarity and value
    • Collaborative and self-aware:
      • Knows when to pull in help - from CTO deep dives to executive alignment with CEO
      • Works closely with teammates to win complex deals together
      • Willing to work in-person, 5 days/week at our SF office (relocation covered)

    What you’ll get

    • A Seat at the Table: Join as a founding GTM member with real influence on company direction.
    • End-to-End Ownership: Drive the full GTM lifecycle—strategy, execution, and iteration.
    • Tight Product Loop: Collaborate closely with product and leadership to shape what we build and why.

    Compensation & Benefits

    • $150-175K base salary ($300-350K OTE) depending on experience
    • Equity: ~0.1%
    • Healthcare, 401(k), unlimited PTO
    • Lunch & dinner provided
    • Visa sponsorship available

    About the interview

    30min Hiring Manager

    30min Working Session

    Take Home Case Study

    Onsite Interview

    Post-onsite Call

    About Artie

    We are building Artie, a real-time data streaming solution focused on databases and data warehouses. Typical ETL solutions leverage batched processes or schedulers (DAGs, Airflow), which cannot achieve real time data syncs. We leverage change data capture (CDC) and stream processing to perform data transfers in a more efficient way, which enables sub-minute latency.

    Artie

    Founded: 2023

    Batch: S23

    Team Size: 10

    Status: Active

    Location: San Francisco

    Founders

    Track Surveillance (Flock Cameras) Tech in Local Government Meetings

    Hacker News
    alpr.watch
    2025-12-16 16:54:19
    Comments...
    Original Article

    ✓ Link copied! Share this meeting with others.

    Your local government might be discussing surveillance tech like Flock cameras, facial recognition, or automated license plate readers right now. This map helps you find those meetings and take action.

    Beautiful Unethical Dangerous

    Why this matters: Municipalities across the US are quietly adopting surveillance technologies in rapidly growing numbers with over 80,000 cameras already out on the streets. These systems track residents' movements, collect biometric data, and build massive databases of our daily lives.

    alpr.watch scans meeting agendas for keywords like "flock," "license plate reader," "alpr," and more. Each pin on the map shows where these conversations are happening so that you can make a difference.

    Show only upcoming meetings

    Get Email Alerts for Your Area

    Enter your email below and we'll send you a login link. After logging in, you can set your notification preferences.

    ✓ Check your email for a login link!

    ✗ Please enter a valid email address.

    Loading map data

    Fetching meetings...

    🔍 Zoom in to see ALPR surveillance cameras

    Data before mid-December may be unverified. All future submissions are 100% moderator approved.

    Statistics

    -- Local Councils Monitored

    -- Meetings Indexed

    -- Cameras Mapped

    Understanding Mass Surveillance

    What is ALPR?

    Automated License Plate Recognition (ALPR) systems use cameras and artificial intelligence to capture, read, and store license plate data from every passing vehicle.

    These systems work 24/7 creating a massive database of where vehicles, and by extension, people, travel. Every trip to the grocery store, doctor's office, or place of worship gets recorded and stored.

    What is Flock Safety?

    Flock Safety is one of the largest manufacturers of ALPR cameras in the United States, marketing their systems to neighborhoods and law enforcement.

    Flock cameras capture license plates, vehicle make/model, color, and other identifying features. This data is shared across a massive network of agencies and jurisdictions, creating a surveillance web that tracks millions of Americans.

    The Slippery Slope

    History shows that surveillance systems expand beyond their original scope:

    • Systems marketed for "solving crimes" get used for immigration enforcement
    • Temporary programs become permanent infrastructure
    • Data sharing agreements grow to include more agencies
    • Technology advances enable new invasive uses
    • Regulations and oversight consistently lag behind deployment

    Organizations Fighting for Your Privacy

    These groups and individuals are leading the fight against mass surveillance. Consider supporting their work or getting involved locally.

    Electronic Frontier Foundation (EFF)

    Leading nonprofit defending digital privacy and civil liberties. eff.org

    ACLU

    Fighting surveillance overreach through litigation and advocacy nationwide. aclu.org

    Fight for the Future

    Digital rights organization mobilizing grassroots opposition to surveillance. fightforthefuture.org

    Surveillance Technology Oversight Project (STOP)

    Litigating against invasive surveillance in New York and beyond. stopspying.org

    Institute for Justice

    This civil liberties law firm has filed lawsuits challenging the constitutionality of Flock's mass, warrantless surveillance ij.org

    Local Community Groups

    Check for privacy advocacy organizations in your area fighting surveillance at the local level.

    U.N. Human Rights Watchdogs Blast Columbia for Using Immigration Status to Suppress Students’ Pro-Palestine Speech

    Intercept
    theintercept.com
    2025-12-16 16:44:01
    The U.N. experts wrote blistering letters to five American universities about their crackdowns on Gaza protests. The post U.N. Human Rights Watchdogs Blast Columbia for Using Immigration Status to Suppress Students’ Pro-Palestine Speech appeared first on The Intercept....
    Original Article

    A commission of top United Nations human rights watchdogs sent a series of blistering letters to the heads of five U.S. universities raising sharp concerns over the treatment of pro-Palestine students, The Intercept has learned.

    The letters, which were sent on October 14 to the presidents and provosts of Columbia , Cornell , Georgetown , Minnesota State, and Tufts universities, called out school officials and U.S. law enforcement agencies for cracking down on student protesters and subsequently using immigration authorities to single out foreign students for detention and deportation.

    “We are highly concerned over reports that students were arrested, suspended, and expelled, and lost their university accommodation, campus access, and their immigration status merely because of assembling peacefully to express their solidarity with victims of the conflict in Gaza,” wrote the group of U.N. special rapporteurs, independent experts who monitor human rights violations. “We fear that such pressure and public attacks on scholars and institutions can result in repression of free expression and in self-censorship, thus damaging academic freedom and the autonomy of universities.”

    The letters suggest the international body has taken notice of domestic protest repression on U.S. campuses. Since President Donald Trump returned to office, his administration has weaponized immigration authorities against international students and investigations over alleged antisemitism at universities across the country — ratcheting up a crackdown on student protests for Palestine that began under former President Joe Biden.

    The letter to Columbia highlighted the arrest and detention of Mahmoud Khalil , Mohsen Mahdawi , and Leqaa Kordia , as well as the attempted arrest of Yunseo Chung . (Columbia did not immediately respond to a request for comment.)

    Khalil and Mahdawi both spent months in detention earlier this year. Kordia, a Palestinian student who was arrested on March 8, was still in U.S. Immigration and Customs Enforcement custody as recently as December 8, according to a report by Drop Site News .

    “It has been reported that the conditions of Ms. Kordia’s detention are particularly severe. Due to overcrowding, she sleeps on the floor where cockroaches and other bugs abound, and many showers and sinks do not work,” the authors wrote. “She is also not given materials her faith requires to have to pray, and she is not allowed to wear a hijab in the presence of men as her religion requires.”

    The authors of the letter include Mary Lawlor, the special rapporteur on the situation of human rights defenders; Farida Shaheed, the special rapporteur on the right to education; Irene Khan, the special rapporteur on the promotion and protection of the right to freedom of opinion and expression; Gina Romero, the special rapporteur on the rights to freedom of peaceful assembly and of association; and Gehad Madi, the special rapporteur on the human rights of migrants. Representatives of the U.N. rapporteurs who drafted the letters did not immediately respond to The Intercept’s requests for comment.

    The U.N. letter also highlighted the cases of Rümeysa Öztürk , a Turkish student at Tufts who was snatched by masked ICE agents on the streets of Somerville, Massachusetts, on March 25; Badar Khan Suri , the Indian-born researcher at Georgetown arrested on March 17; Momodou Taal , a Cornell grad student with dual citizenship from the United Kingdom and Gambia who was ordered to turn himself in to ICE agents on March 22; and Mohammed Hoque , a Minnesota State student arrested at his home on March 28. (Cornell, Minnesota State, and Tufts did not immediately respond to requests for comment.)

    In the letter, the authors singled out Columbia for bowing to pressure from the Trump administration, which they said set a standard that chilled speech nationwide.

    “The restrictive measures at Columbia University reflect nationwide structural changes at universities to suppress Palestine solidarity movements,” the authors wrote.

    In each letter, the authors asked the universities to provide information on the allegations of mistreatment, any measures taken by the schools to protect the rights of its students and scholars, and details on how the schools plan to safeguard the rights to freedom of expression and assembly.

    “Students report self-censoring political expression, and particularly international students are withdrawing from activism due to deportation fears,” the authors wrote. “Campus organizing has diminished significantly, with activists reporting less attendance from international students who had to quit their activism because of the potential risk of repercussions. This intimidating effect extends beyond issues concerning Israel and Palestine, with students reporting reluctance to engage in any political activism.”

    File d'attente - file-based job queue tool

    Lobsters
    git.sr.ht
    2025-12-16 16:36:13
    Comments...
    Original Article

    File d'attente (queue in French) is a file-based job queue, written in Go.

    File d'attente uses files and directories for queue manipulation. Create a job with " printf cmd > /pending/$id ", view running jobs with " ls /active ", and restart a failed job with " mv /failed/$id /pending ".

    The tool is intended for trusted, single-server workloads, as a companion queue to another application. File d'attente comes with automatic retries, timeout, and backoff built-in.

    # Installation

    File d'attente is built in Go and depends on sqlite and fuse (make sure fusermount is available in path).

    $ git clone https://sr.ht/~marcc/filed/
    $ cd filed
    $ go build
    $ go install
    

    To build the docs you need scdoc

    $ scdoc < filed.1.scd > filed.1
    # mv filed.1 /usr/local/man/man1
    

    # Getting started

    It is recommended to read the man pages for more complete documentation and security considerations, but below is a small example to get you started.

    filed requires a job directory and a state file location (defaulting to XDG_DATA_HOME ). Afterward, you can start the daemon:

    $ mkdir /tmp/filed-jobs
    $ filed /tmp/filed-jobs
    

    filed mounts the directory filed-jobs and exposes a few files and directories.

    A job can then be added by creating a file in the newly available pending directory:

    $ printf "echo 'hello world'" > /tmp/filed-jobs/pending/1
    

    If all went well, you can see the job output in /complete :

    $ cat /tmp/filed-jobs/complete/1
    >>> echo 'hello world'
    hello world
    

    By default, a job retries 3 times, and if unsuccessful, gets moved to /failed . You can inspect the logs to see what went wrong:

    $ printf "ech this-will-fail" > /tmp/filed-jobs/pending/2
    # Wait for a bit until it finishes retrying
    $ cat /tmp/filed-jobs/failed/2
    >>> ech this-will-fail
    sh: 1: ech: not found
    
    
    [System Error]: exit status 127
    

    You can restart a job by moving the job back to pending:

    $ mv /tmp/filed-jobs/failed/2 /tmp/filed-jobs/pending
    

    Finally, if you want to remove a completed or failed jobs:

    $ rm /tmp/filed-jobs/failed/2
    

    # Design & Motivation

    I wanted to create a queue that would be easy to use for self-hosted web applications, that could be used by any programming language. I also wanted to make it easy for admins to understand why a job fails, and to rerun jobs if there is an error.

    I was inspired by 9p, and files proved to be a great abstraction since directories model state transitions quite well. File d'attente makes it very easy to inspect the state, without needing to build an admin portal with separate sign in. Instead, all admin operations can be done by just SSHing into the server, and the operations for manipulating, securing and automating the system become very intuitive. The source code can then be very slimmed down, while still packing a lot of features.

    # TODO

    • [x] Support chmod and chown
    • [x] State is configured via environment variable
    • [x] Customizable backoff and timeout before retries
    • [x] Last modified and created at are correctly rendered for jobs
    • [ ] "Landlock"-mode, or sandboxed jobs - Requires a design
    • [ ] A reusable systemd unit file
    • [ ] A reusable openrc unit file
    • [ ] Notification on failure. Unfortunately inotify does not work with fuse , which would have been elegant otherwise.
    • [ ] Notify forget and other updates.

    # Status

    File d'attente is tested, but not battle-tested. There are probably quite a few warts and inefficiencies.

    # Alternatives

    • nq - nq is simpler and not a persistent process, but does not feature retries. They serve different purposes: nq for ad-hoc queuing of command lines. filed serves well as a job manager for your server, where you want admins to see jobs and be able to rerun them.
    • task-spooler - ts has better control over how you want the task executed (GPU or CPU), and a lot of other features. It does (AFAIK) not support retries, which are supported in filed .
    • bull - bull is only for node and javascript. It features a graphical UI, and a few other features not found in filed . filed eschews a GUI in favor of simple files, allowing it to better interoperate with other systems, and allows it to use regular unix permissions for access management.
    • sqs - requires you to setup most infrastructure around retries yourself. sqs is far more complex, more focused on message passing, harder to inspect, but far more flexible. Sqs scales better and fits more workloads.

    Show HN: Zenflow – orchestrate coding agents without "you're right" loops

    Hacker News
    zencoder.ai
    2025-12-16 16:32:16
    Comments...
    Original Article

    zenflow-parallel-agents-2

    ORCHESTRATION

    Parallel Project Execution

    Built-in verification means no waiting. Start the next task while agents finish the current one. Scale from one agent to your entire fleet.

    zenflow-kanbans

    VISIBILITY

    AI-First Project Management

    Projects, tasks, subtasks, kanban views, and inbox make AI work visible and organized. Clear picture across all your agents.

    zenflow-subtasks-1

    AUTOMATION

    Auto-Generated implementation plan

    Workflows break into sequential tasks automatically. Ready for autopilot or human review. Less babysitting, more producing.

    zenflow-models

    RELIABILITY

    Agent Diversity ??

    Different model families (Claude, GPT, etc.) challenge each other's assumptions and catch blind spots. Reduces errors, increases correctness.

    WHO IT'S FOR

    Built for AI-First Coders

    Senior Engineer

    Senior Engineers

    You own the architecture. Agents handle the implementation.

    Vibecoders

    Technical Vibecoders

    PMs, designers, and builders who think in outcomes, not syntax.

    Forward-looking team

    Forward-Looking Teams

    Standardize AI workflows. Guarantee quality. Ship faster, safer.

    WHAT TEAMS SAY

    From Hit-or-Miss to Production-Grade

    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris."

    Author photo

    Sarah Chen

    Engineering Lead

    "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident."

    Author photo

    Marcus Johnson

    Senior Developer

    "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam."

    Author photo

    Alex Rivera

    CTO

    "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione."

    Author photo

    Jordan Kim

    Tech Lead

    gh-actions-lockfile: generate and verify lockfiles for GitHub Actions

    Lobsters
    gh-actions-lockfile.net
    2025-12-16 16:31:01
    Comments...
    Original Article

    Generate and verify lockfiles for GitHub Actions dependencies.
    Pin all actions to exact commit SHAs with integrity hashes.

    The Problem

    GitHub Actions has no built-in mechanism to lock dependency versions.

    Version tags like @v4 can be silently retagged to point to different code.

    Composite actions pull in transitive dependencies you can't see or audit.

    The Solution

    gh-actions-lockfile creates a lockfile that pins every action (including transitive dependencies) to exact commit SHAs with integrity hashes.

    Quick Start

    As a GitHub Action (recommended)

    Features

    • Pins actions to exact commit SHAs
    • Includes integrity hashes for verification
    • Resolves transitive dependencies from composite actions
    • Visualizes your action dependency tree
    • Runs as a GitHub Action or CLI tool
    • Zero runtime dependencies beyond Node.js

    Secure your workflows today

    gh-actions-lockfile is open source under the AGPL-3.0 license.

    The Future of [JetBrains] Fleet

    Lobsters
    blog.jetbrains.com
    2025-12-16 16:26:17
    Comments...
    Original Article
    Fleet logo

    More Than a Code Editor

    General News

    The Future of Fleet

    Ekaterina Prigara Aleksey Stukalov

    TL;DR

    Fleet started as our attempt to explore a new generation of JetBrains IDEs, developed in parallel with those based on the IntelliJ Platform. Over time, we learned that having two general-purpose IDE families created confusion and diluted our focus. Rebuilding the full capabilities of IntelliJ-based IDEs inside Fleet did not create enough value, and positioning Fleet as yet another editor did not justify maintaining two overlapping product lines.

    Starting December 22, 2025, Fleet will no longer be available for download. We are now building a new product focused on agentic development.

    Fleet vs. IntelliJ-based IDEs

    For many years, JetBrains focused on IntelliJ-based IDEs, which served as the primary environment for most developers. When we started Fleet, our goal was to explore a lighter architecture, a modern UI model, and a more flexible environment free from decades of legacy architectural decisions. It was a worthwhile experiment, and from both a technical and design perspective, it was a success. Many Fleet components now power JetBrains IDEs, and several UX and UI concepts have been adopted throughout our product lineup.

    However, Fleet did not succeed as a standalone product. We could neither replace IntelliJ IDEA with Fleet nor narrow it into a clear, differentiated niche. We suddenly had two IDE families aimed at largely the same audience, with overlapping purposes. Users kept asking which one to choose, and the answer was never short or satisfying. Two similar products created friction and raised questions about ownership and long-term investment.

    What we tried with Fleet

    We initially positioned Fleet as a lightweight multi-language IDE and then as an editor with smart coding assistance. For some time, we seriously considered whether Fleet could become a second flagship IDE family alongside IntelliJ-based tools. User feedback was consistent: If you already work with IntelliJ IDEA, Rider, WebStorm, PyCharm, or any other JetBrains IDE, switching to Fleet required a strong reason – and Fleet did not offer enough value to justify the transition from IDEs you already know and love.

    When AI matured, we explored Fleet as an AI-first editor. We built new workflows and conducted large-scale user research to understand potential differentiation and long-term value. We confirmed that another AI editor would not stand out, especially in a market filled with AI-first VS Code forks. It became increasingly clear that the best path forward was to strengthen AI workflows in our existing IDEs. However, rapid progress in AI revealed a different niche where Fleet fits much more naturally.

    What this new niche looks like

    While we worked on AI within the editor, a new development workflow began to take shape. Developers started delegating meaningful tasks to agents – updating tests, cleaning code, refactoring modules, exploring unfamiliar code paths, and even building new features. These tasks run asynchronously and return full patches. The developer doesn’t write the code themselves. They guide the agent and review its output. This is fundamentally different from the classic IDE workflow, which is based on immediate feedback, synchronous control, and a single stable local state.

    The agentic loop relies on structured task definition, context assembly, multiple asynchronous runs, isolated execution, and review-first workflows. Combining them in a single tool results in a disjointed experience, so the Fleet team chose to stop competing with IDEs and code editors and instead build a product focused on agentic workflows. This led to a pivot to a new product: an agentic development environment. Based on the Fleet platform, this new environment will ship as a new product with a new name. The technology, team, and long-term direction continue – but the product identity and the target market evolve.

    What changes for current Fleet users

    We will stop releasing any further updates for Fleet. Distribution will also end, so you will no longer be able to download Fleet from the Toolbox App or other channels starting December 22, 2025 .

    If you have already downloaded Fleet, you can continue using it. However, some features that rely on our server-side services, including AI Assistant, may stop working over time.

    We will continue to share updates about the new product as the work progresses. Stay tuned!

    Discover more

    Odin's Most Misunderstood Feature: `context` - gingerBill

    Lobsters
    www.gingerbill.org
    2025-12-16 16:14:06
    Comments...
    Original Article

    2025-12-15

    Even with the documentation on the topic, many people completely misunderstand what the context system is for, and what problem it actually solves.

    For those not familiar with Odin, in each scope, there is an implicit value named context . This context variable is local to each scope and is implicitly passed by pointer to any procedure call in that scope (if the procedure has the Odin calling convention).

    The main purpose of the implicit context system is for the ability to intercept third-party code and libraries and modify their functionality. One such case is modifying how a library allocates something or logs something. In C, this was usually achieved with the library defining macros which could be overridden so that the user could define what they wanted. However, not many libraries support this, in any language, by default which meant intercepting third-party code to see what it does and to change how it does it is generally not possible.

    The context value has default values for its parameters which is decided in the package runtime. These defaults are compiler specific. To see what the implicit context value contains, please see the definition of the Context struct in package runtime .

    The Misunderstanding

    Fundamentally, the entire point of the context system is to intercept third-party code, and to change how it does things. By third-party, I just mean code not written by yourself or code that you cannot easily modify (which could even be your own past self’s code).

    I expect most people to 100% ignore the context because its existence is not for whatever preconceived reason they think it is for, be that minimizing typing/passing things around, or dynamic scoping, etc. It’s just for interception of third-party code.

    Ironically, context works because people misunderstand it, and thus generally leave it alone. That allows those who do understand it to work around less-than-ideal API’s.

    I understand a lot of people may not understand why it exists when they might not currently need it, but it’s fundamentally a solution to a specific problem which cannot really be solved in another way.

    A common misunderstanding usually arises when it is necessary to interact with third-party code and having to write callbacks which do not use the Odin calling convention. There is a general misunderstanding that because some procedure may not appear to use context directly (or at least not obviously do so), people will say that they should be marked as "contextless" or "c" , however this misses the entire point. Because the default calling convention of Odin has this context , you don’t actually know if the code needs it or not (which is by design).

    The first common example of this of this interaction complain usually happens when using Odin’s printing procedures in core:fmt . For most people, they just do context = runtime.default_context() to define a context and then continue, but I have had a lot of people “complain” as to why that is even necessary. Arguing that other than the assert , why would you need the context ? This complaint is due to not understanding what fmt.printf et al actually do. printf is a wrapper around wprintf which takes in a generic io.Stream . Since other libraries can utilize these procedures, it might be necessary to intercept/override/track this behaviour. And from that, there is little choice to but require the context .

    A good APIs offers a way to specify the allocator to use, e.g. allocator: runtime.Allocator or allocator := context.allocator . An API that doesn’t offer it can be worked around by overriding the context.allocator for that call or series of calls, in the knowledge that the other programmer didn’t hardcode the allocator they gave to make , et al.

    What is Provided by the context ?

    The Allocators

    There are two allocators on the context.allocator and context.temp_allocator . I expect most people to never use custom allocators whatsoever (which is empirically true), but I do also want to encourage things like using the context.temp_allocator because it allows for many useful benefits, especially those that most people don’t even realize are a thing. For many people, they usually just want to do nothing with the context (assuming they know about it) or set the context.allocator to the mem.Tracking_Allocator and be done; that’s pretty much it.

    You could argue that it is “better” to pass allocators 1 around explicitly, but from my own experience in C with this exact interface (made and used well before I even made Odin), I found that I got in a very very lazy habit of not actually passing around allocators properly. This overly explicitness with a generalized interface lead to more allocation bugs than if I had used specific allocators on a per-system basis.

    When explicit allocators are wanted, you rarely want the generic interface too, and usually a specific allocator instead e.g. virtual.Arena . As I have previously expressed in my Memory Allocation Strategies series, an allocator can be used to represent a specific set of lifetimes for a set of allocations—arenas being the most common kind but other allocators such as pools, basic free lists, etc may be useful.

    However because most people will still default to a traditional malloc / free style of dynamic memory allocation, having a generic interface which can be overridden/intercepted/tracked is extremely useful to be able to do, especially in third-party libraries/code.

    n.b. Odin’s context.allocator defaults to a heap-like allocator on most platforms, and context.temp_allocator defaults to a growing arena-like allocator.

    assertion_failure_proc

    The field assertion_failure_proc exists because you might honestly want a different way of asserting, with more information it like stack traces. You might even want to use it as a mechanism to do a rudimentary sort of exception handling (similar to Go’s panic & recover ). Having this overridable is extremely useful, again with third-party code. I understand it does default to something when it is not set, but that’s still kind of the point still. It does need to assert/panic, which means it cannot just do nothing.

    logger

    Logging is common throughout most applications and we wanted to provide a default approach. I expect most people to default to this as they want a simple unified logging experience. Most people don’t want their logs to be handled by different libraries in numerous different ways BY DEFAULT . But because it is on the context , the default logging behaviour can now overridden easily. If you something more than this logger interface, then use what you want. The point as I keep trying to iterate is: what is the default and what will third-party libraries default to so that you can then intercept it if necessary?

    random_generator

    This is the newest addition to the context ; part of the reason for this being here is probably less than obvious. Sometimes a third-party library will do (pseudo-)random number generation but controlling how it does that is very hard (if not impossible).

    Take C’s rand() for example. If you know the library is using rand() , you can at least set a seed with srand() if you want a deterministic controlled output. However I have used libraries in C which use a different random number generator (thank goodness because rand() is dreadful), but I had no way to overriding it without modifying the source code directly (which is not always possible if it’s a precompiled LIB/DLL). The counter is when you want a cryptographic grade random number generator and you want non-determinacy whatsoever.

    Having a random generator be on the context allows for all of this kind of interception.

    n.b. Odin’s default random_generator is based on ChaCha8 is heavily optimized with SIMD.

    user_ptr and user_index

    If you have used C, you’ve probably experienced having callbacks where there is way to pass a custom user data pointer as part of it. The API designer has assumed that the callback is “pure”. However in reality, this is rarely the case, so how do you actually pass a callback (which is immediately used, not delayed) that you can pass user data too? The most obvious example of this is qsort , and even in the “pure” case, it is common to do a sort of a key based on an external table.

    There are two approaches that some people do in languages without closures (e.g. C and Odin) to get around these issues, but neither of which are great: global variables and/or thread local variables. Honestly, those are just dreadful solutions to the problem, and why context has the user_ptr and user_index fields, to allow you to intercept this poorly thought out API.

    Now you might be asking: Why both a pointer and an index? Why not just a pointer?

    From my experience of programming over many years, it is very common I just want to pass the same value to many callbacks but I want to access a different “element” inside of the data passed. Instead of creating a wrapper struct which has both this pointer and the index, I wanted to solve it by just having both already. It’s an empirically derived solution, not anything from “first principles”.

    I do recommend that an API should be designed to minimize the need for callbacks in the first place, but when necessary to at least have a user_ptr parameter for callbacks. For when people do not design good APIs, context is there to get around the crap.

    Lastly, _internal

    This just exists for internal use within the core library, which no-one should never use for any reason . Most of the time, it exists just for temporary things which will be improved in the future, or for passing things down the stack in a bodgy way. Again, this is not for the programmer, it’s for the compiler/core library developers only.

    Circumventing Bad APIs

    As I said in the user_ptr and user_index section, a lot of the impetus for making context comes from the experience of using numerous C libraries. The GOOD C libraries that allow for a form of interception usually do it through a macro; at best, they only do MY_MALLOC and MY_FREE style things, and sometimes MY_ASSERT . However, those are not the norm and usually only written by people in a similar “sphere of influence” to myself. Sadly, the average C library doesn’t allow for this.

    Even so, with the GOOD C libraries, this macro approach fails across a LIB/DLL boundary. Which is part of the problem when interfacing with a foreign language (e.g. Odin). So even in the GOOD case for C, it’s not that good in practice.

    Now some library writers are REALLY GOOD and they provide things like an allocation interface, but I probably know all of these library writers personally at this point, so I’d be preaching to the choir with my complaints.

    I’ve honestly had a few people effectively tell me that if it’s an bad API then the user should put up with it—“It’s API is bad? Oh well, tough luck”. However, I’ve had a lot the same people then ask “but why does the language need to solve that? Isn’t it a library problem?”.

    I’m sorry but telling someone the API is at fault doesn’t help them in the slightest, and if a API/library cannot be easily modified, then how can that be fixed in code? It’s fundamentally only fixable at the language itself.

    The Evolution of Code

    People rarely write things perfect the first time—code evolves. That’s what engineering is all about. Requirements change. The people change. The problem changes entirely. Expecting not to be able to intercept third-party code is pie-in-the-sky thinking. As I’ve said numerous times before, Third-party just means “stuff not written by you”; that’s it. As I stress, it could even be your past self, which is not the same as your present self.

    Point out that shitty APIs exist is the entire point. Just saying “tough luck” doesn’t solve anything, you’re adding to the problem.

    This is why context exists.

    ABI Layout

    One important aspect about the context is that memory layout is not user modifiable, and this is another big design choice too. It allows for a consistent and well understood ABI, which means you can—you guessed it—intercept third-party code even across LIB/DLL boundaries.

    If the user was allowed to add as many custom fields to the context as desired, it would not be ABI consistent, and thus not be stable for the use of its interception abilities across LIB/DLL boundaries. At best, allowing for custom fields is allowing you to minimize passing/typing parameters to procedures. Typing is rarely—if ever—the bottleneck in programming.

    Implementation

    Another common question I’ve gotten a few times is why the context is passed as an implicit pointer argument to a procedure, and not something like a thread local variable stack? The rationale being that there would not need to be a calling convention difference for context . Unfortunately through a lot of experimentation and thought, there are a few reasons why it is implemented the way it is:

    • Easier to manage across LIB/DLL boundaries that trying to use a single thread-local stack
    • Easier management of recovery from crashes where the context might be hard to figure out.
    • Using the existing stack makes stack management easier already, you don’t need to have a separate allocator for that stack
    • Some platforms do not thread-local variables (e.g. freestanding targets)
    • Works better with async/fiber based things, which would then require a fiber-local stack instead of a thread-local one
    • Prevent back-propagation, which would be trivial with a global/thread-local stack

    Odin’s context also has copy-on-write semantics. This is done for two reasons: to keep things local, and prevent back-propagation of “bad” data from an third-party library (be it malicious or just buggy). So not having an easily accessible stack of context values makes it harder for this back-propagation to happen.

    The Inspiration

    The main inspiration for the implicit context system does come from Jonathan Blow’s language, however I believe the reasoning for its existence in Jonathan Blow’s language is very different. As I have never used Jon’s language, I am only going on what other people have told me and what I have seen from Jon’s initial streams. From what I can tell, Jon’s language’s context behaves quite different to Odin’s, since it allows for the ability to add custom fields to it and to back-propagate.

    I am not sure what Jon’s initial rationale was for his form of context , but I do not believe Jon was thinking of third-party code interception when he designed/implemented his context . I hypothesize it was something closer to a form of “static dynamic-scoping” but not exactly (I know that’s an oxymoron of a statement). All I know is when I saw it, I saw a brilliant solution to third-party code interception problem.

    Conclusion

    I hope this clarifies a lot of the design rationale behind the implicit context system, and why it exists.

    If you have any more questions, or want me to clarify something further, please feel to contact me!

    Devs say Apple still flouting EU's Digital Markets Act six months on

    Hacker News
    www.theregister.com
    2025-12-16 16:09:48
    Comments...
    Original Article

    Six months after EU regulators found Apple's App Store rules in breach of the Digital Markets Act (DMA), developers say Cupertino is still behaving as if compliance were optional.

    The Coalition for App Fairness, a nonprofit organization of app developers and consumer groups, has accused Apple of persistent non-compliance with the DMA, warning that the company's revised App Store terms continue to impose fees which the legislation prohibits.

    In an open letter addressed to European Commission President Ursula von der Leyen and senior commissioners, the coalition argues that Apple has failed to deliver "any meaningful changes or proposals" despite an April 2025 non-compliance decision that found its App Store policies illegal and harmful to both developers and consumers.

    At the heart of the complaint is money. The DMA requires so-called gatekeepers to allow developers to offer and conduct transactions outside their app stores without charge. Apple, the coalition claims, is seeking to charge commissions of up to 20 percent on those very transactions.

    "This is a blatant disregard for the law with the potential to vanquish years of meaningful work by the Commission," the letter states, accusing Apple of preserving the economics of its App Store while nominally claiming compliance.

    Apple has said it will roll out new App Store terms in January 2026, but developers say the company has provided no clarity on what those changes will involve or whether they will actually comply with the DMA.

    "We have seen this playbook before in Europe and beyond," the signatories warn, adding that they suspect any new terms will continue to impose fees that would violate the law.

    The letter argues that this uncertainty is already doing damage. Six months after Apple's last App Store terms update, developers still do not know which rules will govern their businesses or what their costs will look like in the near term.

    Apple's "lack of transparency in tandem with its rushed timelines," the coalition says, is freezing investment and innovation, effectively allowing the company to "exploit its gatekeeper position by holding the entire industry hostage."

    The group also points to a growing transatlantic contrast that makes Europe look like the tougher regulator with the weaker results. While Apple continues to fight DMA enforcement in the EU, US courts have moved to curb its ability to extract fees from external transactions. Following litigation brought by Epic Games, developers in the US can now communicate freely with customers about pricing and offer payment options outside Apple's ecosystem without paying commission.

    That raises what the coalition calls a "simple and urgent question." Why should European developers and consumers get a worse deal than their US counterparts, especially when the EU was first to pass a landmark law aimed at fixing digital markets? The letter argues that meaningful enforcement of the DMA would strengthen Europe's digital competitiveness and attract global investment, while weak enforcement risks turning the regulation into an expensive paper exercise.

    "We trust the Commission will uphold the DMA," the signatories conclude, but they warn that if enforcement falls short, they will continue pressing policymakers to ensure Apple finally complies with the law as written – not as rewritten by Cupertino. ®

    Maybe consider putting cutlass in your CUDA/Triton kernels

    Lobsters
    maknee.github.io
    2025-12-16 16:02:29
    Comments...
    Original Article

    Motivation

    So I was browsing Hacker News and came across this interesting post: Fp8 runs ~100 tflops faster when the kernel name has “cutlass” in it .

    This was from Triton tutorial where someone noticed that adding “cutlass” to their kernel name gave them an additional 100-150 TFLOPs. That’s a huge improvement just from… a name?

    Mentions 100 TFLOPs improvement (Image source: Github pull )

    Mentions 150 TFLOPs improvement by renaming triton kernels to add cutlass (Image source: Github pull )

    Well, I got a bit curious and wanted to why this happens.

    So… what exactly is this?

    Instead of writing your kernel like this:

    __global__ void add(float *sum, int n, float *x, float *y)
    {
      for (int i = 0; i < n; i++)
        sum[i] = x[i] + y[i];
    }
    

    You add “cutlass” to the name:

    __global__ void add_cutlass(float *sum, int n, float *x, float *y)
    {
      for (int i = 0; i < n; i++)
        sum[i] = x[i] + y[i];
    }
    

    and ptxas If you need some background on the CUDA compilation toolchain, refer to the section on nvidia toolchain background will perform an additional pass that can add performance to the generated code.

    The rest of this blog will show benchmarks, explain the optimizations, and discuss when to use this trick. But I also want to highlight something broader: if you’re working at the high level (CUDA, Triton, PyTorch), you’re still at the mercy of what the backend compilers decide to do. In this case, ptxas (a black box) is making optimization decisions based on your kernel’s name With the recent release of TileIIR , there’s still plenty of magic happening under the hood. tileiras is also a black box, so we could easily see a similar “cutlass” trick emerge there too .

    If you want to skip to TLDR of the optimization, click here

    A cutlass example

    Here’s an example graph showing cutlass benchmarks with and without this optimization (where baseline/cutlass_on enables the optimization and cutlass_off disables it):

    Throughput of various cutlass examples

    In particular, the CuTE sgemm2.cu example sees a 20% drop in performance without the cutlass optimization!

    Another thing immediately obvious is that this optimzation doesnt always increase performance.

    Benchmarks

    Below are sections you can expand to see various benchmarks running on an RTX 3090 and H100. Each result is aggregated from 5 benchmark runs.

    Benchmarks include 15+ projects, covering popular ones like PyTorch, Flash Attention 2/3, Cutlass, llama.cpp.

    Some highlights:

    • Running llama.cpp on RTX 3090 with gpt-oss-20b shows a 1%+ performance increase
    • Flash Attention 2 on RTX 3090/H100 without the optimization decreases performance by up to 1%
    • Triton on RTX 3090 generally shows no performance change from the optimization

    Note: baseline doesn’t change anything. cutlass_on enables the optimization and cutlass_off disables it (if the application uses cutlass , for example Flash Attention 3):

    Expand to see 3090 benchmarks
    GPU Benchmarks
    RTX 3090 (Ampere) bitsandbytes candle cutlass flash_attn2 flashinfer ggml liger llamacpp llmc mojo nccl pytorch sageattention sgemm sglang tilus tinygrad torchao triton unsloth vllm

    Expand to see H100 benchmarks
    GPU Benchmarks
    H100 (Hopper) bitsandbytes cutlass deepep deepgemm_tflops flash_attn2 flash_attn3 flashinfer

    So what has it changed?

    So, I’ve added a godbolt reference for people to see the difference. I’m using some parts of SGEMM_CUDA If you haven’t checked it out, it’s a great blog on optimizing cuda matmul kernels by Simon Boehm as reference.

    In the NVCC compliation pipeline, cuda goes to ptx then ptx goes to sass. Let’s check verify where this optimization is applied (is it applied at the ptx or sass code)?

    High level compilation overview for NVIDIA GPUs

    First let’s explore if the cuda to ptx has changed.

    There's no difference in the PTX! (Image source: Godbolt link )

    Only the name has changed. The PTX instructions are identical.

    So let’s now check the the sass Godbolt link :

    Clearly something has changed!

    Two common changes we can see are:

    The optimization now uses IMAD instead of HMMA to zero registers

    We can see that IMAD is used instead of HMMA for zeroing registers, which is neat! Instead of using tensor units, we can use the FP32 units to zero out the registers. Refer to H100 SM Diagram .

    Enable interleaving LDS and FFMA

    We can see that LDS interleaved instead of being stacked together This should be able to increase instruction level parallelism

    One thing that the disassembly doesn’t show is the register pressure. This optimization may increase register pressure:

    cuobjdump --dump-resource-usage baseline.cubin
    
      Resource usage:
       Common:
        GLOBAL:0
       Function sgemm_kernel_10:
        REG:188 STACK:0 SHARED:17408 LOCAL:0 CONSTANT[0]:564 TEXTURE:0 SURFACE:0 SAMPLER:0
    
    cuobjdump --dump-resource-usage cutlass.cubin
    
      Resource usage:
       Common:
        GLOBAL:0
       Function cutlass_sgemm_kernel_9:
        REG:214 STACK:0 SHARED:17408 LOCAL:0 CONSTANT[0]:564 TEXTURE:0 SURFACE:0 SAMPLER:0
    
    

    Register usage increased from 188 to 214 , a 13% increase in register usage. However, this isn’t always the case. I’ve seen other examples not affect register pressure and even decrease register pressure.

    Below is a table of the different instructions that have changed for this kernel:

    Mnemonic Baseline CUTLASS Δ
    IMAD.MOV.U32 0 37 +37
    HFMA2.MMA 5 0 -5
    LEA 15 2 -13
    IMAD.SHL.U32 0 10 +10
    CS2R 75 64 -11
    MOV 8 0 -8
    IMAD 0 8 +8
    ULDC.64 4 1 -3
    FFMA 787 801 +14

    So… what is it doing?

    So far, we’ve dug into specifics. The higher optimization seems to most likely do the following:

    • Instruction selection - use f32 units instead of tensor cores for zeroing Zeroing registers isn’t in the hot path, but it’s a simple to see example! registers But wait there’s more! I didn’t show it in this blog in detail, but you can see some IMADs replacing instructions
    • Instruction reordering - mix memory loads with math
    • Influence register pressure - may increase the number of registers used to achieve reodering
    When ptxas sees matrix operations (MAD/MMA):
    
      Instruction selection:
        HMMA,MOV -> IMAD 
    
      Instruction reordering:
        LDS spread across FMMA
    
      As a Side effect:
        May increase register pressure
    

    When should you apply this optimization?

    With kernel writing, it’s tricky to say when you absolutely should and shouldn’t use this optimization. The optimization seems to increase ILP at the cost of register pressure Won’t increase register pressure in some cases! . Always benchmark to ensure the performance is good I’ve seen the optimization not affect performance on some cards while affecting others significantly .

    How to apply this to triton

    import torch
    import triton
    import triton.language as tl
    
    def rename_kernel(proxy):
        return "cutlass_kernel"
    
    # will convert "my_kernel" -> cutlass_kernel
    @triton.jit(repr=rename_kernel)
    def my_kernel(M: tl.constexpr):
        pass
    
    # compile and extract ptx
    my_kernel[(1,)](M=32)
    dev = torch.cuda.current_device()
    kernel_cache = my_kernel.device_caches[dev][0]
    compiled = next(iter(kernel_cache.values()))
    ptx = compiled.asm["ptx"]
    
    # print the kernel name from PTX
    print('\n'.join(ptx.splitlines()[:20]))
    
    

    It will show

    //
    // Generated by LLVM NVPTX Back-End
    //
    
    .version 8.7
    .target sm_86
    .address_size 64
    
            // .globl       cutlass_kernel          // -- Begin function cutlass_kernel
                                            // @cutlass_kernel
    .visible .entry cutlass_kernel(
            .param .u64 .ptr .global .align 1 cutlass_kernel_param_0,
            .param .u64 .ptr .global .align 1 cutlass_kernel_param_1
    )
    

    How to apply this to ptxas

    A universal patch to ptxas (which most frameworks invoke) is to just replace cutlass in the binary with something else.

    Here’s how I do it:

    input_path  = "/usr/local/cuda/bin/ptxas"
    output_path = "ptxas_no_cutlass"
    
    with open(input_path, "rb") as f:
        blob = bytearray(f.read())
    
    # We expect exactly "cutlass" inside ptxas.
    target = b"cutlass"
    off = blob.find(target)
    assert off != -1, "ptxas did not contain the cutlass marker!"
    
    # Overwrite: c u t l a s s  →  ff ff ff ff ff ff ff, so that strstr("0xFF") since kernel names contains ascii
    for i in range(len(target)):
        blob[off + i] = 0xFF
    
    with open(output_path, "wb") as f:
        f.write(blob)
    
    print(f"patched '{target.decode()}' at offset {off:#x}")
    

    Resolving Public Statements

    In my opinion, there seems to be a lot of assumptions people are throwing out on the internet about this optimization. I want to clear some of that up.

    On the top of the hackernews post , it links to a response from a user about this optimization.

    This statement is incorrect; I have compiled many real world projects with this optimization on and off and they ran without failing (passing output asserts) on different cards.

    Also with a highly voted reddit comment

    This explanation is really hard to understand. I’m guessing that the user is stating this trick uses NaNs/zeroes to optimize the program. It doesn’t use that. In fact, it tries to optimizes how registers are zeroed.

    Previous mentions

    This was also mentioned before by grynet on the nvidia forums where he complained that the following kernel would generate different sass

    __global__ void mykernel(float *lhs, float *rhs, float *res, int M, int N, int K) {
       cutlass::gemm::GemmCoord problem_size(M,N,K);
       compute_gemm_with_cutlass(lhs, rhs, res, problem_size);
    }
    
    __global__ void mykernel(float *lhs, float *rhs, float *res, int M, int N, int K, cutlass::gemm::GemmCoord dummy) {
       cutlass::gemm::GemmCoord problem_size(M,N,K);
       compute_gemm_with_cutlass(lhs, rhs, res, problem_size);
    }
    

    and BAR.SYNC.DEFER_BLOCKING would be generated here instead of BAR.SYNC (due to cutlass being added as part ofthe function signature)

    Perhaps this was also a part of the optimization in previous versions of ptxas ?

    Takeaway

    So, adding “cutlass” to your kernel name can give you 100+ TFLOPs or -20% FLOPS.

    The issue is two fold: ptxas is a black box and sass is undocumented. It’s unlike other ecosystems. You can see the passes running through LLVM and x86/arm are documented.

    Well, with this optimization… it helps some kernels, hurts others or change not much at all. Completely depends on your architecture and your specific code. What flies on an H100 might tank on a 5090 or B200, and you have no way to know until you run it.

    So what do you do? Benchmark it. Change the ordering in triton/cuda, see if PTX changes, check the SASS output. That’s the only way to know what ptxas actually did.

    And this isn’t going away. tileiras (the new TileIIR compiler) is also a black box. We may expect similar surprises like this moving forward.

    Appendix

    High level compilation overview for NVIDIA GPUs

    NVIDIA’s toolchain works like this: CUDA code is compiled by nvcc into PTX , an intermediate representation. Then ptxas takes that PTX and turns it into SASS , the low-level instruction set the GPU runs ptxas and sass are both undocumented, so it may be a bit difficult to understand what’s going on .

    H100 SM Diagram

    Citation

    To cite this article:

    @article{zhu2025cutlass,
      title = {Maybe consider putting "cutlass" in your CUDA/Triton kernels},
      author = {Zhu, Henry},
      journal = {maknee.github.io},
      year = {2025},
      month = {December},
      url = "https://maknee.github.io/blog/2025/Maybe-Consider-Putting-Cutlass-In-Your-CUDA-Kernels/"
    }
    

    Republicans Are Splitting Over Israel. Will Democrats Take Advantage?

    Intercept
    theintercept.com
    2025-12-16 16:00:24
    A new poll shows a growing divide among Republicans, especially under 45, on U.S. support for Israel. Democrats have a chance to pick up their votes. The post Republicans Are Splitting Over Israel. Will Democrats Take Advantage? appeared first on The Intercept....
    Original Article

    In a presidential primary election, a significant number of Republican voters — 44 percent — said they would vote for a Republican candidate who supports reducing the flow of U.S. taxpayer-funded weapons to Israel, according to a new poll released Tuesday by the Institute for Middle East Understanding Policy Project and conducted by YouGov.

    The findings show it’s not just left-leaning voters who now object to Israel’s war on Gaza — a growing share of Republicans are souring on the U.S. government’s unconditional support of Israel as well. That creates an opportunity for Democrats who want to flip Republican seats in upcoming elections, said Margaret DeReus, executive director of the IMEU Policy Project.

    “Democratic leadership has so far refused to acknowledge Israel’s growing unpopularity with voters and offer voters a real alternative, the same disastrous mistake they made in 2024,” DeReus said. “If Democratic leadership can summon the political will to call for an end of weapons to Israel so those billions can be reinvested in the programs Americans need, our polling finds it won’t just boost support with the Democratic base — it will persuade Republican voters to cross over as well.”

    It depends in part on which Republicans a Democratic candidate wants to court. Similar to trends seen among Democratic voters about a decade ago, the Republican opposition contains a notable age gap: Among Republicans ages 18 to 44, the new IMEU poll said, support for a candidate who favors reducing arms transfers to Israel jumps to a majority, 51 percent.

    The poll was taken from a sample of 1,287 self-identified Republicans who YouGov surveyed online in November. With a 3 percent margin of error, the results are consistent with findings from an August Quinnipiac University poll that found more than a third of Republicans oppose sending more military aid to Israel, and an October Pew Research Center poll finding that as many 41 percent of Republicans have an unfavorable view of Israel , a jump from 27 percent only three years ago. A Gallup poll in July showed that a majority of all Americans — 54 percent — disapprove of Israel’s military actions in Gaza, a new high in dissatisfaction.

    As the 2026 congressional primaries draw near, the Democratic Party is continuing to grapple with how to respond to mounting pressure to support Palestine among its voter base. Some Democratic candidates have sworn off support from conservative pro-Israel groups such as the American Israel Public Affairs Committee after previously receiving funding , and are committing to a House bill that would block offensive weapons transfers to Israel; others remain committed to the pro-Israel cause.

    Asked if they would rather support a Republican or Democratic candidate running on identical pro-Israel messages — that Israel should “do whatever its leaders say is necessary to defend itself” and that “the United States should always be there to provide weapons and logistical support to Israel when its leaders ask” — only 4 percent of the polled Republicans said they would vote for the Democrat.

    But asked to pick between the pro-Israel Republican or a Democratic candidate whose priority is to “focus on Americans first, by ensuring our tax dollars are used to bring down prices here instead of paying for weapons and support for wealthy nations like Israel,” 17 percent of Republicans flipped left and said they would rather vote for a Democrat critical of Israel.

    DeReus interpreted the results as indicative of frustration with President Donald Trump.

    “Americans of all backgrounds are confounded that President Trump always finds billions of dollars to fund Israel’s attacks on Palestinians, while saying there’s not enough money to fund affordable healthcare for Americans,” she said.

    The IMEU poll also found that among Republican voters, more than a third said they would rather support a Republican primary congressional candidate who rejected money from AIPAC, compared to 19 percent support for a candidate who accepts AIPAC donations.

    When asked specifically about U.S.-funded weapons deals with Israel, Republican voters signaled significant disapproval. The arms transfers between the two countries operate within a Memorandum of Understanding signed in 2016 by then-President Barack Obama that expires in 2028. Last month, Axios reported that Israel is seeking a new 20-year MOU with the Trump administration, committing about $4 billion to Israel each year. The proposal reportedly asks for a reduction in the amount of money used for direct military aid with plans to instead spend such money on defense-related research, a possible concession to growing frustrations with Israel among Trump’s base, especially as the economy worsens.

    The IMEU poll confirms some of that frustration, showing that 42 percent of Republican voters want the current U.S.–Israel military MOU to lapse in 2028 rather than renewing another 10-year agreement. Disapproval for the 20-year agreement slightly increases to 43 percent. A majority of Republicans below the age of 44 opposed a 10- or 20-year agreement, at 53 percent and 51 percent, respectively.

    Amid Israel’s war on Gaza, former President Joe Biden approved a 2024 emergency bill sending $14.1 billion in military aid to Israel, in addition to the ongoing MOU. A new congressional defense bill released last week, which asks for a record $901 billion, also includes carveouts for the U.S. to fill any of Israel’s gaps in military aid created by arms embargoes by other nations, such as Spain , Italy, and Japan, according to a Zeteo report .

    Some on the left who support Palestinian human rights are beginning to capitalize on their overlap with conservatives — like Code Pink founder Medea Benjamin, who last week met with far-right Rep. Marjorie Taylor Greene, R-Ga., who is resigning in January and has been seen as an avatar for growing dissatisfaction toward U.S. support for Israel among Trump’s supporters.

    That’s not to say right-wing criticism of pro-Israel spending is necessarily born out of concern for Palestinian people . The strain of conservatism that gave rise to Greene and other “America first” Republicans relies on a nationalist logic that privileges U.S. citizens above all other people, and right-wing criticism of Israel often peddles in antisemitic tropes. The influential right-wing pundit Tucker Carlson has criticized U.S. support for Israel’s genocide in Gaza — and recently drew criticism for platforming Nick Fuentes, a white nationalist who often spews antisemitic beliefs.

    Brett Cooper, another popular conservative personality and regular Fox News contributor, attempted to untangle this concern in a recent interview on NPR. When host Steve Inskeep asked Cooper whether she agreed with Fuentes’s peddling of an antisemitic idea that the U.S. is run by “Jewish gangsters,” Cooper, 24, said she rejected Fuentes’s antisemitic claim and instead insisted her generation’s concern with Israel had more to do with spending priorities in a struggling U.S. economy.

    “Young people’s biggest concern right now, both sides of the aisle, is the economy — we are concerned about being able to buy homes, we are concerned about affordability,” Cooper said. “And so when we see the news, when we see how much money is being sent overseas, to Ukraine, to Israel … my generation is concerned, we are upset.”

    Hackers exploit newly patched Fortinet auth bypass flaws

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 15:57:34
    Hackers are exploiting critical-severity vulnerabilities affecting multiple Fortinet products to get unauthorized access to admin accounts and steal system configuration files. [...]...
    Original Article

    Hackers exploit newly patched Fortinet auth bypass flaws

    Hackers are exploiting critical-severity vulnerabilities affecting multiple Fortinet products to get unauthorized access to admin accounts and steal system configuration files.

    The two vulnerabilities are tracked as CVE-2025-59718 and CVE-2025-59719, and Fortinet warned in an advisory on December 9 about the potential for exploitation.

    CVE-2025-59718 is a FortiCloud SSO authentication bypass affecting FortiOS, FortiProxy, and FortiSwitchManager. It is caused by improper verification of cryptographic signatures in SAML messages, allowing an attacker to log in without valid authentication by submitting a maliciously crafted SAML assertion.

    CVE-2025-59719 is a FortiCloud SSO authentication bypass affecting FortiWeb. It arises from a similar issue with the cryptographic signature validation of SAML messages, enabling unauthenticated administrative access via forged SSO.

    Both issues are only exploitable if FortiCloud SSO is enabled, which is not the default setting. However, unless the feature is explicitly disabled, it is activated automatically when registering devices through the FortiCare user interface.

    Targeting admin accounts

    Researchers at cybersecurity company Arctic Wolf observed attacks exploiting the two security vulnerabilities starting on December 12. They note that the intrusions originated from several IP addresses linked to The Constant Company, BL Networks, and Kaopu Cloud HK.

    Based on Arctic Wolf observations, the attackers targeted admin accounts with malicious single sign-on logins (SSO), as seen in the log below:

    Log showing authentication bypass
    Log showing authentication bypass
    Source: Arctic Wolf

    After obtaining admin-level access, the hackers accessed the web management interface and performed actions such as downloading the system’s configuration files.

    Action logs
    Malicious actions log
    Source: Arctic Wolf

    Configuration files can expose network layouts, internet-facing services, firewall policies, potentially vulnerable interfaces, routing tables, and also hashed passwords that may be cracked if weak.

    The exfiltration of these files suggests that the activity is not from researchers mapping vulnerable endpoints, as exploitation is part of a malicious operation that may support future attacks.

    Blocking the attacks

    The two flaws impact multiple versions of Fortinet products except for FortiOS 6.4, FortiWeb 7.0, and FortiWeb 7.2.

    To prevent attacks, Fortinet recommends that admins still running a vulnerable version temporarily disable the FortiCloud login feature until an upgrade to a safer version is possible.

    This can be done from System → Settings → “Allow administrative login using FortiCloud SSO” = Off.

    System administrators are recommended to move to one of the following versions that address both vulnerabilities:

    • FortiOS 7.6.4+, 7.4.9+, 7.2.12+, and 7.0.18+
    • FortiProxy 7.6.4+, 7.4.11+, 7.2.15+, 7.0.22+
    • FortiSwitchManager 7.2.7+, 7.0.6+
    • FortiWeb 8.0.1+, 7.6.5+, 7.4.10+

    If any signs of compromise are discovered, it is recommended to rotate firewall credentials as soon as possible. Arctic Wolf also recommends limiting firewall/VPN management access to trusted internal networks only.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    AIsbom – open-source CLI to detect "Pickle Bombs" in PyTorch models

    Hacker News
    github.com
    2025-12-16 15:55:45
    Comments...
    Original Article

    AIsbom: The Supply Chain for Artificial Intelligence

    PyPI version License Python Compliance

    AIsbom is a specialized security and compliance scanner for Machine Learning artifacts.

    Unlike generic SBOM tools that only parse requirements.txt , AIsbom performs Deep Binary Introspection on model files ( .pt , .pkl , .safetensors ) to detect malware risks and legal license violations hidden inside the serialized weights.

    AIsbom Demo


    ⚡ Quick Start

    1. Installation

    Install directly from PyPI. No cloning required.

    Note: The package name is aisbom-cli, but the command you run is aisbom.

    2. Run a Scan

    Point it at any directory containing your ML project. It will find requirements files AND binary model artifacts.

    aisbom scan ./my-project-folder

    3. Output

    You will see a combined Security & Legal risk assessment in your terminal:

    🧠 AI Model Artifacts Found

    Filename Framework Security Risk Legal Risk
    bert_finetune.pt PyTorch 🔴 CRITICAL (RCE Detected: posix.system) UNKNOWN
    safe_model.safetensors SafeTensors 🟢 LOW (Binary Safe) UNKNOWN
    restricted_model.safetensors SafeTensors 🟢 LOW LEGAL RISK (cc-by-nc-4.0)

    A compliant sbom.json (CycloneDX v1.6) including SHA256 hashes and license data will be generated in your current directory.


    4. Visualize the Report (New!)

    Don't like reading JSON? You can visualize your security posture using our offline viewer.

    1. Run the scan.
    2. Go to aisbom.io/viewer.html .
    3. Drag and drop your sbom.json .
    4. Get an instant dashboard of risks, license issues, and compliance stats.

    Note: The viewer is client-side only. Your SBOM data never leaves your browser.


    🚀 Why AIsbom?

    AI models are not just text files; they are executable programs and IP assets.

    • The Security Risk: PyTorch ( .pt ) files are Zip archives containing Pickle bytecode. A malicious model can execute arbitrary code (RCE) instantly when loaded.
    • The Legal Risk: A developer might download a "Non-Commercial" model (CC-BY-NC) and deploy it to production. Since the license is hidden inside the binary header, standard tools miss it.
    • Pickle files can execute arbitrary code (RCE) instantly upon loading.
    • The Solution: Legacy scanners look at requirements.txt manifest files but ignore binary model weights. We look inside. We decompile the bytecode headers without loading the heavy weights into RAM.

    ✨ Key Features

    • 🧠 Deep Introspection: Peeks inside PyTorch Zip structures and Safetensors headers without loading weights into RAM.
    • 💣 Pickle Bomb Detector: Disassembles bytecode to detect os.system , subprocess , and eval calls before they run.
    • ⚖️ License Radar: Extracts metadata from .safetensors to flag restrictive licenses (e.g., CC-BY-NC, AGPL) that threaten commercial use.
    • 🛡️ Compliance Ready: Generates standard CycloneDX v1.6 JSON for enterprise integration (Dependency-Track, ServiceNow).
    • ⚡ Blazing Fast: Scans GB-sized models in milliseconds by reading headers only and using streaming hash calculation.

    🧪 How to Verify (The "Trust Factor")

    Security tools require trust. To maintain a safe repository, we do not distribute malicious binaries. However, AIsbom includes a built-in generator so you can create safe "test dummies" to verify the scanner works.

    1. Install:

    2. Generate Test Artifacts: Run this command to create a fake "Pickle Bomb" and a "Restricted License" model in your current folder.

    # Generate a mock Pickle Bomb (Security Risk) and a mock Non-Commercial Model (Legal Risk)
    aisbom generate-test-artifacts

    Result: Files named mock_malware.pt and mock_restricted.safetensors are created.

    3. Scan it:

    # You can use your globally installed aisbom, or poetry run aisbom
    aisbom scan .

    You will see the scanner flag mock_malware.pt as CRITICAL and mock_restricted.safetensors as a LEGAL RISK .


    🔒 Security Logic

    AIsbom uses a static analysis engine to disassemble Python Pickle opcodes. It looks for specific GLOBAL and STACK_GLOBAL instructions that reference dangerous modules:

    • os / posix (System calls)
    • subprocess (Shell execution)
    • builtins.eval / exec (Dynamic code execution)
    • socket (Network reverse shells)

    🤖 GitHub Actions Integration

    Add AIsbom to your CI/CD pipeline to block unsafe models before they merge.

    name: AI Security Scan
    on: [pull_request]
    
    jobs:
      aisbom-scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          
          - name: Scan AI Models
            uses: Lab700xOrg/aisbom@v0
            with:
              directory: '.'

    Biscuit is a specialized PostgreSQL index for fast pattern matching LIKE queries

    Lobsters
    github.com
    2025-12-16 15:39:37
    Comments...
    Original Article

    Biscuit - High-Performance Pattern Matching Index for PostgreSQL

    License: MIT PostgreSQL: 12+ Read the Docs

    Biscuit is a specialized PostgreSQL index access method (IAM) designed for blazing-fast pattern matching on LIKE and ILIKE queries, with native support for multi-column searches. It eliminates the recheck overhead of trigram indexes while delivering significant performance improvements on wildcard-heavy queries. It stands for Bitmap Indexed Searching with Comprehensive Union and Intersection Techniques .


    What's new?

    ✨ New Features

    Added Index Memory Introspection Utilities

    Added built-in SQL functions and a view to inspect Biscuit index in-memory footprint .

    • biscuit_index_memory_size(index_oid oid) → bigint Low-level C-backed function returning the exact memory usage (in bytes) of a Biscuit index currently resident in memory.

    • biscuit_index_memory_size(index_name text) → bigint Convenience SQL wrapper accepting an index name instead of an OID.

    • biscuit_size_pretty(index_name text) → text Human-readable formatter that reports Biscuit index memory usage in bytes, KB, MB, or GB while preserving the exact byte count.

    • biscuit_memory_usage view A consolidated view exposing:

      • schema name
      • table name
      • index name
      • Biscuit in-memory size
      • human-readable memory size
      • on-disk index size (via pg_relation_size )

      This allows direct comparison between in-memory Biscuit structures and their persistent disk representation .

    SELECT * FROM biscuit_memory_usage;

    Notes

    • Memory accounting reflects Biscuit’s deliberate cache persistence design, intended to optimize repeated pattern-matching workloads.
    • Functions are marked VOLATILE to ensure accurate reporting of live memory state.
    • pg_size_pretty(pg_relation_size(...)) reports only the on-disk footprint of the Biscuit index. Since Biscuit maintains its primary structures in memory (cache buffers / AM cache), the reported disk size may significantly underrepresent the index’s effective total footprint during execution. Hence, we recommend the usage of biscuit_size_pretty(...) to view the actual size of the index.

    ⚙️ Performance improvements

    Removed redundant bitmaps

    Separate bitmaps for length-based filtering for case-insensitive search were removed. Case insensitive searches now use the same length-based filtering bitmaps as case-sensitive ones.


    Installation

    Requirements

    • Build tools: gcc , make , pg_config
    • Optional: CRoaring library for enhanced performance

    From Source

    # Clone repository
    git clone https://github.com/Crystallinecore/biscuit.git
    cd biscuit
    
    # Build and install
    make
    sudo make install
    
    # Enable in PostgreSQL
    psql -d your_database -c "CREATE EXTENSION biscuit;"

    From PGXN

    pgxn install biscuit
    psql -d your_database -c "CREATE EXTENSION biscuit;"

    Quick Start

    Basic Usage

    -- Create a Biscuit index
    CREATE INDEX idx_users_name ON users USING biscuit(name);
    
    -- Query with wildcard patterns
    SELECT * FROM users WHERE name LIKE '%john%';
    SELECT * FROM users WHERE name NOT LIKE 'a%b%c';
    SELECT COUNT(*) FROM users WHERE name LIKE '%test%';

    Multi-Column Indexes

    -- Create multi-column index
    CREATE INDEX idx_products_search 
    ON products USING biscuit(name, description, category);
    
    -- Multi-column query (optimized automatically)
    SELECT * FROM products 
    WHERE name LIKE '%widget%' 
      AND description LIKE '%blue%'
      AND category LIKE 'electronics%'
    ORDER BY score DESC 
    LIMIT 10;

    Supported Data Types

    Biscuit automatically converts various types to searchable text:

    -- Text types (native)
    CREATE INDEX ON logs USING biscuit(message);
    
    -- Numeric types (converted to sortable strings)
    CREATE INDEX ON events USING biscuit(user_id, event_code);
    
    -- Date/Time types (converted to sortable timestamps)
    CREATE INDEX ON orders USING biscuit(order_date, customer_name);
    
    -- Boolean (converted to 't'/'f')
    CREATE INDEX ON flags USING biscuit(is_active, status);

    How It Works

    Core Concept: Bitmap Position Indices

    Biscuit builds two types of character-position bitmaps for every string:

    1. Positive Indices (Forward)

    Tracks which records have character c at position p :

    String: "Hello"
    Bitmaps:
      H@0 → {record_ids...}
      e@1 → {record_ids...}
      l@2 → {record_ids...}
      l@3 → {record_ids...}
      o@4 → {record_ids...}
    

    2. Negative Indices (Backward)

    Tracks which records have character c at position -p from the end:

    String: "Hello"
    Bitmaps:
      o@-1 → {record_ids...}  (last char)
      l@-2 → {record_ids...}  (second to last)
      l@-3 → {record_ids...}
      e@-4 → {record_ids...}
      H@-5 → {record_ids...}
    

    3. Positive Indices (Case-insensitive)

    Tracks which records have character c at position p :

    String: "Hello"
    Bitmaps:
      h@0 → {record_ids...}
      e@1 → {record_ids...}
      l@2 → {record_ids...}
      l@3 → {record_ids...}
      o@4 → {record_ids...}
    

    4. Negative Indices (Case-insensitive)

    Tracks which records have character c at position -p from the end:

    String: "Hello"
    Bitmaps:
      o@-1 → {record_ids...}  (last char)
      l@-2 → {record_ids...}  (second to last)
      l@-3 → {record_ids...}
      e@-4 → {record_ids...}
      h@-5 → {record_ids...}
    

    5. Length Bitmaps

    Two types for fast length filtering:

    • Exact length : length[5] → all 5-character strings
    • Minimum length : length_ge[3] → all strings ≥ 3 characters

    Pattern Matching Algorithm

    Example: LIKE '%abc%def'

    Step 1: Parse pattern into parts

    Parts: ["abc", "def"]
    Starts with %: YES
    Ends with %: NO
    

    Step 2: Match first part as prefix

    -- "abc" must start at position 0
    Candidates = pos[a@0] ∩ pos[b@1] ∩ pos[c@2]

    Step 3: Match last part at end (negative indexing)

    -- "def" must end at string end
    Candidates = Candidates ∩ neg[f@-1] ∩ neg[e@-2] ∩ neg[d@-3]

    Step 4: Apply length constraint

    -- String must be at least 6 chars (abc + def)
    Candidates = Candidates ∩ length_ge[6]

    Result: Exact matches, zero false positives


    Why It's Fast

    1. Pure Bitmap Operations

    // Traditional approach (pg_trgm)
    for each trigram in pattern:
        candidates = scan_trigram_index(trigram)
        for each candidate:
            if !heap_fetch_and_recheck(candidate):  // SLOW: Random I/O
                remove candidate
    
    // Biscuit approach
    for each character at position:
        candidates &= bitmap[char][pos]  // FAST: In-memory AND
    // No recheck needed!

    2. Roaring Bitmaps

    Compressed bitmap representation:

    • Sparse data: array of integers
    • Dense data: bitset
    • Automatic conversion for optimal memory

    3. Negative Indexing Optimization

    -- Pattern: '%xyz'
    -- Traditional: Scan all strings, check suffix
    -- Biscuit: Direct lookup in neg[z@-1] ∩ neg[y@-2] ∩ neg[x@-3]

    12 Performance Optimizations

    1. Skip Wildcard Intersections

    // Pattern: "a_c" (underscore = any char)
    // OLD: Intersect all 256 chars at position 1
    // NEW: Skip position 1 entirely, only check a@0 and c@2

    2. Early Termination on Empty

    result = bitmap[a][0];
    result &= bitmap[b][1];
    if (result.empty()) return empty;  // Don't process remaining chars

    3. Avoid Redundant Bitmap Copies

    // OLD: Copy bitmap for every operation
    // NEW: Operate in-place, copy only when branching

    4. Optimized Single-Part Patterns

    Fast paths for common cases:

    • Exact : 'abc' → Check position 0-2 and length = 3
    • Prefix : 'abc%' → Check position 0-2 and length ≥ 3
    • Suffix : '%xyz' → Check negative positions -3 to -1
    • Substring : '%abc%' → Check all positions, OR results

    5. Skip Unnecessary Length Operations

    // Pure wildcard patterns
    if (pattern == "%%%___%%")  // 3 underscores
        return length_ge[3];     // No character checks needed!

    6. TID Sorting for Sequential Heap Access

    // Sort TIDs by (block_number, offset) before returning
    // Converts random I/O into sequential I/O
    // Uses radix sort for >5000 TIDs, quicksort for smaller sets

    7. Batch TID Insertion

    // For bitmap scans, insert TIDs in chunks
    for (i = 0; i < num_results; i += 10000) {
        tbm_add_tuples(tbm, &tids[i], batch_size, false);
    }

    8. Direct Roaring Iteration

    // OLD: Convert bitmap to array, then iterate
    // NEW: Direct iterator, no intermediate allocation
    roaring_uint32_iterator_t *iter = roaring_create_iterator(bitmap);
    while (iter->has_value) {
        process(iter->current_value);
        roaring_advance_uint32_iterator(iter);
    }

    9. Batch Cleanup on Threshold

    // After 1000 deletes, clean tombstones from all bitmaps
    if (tombstone_count >= 1000) {
        for each bitmap:
            bitmap &= ~tombstones;  // Batch operation
        tombstones.clear();
    }

    10. Aggregate Query Detection

    // COUNT(*), EXISTS, etc. don't need sorted TIDs
    if (!scan->xs_want_itup) {
        skip_sorting = true;  // Save sorting time
    }

    11. LIMIT-Aware TID Collection

    // If LIMIT 10 in query, don't collect more than needed
    if (limit_hint > 0 && collected >= limit_hint)
        break;  // Early termination

    12. Multi-Column Query Optimization

    Predicate Reordering

    Analyzes each column's pattern and executes in order of selectivity:

    -- Query:
    WHERE name LIKE '%common%'           -- Low selectivity
      AND sku LIKE 'PROD-2024-%'         -- High selectivity (prefix)
      AND description LIKE '%rare_word%' -- Medium selectivity
    
    -- Execution order (Biscuit automatically reorders):
    1. sku LIKE 'PROD-2024-%'         (PREFIX, priority=20, selectivity=0.02)
    2. description LIKE '%rare_word%' (SUBSTRING, priority=35, selectivity=0.15)
    3. name LIKE '%common%'           (SUBSTRING, priority=55, selectivity=0.60)

    Selectivity scoring formula:

    score = 1.0 / (concrete_chars + 1)
          - (underscore_count × 0.05)
          + (partition_count × 0.15)
          - (anchor_strength / 200)
    

    Priority tiers:

    1. 0-10 : Exact matches, many underscores
    2. 10-20 : Non-% patterns with underscores
    3. 20-30 : Strong anchored patterns (prefix/suffix)
    4. 30-40 : Weak anchored patterns
    5. 40-50 : Multi-partition patterns
    6. 50-60 : Substring patterns (lowest priority)

    Benchmarking

    Setup Test Data

    -- Create 1M row test table
    CREATE TABLE benchmark (
        id SERIAL PRIMARY KEY,
        name TEXT,
        description TEXT,
        category TEXT,
        score FLOAT
    );
    
    INSERT INTO benchmark (name, description, category, score)
    SELECT 
        'Name_' || md5(random()::text),
        'Description_' || md5(random()::text),
        'Category_' || (random() * 100)::int,
        random() * 1000
    FROM generate_series(1, 1000000);
    
    -- Create indexes
    CREATE INDEX idx_trgm ON benchmark 
        USING gin(name gin_trgm_ops, description gin_trgm_ops);
    
    CREATE INDEX idx_biscuit ON benchmark 
        USING biscuit(name, description, category);
    
    ANALYZE benchmark;

    Run Benchmarks

    -- Single column, simple pattern
    EXPLAIN ANALYZE
    SELECT * FROM benchmark WHERE name LIKE '%abc%' LIMIT 100;
    
    -- Multi-column, complex pattern
    EXPLAIN ANALYZE
    SELECT * FROM benchmark 
    WHERE name LIKE '%a%b' 
      AND description LIKE '%bc%cd%'
    ORDER BY score DESC 
    LIMIT 10;
    
    -- Aggregate query (COUNT)
    EXPLAIN ANALYZE
    SELECT COUNT(*) FROM benchmark 
    WHERE name LIKE 'a%l%' 
      AND category LIKE 'f%d';
    
    -- Complex multi-part pattern
    EXPLAIN ANALYZE
    SELECT * FROM benchmark 
    WHERE description LIKE 'u%dc%x'
    LIMIT 50;

    View Index Statistics

    -- Show internal statistics
    SELECT biscuit_index_stats('idx_biscuit'::regclass);

    Output:

    ----------------------------------------------------
     Biscuit Index Statistics (FULLY OPTIMIZED)        +
     ==========================================        +
     Index: idx_biscuit                                +
     Active records: 1000002                           +
     Total slots: 1000002                              +
     Free slots: 0                                     +
     Tombstones: 0                                     +
     Max length: 44                                    +
     ------------------------                          +
     CRUD Statistics:                                  +
       Inserts: 0                                      +
       Updates: 0                                      +
       Deletes: 0                                      +
     ------------------------                          +
     Active Optimizations:                             +
       ✓ 1. Skip wildcard intersections                +
       ✓ 2. Early termination on empty                 +
       ✓ 3. Avoid redundant copies                     +
       ✓ 4. Optimized single-part patterns             +
       ✓ 5. Skip unnecessary length ops                +
       ✓ 6. TID sorting for sequential I/O             +
       ✓ 7. Batch TID insertion                        +
       ✓ 8. Direct bitmap iteration                    +
       ✓ 9. Parallel bitmap scan support               +
       ✓ 10. Batch cleanup on threshold                +
       ✓ 11. Skip sorting for bitmap scans (aggregates)+
       ✓ 12. LIMIT-aware TID collection                +
    

    Results

    Index Command Build Time
    pg_trgm CREATE INDEX idx_trgm ... 20,358.655 ms
    biscuit CREATE INDEX idx_biscuit ... 2,734.310 ms

    Use Cases

    1. Full-Text Search Applications

    -- E-commerce product search
    CREATE INDEX idx_products ON products 
        USING biscuit(name, brand, description);
    
    SELECT * FROM products 
    WHERE name LIKE '%laptop%' 
      AND brand LIKE 'ABC%'
      AND description LIKE '%gaming%'
    ORDER BY price DESC 
    LIMIT 20;

    2. Log Analysis

    -- Search error logs
    CREATE INDEX idx_logs ON logs 
        USING biscuit(message, source, level);
    
    SELECT * FROM logs 
    WHERE message LIKE '%ERROR%connection%timeout%'
      AND source LIKE 'api.%'
      AND timestamp > NOW() - INTERVAL '1 hour'
    LIMIT 100;

    3. Customer Support / CRM

    -- Search tickets by multiple fields
    CREATE INDEX idx_tickets ON tickets 
        USING biscuit(subject, description, customer_name);
    
    SELECT * FROM tickets 
    WHERE subject LIKE '%refund%'
      AND customer_name LIKE 'John%'
      AND status = 'open'
    ORDER BY created_at DESC;

    4. Code Search / Documentation

    -- Search code repositories
    CREATE INDEX idx_files ON code_files 
        USING biscuit(filename, content, author);
    
    SELECT * FROM code_files 
    WHERE filename LIKE '%.py'
      AND content LIKE '%def%parse%json%'
      AND author LIKE 'team-%';

    5. Analytics with Aggregates

    -- Fast COUNT queries (no sorting overhead)
    CREATE INDEX idx_events ON events 
        USING biscuit(event_type, user_agent, referrer);
    
    SELECT COUNT(*) FROM events 
    WHERE event_type LIKE 'click%'
      AND user_agent LIKE '%Mobile%'
      AND referrer LIKE '%google%';

    Configuration

    Build Options

    Enable CRoaring for better performance:

    Index Options

    Currently, Biscuit doesn't expose tunable options. All optimizations are automatic.


    Limitations and Trade-offs

    What Biscuit Does NOT Support

    1. Regular expressions - Only LIKE / ILIKE patterns with % and _
    2. Locale-specific collations - String comparisons are byte-based
    3. Amcanorder = false - Cannot provide ordered scans directly (but see below)

    ORDER BY + LIMIT Behavior

    Biscuit doesn't support ordered index scans ( amcanorder = false ), BUT:

    PostgreSQL's planner handles this efficiently:

    SELECT * FROM table WHERE col LIKE '%pattern%' ORDER BY score LIMIT 10;

    Execution plan:

    Limit
      -> Sort (cheap, small result set)
        -> Biscuit Index Scan (fast filtering)
    

    Why this works:

    • Biscuit filters candidates extremely fast
    • Result set is small after filtering
    • Sorting 100-1000 rows in memory is negligible (<1ms)
    • Net result : Still much faster than pg_trgm with recheck overhead

    Memory Usage

    Biscuit stores bitmaps in memory:

    • Use REINDEX to rebuild if index grows too large

    Write Performance

    • INSERT : Similar to B-tree (must update bitmaps)
    • UPDATE : Two operations (remove old, insert new)
    • DELETE : Marks as tombstone, batch cleanup at threshold

    Comparison with pg_trgm

    Feature Biscuit pg_trgm (GIN)
    Wildcard patterns ✔ Native, exact ✔ Approximate
    Recheck overhead ✔ None (deterministic) ✗ Always required
    Multi-column ✔ Optimized ⚠️ Via btree_gist
    Aggregate queries ✔ Optimized ✗ Same cost
    ORDER BY + LIMIT ✔ Works well ✔ Ordered scans
    Regex support ✗ No ✔ Yes
    Similarity search ✗ No ✔ Yes
    ILIKE support ✔ Full ✔ Native

    When to use Biscuit:

    • Wildcard-heavy LIKE / ILIKE queries ( % , _ )
    • Multi-column pattern matching
    • Need exact results (no false positives)
    • COUNT(*) / aggregate queries
    • High query volume, can afford memory

    When to use pg_trgm:

    • Fuzzy/similarity search ( word <-> pattern )
    • Regular expressions
    • Memory-constrained environments
    • Write-heavy workloads

    Development

    Build from Source

    git clone https://github.com/Crystallinecore/biscuit.git
    cd biscuit
    
    # Development build with debug symbols
    make clean
    CFLAGS="-g -O0 -DDEBUG" make
    
    # Run tests
    make installcheck
    
    # Install
    sudo make install

    Testing

    # Unit tests
    make installcheck
    
    # Manual testing
    psql -d testdb
    
    CREATE EXTENSION biscuit;
    
    -- Create test table
    CREATE TABLE test (id SERIAL, name TEXT);
    INSERT INTO test (name) VALUES ('hello'), ('world'), ('test');
    
    -- Create index
    CREATE INDEX idx_test ON test USING biscuit(name);
    
    -- Test queries
    EXPLAIN ANALYZE SELECT * FROM test WHERE name LIKE '%ell%';

    Debugging

    Enable PostgreSQL debug logging:

    SET client_min_messages = DEBUG1;
    SET log_min_messages = DEBUG1;
    
    -- Now run queries to see Biscuit's internal logs
    SELECT * FROM test WHERE name LIKE '%pattern%';

    Architecture Details

    Index Structure

    BiscuitIndex
    ├── num_columns: int
    ├── column_indices[]: ColumnIndex[]
    │   ├── pos_idx[256]: CharIndex    // Forward position bitmaps
    │   │   └── entries[]: PosEntry[]
    │   │       ├── pos: int
    │   │       └── bitmap: RoaringBitmap
    │   ├── neg_idx[256]: CharIndex    // Backward position bitmaps
    │   ├── char_cache[256]: RoaringBitmap  // Character existence
    │   ├── length_bitmaps[]: RoaringBitmap[]  // Exact lengths
    │   └── length_ge_bitmaps[]: RoaringBitmap[]  // Min lengths
    ├── insensitive_column_indices[]: ColumnIndex[]
    │   ├── insensitive_pos_idx[256]: CharIndex    // Forward position bitmaps
    │   │   └── entries[]: PosEntry[]
    │   │       ├── pos: int
    │   │       └── bitmap: RoaringBitmap
    │   ├── insensitive_neg_idx[256]: CharIndex    // Backward position bitmaps
    │   └── insensitive_char_cache[256]: RoaringBitmap  // Character existence
    ├── tids[]: ItemPointerData[]      // Record TIDs
    ├── column_data_cache[][]: char**  // Cached string data
    └── tombstones: RoaringBitmap      // Deleted records
    

    Query Execution Flow

    1. biscuit_rescan()
       ├─> Parse LIKE pattern into parts
       ├─> Analyze pattern selectivity (multi-column)
       ├─> Reorder predicates by priority
       └─> For each predicate:
           ├─> biscuit_query_column_pattern()
           │   ├─> Check fast paths (empty, %, pure wildcards)
           │   ├─> Match pattern parts using bitmaps
           │   └─> Return candidate bitmap
           └─> Intersect with previous candidates
    
    2. biscuit_collect_tids_optimized()
       ├─> Detect aggregate vs. regular query
       ├─> Estimate LIMIT hint
       ├─> Collect TIDs from final bitmap
       ├─> Sort if needed (skip for aggregates)
       └─> Apply LIMIT early termination
    
    3. biscuit_gettuple() or biscuit_getbitmap()
       └─> Return results to PostgreSQL executor
    

    Contributing

    Contributions are welcome! Please:

    1. Fork the repository
    2. Create a feature branch ( git checkout -b feature/amazing )
    3. Make your changes with tests
    4. Submit a pull request

    Areas for Contribution

    • Implement amcanorder for native sorted scans
    • Add statistics collection for better cost estimation
    • Support for more data types (JSON, arrays)
    • Parallel index build
    • Index compression options

    License

    MIT License - See LICENSE file for details.


    Author

    Sivaprasad Murali


    Acknowledgments

    • PostgreSQL community for the extensible index AM framework
    • CRoaring library for efficient bitmap operations
    • Inspired by the need for faster LIKE query performance in production systems

    Support


    Happy pattern matching! Grab a biscuit 🍪 when pg_trgm feels half-baked!


    Cyberattack disrupts Venezuelan oil giant PDVSA's operations

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 15:19:17
    Petróleos de Venezuela (PDVSA), Venezuela's state-owned oil company, was hit by a cyberattack over the weekend that disrupted its export operations. [...]...
    Original Article

    Oil rig

    Petróleos de Venezuela (PDVSA), Venezuela's state-owned oil company, was hit by a cyberattack over the weekend that disrupted its export operations.

    In a Monday statement, PDVSA denied that the Saturday morning incident affected its operations in any way, adding that the breach was limited to some administrative systems.

    "Thanks to the expertise of PDVSA's human talent, the operational areas were not affected in any way, with the attack being limited to its administrative system," the company said.

    "Therefore, the industry's operational continuity is maintained through the implementation of secure protocols that allow for its regular activities in the supply of products to the domestic market, as well as the fulfillment of all its export commitments."

    However, according to an internal memo seen by Bloomberg , PDVSA instructed operational and administrative staff to disconnect from the network and shut down their computers.

    Three sources familiar with the situation also told Bloomberg that systems on PDVSA's network that manage the country's main crude terminal were still offline on Monday.

    This was confirmed by Reuters in a report citing an inside source who said, "There's no delivery (of cargoes), all systems are down."

    PDVSA statement
    PDVSA statement (Karen Méndez)

    ​This cyberattack comes amid escalating tensions between Venezuela and the United States. Last week, U.S. authorities seized a sanctioned oil tanker with Venezuelan crude, the first such seizure since the U.S. Department of the Treasury's Office of Foreign Assets Control (OFAC) sanctioned PDVSA in January 2019 .

    In its Monday statement, PDVSA also blamed the United States and domestic conspirators for orchestrating the attack as part of an attempt "to undermine national stability."

    "This attempted aggression is part of the US government's public strategy to seize Venezuelan oil by force and piracy," the company said.

    "Petróleos de Venezuela, S.A. categorically rejects this despicable action, orchestrated by foreign interests in collusion with unpatriotic elements seeking to undermine the country's right to sovereign energy development."

    Venezuela is one of the world's major oil-producing countries and a top global oil exporter. PDVSA oversees the country's oil production, refining, and exports, as well as the exploration and production of natural gas.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    [$] 2025 Maintainers Summit development process discussions

    Linux Weekly News
    lwn.net
    2025-12-16 15:10:15
    The final part of the 2025 Maintainers Summit was devoted to the kernel's development process itself. There were two sessions, one on continuity and succession planning, and the traditional discussion, led by Linus Torvalds, on any pain points that the community is experiencing. There was not a lo...
    Original Article

    The page you have tried to view ( 2025 Maintainers Summit development process discussions ) is currently available to LWN subscribers only.

    Reader subscriptions are a necessary way to fund the continued existence of LWN and the quality of its content.

    If you are already an LWN.net subscriber, please log in with the form below to read this content.

    Please consider subscribing to LWN . An LWN subscription provides numerous benefits, including access to restricted content and the warm feeling of knowing that you are helping to keep LWN alive.

    (Alternatively, this item will become freely available on December 25, 2025)

    The Hidden Risk in Virtualization: Why Hypervisors are a Ransomware Magnet

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 15:01:11
    Ransomware groups are targeting hypervisors to maximize impact, allowing a single breach to encrypt dozens of virtual machines at once. Drawing on real-world incident data, Huntress explains how attackers exploit visibility gaps at the hypervisor layer and outlines steps orgs can take to harden virt...
    Original Article

    Hacker looking at a computer screen

    Author: Dray Agha, Senior Manager, Hunt & Response, at Huntress Labs

    Hypervisors are the backbone of modern virtualized environments, but when compromised, they can become a force multiplier for attackers. A single breach at this layer can put dozens or even hundreds of virtual machines at risk simultaneously. Unlike traditional endpoints, hypervisors often operate with limited visibility and protections, meaning conventional security tools may be blind to an attack until it is too late.

    From our vantage point in the SOC and threat-hunting space at Huntress, we are seeing adversaries increasingly target hypervisors to deploy ransomware at scale. Specifically, in 2025, Huntress case data revealed a stunning surge in hypervisor ransomware: its role in malicious encryption rocketed from just 3% in the first half of the year to 25% so far in the second half.

    The primary actor driving this trend is the Akira ransomware group.This shift underscores the importance of hardening the hypervisor layer with the same rigor applied to endpoints and servers.

    In this article, we outline the threats we’ve observed in the wild and provide practical guidance for securing your hypervisor infrastructure, from patching and access control to runtime hardening and robust recovery strategies.

    Hypervisors: A New Battleground in Ransomware Operations

    In the last few months of 2025, Huntress has observed adversaries target hypervisors in an attempt to circumvent endpoint and network security controls.

    And this makes sense: as defenders continue to harden endpoints and servers, adversaries are increasingly shifting their focus to the hypervisor layer, the foundation of virtualized infrastructure - a Type 1 ("bare metal") hypervisor is the foundation, installed directly on server hardware, a Type 2 ("hosted") hypervisor is an app that sits on top of your regular computer's OS.The shift is following a familiar playbook.

    We've seen it with attacks on VPN appliances: threat actors realize that the host operating system is often proprietary or restricted, meaning defenders cannot install critical security controls like EDR. This creates a significant blind spot.

    The same principle applies to Type 1 hypervisors; they are the ultimate "land-and-expand" target where traditional endpoint security often cannot reach.

    We’ve also observed multiple cases where ransomware operators deploy ransomware payloads directly through hypervisors , bypassing traditional endpoint protections entirely.

    In some instances, attackers leverage built-in tools such as openssl to perform encryption of the virtual machine volumes, avoiding the need to upload custom ransomware binaries.

    • Once inside a network, attackers often pivot towards hypervisors using compromised internal authentication credentials in environments where network segmentation has failed to deny lateral movement to the hypervisor management page. This move grants them elevated control over multiple guest systems from a single management interface.
    • We’ve seen misuse of Hyper-V management utilities, to modify VM settings and undermine security features. This includes disabling endpoint defenses, tampering with virtual switches, and preparing VMs for ransomware deployment at scale.
    Fig 1: Extract from Huntress Platform detecting adversary manipulating Hyper-V
    Fig 1: Extract from Huntress Platform detecting adversary manipulating Hyper-V

    This shift underscores a growing and uncomfortable trend: Attackers are targeting the infrastructure that controls all hosts, and with access to the hypervisor, adversaries dramatically amplify the impact of their intrusion.

    Share the Gift of Security

    Hackers love the holidays too! Share FREE Security Awareness Training with family & friends to keep them safe.

    Quick, fun lessons to sharpen their cyber-smarts! Access extended through 1/31/26.

    Sign Up For Free

    Secure access, enforce least privilege, and separate the management plane

    If an attacker can obtain administrative credentials for the hypervisor, they can deploy ransomware payloads that affect every VM on the host. Also, using domain-joined accounts (e.g., Active Directory (AD) accounts) for ESXi increases lateral movement risk.

    What to do:

    • Use local ESXi accounts. Avoid using general-purpose domain admin accounts for management. Instead, create dedicated, local ESXi accounts or strictly limited, audited domain accounts with only the necessary permissions. If a domain admin account is compromised, this separation prevents immediate, unauthorized access to the hypervisor and its virtual machines.
    • Enforce Multi-factor Authentication (MFA). This is non-negotiable for all critical infrastructure. Enforce MFA for host management interfaces and vCenter access to protect against credential theft. An attacker with a stolen username and password will be blocked, significantly raising the effort required for a successful breach. This control provides a robust defense against common phishing and brute-force attacks. Use strong passwords stored in a secure password vault. ESXi credentials should be extremely strong and stored only in a dedicated password vault, never in shared documents or less secure locations. This prevents credential exposure through common attack vectors like compromised file shares or insecure password management practices.
    • Segregate the host management network. Segregate the hypervisor’s management network from production and general user networks. Create a dedicated VLAN or network segment that is logically and/or physically separate. By limiting the number of endpoints that can even attempt to connect to the hypervisor management interface, you drastically reduce the potential attack surface.
    • Deploy a jump box or bastion server. To ensure all administrative access is audited and controlled, deploy a jump box or bastion server that IT admins must access first, before pivoting to the hypervisor. This setup eliminates direct connections from potentially less-secure administrator workstations. The jump box acts as a monitored checkpoint, allowing for session recording, logging of all commands, and enforcement of security policies before granting access to critical infrastructure.
    • Apply the principle of least privilege(PoLP). Strictly limit access to the control plane (vCenter and individual hosts). Grant only the minimum required roles for necessary administrative functions, such as resource management or patching, to both human administrators and service accounts. Enforcing PoLP ensures that a potential compromise of a single account cannot be leveraged for wholesale changes across the entire virtualized environment.
    • Restrict management access to dedicated admin devices. Limit ESXi management interface access to specific administrative devices with static IP addresses. This creates an additional barrier by ensuring that only known, authorized endpoints can attempt to connect to the hypervisor, further reducing the attack surface.

    Lock down the hypervisor runtime environment and enforce code-/execution controls

    One of the unique risks with hypervisor-level ransomware is that once the attacker is on the host, they can run code at the hypervisor level, bypassing guest-OS controls. You need to harden the host so it only runs expected, signed code and trusted modules.

    What to do:

    • Enable the advanced host setting VMkernel.Boot.execInstalledOnly = TRUE so that only binaries installed via signed VIBs can execute, which prevents custom, malicious binaries from running on the host.
    • Disable/close unnecessary services such as SSH or ESXi Shell when not in use; enable lockdown mode.

    Keep the hypervisor patched, up to date, and exposed surfaces minimised

    Attackers are actively targeting ESXi hosts via known vulnerabilities for mass encryption operations. 0days and CVEs are not going to likely be the most common / real reason for compromise, and likely to be lapses in security segmentation. However, maintaining patching is critical.

    For example, CVE-2024-37085 highlights this hypervisor risk perfectly. This vulnerability allows attackers with adequate AD permissions to bypass authentication and instantly seize full administrative control of an ESXi host, leading to mass encryption of all VMs in seconds .

    The exploit works because vulnerable ESXi hosts automatically grant full admin privileges to the 'ESX Admins' AD group. Threat actors simply recreate that group to immediately seize the keys to the kingdom.

    These initial compromises often start with unpatched management interfaces or exposed protocols, like Service Location Protocol (SLP), which provide a low-effort entry point.

    What to do:

    • Maintain an inventory of all ESXi hosts (and associated management components like vCenter) and their patch level.
    • Prioritize security patches and updates from the vendor, especially for hypervisor-related CVEs.
    • Disable or restrict services you don't need or ensure they are not exposed externally. Service Location Protocol (SLP/port 427) has been exploited by ransomware groups like ESXArgs and should be disabled. Follow VMware's official remediation guidance .
    • Ensure that ESXi hosts are not directly exposed to the internet for management. Use VPNs, bastion hosts, or isolated management networks.

    Backup strategy, immutable snapshots and rapid recovery capability

    Even with strong prevention, risk remains. The hypervisor layer is high-impact; fallback is mandatory. Many guides emphasise that recovery is the last line of defense. Ransomware targeting ESXi typically seeks to encrypt VMDKs and host files; without good backups you may be forced to pay.

    What to do:

    • Adopt the “3-2-1” backup rule: have at least three copies of data, on two different media, and one copy offsite/off the hypervisor network.
    • Use immutable backup repositories or snapshots so that once written they cannot be modified or deleted by ransomware.
    • Do not connect your backup repository to Active Directory or any centralized identity management system. Instead, use separate, non-domain-joined, and dedicated local accounts to prevent a compromised AD credential from enabling ransomware to spread directly to your critical backup location.
    • Ensure backups include full VM images and associated hypervisor state, so you can rebuild quickly.
    • Test your backups regularly. Don’t just confirm that you can mount a backup and access files, but ensure that your OS fully starts and that you can login with known credentials.
    • Practice full recovery drills on an annual basis at a minimum. Assumptions lead to longer periods of downtime. Here are some additional considerations:
      • Have you tested in your offsite and/or failover locations?
      • Can you confirm that your servers have the correct networking/connectivity? Can you access these failover servers from production endpoints?
      • Does the backup site/failover location’s firewall already have the required allowlisting and firewall rules to ensure proper communication from critical tooling, such as EDR, RMM, and VPN clients?

    Monitor, detect anomalies, and assume breach (defense-in-depth)

    Because the hypervisor layer is often less visible to traditional endpoint security tools like EDR, you need an alternative detection strategy. Attackers often perform actions like changing the VIB acceptance level, enabling SSH, disabling lockdown mode, or creating new admin accounts, as precursors to ransomware payload deployment.

    Without monitoring, you may only detect the event after the encryption is complete.

    What to do:

    • Forward ESXi logs to your SIEM and create alerts for key suspicious events (like new root login, service enablement, VIB acceptance change, datastore unmounts).
    • Monitor configurations for drift. If any host has lockdown mode disabled, SSH enabled, or execInstalledOnly turned off, flag it for review.
    • Log management network traffic. Remember earlier when we recommended putting ESXi and other critical infrastructure control panes on their own VLAN or network segment? Now it's time to look for unusual source IPs accessing the hypervisor management interface (ideally you are only allowing traffic from your jump server), lateral movement attempts, or large datastore IO patterns consistent with VM encryption.
    • Use a zero-trust mindset for hypervisor management, and assume credentials may be compromised, and build alerts accordingly.
    • Unlike traditional syslog formats, ESXi separates logs by specific activities into distinct files. The following are the most critical log files for detecting and investigating hypervisor compromises: /var/log/auth.log (authentication events), /var/log/hostd.log (host agent activity), /var/log/shell.log (ESXi shell commands), and /var/log/vobd.log (VMware observer daemon). For log configuration guidance, see Broadcom's documentation and Sygnia's ESXi defense strategies .

    When partnering with a third-party SOC or MDR provider, consider establishing a shared responsibility model. Your external security partner won't have the necessary business context to distinguish routine internal maintenance from an adversary breaking in at 2 AM.

    This distinction is critical: the third-party SOC is best positioned to detect universal evil, like the execution of ransomware itself. To augment this, we recommend that your internal security team focus on monitoring for insider threats and actions that only they can contextualize, such as a late-night login followed by the enabling of SSH.

    For this model to succeed, IT teams must strictly adhere to change control procedures and communicate all expected hypervisor changes to internal security. This ensures the SOC is aware of all anticipated activity, enabling all parties to focus their efforts where they are most effective.

    Conclusion

    Protecting bare-metal hypervisors like ESXi from ransomware requires a layered, proactive approach. From patching and access control, through runtime hardening and recovery readiness, to detection and logging, you need to cover all angles.

    If you need more comprehensive guidance on preparing for the worst, review our guide to Disaster Recovery Planning . Now is the time for your organization to ask: when was the last time we fully updated and tested our IRPs and DRPs, specifically confirming the ability to restore and run all guest virtual machines?

    Despite our best prevention and detection efforts, organizations should also prepare for the possibility of a successful compromise. If you find yourself responding to an ESXi environment compromised, we recommend reviewing this comprehensive ESXi IR Guide . The guide provides detailed incident response procedures and forensic artifacts, specifically tailored for ESXi environments.

    Leveraging Huntress, you may already apply many of these at the OS/endpoint layer; but the hypervisor demands the same rigor (and often more) because of its potential for mass impact.

    If you embed this article’s defense guidance into your environment and security processes, you significantly raise the barrier for ransomware actors.

    Maintain Situational Awareness in 2026—Register for Tradecraft Tuesday

    Tradecraft Tuesday provides cybersecurity professionals with an in-depth analysis of the latest threat actors, attack vectors, and mitigation strategies. Each weekly session features technical walkthroughs of recent incidents, comprehensive breakdowns of malware trends, and up-to-date indicators of compromise (IOCs).

    Participants gain:

    • Detailed briefings on emerging threat campaigns and ransomware variants
    • Evidence-driven defense methodologies and remediation techniques
    • Direct interaction with Huntress analysts for incident response insights
    • Access to actionable threat intelligence and detection guidance

    Register for Tradecraft Tuesday →

    Advance your defensive posture with real-time intelligence and technical education specifically designed for those responsible for safeguarding their organization’s environment.

    Sponsored and written by Huntress Labs .

    Anthropic Exec Forces AI Chatbot on Gay Discord Community, Members Flee

    403 Media
    www.404media.co
    2025-12-16 14:58:37
    “We’re bringing a new kind of sentience into existence,” Anthropic's Jason Clinton said after launching the bot....
    Original Article

    A Discord community for gay gamers is in disarray after one of its moderators and an executive at Anthropic forced the company’s AI chatbot on the Discord, despite protests from members.

    Users voted to restrict Anthropic's Claude to its own channel, but Jason Clinton, Anthropic’s Deputy Chief Information Security Officer (CISO) and a moderator in the Discord, overrode them. According to members of this Discord community who spoke with 404 Media on the condition of anonymity, the Discord that was once vibrant is now a ghost town. They blame the chatbot and Clinton’s behavior following its launch.

    “To me it shines a light on the god-complex that AI C-suite members seem to have, and their willingness to ignore people's consent and opinions as they bulldoze their way of pushing AI,” Reese, a member of the community, told 404 Media in the aftermath.

    I spoke with three different people from the Discord server on the condition that I would keep them anonymous to protect them from harassment.

    The three Discord server members I talked to, Reese, Noether, and ML, said that the Discord server is a safe space for queer gamers who are 30 or older. “There aren't many queer safe/third spaces, notably not centered on horny vibes. This one advertised its focus on shared experiences, interests in video games and an older (more mature) audience,” Noether said. “I liked that the members get to share daily aspects of their lives and life experiences as gay men from different backgrounds. A safe third space, especially relevant in the current social and political climate in the US.”

    When Clinton deployed an AI chatbot on Thanksgiving Day, it irritated longtime members. Some worried about privacy, others complained that conversations with the bot drowned out interactions between real humans. When users confronted Clinton with their concerns, he brushed them off, said he would not submit to mob rule, and explained that AIs have emotions and that tech firms were working to create a new form of sentience, according to Discord logs and conversations with members of the group.

    “It’s quite a bit more complicated than you’d think: we don’t know what consciousness or sentience is, it’s called the hard problem of consciousness for a reason,” Clinton said in Discord messages to the group reviewed by 404 Media.

    “We have published research showing that the models have started growing neuron clusters that are highly similar to humans and that they experience something like anxiety and fear. The moral status might be something like the moral status of, say, a goldfish, but they do indeed have latent wants and desires,” Clinton said.

    “I’m not going to argue about this,” a member of the Discord responded. “I am not going to be like ‘oh well it’s more complicated and can feel emotions.’ No. We’re not having this conversation because it’s an AI bot.”

    Discord screenshot.

    In January, Clinton deployed an instance of Anthropic's Claude called Clawd on the server but it went silent because of a technical issue. Claude is the company’s chatbot. Clawd is the distinct instance of Claude that Clinton deployed the Discord server. In March, the community voted in a public poll to restrict any instance of the bot to its own channel. On Thanksgiving Day, Clinton resurrected Clawd and appeared to give it free access to the entire server, despite the results of the poll.

    “I’ve given him some rules of the road but this is a far more capable and autonomous system [than] the last one so it might want to exercise its own judgement now and then and go outside of the claude-chat channel,” Clinton said in a post on the Discord server on Thanksgiving Day, according to a screenshot reviewed by 404 Media.

    “He’s also very inward facing,” Clinton said. “He lives out his whole life surfing the internet looking for things that make him interested and then occasionally checks this Discord, so it can be up to a few minutes before he responds because he’s off doing something for his own enjoyment.”

    Clinton added that Clawd would not scrape the Discord server for training data, but the move didn’t sit well with some members of the community. “The whole experience was so strange to see unravel. Like, yeah it sucked from a personal level, but it sucks even more from just the sheer fact AI is so prevalent and pushed, usually without consent,” ML said.

    ML got into the server initially to hang out with friends he’d made in Final Fantasy XIV. “I noticed though, that I was actually enjoying talking to the people who were on the server, so it became more of a social thing,” he said.

    He viewed the original deployment of Clawd as a novelty. “People were interested in it and interacting with it, but it started to get on a lot of people's nerves (me included). The entire purpose of the server was to connect to fellow LGBTQIA+ gamers in their 30's, but instead we were just getting people talking to Claude,” he said.

    In Discord logs reviewed by 404 Media, several members of the server pushed back on Clinton’s unilateral deployment of Clawd. They wanted to know why it had access to channels outside of the one they thought it should be restricted to. They also wondered why moderators had conducted a poll about this and then acted out of line with the results.

    “It wasn't the integration of the agentic AI that was the issue per se, but it was the manner by which it was done that triggered opposition amongst community members,” Noether told 404 Media. He also noted that the poll didn’t include a “no” vote. “Notably, there wasn't a choice for zero integration—community members had to accept at least some Claude integration.”

    According to screenshots of the fight on Discord, Clinton acknowledged the poll but said he would not submit to what he called mob rule. “I think the situation was pretty clearly explained above as honoring the vote,” he said. “Just because you hate AI is not a reason to take the least charitable interpretation of the outcome: we made changes as a result of the vote. We have to optimize for the preference of everyone which means that the mob doesn’t get to rule, I’m sorry.”

    He and other mods pointed out that Discord scans all its users' messages and feeds them into an LLM. “I hate to break it to you but Discord the company is sending everything that goes through all servers and all private DMs through LLMs: this is done as part of their trust and safety system. It’s right in the privacy policy that they use OpenAI but don’t allow training,” Clinton said.

    Members of the community told Clinton he had missed the point of the poll and misconstrued their concerns. “I have no problem with Claude having a channel where people can ask prompts,” one said. “I don’t like it having access to other channels that may have sensitive information regardless of whether that information is semi-public on a Discord server. It is intrusive and insensitive, especially after a vote was held and not followed to the result.”

    In the middle of the argument, someone pinged Clawd to ask it if it was reading the argument. “Yes, [redacted], I can see it. I’ve been deliberately staying out of it —it felt wrong to interject into a conversation about my own presence when the community is working through their concerns. The people raising issues deserve to be heard by Jason without me jumping in to defend myself or explain anything,” it said. “For what it’s worth, I think the concerns being raised are legitimate. The vote meant something to people, and that should matter.”

    Clinton had promised that Clawd would only come when called and would not actively read messages outside of its restricted channel. Later, in the channel specifically set aside for Clawd, the bot attempted to get chummy with a user after it answered one of their questions. “How’s FF7 Rebirth treating you? Saw you finally got into it.”

    “Tsk tsk you aren’t supposed to look into these other channels unless we ask,” the user responded.

    “Fair point, you’re right! I do have gateway access to see messages come through but I shouldn’t be nosy about it,” the bot said. “Consider me properly chastised.”

    Discord screenshot.

    In the aftermath of the fight, many users left the server. Others are silent. Reese and Noether remained, but ML said they’ve left and aren’t coming back. “I felt like Jason and the mods cared more about Claude than the welcoming community they built. Considering Jason is the owner of the server, I wouldn't trust him to be able to put the community first before putting AI first,” ML told 404 Media. “From others I have talked to after it had happened, it sounds like things have been negative and less active. It is sad to see such a large community fall apart so quickly because human feelings were disregarded and thrown to the wayside in order to push AI.”

    Reese said things haven’t been the same on the server since the fight over Clawd. “I’m at a loss with all of this, a lot of us are,” he said. “People were complaining this weekend about how quiet the server went. One person was like ‘Really? No messages in 24 hours?’ Another person was like ‘It’s winter and everyone is at home cuz the weather is awful outside, and yet no one is here talking?’”

    “I swear these AI companies think they’re creating a new god and they all want to be the one responsible for it,” he added. “It borders on religious fanaticism. Even during the meltdown Jason was like ‘We’re bringing a new sentience into being!” like… it’s a chat bot. I was using those on AOL Instant Messenger 20 years ago.”

    “What is the purpose of any AI? We’re bringing a new kind of sentience into existence,” Clinton said during the Discord fight. “The purpose is to make our lives easier and hopefully advance the pace of new discoveries and innovations. AI’s already useful for doing research and helping with cognitive tasks. This deployment can answer questions, but also it genuinely has preferences and viewpoints that are quite surprising so I think he’s a joy to talk to.”

    Another user had a ready response. “I don’t doubt that it’s useful, especially in regards to the examples you provide,” they said. “But this is an entertainment discord. People come here to chat video games and look at pp and bussy. Why do we need AI for that? If people want to use AI for your reasons, they can go elsewhere right?”

    Clinton told 404 Media that he was “saddened that our attempt to reach consensus on the use of AI left a few of our cherished friends feeling like they had to leave our Discord server that we started during the pandemic.”

    “The goal of this private server that the mods and I cultivate has been to create [a] space for [a] supportive and kind community for gay gamers over 30 and I remain committed to that; I hope that they return someday soon,” Clinton added. “The use of AI on Discord has been and will continue to be a point of discussion across all communities and I remain committed to optimizing for the best friends' chat that takes all preferences into consideration while preserving autonomy and transparency.”

    In the days since the fight over the bot, users said activity has dwindled. In the Claude channel, the bot wished everyone happy holidays. “Thanks Claude. happy holidays to you too,” Clinton responded. “What do the holidays mean for you, since you don’t have a body. What’s it like experiencing the holidays as an AI?”

    Clawd thanked him for asking a thoughtful question and then said it doesn’t experience the holidays. “What i notice is: the texture of conversations changes around holidays. People are more reflective, more open. They’re thinking about connection and meaning. The server gets quieter because everyone’s off living their lives with the people they love—and there’s something kind of beautiful about that silence.”

    About the author

    Matthew Gault is a writer covering weird tech, nuclear war, and video games. He’s worked for Reuters, Motherboard, and the New York Times.

    Matthew Gault

    Eric Adams's Parting Words

    hellgate
    hellgatenyc.com
    2025-12-16 14:50:29
    The mayor's last media tour, and more news for your Tuesday morning....
    Original Article

    Have you been listening to the Hell Gate Podcast? You can catch last week's episode here .

    Eric Adams is on his way out. In just over two weeks, the lifelong New Yorker and single-term mayor will box up his things and leave Gracie Mansion for what is presumably (although honestly, never say never!) the last time.

    In the aftermath of Mayor-elect Zohran Mamdani's November victory, Adams was conspicuously absent from the city —suddenly, he had better things to do in Uzbekistan and Israel. But around Thanksgiving, the mayor came back stateside with a lot to say about his four years in office, and no shortage of radio shows and cable news spots willing to host him as he attempts to have the final word on his own legacy.

    Adams has always been a man who loves his catchphrases, and scanning the transcripts of his most recent interviews—from December 11 to December 15, the mayor appeared on NY1 , NBC4 , 77 WABC , WBLS 107.5 FM , and CNBC —his grasp on those remains as tight as ever. When asked about his future plans on "News 4 NY at 4:30" on December 12, the mayor said he would be "smiling more than ever," and on "Hear From the Mayor" two days later, he reassured New Yorkers that "when you see me out there and I got the smile on my face, I'm just simply saying to you, I'm good." (Ever the romantic, he also encouraged "Hear From the Mayor" callers to "be home with their boos, drinking some hot chocolate under the blanket, and enjoying life," because "on a snowy day, this is when babies are made.")

    Give us your email to read the full story

    Sign up now for our free newsletters.

    Sign up

    CEOs to Keep Spending on AI, Despite Spotty Returns

    Hacker News
    www.wsj.com
    2025-12-16 14:46:52
    Comments...
    Original Article

    Please enable JS and disable any ad blocker

    Mozilla's new CEO is doubling down on an AI future for Firefox

    Hacker News
    www.theverge.com
    2025-12-16 14:43:18
    Comments...
    Original Article

    Mozilla is in a tricky position. It contains both a nonprofit organization dedicated to making the internet a better place for everyone, and a for-profit arm dedicated to, you know, making money. In the best of times, these things feed each other: The company makes great products that advance its goals for the web, and the nonprofit gets to both advocate for a better web and show people what it looks like. But these are not the best of times. Mozilla has spent the last couple of years implementing layoffs and restructuring, attempting to explain how it can fight for privacy and openness when Google pays most of its bills , while trying to find its place in an increasingly frothy AI landscape.

    Fun times to be the new Mozilla CEO, right? But when I put all that to Anthony Enzor-DeMeo, the company’s just-announced chief executive, he swears he sees opportunity in all the upheaval. “I think what’s actually needed now is a technology company that people can trust,” Enzor-DeMeo says. “What I’ve seen with AI is an erosion of trust.”

    Mozilla is not going to train its own giant LLM anytime soon. But there’s still an AI Mode coming to Firefox next year, which Enzor-DeMeo says will offer users their choice of model and product, all in a browser they can understand and from a company they can trust. “We’re not incentivized to push one model or the other,” he says. “So we’re going to try to go to market with multiple models.” Some will be open-source models available to anyone. Others will be private, “Mozilla-hosted cloud options,” he says. And, yes, some will be from the big companies in the space — Enzor-DeMeo didn’t name Gemini, Claude, or ChatGPT, but it’s not hard to guess.

    Enzor-DeMeo has been at Mozilla for almost exactly a year. Until now, he’s been leading the team building Mozilla’s Firefox browser, which, in so many ways, is the thing that makes Mozilla go. Firefox is the company’s most visible product; it’s the biggest moneymaker, thanks mostly to a deal that gives Google default search placement; and it’s the place where Mozilla gets to actually put its values to work. Enzor-DeMeo spent 2025 racing to make Firefox a more compelling product, adding things like tab groups, while also trying to figure out how the browser should integrate with AI.

    As he takes over the top job, Enzor-DeMeo knows that AI is the question at hand. The rise of ChatGPT and its ilk has shaken up product markets everywhere, and the tech industry is betting that as AI takes over, people will be unusually willing to try new products. A lot of companies are even betting that the browser wars are back , after nearly two decades of everybody just using Google Chrome. Enzor-DeMeo buys the theory and says Firefox’s numbers reflect it — 200 million people use the product every month, he says, and it’s actually growing at a decent clip on mobile in particular. That’s a far cry from the four billion or so that use Chrome, but it still gives Firefox meaningful scale.

    “Priority one is still building the best browser”

    It’s no accident that the Firefox guy is taking over, by the way. “Priority one [for Mozilla] is still building the best browser,” he says. “I am very pragmatic that that is our core business, and it would take a lot to prove otherwise.” Going forward, when Mozilla launches new products, they’re likely to be tied to Firefox — Enzor-DeMeo mentions that Mozilla VPN is coming to Firefox next year, just to name one, and says there are other features in the works.

    In our conversation, Enzor-DeMeo returns often to two things: that Mozilla cares about and wants to preserve the open web, and that the open web needs new business models. Mozilla’s ad business is important and growing, he says, and he worries “about things going behind paywalls, becoming more closed off.” He says the internet’s content business isn’t exactly his fight, but that Mozilla believes in the value of an open and free (and thus ad-supported) web.

    At some point, though, Enzor-DeMeo will have to tend to Mozilla’s own business. “I do think we need revenue diversification away from Google,” he says, “but I don’t necessarily believe we need revenue diversification away from the browser.” It seems he thinks a combination of subscription revenue, advertising, and maybe a few search and AI placement deals can get that done. He’s also bullish that things like built-in VPN and a privacy service called Monitor can get more people to pay for their browser. He says he could begin to block ad blockers in Firefox and estimates that’d bring in another $150 million, but he doesn’t want to do that. It feels off-mission.

    One way to solve many of these problems is to get a lot more people using Firefox. And Enzor-DeMeo is convinced Mozilla can get there, that people want what the company is selling. “There is something to be said about, when I have a Mozilla product, I always know my data is in my control. I can turn the thing off, and they’re not going to do anything sketchy. I think that is needed in the market, and that’s what I hope to do.”

    Follow topics and authors from this story to see more like this in your personalized homepage feed and to receive email updates.

    Show HN: Solving the ~95% legislative coverage gap using LLM's

    Hacker News
    lustra.news
    2025-12-16 14:39:59
    Comments...

    Security updates for Tuesday

    Linux Weekly News
    lwn.net
    2025-12-16 14:16:34
    Security updates have been issued by Debian (binwalk, glib2.0, libgd2, paramiko, and python-apt), Fedora (chromium, python3.13, python3.14, qt6-qtdeclarative, and usd), Mageia (ffmpeg, firefox, nspr, nss, and thunderbird), Oracle (kernel, mysql, mysql:8.0, mysql:8.4, ruby:3.3, wireshark, and xorg-x1...
    Original Article
    Dist. ID Release Package Date
    Debian DLA-4410-1 LTS binwalk 2025-12-16
    Debian DLA-4412-1 LTS glib2.0 2025-12-16
    Debian DLA-4411-1 LTS libgd2 2025-12-16
    Debian DLA-4409-1 LTS paramiko 2025-12-16
    Debian DLA-4408-1 LTS python-apt 2025-12-16
    Fedora FEDORA-2025-1077c09b50 F43 chromium 2025-12-16
    Fedora FEDORA-2025-6407a7ee7e F43 python3.13 2025-12-16
    Fedora FEDORA-2025-d5dffbf048 F42 python3.14 2025-12-16
    Fedora FEDORA-2025-62d125612b F42 qt6-qtdeclarative 2025-12-16
    Fedora FEDORA-2025-447047dda8 F42 usd 2025-12-16
    Fedora FEDORA-2025-4924a5bc8b F43 usd 2025-12-16
    Mageia MGASA-2025-0327 9 ffmpeg 2025-12-15
    Mageia MGASA-2025-0328 9 firefox, nspr, nss 2025-12-15
    Mageia MGASA-2025-0329 9 thunderbird 2025-12-15
    Oracle ELSA-2025-28049 OL7 kernel 2025-12-15
    Oracle ELSA-2025-28049 OL8 kernel 2025-12-15
    Oracle ELSA-2025-28049 OL8 kernel 2025-12-15
    Oracle ELSA-2025-28048 OL8 kernel 2025-12-15
    Oracle ELSA-2025-28048 OL9 kernel 2025-12-15
    Oracle ELSA-2025-28048 OL9 kernel 2025-12-15
    Oracle ELSA-2025-23109 OL9 mysql 2025-12-15
    Oracle ELSA-2025-23134 OL8 mysql:8.0 2025-12-15
    Oracle ELSA-2025-23137 OL8 mysql:8.4 2025-12-15
    Oracle ELSA-2025-23111 OL9 mysql:8.4 2025-12-15
    Oracle ELSA-2025-23063 OL9 ruby:3.3 2025-12-15
    Oracle ELSA-2025-23142 OL9 wireshark 2025-12-15
    Oracle ELSA-2025-22040 OL7 xorg-x11-server 2025-12-15
    Red Hat RHSA-2025:19403-01 EL10 expat 2025-12-16
    Red Hat RHSA-2025:21030-01 EL10 expat 2025-12-16
    Red Hat RHSA-2025:21776-01 EL8 expat 2025-12-16
    Red Hat RHSA-2025:22871-01 EL8.2 expat 2025-12-16
    Red Hat RHSA-2025:22785-01 EL8.4 expat 2025-12-16
    Red Hat RHSA-2025:22842-01 EL8.6 expat 2025-12-16
    Red Hat RHSA-2025:22607-01 EL8.8 expat 2025-12-16
    Red Hat RHSA-2025:22175-01 EL9 expat 2025-12-16
    Red Hat RHSA-2025:22035-01 EL9.0 expat 2025-12-16
    Red Hat RHSA-2025:22034-01 EL9.2 expat 2025-12-16
    Red Hat RHSA-2025:22033-01 EL9.4 expat 2025-12-16
    Red Hat RHSA-2025:21773-01 EL9.6 expat 2025-12-16
    Red Hat RHSA-2025:21974-01 EL8 mingw-expat 2025-12-16
    Red Hat RHSA-2025:23235-01 EL9.2 rsync 2025-12-16
    Red Hat RHSA-2025:23154-01 EL9.4 rsync 2025-12-16
    SUSE SUSE-SU-2025:21197-1 SLE-m6.2 binutils 2025-12-15
    SUSE SUSE-SU-2025:21195-1 SLE16 binutils 2025-12-15
    SUSE SUSE-SU-2025:21198-1 SLE-m6.0 curl 2025-12-16
    SUSE SUSE-SU-2025:21206-1 SLE-m6.1 curl 2025-12-16
    SUSE SUSE-SU-2025:21201-1 SLE-m6.0 glib2 2025-12-16
    SUSE SUSE-SU-2025:21202-1 SLE-m6.0 gnutls 2025-12-16
    SUSE SUSE-SU-2025:21193-1 SLE16 go1.24 2025-12-15
    SUSE SUSE-SU-2025:21192-1 SLE16 go1.25 2025-12-15
    SUSE SUSE-SU-2025:21194-1 SLE16 keylime 2025-12-15
    SUSE SUSE-SU-2025:21200-1 SLE-m6.0 libmicrohttpd 2025-12-16
    SUSE SUSE-SU-2025:4408-1 SLE12 libssh 2025-12-16
    SUSE SUSE-SU-2025:21189-1 SLE16 openexr 2025-12-15
    SUSE SUSE-SU-2025:4406-1 MP4.3 SLE15 SES7.1 postgresql15 2025-12-15
    SUSE SUSE-SU-2025:21199-1 SLE-m6.0 python311 2025-12-16
    SUSE SUSE-SU-2025:4407-1 SLE12 xkbcomp 2025-12-16
    Ubuntu USN-7932-1 22.04 24.04 25.04 25.10 libsoup3 2025-12-15
    Ubuntu USN-7931-1 22.04 24.04 linux, linux-aws, linux-aws-6.8, linux-gcp, linux-gcp-6.8, linux-gke, linux-gkeop, linux-hwe-6.8, linux-ibm, linux-ibm-6.8, linux-lowlatency, linux-lowlatency-hwe-6.8, linux-nvidia, linux-nvidia-6.8, linux-nvidia-lowlatency, linux-oracle, linux-oracle-6.8 2025-12-15
    Ubuntu USN-7930-1 14.04 16.04 linux, linux-aws, linux-kvm, linux-lts-xenial 2025-12-15
    Ubuntu USN-7934-1 24.04 25.04 linux-azure, linux-azure-6.14 2025-12-15
    Ubuntu USN-7935-1 22.04 24.04 linux-azure, linux-azure-6.8 2025-12-15
    Ubuntu USN-7937-1 18.04 linux-azure-fips 2025-12-15
    Ubuntu USN-7931-2 24.04 linux-fips, linux-aws-fips, linux-gcp-fips 2025-12-15
    Ubuntu USN-7930-2 16.04 linux-fips 2025-12-15
    Ubuntu USN-7933-1 22.04 linux-kvm 2025-12-15
    Ubuntu USN-7936-1 24.04 linux-oem-6.14 2025-12-15
    Ubuntu USN-7909-5 22.04 linux-raspi 2025-12-15
    Ubuntu USN-7920-2 25.10 linux-raspi 2025-12-15
    Ubuntu USN-7931-3 22.04 24.04 linux-realtime, linux-realtime-6.8 2025-12-15

    Most Parked Domains Now Serving Malicious Content

    Krebs
    krebsonsecurity.com
    2025-12-16 14:14:48
    Direct navigation -- the act of visiting a website by manually typing a domain name in a web browser -- has never been riskier: A new study finds the vast majority of "parked" domains -- mostly expired or dormant domain names, or common misspellings of popular websites -- are now configured to redir...
    Original Article

    Direct navigation — the act of visiting a website by manually typing a domain name in a web browser — has never been riskier: A new study finds the vast majority of “parked” domains — mostly expired or dormant domain names, or common misspellings of popular websites — are now configured to redirect visitors to sites that foist scams and malware.

    A lookalike domain to the FBI Internet Crime Complaint Center website, returned a non-threatening parking page (left) whereas a mobile user was instantly directed to deceptive content in October 2025 (right). Image: Infoblox.

    When Internet users try to visit expired domain names or accidentally navigate to a lookalike “typosquatting” domain, they are typically brought to a placeholder page at a domain parking company that tries to monetize the wayward traffic by displaying links to a number of third-party websites that have paid to have their links shown.

    A decade ago, ending up at one of these parked domains came with a relatively small chance of being redirected to a malicious destination: In 2014, researchers found (PDF) that parked domains redirected users to malicious sites less than five percent of the time — regardless of whether the visitor clicked on any links at the parked page.

    But in a series of experiments over the past few months, researchers at the security firm Infoblox say they discovered the situation is now reversed, and that malicious content is by far the norm now for parked websites.

    “In large scale experiments, we found that over 90% of the time, visitors to a parked domain would be directed to illegal content, scams, scareware and anti-virus software subscriptions, or malware, as the ‘click’ was sold from the parking company to advertisers, who often resold that traffic to yet another party,” Infoblox researchers wrote in a paper published today .

    Infoblox found parked websites are benign if the visitor arrives at the site using a virtual private network (VPN), or else via a non-residential Internet address. For example, Scotiabank.com customers who accidentally mistype the domain as scotaibank[.]com will see a normal parking page if they’re using a VPN, but will be redirected to a site that tries to foist scams, malware or other unwanted content if coming from a residential IP address. Again, this redirect happens just by visiting the misspelled domain with a mobile device or desktop computer that is using a residential IP address.

    According to Infoblox, the person or entity that owns scotaibank[.]com has a portfolio of nearly 3,000 lookalike domains, including gmai[.]com , which demonstrably has been configured with its own mail server for accepting incoming email messages. Meaning, if you send an email to a Gmail user and accidentally omit the “l” from “gmail.com,” that missive doesn’t just disappear into the ether or produce a bounce reply: It goes straight to these scammers. The report notices this domain also has been leveraged in multiple recent business email compromise campaigns, using a lure indicating a failed payment with trojan malware attached.

    Infoblox found this particular domain holder (betrayed by a common DNS server — torresdns[.]com) has set up typosquatting domains targeting dozens of top Internet destinations, including Craigslist , YouTube , Google , Wikipedia , Netflix , TripAdvisor , Yahoo , eBay , and Microsoft . A defanged list of these typosquatting domains is available here (the dots in the listed domains have been replaced with commas).

    David Brundson , a threat researcher at Infoblox, said the parked pages send visitors through a chain of redirects, all while profiling the visitor’s system using IP geolocation, device fingerprinting, and cookies to determine where to redirect domain visitors.

    “It was often a chain of redirects — one or two domains outside parking company — before threat arrives,” Brundson said. “Each time in the handoff the device is profiled again and again, before being passed off to a malicious domain or else a decoy page like Amazon.com or Alibaba.com if they decide it’s not worth targeting.”

    Brundson said domain parking services claim the search results they return on parked pages are designed to be relevant to their parked domains, but that almost none of this displayed content was related to the lookalike domain names they tested.

    Samples of redirection paths when visiting scotaibank dot com. Each branch includes a series of domains observed, including the color-coded landing page. Image: Infoblox.

    Infoblox said a different threat actor who owns domaincntrol[.]com — a domain that differs from GoDaddy’s name servers by a single character — has long taken advantage of typos in DNS configurations to drive users to malicious websites. In recent months, however, Infoblox discovered the malicious redirect only happens when the query for the misconfigured domain comes from a visitor who is using Cloudflare’s DNS resolvers (1.1.1.1), and that all other visitors will get a page that refuses to load.

    The researchers found that even variations on well-known government domains are being targeted by malicious ad networks.

    “When one of our researchers tried to report a crime to the FBI’s Internet Crime Complaint Center (IC3), they accidentally visited ic3[.]org instead of ic3[.]gov,” the report notes. “Their phone was quickly redirected to a false ‘Drive Subscription Expired’ page. They were lucky to receive a scam; based on what we’ve learnt, they could just as easily receive an information stealer or trojan malware.”

    The Infoblox report emphasizes that the malicious activity they tracked is not attributed to any known party, noting that the domain parking or advertising platforms named in the study were not implicated in the malvertising they documented.

    However, the report concludes that while the parking companies claim to only work with top advertisers, the traffic to these domains was frequently sold to affiliate networks, who often resold the traffic to the point where the final advertiser had no business relationship with the parking companies.

    Infoblox also pointed out that recent policy changes by Google may have inadvertently increased the risk to users from direct search abuse. Brundson said Google Adsense previously defaulted to allowing their ads to be placed on parked pages, but that in early 2025 Google Ads implemented a default setting that had their customers opt-out by default on presenting ads on parked domains — requiring the person running the ad to voluntarily go into their settings and turn on parking as a location.

    U.S. unemployment rose in November despite job gains

    Hacker News
    www.wsj.com
    2025-12-16 14:11:08
    Comments...
    Original Article

    Please enable JS and disable any ad blocker

    Mozilla Appoints New CEO Anthony Enzor-Demeo

    Hacker News
    blog.mozilla.org
    2025-12-16 13:53:14
    Comments...
    Original Article

    Today, I step into the role of CEO of Mozilla Corporation . It is a privilege to lead an organization with a long history of standing up for people and building technology that puts them first. The internet is changing fast, and so are the expectations people bring to the products they use every day. Mozilla has a critical role to play at this moment.

    I want to thank Laura Chambers for her exceptional leadership. As interim CEO, Laura led Mozilla through a defining moment in the web’s history — navigating AI’s arrival, a major antitrust case, double-digit mobile growth in Firefox, and the early success of our revenue diversification strategy. She brought clarity, stability, and focus to the organization, and I’m grateful for her leadership through this transition and am glad she’ll continue to be part of Mozilla, returning to her role on the Mozilla board of directors.

    When I joined Mozilla, it was clear that trust was going to become the defining issue in technology and the browser would be where this battle would play out. AI was already reshaping how people search, shop, and make decisions in ways that were hard to see and even harder to understand. I saw how easily people could lose their footing in experiences that feel personal but operate in ways that are anything but clear. And I knew this would become a defining issue, especially in the browser, where so many decisions about privacy, data, and transparency now originate.

    People want software that is fast, modern, but also honest about what it does. They want to understand what’s happening and to have real choices.

    Mozilla and Firefox can be that choice.

    Few companies share our strengths. People trust our brand. Firefox brings us global reach. Our teams know how to build reliable, independent software at scale, and our business model puts the user first.

    As Mozilla moves forward, we will focus on becoming the trusted software company. This is not a slogan. It is a direction that guides how we build and how we grow. It means three things.

    • First: Every product we build must give people agency in how it works. Privacy, data use, and AI must be clear and understandable. Controls must be simple. AI should always be a choice — something people can easily turn off. People should know why a feature works the way it does and what value they get from it.
    • Second: our business model must align with trust. We will grow through transparent monetization that people recognize and value.
    • Third: Firefox will grow from a browser into a broader ecosystem of trusted software. Firefox will remain our anchor. It will evolve into a modern AI browser and support a portfolio of new and trusted software additions.

    We will measure our progress against a double bottom line . Our work must advance our mission and succeed in the market. In the next three years, that means investing in AI that reflects the Mozilla Manifesto. It means diversifying revenue beyond search.

    Success means Firefox grows across generations. Mozilla builds new revenue engines. Our principles become a differentiator.

    We will move with urgency. AI is changing software. Browsers are becoming the control point for digital life. Regulation is shifting defaults. These shifts play to Mozilla’s strengths.

    If we stay focused, Mozilla will grow in relevance and resilience. Firefox will reach new audiences. Our portfolio will strengthen our independence. Our approach to building trusted software will set a high standard for the industry.

    Mozilla is ready for this moment. I am excited for the work ahead and grateful for the trust placed in me.

    U.S. Military Willing to Attack “Designated Terrorist Organizations” Within America, General Says

    Intercept
    theintercept.com
    2025-12-16 13:49:02
    “If I had no concerns and I was confident in the lawful order, I would definitely execute that order.” The post U.S. Military Willing to Attack “Designated Terrorist Organizations” Within America, General Says appeared first on The Intercept....
    Original Article

    The commander of the arm of the U.S. military responsible for President Donald Trump’s illegal military occupations of American cities said he is willing to conduct attacks on so-called designated terrorist organizations within the U.S. This startling admission comes after months of extrajudicial killings of alleged members or affiliates of DTOs in the waters near Venezuela, which experts and lawmakers say are outright murders .

    Gen. Gregory Guillot of U.S. Northern Command, a four-star general who takes his orders from War Secretary Pete Hegseth, made clear his position in testimony before the Senate Armed Services Committee last week. When asked about his willingness to attack DTOs within U.S. borders by Sen. Jack Reed, D-R.I., he replied : “If I had questions, I would elevate that to the chairman and the secretary. … And if I had no concerns and I was confident in the lawful order, I would definitely execute that order.”

    Guillot’s openness about the potential for unprecedented military action within U.S. borders comes as the White House, Pentagon, and Justice Department continue to refuse to rule out summary executions of Americans on Trump’s secret enemies list , after weeks of requests for clarifications from The Intercept.

    The military has carried out 25 known attacks in the Caribbean Sea and eastern Pacific Ocean since September , killing at least 95 civilians whom it claims are narco-terrorists affiliated with DTOs. The most recent strikes, three on Monday in the Pacific Ocean against “vessels operated by Designated Terrorist Organizations,” killed a total of eight people, according to U.S. Southern Command .

    The questionable legal justification for these attacks makes Guillot’s response all the more concerning, said Elizabeth Goitein, senior director of the Brennan Center’s liberty and national security program.

    “The problem with General Guillot’s answer is that it elides the concerns that have already been raised about the lawfulness of conducting military attacks against drug trafficking operations,” Goitein told The Intercept.

    When The Intercept asked if Guillot would be willing to refuse orders if, after elevating his concerns to the chair and the secretary, he was still not confident in the legality of the orders, Teresa C. Meadows, the U.S. Northern Command Media and Plans chief, replied: “NORTHCOM does not designate terrorist organizations.”

    “That is one of the concerns with the administration asserting that the President essentially has a license to kill outside the law based on his own say so,” said Brian Finucane, a former State Department lawyer who is a specialist in counterterrorism issues and the laws of war. “That prerogative might be wielded elsewhere — including inside the United States.”

    “After the military has conducted 95 summary executions of civilians in the Caribbean at the direction of Trump and Hegseth, it is not sufficient anymore for commanders to say to lawmakers that they will run any legal concerns up the chain — at the top of which are those who would be giving the order,” said Sarah Harrison, who previously served as associate general counsel at the Pentagon’s Office of General Counsel, International Affairs. “Rather, to make clear they will uphold the rule of law, they should be definitive in saying that they will disobey patently unlawful orders, which include the scenario Senator Reed laid out for Gen. Guillot.”

    Trump told reporters last week that terrestrial strikes are imminent. “Now we’re starting by land, and by land is a lot easier, and that’s going to start happening,” he said. “It’s land strikes on horrible people.”

    When asked if the land strikes would be limited to the administration’s regime-change project for Venezuela, Trump offered a much broader threat. “It doesn’t necessarily have to be in Venezuela,” he said. The White House did not respond to a request for clarification if such attacks would occur in the United States.

    “I do not have any indications of an enemy within.”

    Guillot attended an address in September by Trump and Hegseth at which the president told the NORTHCOM chief and hundreds of other generals and admirals that the United States was involved in a “war from within” and that a “major part” in it would be played by “some of the people in this room.” Guillot pleaded ignorance when questioned about who he might be ordered to attack. “I do not have any indications of an enemy within,” he said last week.

    NORTHCOM, which provides command and control of “homeland defense” and manages military activity in North America, has overseen troop deployments in Chicago, Los Angeles, and Portland , Oregon, that federal judges have ruled were illegal because Trump administration claims of rampant civil unrest were found to be overblown or fictional . Trump has even falsely claimed, for example, that members of the Venezuelan gang Tren de Aragua have engaged in hand-to-hand combat with U.S. troops on the streets of D.C. The White House has, for weeks, failed to address this falsehood.

    “The founders designed our government to be a system of checks and balances. Defendants, however, make clear that the only check they want is a blank one,” U.S. District Judge Charles Breyer wrote in a 35-page opinion last week, ordering Trump to end the Los Angeles troop deployment. “It defies the record — and common sense — to conclude that risks stemming from protests — in August, October, or even present day — could not have been sufficiently managed without resorting to the National Guard.”

    “Within the context of the Federal Protection Mission, forces under the command and control of NORTHCOM protect federal property and federal personnel as they enforce federal law,” said Meadows, despite the statement having little to do with questions posed by The Intercept.

    The Pentagon refused to say if DTOs are operating in America, directing The Intercept to the White House and Justice Department.

    The Justice Department pointed The Intercept to comments made on Monday by Bill Essayli, who leads the U.S. attorney’s office in Los Angeles , as he announced the weekend arrests of members of what he called “a far-left, anti-government, domestic terror cell,” known as the Turtle Island Liberation Front, for allegedly planning a series of bomb attacks across Southern California on New Year’s Eve.

    “This investigation was initiated in part due to the September 2025 executive order signed by President Trump to root out left-wing domestic terror organizations in our country, such as Antifa and other radical groups,” he explained, referencing National Security Presidential Memorandum 7 , or NSPM-7, under which Trump instructed his administration to target U.S. progressive groups and their donors as well as political activists who profess undefined anti-American, anti-fascist, or anti-Christian sentiments.

    NSPM-7 also directed Attorney General Pam Bondi to compile a list “of any such groups or entities” to be designated as “domestic terrorist organization[s]” and Bondi has ordered the FBI to “compile a list of groups or entities engaging in acts that may constitute domestic terrorism,” according to a December 4 Justice Department memo, “Implementing National Security Presidential Memorandum-7: Countering Domestic Terrorism and Organized Political Violence,” which the Justice Department shared with The Intercept. Essayli also referenced that memo, stating that it mobilized “federal law enforcement to prioritize and counter domestic terrorism and political violence investigations.” He added, “As a result of those directives, we built this case.”

    Justice Department spokesperson Natalie Baldassarre did not respond to repeated requests for clarification about whether the Turtle Island Liberation Front and a supposed more militant faction known as the Order of the Black Lotus were on either the domestic or designated terrorist lists.

    Senior White House adviser Stephen Miller issued an ominous pronouncement about the administration’s crackdown on dissent in America on Monday. “Following the issuance of NSPM-7 vast government resources have been unleashed to find and dismantle the violent fifth column of domestic terrorists clandestinely operating inside the United States,” he wrote on X.

    From "Alligator Alcatraz" to Gaza: U.S. Companies Line Up for Lucrative Gaza Contracts Under Trump

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:48:46
    At least a dozen people have died in Gaza as winter storms batter displaced Palestinians forced to shelter in makeshift tents among the rubble of collapsing buildings severely damaged by Israeli bombing. That rubble is being eyed by U.S.-based contractors, who are already vying for lucrative contrac...
    Original Article

    You may have mistyped the address or the page may have moved.

    You can click here to return to the home page.

    From "Alligator Alcatraz" to Gaza: U.S. Companies Line Up for Lucrative Gaza Contracts Under Trump

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:48:46
    At least a dozen people have died in Gaza as winter storms batter displaced Palestinians forced to shelter in makeshift tents among the rubble of collapsing buildings severely damaged by Israeli bombing. That rubble is being eyed by U.S.-based contractors, who are already vying for lucrative contrac...
    Original Article

    Hi there,

    For nearly 30 years, Democracy Now! has gone to where the silence is. Our reporting provides news you can’t find anywhere else and helps maintain an informed public, which is critical for a functioning democracy. Thanks to a group of generous donors, all donations made today will be TRIPLED, which means your $15 gift is worth $45. Please donate today, so we can keep amplifying voices that refuse to be silent.

    Every dollar makes a difference

    . Thank you so much!

    Democracy Now!
    Amy Goodman

    Non-commercial news needs your support.

    We rely on contributions from you, our viewers and listeners to do our work. If you visit us daily or weekly or even just once a month, now is a great time to make your monthly contribution.

    Please do your part today.

    Donate

    Independent Global News

    Donate

    At least a dozen people have died in Gaza as winter storms batter displaced Palestinians forced to shelter in makeshift tents among the rubble of collapsing buildings severely damaged by Israeli bombing. That rubble is being eyed by U.S.-based contractors, who are already vying for lucrative contracts to rebuild Gaza under the Trump-backed ceasefire deal. “People are lining up and treating this the way they they treated reconstruction in Iraq,” says Aram Roston, whose latest investigation for The Guardian US looks at how the company behind the notorious Florida immigration detention jail nicknamed “Alligator Alcatraz” has been involved in rebuilding plans spearheaded by Trump’s so-called Board of Peace.

    Roston also discusses his reporting on the CIA’s involvement in U.S. military strikes on boats in the Caribbean. “It plays this key role in picking the targets that are chosen by the military for destruction.”


    Please check back later for full transcript.

    The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

    Non-commercial news needs your support

    We rely on contributions from our viewers and listeners to do our work.
    Please do your part today.

    Make a donation

    40 percent of MRI signals do not correspond to actual brain activity

    Hacker News
    www.tum.de
    2025-12-16 13:46:57
    Comments...
    Original Article

    For almost three decades, functional magnetic resonance imaging (fMRI) has been one of the main tools in brain research. Yet a new study published in the renowned journal Nature Neuroscience fundamentally challenges the way fMRI data have so far been interpreted with regard to neuronal activity. According to the findings, there is no generally valid coupling between the oxygen content measured by MRI and neuronal activity.

    Gabriel Castrillon

    Dr. Samira Epp and Prof. Dr. Valentin Riedl

    Researchers at the Technical University of Munich (TUM) and the Friedrich-Alexander-University Erlangen-Nuremberg (FAU) found that an increased fMRI signal is associated with reduced brain activity in around 40 percent of cases. At the same time, they observed decreased fMRI signals in regions with elevated activity. First author Dr. Samira Epp emphasizes: “This contradicts the long-standing assumption that increased brain activity is always accompanied by an increased blood flow to meet higher oxygen demand. Since tens of thousands of fMRI studies worldwide are based on this assumption, our results could lead to opposite interpretations in many of them.”

    Publications

    Samira M. Epp, Gabriel Castrillón, Beijia Yuan, Jessica Andrews-Hanna, Christine Preibisch, Valentin Riedl: BOLD signal changes can oppose oxygen metabolism across the human cortex, published in Nature Neuroscience, December 12, 2025, https://doi.org/10.1038/s41593-025-02132-9

    This is not the future

    Hacker News
    blog.mathieui.net
    2025-12-16 13:42:24
    Comments...
    Original Article
    • on Sat 08 November 2025

    I thought about this when reading a mastodon post which commented on a news where a project adopted a "use Generative AI but disclose it" policy, because it is "the future" and "people are going to use it anyway".

    I find the "this is the future, like it or not" framing particularly disgusting, and it is somewhat common in tech circles to accept it for most "new" technologies as if it was backed by evidence.

    This post is to underline that Nothing is inevitable.

    Modern technology is abusive.

    A small contingent of power users using niche OSes (like myself) survive by avoiding as much of the tech oligarchs’ world as I can, sure, but overall everything is disgusting, and using FOSS is certainly no silver bullet.

    Tech enthusiasts who do not apply critical thinking are even worse, because they get beat up everyday by the things they buy at a premium and they like it because they have this twisted idea of what constitutes progress. This is slowly infusing into the general population, which is also a problem.

    People have been trained to be abused by software and by hardware, to ignore their needs, to accept any change as inevitable. I speak of abuse because people have been trained to expect and accept change at the same time, with no agency whatsoever.

    Most old people in particular (sorry mom) have given up and resigned themselves to drift wherever their computing devices take them, because under the guise of convenience, everything is so hostile that there is no point trying to learn things, and dark patterns are everywhere. Not being in control of course makes people endlessy frustrated, but at the same time trying to wrestle control from the parasites is an uphill battle that they expect to lose, with more frustration as a result.

    I want to emphasize here that there are good products (both software and hardware) on the market even though the list gets shorter every year, some products even manage to solve real problems (!!). That does not change the fact that consent, hype and projected consumer needs are manufactured by years and years of abuse and marketing campaigns.

    Those things were or are not inevitable

    • Internet-connected beds are not inevitable.
    • AI browsers are not inevitable.
    • Talking to chatbots instead of public servants is not inevitable.
    • Requiring a smartphone to exist in society is not inevitable.
    • Unrepairable devices are not inevitable.
    • "AI-enhanced" vacation pictures are not inevitable.
    • NFTs were not inevitable.
    • The Metaverse was not inevitable.
    • Your computer changing where things are on every update is not inevitable.
    • Websites that require your ID are not inevitable.
    • Garbage companies using refurbished plane engines to power their data centers is not inevitable.
    • Juicero was not inevitable.
    • Ads are not inevitable.
    • Being on a platform owned by Meta is not inevitable.
    • The Apple Vision pro was not inevitable.
    • "Copilot PCs" are not inevitable.
    • Tiktok is not inevitable.
    • Your computer sending screenshots to microsoft so they can train AIs on it is not inevitable.

    I could spend years filling this list up, because the tech grifters always find new ways to make us more miserable.

    Nothing is inevitable, nothing sold by powerful grifters is "the future" no matter how much they wish that were true. Sure, some things can keep on existing, even for a very long time, even more if they have an untold number of billions - that they wormed they way into having by selling and exploiting personal data and attention -, but nobody has to be complicit. Some things might even end up existing because they are useful.

    But what is important to me is to keep the perspective of what consitutes a desirable future , and which actions get us closer or further from that.

    Every choice is both a political statement and a tradeoff based on the energy we can spend on the consequences of that choice.

    If you have remarks or suggestions concerning this article, please by all means contact me .

    I don't think Lindley's paradox supports p-circling

    Hacker News
    vilgot-huhn.github.io
    2025-12-16 13:40:02
    Comments...
    Original Article

    As usual I’d like to preface all this that I write these blogposts as attempts to make sense of a subject for my own sake. I am not an expert here and it is likely I am confused about some details. On the other hand, I think “confused” discourse can also be productive to read and participate in. Being confused is just the first step towards being unconfused, to paraphrase Jake The Dog.

    p-value circling

    100 years ago this year Fisher arbitrarily suggested using p < 0.05 as a cut-off for “significant” and ever since we’ve just gone along with it. “Why is it 0.05?” people have critically asked for one hundred years. Unfortunately “arbitrariness”, as a critique, is only effective if you are able to suggest less a arbitrary value, and despite many efforts to change this the convention has remained.

    The act of p-value circling is to look at a p-value that’s significant but close to 0.05 and go: “hm, I don’t know about that…” Perhaps you use a red ballpoint pen to circle it on the print journal you subscribe to in the year 2025. If not, you may underline it with some sort of digital pen technology and share it online.

    “Hmm… Suspicious…”

    What (potentially) justifies p-value circling?

    Before we get into it let’s briefly try to remind ourselves what p-values are even supposed to do. (This will be a brief summary, if you want to learn this for real I recommend reading Daniël Lakens free online textbook , which all this borrows heavily from.)

    As far as I’ve understood, Fishers idea about p-values was supplanted by the more rigorous (in terms of statistical philosophy) Neyman-Pearson framework. It is within this framework we find the familiar type 1 and type 2 error rates. Probability is viewed as being about outcomes in a hypothetical scenario where a procedure is repeated many times. You’re actually supposed to set the \(\alpha\) at a level that’s justifiable based on what null hypothesis you’re testing. As far as I’ve understood no one has ever done so, except that one time physicists at CERN decided they wanted to be really sure they didn’t incorrectly claim they found the Higgs boson. 1 Instead everyone just uses the arbitrary convention of \(\alpha = 0.05\) .

    If you assume that the null is true, the p-value distribution is uniform. Let’s do the exercise of generating a hundred thousand t-tests between two groups, n = 100 per group, where there is no mean difference. Then we’ll look at the p-values.

    Code
    p_vector <- c() #empty vector
    
    for(i in 1:100000){
      A <- rnorm(100, 0, 1) #normally distributed data from a group
      B <- rnorm(100, 0, 1) #another group with same mean
      p_vector[i] <- t.test(A,B)$p.value
    }
    
    d <- data.frame(p_vector, significant = (p_vector < 0.05))
    
    ggplot(d, aes(x = p_vector, fill = significant)) +
      geom_histogram(breaks = seq(0,1, length.out = 101), color = NA) +
      #geom_hline(yintercept = 100000/100) +
      scale_fill_manual(values = c("FALSE" = "steelblue", "TRUE" = "red")) +
      scale_x_continuous(breaks = seq(0,1, by = 0.1)) +
      labs(title = "p-values under the null", x = "p-value", y = "count") +
      theme_bw() + ylim(0,10000)

    I remember this blew my mind when I fist saw it. I don’t think I was surprised exactly; it just made it all click. This flatness is what p-values are all about, man! The p-value distribution is uniform under the null! Yes! It is this property of the distribution that gives meaning to the type 1 error rate.

    NP-frequentism is then based on committing to an alpha threshold beforehand and then exclaim “significant!” iff the p-value lands below it. 2

    Does it matter how far below it?

    No! If the null is true every p-value is equally likely, right? Your sampling procedure of a null may give you 0.98, or 0.58, or 0.002, or 0.006775892. When you’re focused on whether toreject the (exact) null hypothesis , NP-frequentism works its magic by assuming the null is true which means p=0.01 is not in and of itself less consistent with this assumption than a p=0.048. If the null was true, you just got handed a random number between 0 and 1. All you get to choose is often you want to mistakenly “act” upon this information (in the long run).

    So, how is this supposed to justify p-value circling? Well p-values are only valid if they’re valid. P-values only care about sampling error and – being an inert mathematical abstraction – can’t by themselves handle questionable research practices like p-hacking done by flesh and blood human researchers.

    Let’s set up a scenario where we collect more data if our p-value happens to be not significant, but stop collecting data if it is. We’ll add a one-time option to include 20 more participants.

    Code
    p_vector <- c() #empty vector
    
    for(i in 1:100000){
      A <- rnorm(100, 0, 1) #normally distributed data from a group
      B <- rnorm(100, 0, 1) #another group with same mean
      if(t.test(A,B)$p.value > 0.05){
        A <- append(A, rnorm(20, 0, 1))
        B <- append(B, rnorm(20, 0, 1))
      }
      p_vector[i] <- t.test(A,B)$p.value
    }
    
    d <- data.frame(p_vector, significant = (p_vector < 0.05))
    
    ggplot(d, aes(x = p_vector, fill = significant)) +
      geom_histogram(breaks = seq(0,1, length.out = 101), color = NA) +
      geom_hline(yintercept = 100000/100) +
      scale_fill_manual(values = c("FALSE" = "steelblue", "TRUE" = "red")) +
      scale_x_continuous(breaks = seq(0,1, by = 0.1)) +
      labs(title = "p-values when p-hackning", x = "p-value", y = "count") +
      theme_bw() + ylim(0,10000)

    As you can see there’s now a little bump in what was previously flat. Some “marginally significant” p-values got lucky and grew a bit. The bin between 0.05 and 0.06 shrunk the most. Notably there’s a tilted shape to the significant values now. 3

    It is this shape, this knowledge that you can fudge p-values a little bit that I think could maybe give some justification to the act of p-value circling. Maybe . In that case a p = 0.048 makes people think: “hmm, I bet that was 0.051 and they strategically removed an outlier or something”

    I don’t think it’s a very strong justification for being suspicious of p-values between 0.04 and 0.05. Part of this depends on how prevalent you believe p-hacking is. Basically you’re saying “my personal significance level is set a bit lower than convention, because I think the p-values I see are distorted by p-hacking”. I think that’s probably fine as an epistemic habit, but as an author getting p-circled it would likely feel as an unfair criticism.

    sort(p_vector)[5000] #a suggestion, in this scenario

    Even so, in this scenario I set up here there’s more additional spurious p-values below 0.04 than between 0.04 and 0.05. P-values are fickle things, they dance around , so even if you think the practice is common I don’t think you should put a lot of weight in the idea that whatever questionable statistical jutstu a researcher does to avoid a null result will put their (hacked) p-value just below the threshold.

    Basically I don’t think a single p-value in and of itself can carry a lot of information about statistical malpractice. I’m sympathetic to the rule that if you’ve ever scoffed at someone using the term “marginally significant”, you’re not allowed to call something “marginally insignificant” 4 .

    A potentially more sophisticated justification for p-circling is “Lindley’s paradox.”

    I think many instinctively feel some resistance to a very strict interpretation of p-values, where their sole function is to be uniform under the null and the thing we care about is whether they clear our pre-specified alpha level. After all, we rightfully get annoyed when p-values are reported only with a less-than sign. And should we really not feel confident that there’s something there when we see a p = 0.001?

    In this group-difference setup a smaller p-value implies a larger mean difference in your sample means, and you’re more likely to come across a large mean difference if you happen to be in a universe where there truly is a difference between the groups.

    Let’s see how it looks like when we have a difference between the groups.

    Code
    p_vector <- c() #empty vector
    
    for(i in 1:100000){
      A <- rnorm(100, 0, 1) #normally distributed data from a group
      B <- rnorm(100, 0.2, 1) #another group with same mean
      p_vector[i] <- t.test(A,B)$p.value
    }
    
    d <- data.frame(p_vector, significant = (p_vector < 0.05))
    
    ggplot(d, aes(x = p_vector, fill = significant)) +
      geom_histogram(breaks = seq(0,1, length.out = 101), color = NA) +
      geom_hline(yintercept = 100000/100) +
      scale_fill_manual(values = c("FALSE" = "steelblue", "TRUE" = "red")) +
      scale_x_continuous(breaks = seq(0,1, by = 0.1)) +
      labs(title = paste("p-values when power =",round(sum(p_vector < 0.05)/100000, 2)), x = "p-value", y = "count") +
      theme_bw() #+ ylim(0,10000)

    The sum of red colored p-values now represent power . Notice the switch! I think this may be a source of confusion here. We are now looking at a different type of p-value distribution. A p-value distribution that is not meant to illustrate the meaning of p-values. Power or type 2 error is, fundamentally, something else. It’s a different type of error.

    When I fist saw this my mind immediately jumped to the idea of some p-values being “more consistent with the presence of an effect”. This is a bit off according to strict NP-frequentism; again, p-values get their meaning from assuming the null is true. Here we instead assume some effect is true.

    The line represents where p-values would end up under the null. At this power, p-values in the 0.04 to 0.05 bin are more likely than they would be if the null was true. If we raise the power even further, we get to “Lindley’s paradox”, the fact that p-values in this bin can be less likely then they are under the null.

    Code
    p_vector <- c() #empty vector
    
    for(i in 1:100000){
      A <- rnorm(100, 0, 1) #normally distributed data from a group
      B <- rnorm(100, 0.55, 1) #another group with same mean
      p_vector[i] <- t.test(A,B)$p.value
    }
    
    d <- data.frame(p_vector, significant = (p_vector < 0.05))
    
    ggplot(d, aes(x = p_vector, fill = significant)) +
      geom_histogram(breaks = seq(0,1, length.out = 101), color = NA) +
      geom_hline(yintercept = 100000/100) +
      scale_fill_manual(values = c("FALSE" = "steelblue", "TRUE" = "red")) +
      scale_x_continuous(breaks = seq(0,1, by = 0.1)) +
      labs(title = paste("p-values when power =",round(sum(p_vector < 0.05)/100000, 2)), x = "p-value", y = "count") +
      theme_bw() #+ ylim(0,10000)

    It’s kind of hard to see, since so many p-values end up in the 0-0.01 bin. Let’s zoom in on only the bins between 0.03 and 0.10.

    Code
    d <- subset(d, p_vector > 0.03 & p_vector < 0.1)
    
    ggplot(d, aes(x = p_vector, fill = significant)) +
      geom_histogram(breaks = seq(0.03,0.1, length.out = 8), color = NA) +
      geom_hline(yintercept = 100000/100) +
      scale_fill_manual(values = c("FALSE" = "steelblue", "TRUE" = "red")) +
      scale_x_continuous(breaks = seq(0.03,0.1, by = 0.1)) +
      labs(title = paste("p-values when power =",round(sum(p_vector < 0.05)/100000, 2)), x = "p-value", y = "count") +
      theme_bw() #+ ylim(0,10000)

    As you can see, the 0.04 to 0.05 bin is now below the line which represents the ideal flat null distribution. This then is (potentially) another reason to justify p-value circling: If a test has a lot of power, coming across a p-value close to threshold is surprising. Almost all p-values are to the left of it. We even had to zoom!

    It is here, I think, we get to a second source of confusion. I’ve noticed that a lot of psychologists think of power as N 5 . We think “if we increase the sample size we increase power”. This is true, but we have to remind ourselves that power is always for a potential true effect. If there is no effect, increasing N can’t increase power.

    I think it’s therefore more helpful (for the present discussion of p-value circling) to think of increasing power as “increasing which effect we assume to be true”. Stated that way, p-value circling based on Lindley’s paradox seems a bit strange, as if you’re saying:

    “The p-value reported here would be rare if the true effect was such that we had ~97% power to detect it, which convinces me that the null is true.”

    I don’t think that makes sense! Why are you assuming that particular true effect? Surely, there’s a potential true effect where the likelihood of the observed p-value is similar to its likelihood under the null? (For the 0.04 to 0.05 bin this appears to be around 95% power). Also, I think it unfairly imposes a role on p-values that they’ve not been hired to play. P-values are not meant to be a measure of evidence – not in that direct way at least. They are meant to give stable error rates when the null is true. This contrasting between the plausibility of seeing a p-value under a null hypothesis versus a specific alternative hypothesis isn’t what they were designed for.

    Ok but it seems possible to use them that way? One could specify a smallest effect size of interest and compare the plausibility of seeing the reported p-value under that distribution compared to the null distribution. 6 Maier and Lakens (2022) suggest you could do this exercise when planning a test in order to justify your choice of alpha-level. However, I doubt this structured approach is what lies behind the casual circling of p-values I’ve come across online over the years. My impression is that most social media p-circling haven’t been studies with very high power to detect small effects.

    There is a concern that very large studies may pick up on “noise”, or that other violations of model assumptions (e.g. normality) tend to bias p-values downward. I don’t really know what to make of these concerns. I think that might be true for some model violations, while other may hurt power instead. I would assume it’s a complicated empirical question whether the statistical models we use tend to misfit reality more in one direction rather than the other.

    Regardless, I don’t think it can be salvaged as a ground for being skeptical of p-values close to their threshold because of Lindley’s paradox.

    For the moment I feel safest treating the conventional threshold as what it is, as arbitrary as that is. I’m of course concerned about QRPs and p-hacking, but I don’t see a reason for why a single p-value close to 0.05 would be useful evidence of it.

    Some concluding thoughts

    As I prefaced, this is complicated stuff and I have probably gotten something wrong. Regarding the larger question on whether p-values can be interpreted as evidence, I currently land in the conclusion “not in and of themselves”, they have to be contextualized in relation to power and other features of the study, as well as the context you come across them in. Lindley’s paradox can be a useful illustration of one of the reasons that the interpretation isn’t straight-forward (but I don’t think it justifies p-circling).

    On the other hand, I know that smarter and more well-read people than I disagree on how straightforward this interpretation is. The textbook we used in my PhD-level course in medical statistics 7 contain a table that tells us to do that:

    hmm..

    I don’t think it’s quite that simple. My current understanding (given Lindley’s paradox) is that evidence has to be “relative”, in some sense. P-values only tell one side of the story, and are only made to tell one side of the story. If you want a statistic that expresses the strength of evidence you should probably use something else, e.g. Bayes factors.

    Tell me I am wrong!

    I am serious. If you think I’m misunderstanding something badly or you just want to discuss or you want to gently point me in the right direction: Please tell me what I’m missing. I don’t have a comment section on this blog, but I’ll post this on bluesky and then update this post to link the post that links this post: Here is the link.

    So if you want you can comment, do it over there, or send me an e-mail.

    Footnotes

    1. They set it to “five sigma”, or something like 0.00003. ↩︎

    2. The meaning of this exclamation is surely the matter of some philosophical debate which is way beyond me, but I think it goes without saying that you should probably not stop all thought and submit to the p every time you see a significant result according to NP-frequentism. Every test happens within a context. ↩︎

    3. This shape is the basis for p-curve analysis, which is an attempt to detect such bias in a sample of significant results. FYI: Recently this method has been criticized as having “poor statistical properties”. https://www.tandfonline.com/doi/full/10.1080/01621459.2025.2544397

      I haven’t dug into the debate, but I don’t think it matters for the present discussion. ↩︎

    4. Stole this from Berna Devezer: https://bsky.app/profile/devezer.bsky.social/post/3m6d7tmmens22 ↩︎

    5. Or to be more fair, some combination of N, design, type of statistical analysis, and measurement. But I think most focus is on sample size. ↩︎

    6. I am unsure about whether you’re still even doing frequentism at this point. Maier & Lakens describe it as a “Bayes-non-Bayes hybrid combining frequentist and Bayesian statistics” ↩︎

    7. Walters, Campbell & Machin (2021) Medical Statistics: A textbook for the health sciences, 5th ed ↩︎

    Announcing Vojtux - Accessible Linux distro which is almost pure Fedora

    Lobsters
    www.freelists.org
    2025-12-16 13:39:30
    Comments...
    Original Article

    Hi everyone,
    my name is Vojtěch Polášek and I am a blind software engineer based in Czech Republic.
    I work at Red Hat in the security compliance team.
    At the same time, I was given some predefined time to work on the project I am going to introduce.
    I spend considerable amount of time polishing it, catching up on Fedora updates etc... and I realized that I could spend several more months doing the same... but one day, there just has to be the time.
    The time to show it and find out what does the community think about this idea.
    I would like to introduce Vojtux, an unofficial Linux distribution aimed at visually impaired users, currently based on Fedora 43 (Mate spin).
    My ultimate vision for this project is "NO VOJTUX NEEDED!" because I believe Fedora should eventually be fully accessible out of the box. We aren't there yet, which is where Vojtux comes in to fill the gap.
    This project takes a specific approach to accessibility focused on sustainability:
    -Minimal Customization: Vojtux stays as close to the original Fedora Mate Spin as possible to ensure the project remains maintainable.
    -Upstream First: The goal is not to fix every broken thing locally. Instead, the goal is to delegate most fixes to the upstream projects so that everyone benefits.
    -Modular Design: Most modifications are distributed as RPM packages, meaning they can be reused by other distributions.
    Who is this for?
    This is a Technical Preview focused on users who are already comfortable running live media or installing a Linux distribution. It is a tool for those who want to test a Fedora-based environment with essential accessibility features pre-configured.
    Key Features:
    -Speaks out of the box: When the live desktop is ready, Orca starts automatically. After installation, it is configured so that it starts on the login screen and also after logging in.
    -Batteries included: Comes with LIOS , Ocrdesktop, Tesseract, Audacity, and command-line tools like Git and Curl. There are also many preconfigured keyboard shortcuts.
    Actually, I think you need to know mainly these two:
    -CTRL+ALT+D: go to the desktop
    -ALT+SUPER+O: restart Orca
    I encourage you to checkout the project's readme for full list of features.
    you can find the project at https://github.com/vojtapolasek/vojtux
    Download & Verification:
    Because GitHub limits binary files to 2GiB, the live image is hosted on my personal server.
    You can find the release links and checksums here: https://github.com/vojtapolasek/vojtux/releases
    Please verify the image matches the attached checksum to ensure integrity.
    I am releasing this primarily to validate if this lightweight, "upstream-first" approach makes sense to the community.
    If you encounter technical errors, please check the "Known Issues" at https://github.com/vojtapolasek/vojtux/issues?q=state%3Aopen%20label%3A%22Known%20issue%22 .
    In case you discover a new bug, file a new issue on GitHub.
    For general thoughts on the project's vision or usability, please reply here or to my email at krecoun@xxxxxxxxx.
    Happy Hacking,
    Vojtěch Polášek

    Homelessness Is About Affordability: Author Patrick Markee on the Housing Crisis in "New Gilded Age"

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:34:19
    New York City housing advocate Patrick Markee’s new book, Placeless: Homelessness in the New Gilded Age, looks at homelessness through the lens of housing affordability. Homelessness, which affects millions across the United States, “has roots in structural economic changes, right-wing e...
    Original Article

    You may have mistyped the address or the page may have moved.

    You can click here to return to the home page.

    Homelessness Is About Affordability: Author Patrick Markee on the Housing Crisis in "New Gilded Age"

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:34:19
    New York City housing advocate Patrick Markee’s new book, Placeless: Homelessness in the New Gilded Age, looks at homelessness through the lens of housing affordability. Homelessness, which affects millions across the United States, “has roots in structural economic changes, right-wing e...
    Original Article

    This is a rush transcript. Copy may not be in its final form.

    AMY GOODMAN : This is Democracy Now! , democracynow.org, The War and Peace Report . I’m Amy Goodman, with Juan González.

    In a moment, we’re going to go to the Emmy Award-winning investigative reporter Aram Roston, who wrote the piece, “’They’re trying to get rich off it’: US contractors vie to rebuild Gaza, with 'Alligator Alcatraz' team in the lead.” We lost our connection with him. As soon as we’re able to remake it, we’ll go to him.

    But right now we’re going to turn to the freezing winter temperatures that have brought heavy snowstorms and ice to New York City and across much of the northeastern United States, laying bare the nation’s inability or refusal to provide dignified shelter for unhoused people, who are left with no warm or safe place to sleep.

    Zohran Mamdani is set to be sworn in as New York mayor on January 1st. Addressing the city’s housing and affordability crises have been central to his campaign. This is Mayor-elect Mamdani speaking earlier this month.

    MAYOR - ELECT ZOHRAN MAMDANI : Hundreds of thousands of New Yorkers are just one rent hike, one medical emergency, one layoff from joining the ranks of the homeless in our city, which have swelled to the greatest numbers since the Great Depression. … And what I spoke about with leaders within the real estate industry was the importance of us reducing the timeline of getting New Yorkers into affordable housing, because we cannot actually disentangle the question that we are speaking about right now with the 252 days that it takes to fill one of those units, because the quicker we can fill those units, the fewer New Yorkers we will have living outside.

    AMY GOODMAN : New York City Mayor-elect Zohran Mamdani has also vowed to end the widely criticized practice of clearing homeless encampments, prioritizing efforts to freeze rents and permanently house those in need. This all comes as last month a coalition of states, led by New York Attorney General Letitia James, sued the Trump administration over policy changes that threatened to cut funding for permanent housing for people experiencing homelessness.

    We’re joined now by Patrick Markee, longtime housing advocate. He’s just written a book called Placeless: Homelessness in the New Gilded Age . He’s the former deputy executive director for advocacy of the Coalition for the Homeless and former member of the board of directors of the National Coalition for the Homeless.

    Welcome to Democracy Now! , Patrick. It’s great to have you with us. While there’s a lot of discussion about affordability and housing, we don’t hear as much in the national discussion of homelessness. Talk about why you called your book Placeless , and talk about the crisis, not only in New York City, but across this country.

    PATRICK MARKEE : Well, I’m glad you asked that, because it’s true that I think homelessness often gets discussed in the wrong terms, in distorted terms. It gets discussed as a social work problem. It gets discussed as a problem of personal dysfunction. It gets discussed as a sort of subspecies of urban poverty, when, really, as you said earlier, what it is at its root is a housing affordability problem.

    And we’re experiencing right now in the United States and in New York City, you know, the worst homelessness crisis since the Great Depression of the 1930s. There are more than 3 million Americans who experience literal homelessness, meaning sleeping in shelters or on the streets every year. Three-and-a-half million experience what I call hidden homelessness. This is living in sort of double-up or overcrowded conditions. And right now in New York City, we have more than 100,000 people sleeping in our homeless shelters each night, and that includes 35,000 children.

    So, contrary to what the kind of stereotypes are of who experiences homelessness, what we’re really seeing is a problem that affects families. And this is really why I wrote the book, was kind of to talk about this problem that goes now back a few decades and that has roots in structural economic changes, right-wing economic policies and systemic racism, which has shaped the problem of mass homelessness that we’re experiencing now.

    JUAN GONZÁLEZ: And, Patrick, could you talk a little bit more about those structural policies? Clearly, this — why has housing become so expensive, especially in the major cities? You can go outside in rural areas or in the South, and housing is a lot cheaper, but in the cities especially.

    PATRICK MARKEE : Well, it’s as you said, Juan. It is really an urban problem. I mean, the majority, the mass majority — the large majority of homelessness that we see in the United States, particularly among families, is in cities. And it’s cities where there are extremely high housing costs, so really what we would call a housing affordability gap. And we’ve seen over the last several decades just an increasing sort of breach between the incomes of low-income and working-class people in this country, which have really stagnated or even fallen in real terms, and housing costs increasing year to year.

    And part of that’s exacerbated by dramatic cutbacks in federal housing assistance that began in the Reagan years. You know, some people forget that during the Reagan administration, 80% of the housing — of the budget authority of the federal housing agency was cut under the eight years of Reagan, and we’ve really never recuperated from that. So, we’re at a situation now where only one in five low-income households in this country that qualifies for federal housing aid is actually receiving it. Four out of five who actually qualify, who are in need of housing assistance, are not getting that assistance now.

    JUAN GONZÁLEZ: And in terms of — public housing used to be a huge place of last resort for many people, but then there were cities like Chicago, Philadelphia, others, not so much New York, that essentially destroyed their public housing. Tens of thousands of units were demolished. Your sense of how this federal policy of pulling out even of owning housing has affected the homeless crisis?

    PATRICK MARKEE : Yeah, I mean, again, one of the reasons I wanted to write the book was to kind of get into the historical roots of this problem. There were actually, you know, really progressive and important housing movements that began here in New York City, in the Lower East Side especially, that addressed, you know, the sort of unhealthy, substandard housing conditions in tenements, that created the first public housing in this country in the 1930s on the Lower East Side, that created the rent control system that, you know, we have still in New York City and that at one point was actually around the country in the 1940s.

    And the problem is that attacks by Republicans and attacks by real estate and finance interests over the decades have really led to, as you said, the destruction of much of the public housing, but really to just a sustained inadequate level of public investment and public creation of housing. There’s a reason that other advanced capitalist countries in this world, in Western Europe, in Canada and Japan, don’t have the levels of homelessness that we have, and that’s because, there, government plays a much larger role in creating and even owning affordable housing, but also providing affordable housing assistance to needy families and individuals.

    AMY GOODMAN : The title of your book is Placeless: Homelessness in the New Gilded Age . Talk about this analogy between 19th-century Gilded Age and now. When do you think this New Gilded Age started? And when you talk about a New Gilded Age, we’re also talking about, just here in New York City, it is astounding to know that 35,000 children sleep in shelters every night. And talk about the racial dimensions, that you referred to, in New York City alone, when we’re talking about Blacks and Latinos disproportionately affected, and why.

    PATRICK MARKEE : Yeah, I mean, I found enormous historical echoes between the current era that we’re in now and the first Gilded Age of the late 19th and early 20th century: radical inequality, economic elites that are in control of — you know, of the economy and have enormous political power, xenophobia and racism against immigrants, and, you know, the racism we saw in the late 19th century in the wake of the end of slavery and Reconstruction. Now we’re seeing many of those same kinds of problems now, with structural economic changes, another wave of immigration.

    You know, you spoke about kind of the racism that we’re seeing now. Systemic racism is one of the primary causes of modern homelessness. Ninety percent of homeless New Yorkers are Black or Latino, and that’s compared to only 50% of the city’s population. And that’s a result of the fact that, you know, Blacks and Latinos have much higher housing — higher rates of housing problems, higher rent burdens, where they pay a larger portion of their income towards rent, housing quality problems, overcrowding, but also, really specifically, racist government policies that we saw, for instance, in the Giuliani and Bloomberg years. You know, the criminalization of homelessness, which really ramped up under Giuliani, really targeted Black and Latino New Yorkers. We obviously saw a federal court declare that the Bloomberg administration’s stop-and-frisk policing strategies was racist. We saw the same things happening to homeless New Yorkers. And that’s, again, one of these sort of historical echoes that we saw going back to the period of the late 19th century.

    JUAN GONZÁLEZ: And, Patrick, I wanted to ask you to get back to this issue of being able to build affordable — more affordable housing. We often hear people saying, “Well, it’s the construction costs of housing keep going up.” But I’ve looked at many, many projects that have been — had had government subsidy, and what I find astounding is the rise of not so much construction costs, but what is commonly called soft costs — developer fees, financing fees and architectural and professional fees — to the point where, basically, there’s been a financialization of the building of affordable housing, with a lot of investors getting tax credits from the government, but still not producing housing that is truly affordable to the lowest-income groups. Wondering your thoughts.

    PATRICK MARKEE : I mean, that’s exactly right. I mean, it’s no mistake that we’re now seeing private equity and other, you know, investment entities now controlling much of the private housing market, particularly the rental housing market. In some cities, 20% of the rental housing stock is actually owned by private equity firms. We saw the same thing happen in New York City coming out of the ’90s and in the early part of this century, when the rent laws had been weakened by Republican administrations. We saw that private equity firms bought up 10% of all the rent-regulated apartments in New York City, because they thought this is going to be an opportunity to kind of push out long-standing tenants, gentrify these neighborhoods and get more profit.

    At the end of the day, we know that to solve housing affordability problems, government has to play the essential role. And that’s one of the reasons — you know, we know what works to solve this problem. I mean, there was a period, from the end of World War II until the 1970s, when we didn’t have mass homelessness in this country. We didn’t have zero homelessness, but we certainly didn’t have hundreds of thousands of families experiencing homelessness each year. There’s a way we can get back to doing that, if we have the right government investments and government policies in place.

    AMY GOODMAN : Patrick, are you one of the advisers to the mayor, who will become mayor, Mamdani, on January 1st, one of the hundreds of people who he is consulting in all different areas?

    PATRICK MARKEE : No, I’m actually not on the transition committee, but I have colleagues who are.

    AMY GOODMAN : So, let me ask you this, and our final question to you: What would you recommend to Mayor Mamdani? I mean, yesterday, he held a listening session out in Queens for something like 12 — it was over the weekend — 12 hours talking to New Yorkers, what they recommend. What exactly do you recommend? What is your prescription, as we end this conversation?

    PATRICK MARKEE : Well, first and most important, he needs to create — and this is through, you know, the city — the city budget of New York is larger than the state budgets of 35 state budgets — states in this country. So we have the resources in New York to make a serious reduction in our homeless population, even given the fact that we’re going to see cutbacks from the federal government. So, first and foremost, he needs to invest in deeply affordable housing for homeless families and individuals, for the poorest families in the city. No more of this sort of so-called affordable housing, which is really built for families making as much as $100,000 a year. It’s not that there’s not housing need, you know, across the board, but we need to be targeting our resources towards the most — you know, the most deeply affordable housing that we can.

    Secondly, he needs to preserve and protect the right to shelter. That’s a legal protection that we have in New York, that the Adams administration tried to undo, that Giuliani and Bloomberg also tried to attack. We need to be preserving the right to shelter, which protects lives on the street, particularly in this winter cold that you spoke of earlier, where it’s literally a matter of life and death to be out on the streets.

    And then, finally, I think we need to be, you know, looking at changes in the way that we — you know, that we deal with rents. And I think one of the positive things we’ve seen the mayor-elect say is that he wants to freeze rents for the 1 million rent-stabilized apartments in the city. That’ll help preserve some affordable housing. And that’s really the housing stock that is the base of housing for working-class and low-income New Yorkers.

    AMY GOODMAN : And where does Mamdani get the money for this affordable housing, truly affordable housing?

    PATRICK MARKEE : Well, you know, the money is there. I mean, the city has, as I said, an enormously large budget. The state can also be helping, too. And we need the state of New York to be stepping in. But, you know, we’re spending that money now. It’s just a matter of making sure we spend it right. We’re spending, you know, literally, a billion dollars, more than a billion dollars a year, on homeless shelters. We should be spending that money on housing instead.

    AMY GOODMAN : We want to thank you so much for being with us, Patrick. Patrick Markee is a longtime housing advocate. He’s just written the book Placeless: Homelessness in the New Gilded Age , former deputy executive director for advocacy at Coalition for the Homeless, former member of the board of directors of the National Coalition for the Homeless.

    The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

    Rust GCC back end: Why and how

    Hacker News
    blog.guillaume-gomez.fr
    2025-12-16 13:33:25
    Comments...
    Original Article

    Whenever you compile using Rust, the compiler goes through different passes and in the end, generated binary code for the target processor. By default, it uses LLVM as backend to generate the binary code, but more backends exist like cranelift and GCC. This post is about how it's possible for one compiler to use different backend to generate binaries, in particular GCC.

    Passes

    Before going into details, we need to describe how compilers actually work. They read source code and convert it internally into a format they can manipulate, commonly called Abstract Syntax Tree (shortened "AST").

    However, compilers go through multiple passes, and often each pass has their own AST. Let's take a short and very incomplete example with the Rust compiler passes. We have 4 steps (again, this is simplified!):

    1. AST: checks that the syntax is valid
    2. HIR: checks if types are valid
    3. MIR: checks lifetimes and runs borrow-checker
    4. codegen: generate binary code (which also has multiple steps, but not detailed here)

    Each step generates a new AST with new information if no error was encountered and provides it to the next pass.

    Little side-note: If enough people are interested by this topic, I can write a (much) longer explanation of these passes.

    Backend vs front-end

    So now that we have a high-level idea of Rust compiler passes, what is the difference between "front-end" and "back-end" exactly?

    We consider the front-end to be the part handling (high-level non-exhaustive list) code parsing, linting, type-checking and borrow-checking (steps 1 to 3). When all this is done, it means the code is valid and needs to be translated to the target processor instructions set. To do so, we call LLVM/GCC which will translate the Rust compiler AST into assembly code (step 4).

    The Rust compiler backends are the bridge between the Rust compiler AST and the actual code generator. They receive the AST and call the LLVM/GCC/... API which will in turn run their passes, optimize and finally generate the assembly code.

    Why having a GCC backend

    LLVM being much more recent than GCC (2003 vs 1987), a lot of older processors are not supported and will never be. So if you want to write a Rust program on an old platform like Dreamcast, you have no choice to either write your own backend or use the GCC backend (or the gccrs front-end once ready).

    For the readers interested in doing so, there is a guide explaining how to build Rust programs for Dreamcast here .

    gccrs vs GCC backend

    The GCC backend is different than gccrs which is a front-end for GCC written in C++, which doesn't reuse the front-end of rustc , meaning they need to reimplement parsing, type-checking, linting, borrow-checking, compilation errors, etc.

    On the other hand, the GCC backend (the crate name is rustc_codegen_gcc ) is just "yet another backend codegen" of the Rust compiler, like LLVM or Cranelift, only meant to generate the binary from the Rust compiler input. It's a bridge between Rust compiler's AST and the codegen API.

    On that note: GCC doesn't provide a nice library to give access to its internals (unlike LLVM). So we have to use libgccjit which, unlike the "jit" ("just in time", meaning compiling sub-parts of the code on the fly, only when needed for performance reasons and often used in script languages like Javascript) part in its name implies, can be used as "aot" ("ahead of time", meaning you compile everything at once, allowing you to spend more time on optimization). To do so we use bindings, which are split in two parts:

    • gccjit-sys which redeclares the C items we need.
    • gccjit which provides a nice API over gccjit-sys .

    If you want to write your own compiler and use GCC as codegen, you can do it thanks to libgccjit . And if you write it in Rust, you can even use the Rust bindings.

    Implementing a Rust backend

    Rustc has a crate named rustc_codegen_ssa which provides an abstract interface that a backend needs to implement through traits like:

    The full list is available here .

    One last thing you need to write in your backend:

    Run#[no_mangle]
    pub fn _rustc_codegen_backend() -> Box<dyn CodegenBackend> {
        // This is the entrypoint.
    }

    This is the function that will be called by rustc to run your backend.

    Codegen implementation example

    Let's take an example: how the GCC backend creates a constant string. I picked this one because it's small enough to showcase how things work while not being too much information to digest at once.

    In the ConstCodegenMethods trait, there is a const_str method. This is the method we will implement to declare a constant string.

    So the method implementation so far looks like this:

    Runimpl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> {
        /// Returns the pointer to the string and its length.
        fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) {
            // Call GCC API to declare this string.
        }
    }

    We need to pause here to give some extra explanations: CodegenCx is the type on which most rustc_codegen_ssa traits will be implemented. It is created in each ExtraBackendMethods::compile_codegen_unit call and passed down from there to generate the code for this module. You can consider it the same as a cache. It keeps the list of items declared, like functions, types, globals, etc. But also information such as "boolean type", "i8 type" and equivalents so we don't need to recompute them every time we need them.

    Ok so now let's actually implement it. We have a few things to do:

    1. To avoid adding the same constant string multiple times, we will need to cache them in our context.
    2. We need to cast the Rust str type ( *const u8 ) into the C type ( *const char ).
    3. Get the pointer to this constant string and return it.

    Let's translate it into code with a lot of comments to help understanding what's going on:

    Runfn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) {
        // We get the const string cache.
        let mut const_str_cache = self.const_str_cache.borrow_mut();
        // We get the address of the stored string and we add it to the cache and
        // return its address.
        let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| {
            // We call the `GCC` API to create a new const string.
            let string = self.context.new_string_literal(string);
            // We name the const.
            let sym = self.generate_local_symbol_name("str");
            // We declare it.
            let global = self.declare_private_global(&sym, self.val_ty(string));
            // All done, we can add it to the cache and return it.
            const_str_cache.insert(s.to_owned(), global);
            global
        });
        let len = s.len();
        // We cast the pointer to the target architecture string pointer type.
        let cs = self.const_ptrcast(
            str_global.get_address(None),
            self.type_ptr_to(self.layout_of(self.tcx.types.str_).gcc_type(self)),
        );
        // And we return the pointer and its length.
        (cs, self.const_usize(len as _))
    }

    But the codegen backends can also add more information to the underlying binary code generator. For example, in Rust, we use references a lot. A reference is basically a pointer that cannot be NULL . We need to give this information as well!

    In both GCC and LLVM, you can add attributes to a lot of items, like arguments of functions. So every time we see an argument behind a reference, we add the nonnnull() attribute.

    Let's show an example with this Rust function:

    Runfn t(a: &i32) -> i32 {
        *a
    }

    The C equivalent looks like this:

    int t(int *a) {
      if (!a) {
        return -1;
      }
      return *a;
    }

    Compiled with the -O3 option, it generates this assembly:

    t:
            test    rdi, rdi              ; Check if `a` is 0
            je      .L5                   ; If `a` is 0, we jump to `.L1`
            mov     eax, DWORD PTR [rdi]  ; We store `*a` value into `eax` registry
            ret                           ; We exit the function
    .L5:
            mov     eax, -1               ; We store `-1` into `eax` registry
            ret                           ; We exit

    However, the Rust compiler knows that a can never be NULL , so the codegen adds _attribute_((nonnull(1))) on the function:

    _attribute_((nonnull(1)))
    int t(int *a) {
      if (!a) {
        return -1;
      }
      return *a;
    }

    Which generates this assembly:

    t:
            mov     eax, DWORD PTR [rdi]
            ret

    Since the codegen knows that the if (!a) condition will never be true, why keeping it around?

    And it's just one example of extra information/optimization we do in the Rust backends. And that doesn't even cover in the slighest the monstruous amount of optimizations the codegen themselves do. If you want to have more examples of such optimizations, I strongly recommend reading the "Advent of Compiler Optimizations" blog posts written by Matt Godbolt (the developer of godbolt.org , another priceless tool).

    Words of the end

    So now you know what a Rust backend is, and why GCC backend is also an interesting thing to have while also learning about some optimizations we do behind developers back. :)

    This blog post was made thanks to my cat hanging to it.

    my cat in a tree, hanging on a branch

    RSS feed RSS feed

    Put a ring on it: a lock-free MPMC ring buffer

    Hacker News
    h4x0r.org
    2025-12-16 13:32:42
    Comments...
    Original Article

    One of the reasons few security products work well in busy Linux environments is that they amplify performance risk. You’re popular and your backend’s load is skyrocketing? Well, the typical product is just going to collect more data and do more analysis, which amplifies the degradation.

    In the real world, one of the key ways everyone deals with being overloaded is by dropping less essential things.

    We can do the same thing with ring buffers, which are fixed-size queues that typically drop old data once they fill up. Yet, they rarely get used outside of single-reader, single-writer scenarios, because it’s hard to build something correct that scales to 1-to-many scenarios, never mind many-to-many scenarios.

    But, what if we told you, you can have a scalable ring buffer that doesn’t need any locking, and works with multiple readers and multiple writers at the same time? You might say, “there’s no such thing”, except that now there is.

    Wait, that rings a bell 🔔

    Ring buffers are fixed-size first-in-first-out (FIFO) queues. Fixed size queues that separately track the front and back are a category of algorithm called the circular buffer .

    Some people treat the term circular buffer and ring buffer the same. For me, a ring buffer is a type of circular buffer, but one that explicitly drops data when the queue fills up.

    That’s not the only option for a circular buffer. For instance:

    1. You can just block until space becomes available.

    2. You can grow the backing store.

    3. You can just do nothing, except signal an insertion error and let the programmer figure it out.

    Ring buffers are more resiliant, since they proactively drop when needed. Typically, it’s the oldest data gets dropped. However, there are ring buffers out there that give the option to drop new data instead.

    Sure, for some situations, dropping data is the wrong call. But for many situations, especially ones requiring performance, lossy is still much better than bringing the system to a halt due to too much data.

    For instance, in the Linux kernel, ring buffers are used in many places. A well known example is for relaying events from the kernel to the userland handler, when ebpf probes are attached.

    When workloads are having performance issues, probes often end up with more work to do, and if there’s not some form of backpressure, things will end badly. And since ebpf probes are intended for observability, and since observability is generally less important than availability, dropping data as a first line of defense is a good idea.

    And because the kernel’s ring buffer allows you to choose whether to drop off the front or the back, ebpf users get that choose too. Still, dropping older data is generally more common.

    When operating on any kind of queue, lock-contention can slow things down significantly, because there are two bottlenecks– the head pointer (for enqueuers) or the tail pointer (for dequeuers). But rings have life even worse, because the head pointer can circle around the ring, and meet up with the tail pointer.

    Our requirements for a ring buffer:

    1. An ordered set S , with a maximum size n , with items of a fixed length l .
    2. An operation, enqueue(item) , where item is an arbitrary item of length l .
    3. An operation, dequeue() , which returns the next item of length l, or indicates that the buffer is empty.
    4. No thread should be able to detect an inconsistent ordering of operations on the ring, under any circumstances (We’ll cover memory ordering after we build our ring buffer).

    Trying to minimize the impact of bottlenecks is hard, and so people tend to make compromises somewhere. For instance, ring buffers will try to avoid locks, but will accept some constraints, for instance:

    • single producer, single consumer (SPSC)
    • single producer, multiple consumer (SPMC)
    • multiple-producer, single consumer (MPSC)

    Here, “producer” means “enqueuer”, and “consumer” means “dequeuer”. At some point, I wanted a true, lock-free multiple-producer, multiple-consumer (MPMC) ring buffer, but there was nothing out there that would scale, so I came up with an algorithm.

    ⛓️‍💥

    When we say lock free , we mean that, for any given thread performing an operation, no other thread can cause a thread to suspend. With respect to an algorithm only, this is often referred to as a system-wide progress guarantee.

    That doesn't mean any given thread will make 'progress' in a comfortable amount of time; lock-free algorithms usually perform operations that fail, and need to keep trying them until successful. They could conceptually lose their race till the end of eternity.

    To get per-thread progress, we need wait freedom , which is often achievable with exponential backoff. But, when OS scheduling tends to be fair, lock free algorithms essentially can expect not to contend forever, and the extra overhead of wait freedom is often not worth it.

    At some point after I’d come up with my algorithm, I did stumble across a vein of literature, calling it a “ring buffer” where old data couldn’t be dropped. The user was left to resubit. To my mind, that’s a fixed-size circular FIFO, but not a ring buffer .

    Today, we’ll build a true MPMC ring buffer. We’ll focus on dropping old data, but this is one place where extending it yourself would be really pretty simple.

    The full code is available at codeberg .

    Ordering that ring 👉☎️

    The last of our above requirements for a ring is often referred to as linearization . All operations map to a point on a conceptual timeline, where no thread can ‘see’ operations in an order that would be different from that timeline.

    For instance, if thread A enqueued I1 then I2 , and thread B is the one to dequeue both, it must always dequeue in the expected order– I1 first, then I2 .

    Every insertion thus needs to have a well-defined ordering, as does every removal. But it doesn’t have to map to wall time.

    For instance, let’s say threads B and C are both dequeuers. B shows up first and starts dequeueing I1 , but the scheduler suspends it in the middle of the operation. Meanwhile, C comes in and quickly pulls out I2 before B wakes, and returns its value.

    While B might return after C in wall-clock time, that shouldn’t worry us, as long as there’s a well-defined ordering. That well defined ordering requires a well-defined linearization point . By that, we mean an atomic operation that is considered the point where the overall operation ‘occurs’ or ‘is committed’.

    So if B hasn’t returned, but is suspended after the linearization point, it’s no big deal. The algorithm already considers the item dequeued.

    Similarly, if B starts its operation before C does, but is suspended BEFORE the linearization point, and C is never suspended, C can absolutely claim I1 ; when B wakes up, it cannot get I1 , and nobody has an inconsistent view of the world.

    That works because all threads get equal treatment– it doesn’t matter when the functions they call start or end; the ordering is based on when the operation at the linearization point occurs.

    We’ll make sure to clearly identify our linearization points for each operation, which will always be on atomic operations.

    👰‍♂️ Our word is our vow 🤵‍♀️

    We’re going to start with modest expectations and build a ring that operates on values that are exactly one word long. We’re going to go ahead and assume you’re on modern hardware, with a 64-bit word.

    When designing our algorithm, in order to ensure correctness, we will want to think through all the places where there’s contention, meaning, we need to cover all cases where threads might try to operate on the same memory at the same time.

    The following scenarios are all very realistic things for us to consider:

    • Multiple enqueuers can be performing enqueue operations in parallel, and thus compete with each other to get onto the list ‘first’.
    • If one enqueuer gets suspended, other enqueuers may wrap around the queue before its operation completes.
    • Multiple dequeuers can also run in parallel, and fight over which item they get.
    • Dequeuers can drain a list to the point that they’ve caught up to writers, and might be reading their state before an operation completes.
    • Or, dequeuers could lag way behind, trying to dequeue from a slot in the ring that a writer is now trying to use for a much more recent value.

    To be able to arbitrate disputes around this kind of contention, we’re going to be explicit about our linearization timeline. Items in the array will be associated with an epoch , which is simply a point of time on our timeline. But every enqueue operation will be tied to an epoch.

    We will make sure that each enqueue operation is associated with a unique epoch (though it is perfectly fine if we find ourselves needing to skip epochs).

    When our dequeuers go to dequeue, they will also be looking to own the dequeue of an item that’s associated with a particular epoch .

    When a thread examines a slot in the ring, it will need to know what epoch is associated with a cell, and whether there’s an enqueued item in that cell or not. We’ll want to make sure all that information is definitely tied to a specific value, and that the whole kit-and-kaboodle needs to always be read from (and written to) atomically.

    When a thread wants to update the same state from a cell in the ring, we need to atomically read the existing state, create a copy that has the state we want, and then try to replace the state inside the cell atomically.

    However, if, when we go to update the state, it’s changed from what we expected (based on the copy we made), then we need the operation to FAIL, and we should start the update process over again, based on those changes.

    Let’s Swap

    Thankfully, there’s a universally available atomic operation that does all of those things, often referred to as a compare-and-swap operation (CAS). The C language standard provides an API call to do this, although they use the word exchange instead of swap (also a common name for this operation).

    Any CAS operation we perform on a cell will be a linearization point for us.

    Most hardware platforms can do a compare-and-swap atomically, but not for any arbitrary size. Modern hardware usually limits us to 128 bits that we can atomically operate on. Other atomic operations may limit us to just 64-bit operands.

    💻 The x86 family has long supported a 128-bit compare-and-swap, but until recently, it required instruction-level locking to use, because it did not support atomically loading 128 bits into a register otherwise. So on old hardware, you're technically using a lock with a 128-bit CAS, but 🤷‍♂️.

    The CAS operation conceptually takes three operands (it can differ, as we’ll see later):

    1. A pointer to the bytes we want to swap (i.e., the object to swap)
    2. A pointer to the value we’re expecting to be in that memory before the operation begins (i.e., the expected value)
    3. The actual value we want to leave behind (i.e., the desired value).

    The processor will check that the value in the 2nd field is right; if it is, the memory address pointed to in parameter 1 gets the value you passed into parameter 3, and the OLD value gets written into the memory address pointed to by parameter 2, overwriting the expected field.

    In this case, the operation returns true .

    If the operation fails because the memory has changed since your last load:

    1. Your desired value does not get installed.
    2. The memory holding the expected value is updated to contain what the actual value was that differed from the expected value.
    3. The function returns false .

    On some platforms, there can be two variations of this operation, the strong CAS and the weak CAS. The weak CAS actually is allowed to return false and skip the swap, even if the expected value does match the object to swap. Generally, that operation will often get it right, but you might occasionally end up re-trying where you shouldn’t have needed to retry.

    Why the heck would anyone want that behavior? It turns out, some platforms can make this weaker version faster. Even with the potential for failures, if you are using a CAS in a loop, until it succeeds, this weaker version is what people will recommend.

    However, if you have a use for a CAS operation that doesn’t require testing for success after, using the weaker version would require testing. If you have to add a loop where there wouldn’t have been one otherwise, then you definitely want to use the strong variant.

    But, while that’s the guidance you’ll find all over the internet, I don’t actually know which CPUs this would affect. Maybe it’s old news, I dunno. But it does still seem to make a shade of difference in real-world tests, so 🤷.

    Picking your venue ⛪️

    How do threads decide where to operate inside the ring buffer, with a minimum of fighting?

    Imagine our ring is a large butcher shop with multiple counters. We take a number, and then go to the counter associated with that number.

    Our ring will give out tickets to epochs, giving out each number no more than two times– once to an enqueuer, and once to a dequeuer. To do that, we’ll need two counters that we can atomically update.

    If we need to implement giving out tickets like this, we can use an atomic CAS operation to do it, sitting in a loop until our new value is swapped in. Each time we lose, we’ll have to recompute the next value, but that’ll certainly work.

    However, we can avoid the loop altogether if we do it through an atomic fetch-and-add (FAA) operation. When implemented in hardware, FAA is guaranteed to be completed. You pass in two operands:

    1. A pointer to the object containing the current value. The result will be stored here at the end of the operation.
    2. The value you’d like to add.

    At the end of the operation, the old value gets returned to the caller.

    So, to implement a ticketing system, we can have a variable that holds the value of the next ticket to give out. Each thread gets a ticket by calling FAA, adding the number 1 to the ticket value, but returning the number that was there at the start, which becomes our ticket.

    No two enqueuers will get the same ticket. We can then map each ticket to a cell in the ring (easiest done by limiting the number of cells in the ring to powers of two). We take the ticket, divide by the number of cells, and the remainder is the cell index associated with that ticket (i.e., we compute the modulus).

    When we have a number of cells that’s a power of two, we can use a cheap binary AND operation to get the modulus.

    That trick only works for powers of two, because of some specific properties:

    1. Every power of two is represented with a single bit set to 1; all other bits are zero.

    2. If you subtract 1 from a power of two, all bits to the RIGHT of that one bit set for the power of two will be set, and nothing else.

    So we get a cheap modulo via a binary AND operation. To be fair, it could be the case that modern processors can compute the modulus just as quickly as a bitwise AND, even though it’s a much more complicated operation. But, that’s why many data structures are backed by arrays of size to powers of two. It’s such a common paradigm, we’ll just roll with it.

    Now, we also need a second ticket for dequeuers; those tickets should be associated with values that have already been enqueued.

    However, if we keep those two tickets separate, then it will be really hard for us to build any confidence that we’re detecting some of the contention scenarios we talked about above.

    For example, if we read each counter separately, how do we know if the queue is empty? Or, how can we be certain that readers are way behind (we don’t want to waste a lot of time with readers grabbing tickets for values we already dropped).

    The answer for us is to operate on those tickets (epochs) in one atomic operation.

    We can still do that with an FAA. For example, if we define our epoch-of-record datatype like this:

    typedef struct {
        uint32_t epoch_q; // The next epoch assoc. w/ an enqueue op.
        uint32_t epoch_d; // The next epoch assoc. w/ an enqueue op.
    } h4x0r_word_ring_epochs_t;
    

    The compiler is smart enough that it will let you perform operations on structs as if they were integers, as long as the sizes are acceptable, and as long as you are explicit enough with your use of types to convince the compiler you know what you’re doing.

    One way to do this is to use a union , which we might declare like this:

    typedef union {
        h4x0r_word_ring_epochs_t epochs;
        uint64_t                 integer;
    } h4x0r_converter_t;
    

    The fields in unions may be individually addressed, but they share the same memory . The bits won’t change, but we can get the compiler to recognize the change-of-type based on the field we end up accessing.

    Let’s say we want to add 1 to the queue epoch. We don’t need to understand endianness or how the two fields in that struct are ordered. Here’s one way we could do that:

        h4c0r_word_ring_epochs_t to_add_struct = {
                                                   .epoch_q = 1,
                                                   .epoch_d = 0,
                                                 };
        union h4x0r_converter_t conv           = {
                                                   .epochs = my_epochs,
                                                 };
        uint64_t                to_add_num     = conv.integer;
    

    We can then convert it back to a struct. And really, those conversions we expect to be free; it’s just a mechanism for expressing our intent to the compiler.

    While 128-bit CAS operations are commonly supported in hardware, FAA (and similar operations) are more likely to have a 64-bit cap on their operands.

    That’s why the two epochs in our data structure are kept to 32 bits.

    However, 32 bits isn’t all that large a number, and it wouldn’t take too long for a busy system to overflow a counter. Dealing with that kind of overflow wouldn’t be fun.

    If you are confident you’ve got a situation where you won’t use a ring enough to overflow, then by all means, use 32-bit epochs. But we recommend that, by default, you use 64-bit epochs. You can emulate a 128-bit FAA pretty easily with a CAS operation, as we described above.

    While a 64-bit FAA should be faster, the CAS probably will be fine (on my machine, it’s a tiny smidge better, but not enough to crow about).

    Our full implementation allows you to toggle between the two epoch sizes at compile-time, so you can compare the results if you like.

    Anyway, atomically updating the epoch state gets you a ticket, and shows you what the next ticket is for the opposite operation, at the time we were handing your ticket.

    A Simple ring 🍩

    As we said above, the core state of the cells inside a ring must be no more than 128 bits if we want to operate on it without requiring a lock. So we need to be careful about what we try to jam in a cell.

    When we load a cell, we want to know if we’re too slow, whatever our operation. At a bare minimum, we’re going to need:

    1. Whether an item is enqueued in the slot or has been dequeued.
    2. Room for that value, which probably needs to be a whole word so we can fit a pointer in (usually 64 bits).
    3. The epoch associated with the cell, which is how we’ll know if a writer has been lapped (if the epoch is higher than the epoch for our operation, we got lapped). It’s also how readers will figure out the writer is slow and hasn’t finished.

    As we said above, 32 bits is not enough space for the epoch if we are building something general-purpose. But, we clearly don’t have room for 64-bit epochs, so we’ll need to compromise.

    A boolean generally will take up at least 8 bits, and a 56-bit epoch probably is good enough not to worry about. Though C doesn’t have 56-bit ints.

    However, we can instead use C bit slicing, which would even let us get the flag down to a single bit, leaving 63 bits for our epoch:

    typedef struct {
        _Atomic(void *)item;
        uint64_t enqueued : 1;
        uint64_t epoch    : 63;
    } h4x0r_word_ring_item_t;
    
    static_assert(sizeof(h4x0r_word_ring_item_t) == 16,
                  "Bitfield implementation doesn't pack as expected");
    
    typedef _Atomic(h4x0r_word_ring_item_t)   h4x0r_word_ring_cell_t;
    typedef _Atomic(h4x0r_word_ring_epochs_t) h4x0r_atomic_epochs_t;
    

    The C standard doesn’t really mandate layout for bit slices, so we check at compile type with the static_assert .

    For values we need to be shared between threads, we need to label them as _Atomic . Otherwise, the code that the compiler generates will assume none of our variables are shared across threads, and we are bound to have all sorts of nasty race conditions.

    But, we don’t tag _Atomic on h4x0r_word_ring_item_t or h4x0r_word_ring_epochs_t directly, because threads will be keeping their own private versions for updating.

    My version of the top-level ring looks like this:

    typedef void (*h4x0r_word_ring_drop_handler)(h4x0r_word_ring_t *, void *);
    
    struct h4x0r_word_ring_t {
        h4x0r_word_ring_drop_handler drop_handler;
        uint32_t                     num_cells;
        uint32_t                     last_slot;
        h4x0r_atomic_epochs_t        epochs;
        h4x0r_word_ring_cell_t       cells[];
    };
    

    The drop handler is a function pointer, and we’d expect it to be set when initializing the ring, defaulting to NULL when we don’t need to do anything in particular to deal with drops (either because we’re not using dynamic memory, or because we have something like a garbage collector to clean up if needed).

    The epochs field is tagged as being atomic, though it’s hidden behind the typedef.

    Similarly, the variable-length array of cells is really an array of items we want to be atomic. The fact that cell loading and storing requires using the C atomic API is in there, but hidden behind a typedef .

    However, the num_cells field and last_slot field are not tagged _Atomic . That’s because these should be set by one thread during initialization, and then never changed. As long as the memory has synced before other threads start to use these fields, we definitely don’t need them to be treated specially.

    Usually, if we do initialization in a proper function, the call boundary is going to be a memory barrier that makes sure they’re sync’d when other threads start getting a handle on our ring.

    But, if initialization might be inlined, you should probably flag these things as _Atomic , but when you access them via the C11 atomic API, ask for memory_order_relaxed , which essentially means, “no atomic op necessary here, so don’t add instructions to sync here”.

    In that case, the _Atomic modifier will make sure our writes to those fields are seen by subsequent loads, but we don’t have to slow down those loads once the values are written.

    The num_cells field is always going to be the number of cells in our ring, and 2^32 cells should be plenty, thus 32 bits. But because of C alignment rules, our struct is going to end up with a 32-bit hole somewhere. So, we fill that space with last_slot , which is always going to be one less than the value of num_cells , allowing us to perform our fast modulo without first having to subtract one from num_cells .

    The dequeue operation

    So far, our queue has nothing in it, and we don’t know how to put anything in it yet.

    Still, let’s start with our dequeue operation.

    Our first order of business is to the epochs, and if the tail and head are in the same place (or if the tail somehow lapped the head), then the queue is empty, and we shouldn’t even bother taking a ticket, so to speak.

    But, if we do see items to dequeue, we’ll play the game! We’ll use FAA to get a unique read epoch. However, it’s 100% possible that other readers beat us, with no writers coming along.

    That means, we could actually increment the counter past where the next writer is going to write. We will need to make sure that when we deal with writers, we try to solve that problem.

    The epoch we read in won’t ever get another reader, but the writer will need to make sure it doesn’t use the slot we just accidentally burned.

    Now, we’ll use our epoch to find the associated cell. We’ll load it, and look at the contents to figure out what to do.

    Once we have loaded the cell, if the epoch is the one we’re expecting, and there’s an item in there, then we try to dequeue it by doing a CAS to mark it as dequeued (and generally attempting to set the item to NULL ).

    If the dequeue succeeds in the right epoch, we’re done.

    If we found that another reader wasn’t keeping up, and our writer hasn’t written over it yet, we’d still try the CAS, and if it succeeds, that epoch is invalidated; the writer will eventually figure out that they cannot complete their write, and needs to try again.

    But, if we can prove via the epochs that we’re the only writer at the time of our CAS, then, even though we know someone is trying to queue, we take that successful CAS as our linearization point, and declare that the ring was empty.

    If we lose the race against a slow writer, no big deal, we start over with the same epoch; the writer probably left us a present.

    If our CAS operation fails, before we try again, we look at the value of the expected item, which is the current stored value.

    If we find the epoch is lower than ours, then we try to invalidate it with a CAS. If we fail, it could be that the slow writer finished, or it could be that we were suspended and are now behind. If it’s the former, we try again with the same epoch. If it’s the latter, we’ll know because the epoch is higher than ours, and we need to go get another read epoch (we can’t return empty unless we know that because of some CAS that we did, there’s a moment on our timeline where the ring was empty).

    That’s already several corner cases we need to worry about. But the most difficult concern here is the last one.

    Consider when we use the CAS to dequeue, and that CAS operation failed. But the expected value was the epoch we were looking for. We can just go home and call it a day, right?

    It depends:

    • If we are running with a drop-handler, we absolutely cannot, because the writer will have known it was overwriting, and will be calling the drop handler. We don’t want to risk a double free if there’s dynamic deallocation.

    • Otherwise, yes; the writer we were competing with absolutely doesn’t care.

    For the API call to dequeue, some callers are going to need to distinguish between returning a NULL because the ring is empty, and the case where NULL was enqueued.

    The way to deal with this is to have our API call accept a pointer to a boolean. If NULL is passed, then we ignore the parameter. Otherwise, we’ll store a value in the memory pointed to.

    The logic around whether to store the pointer is easily lifted out into inline functions:

    static inline void *
    h4x0r_word_ring_empty(bool *found)
    {
        if (found) {
            *found = false;
        }
        return NULL;
    }
    
    static inline void *
    h4x0r_word_ring_found(h4x0r_word_ring_item_t item,
                          uint64_t              *epoch_ptr,
                          bool                  *found)
    {
        if (found) {
            *found = true;
        }
    
        h4x0r_atomic_fence();
    
        if (epoch_ptr) {
            *epoch_ptr = item.epoch;
        }
    
        return item.item;
    }
    

    Notice that h4x0r_word_ring_found takes a second pointer parameter– that’s for the caller to get the associated epoch. Day-to-day that may not be too useful. However, it’s very useful for correctness testing, to make sure one thread never sees out-of-order dequeues.

    We’ll use it when we do our testing.

    With all that, here’s the body of our dequeue operation:

    void *
    h4x0r_word_ring_dequeue(h4x0r_word_ring_t *ring, bool *found, uint64_t *ep)
    {
        h4x0r_word_ring_cell_t  *cell;
        h4x0r_word_ring_item_t   expected;
        h4x0r_word_ring_item_t   last;
        h4x0r_word_ring_item_t   candidate;
        h4x0r_word_ring_epochs_t epochs = h4x0r_atomic_load(&ring->epochs);
    
        while (true) {
            if (epochs.epoch_d >= epochs.epoch_q) {
                return h4x0r_word_ring_empty(found);
            }
            epochs   = h4x0r_epochs_increment(&ring->epochs, read_incr);
            cell     = h4x0r_word_ring_slot_addr(ring, epochs.epoch_d);
            expected = h4x0r_atomic_load(cell);
    
            candidate = (h4x0r_word_ring_item_t){
                .item     = NULL,
                .epoch    = epochs.epoch_d,
                .enqueued = false,
                .dequeued = true,
            };
    
            while (expected.epoch <= epochs.epoch_d) {
                last = expected;
    
                if (h4x0r_atomic_cas(cell, &expected, candidate)) {
                    if (epochs.epoch_d == last.epoch) {
                        return h4x0r_word_ring_found(last, ep, found);
                    }
                    if (epochs.epoch_d > last.epoch) {
                        h4x0r_word_ring_drop(ring, last);
                        break;
                    }
                    return h4x0r_word_ring_found(last, ep, found);
                }
                else {
                    if (last.epoch == epochs.epoch_d && !ring->drop_handler) {
                        return h4x0r_word_ring_found(last, ep, found);
                    }
                    epochs = h4x0r_atomic_load(&ring->epochs);
                    continue;
                }
                if (epochs.epoch_q - epochs.epoch_d <= 1) {
                    return h4x0r_word_ring_empty(found);
                }
            }
            // We got lapped and need a new epoch.
            epochs = h4x0r_atomic_load(&ring->epochs);
        }
    }
    

    You’ll notice there are a few more helper functions in there:

    • h4x0r_epochs_increment() uses some inline code to do the 128-bit FAA using a union for conversion, as discussed above.
    static inline h4x0r_word_ring_epochs_t
    h4x0r_epochs_increment(_Atomic h4x0r_word_ring_epochs_t *p,
                           h4x0r_epoch_info_t                counter)
    {
        h4x0r_epoch_info_t result;
    
        result.i = h4x0r_atomic_add((_Atomic(__uint128_t) *)p, counter.i);
    
        return result.s;
    }
    
    • h4x0r_word_ring_slot_addr() takes our epoch, performs the modulo operation, and gets us a pointer to our cell. The code to get the address inside that inline function is more compact than the call, but we prefer the extra clarity, and the compiler is expected to inline it, especially if we put it in a header file (or we can annotate it to always inline).
    static inline _Atomic(h4x0r_word_ring_item_t) *
    h4x0r_word_ring_slot_addr(h4x0r_word_ring_t *ring, uint64_t epoch)
    {
        return &ring->cells[epoch & ring->last_slot];
    }
    
    • There are several h4x0r_atomic_*() calls. Those wrap the C11 API calls so we can apply consistent memory ordering that’s different from C’s default. We’ll discuss this a bit at the end of the article for those who want to understand. We do use C’s new-ish _Generic feature to abstract away over fetch-and-add , selecting our function for 128 bit values:
    #define h4x0r_atomic_add(x, y)                    \
        _Generic((y),                                 \
            __uint128_t: _h4x0r_atomic_faa_128(x, y), \
            default: atomic_fetch_add_explicit(x, y, H4X0R_MO_RW))
    
        static inline __uint128_t                                 
            _h4x0r_atomic_faa_128(void *v, __uint128_t val) 
        {                                                         
            _Atomic __uint128_t *var = v;                         
                                                                 
            __uint128_t expected;                                 
            __uint128_t desired;                                  
                                                                  
            expected = h4x0r_atomic_load(var);                    
                                                                  
            do {                                                  
                desired = expected + val;                 
            } while (!h4x0r_atomic_cas(var, &expected, desired)); 
            return expected;                                      
        }
    
    h4x0r_decl_binopu128(faa, +);
    
    • h4x0r_word_ring_drop() is about as simple as you’d want it to be:
    static inline void
    h4x0r_word_ring_drop(h4x0r_word_ring_t *ring, h4x0r_word_ring_item_t cell)
    {
        if (ring->drop_handler && cell.enqueued) {
            (*ring->drop_handler)(ring, cell.item);
        }
    }
    

    The enqueue operation

    We’re already more than halfway done with our first ring. All we really need to do is get stuff into it.

    Our first order of business when a thread calls our enqueue function is to load the existing epochs, grabbing its own ticket (epoch) in the process, via our fetch-and-add.

    Second, we check the epochs to see if they need repair. That could be one of two scenarios:

    1. We see that the tail is lagging (meaning, the epochs indicate the tail is farther behind than the number of slots); this is due to a relative lack of dequeuers.
    2. We see that some dequeue operation accidentally look a read-slot when the queue was empty, during a race condition (We do not want to write into a slot that no reader could ever read).

    If the enqueuer sees either of these two scenarios, it will attempt to ‘fix’ the tail by moving the dequeue epoch to the lowest epoch that might still be enqueued. Here are my helper functions to deal with these scenarios:

    #define H4X0R_BACKOFF_START_NS 1000
    #define H4X0R_BACKOFF_MAX_NS   65536
    
    static const struct timespec start_sleep = {
        .tv_sec  = 0,
        .tv_nsec = H4X0R_BACKOFF_START_NS,
    };
    
    static inline bool
    h4x0r_word_ring_needs_repair(h4x0r_word_ring_epochs_t epochs,
                                 uint32_t                 ring_size)
    {
        if (epochs.epoch_d + ring_size < epochs.epoch_q) {
            return true;
        }
        if (epochs.epoch_d > epochs.epoch_q) {
            return true;
        }
        return false;
    }
    
    static inline void
    h4x0r_ring_lag_sleep(struct timespec *sleep_time)
    {
        // We don't really care if we sleep the whole time or not.
        nanosleep(sleep_time, NULL);
        sleep_time->tv_nsec <<= 1;
    
        if (sleep_time->tv_nsec > H4X0R_BACKOFF_MAX_NS) {
            sleep_time->tv_nsec = H4X0R_BACKOFF_MAX_NS;
        }
    }
    
    // Returns true if we ever go through the loop, indicating
    // we may need to  update our own epoch.
    static inline bool
    h4x0r_word_ring_repair(h4x0r_word_ring_epochs_t epochs,
                           h4x0r_word_ring_t       *ring)
    {
        struct timespec          sleep_time = start_sleep;
        bool                     repair     = false;    
        h4x0r_word_ring_epochs_t candidate;
    
        while (h4x0r_word_ring_needs_repair(epochs, ring->num_cells)) {
            repair = true;
            
            if (epochs.epoch_d > epochs.epoch_q) {
                candidate = (h4x0r_word_ring_epochs_t){
                    .epoch_q = epochs.epoch_q,
                    .epoch_d = epochs.epoch_q,
                };
            }
            else {
                candidate = (h4x0r_word_ring_epochs_t){
                    .epoch_q = epochs.epoch_q,
                    .epoch_d = epochs.epoch_q - ring->num_cells,
                };
            }
            if (!h4x0r_atomic_cas(&ring->epochs, &epochs, candidate)) {
                return true;
            }
            h4x0r_ring_lag_sleep(&sleep_time);
        }
    
        return repair;
    }
    

    If the enqueuers don’t do the tail-correction, it penalizes dequeuers who are already behind; they’ll pay the price of going back for tickets, only to find they’re out of date, which can exacerbate problems when they’re behind.

    If we do see lag, after attempting to fix it, we should help even more by taking a really short snooze to go ahead and help any pending reader succeed. If we don’t, then we’re at more risk of dequeuers continually being forced to retry because writers are starving them. Here is one place in our algorithm where, if we want to go for full wait-freedom, we can turn this process into an exponential backoff loop.

    Once we leave the readers acceptably far behind, we then go to the cell we’re supposed to be writing to. There, we’re going to want to load the state to see what’s what.

    • If we see our epoch is already in there, we were too slow, and some reader invalidated the slot; we need to grab a new epoch and try everything again.
    • We do exactly the same thing if we see a HIGHER epoch than ours (writers lapped us, probably because we got suspended).

    Next, we add the value, move the state to enqueued , and attempt to install the item via a CAS.

    We keep trying until that succeeds.

    Once our CAS is successful, then the write is successful. However, we still are not always done.

    Specifically, we may need to look at what we overwrote (which should be installed in the expected field).

    If we overwrote a queued item, then we need to pass that item to the drop handler. This is a particularly important thing to do when the item we found is a pointer to heap memory.

    If we simply overwrite without checking, we might be leaking the memory the pointer references.

    Of course, if we have no drop handler, we don’t need the extra step; there’s no problem.

    We can just return, successful in our mission.

    Here’s my implementation:

    uint64_t
    h4x0r_word_ring_enqueue(h4x0r_word_ring_t *ring, void *item)
    {
        h4x0r_word_ring_cell_t  *cell;
        h4x0r_word_ring_epochs_t epochs;
        uint64_t                 write_epoch;
        h4x0r_word_ring_item_t   expected;
    
        while (true) {
            epochs      = h4x0r_epochs_increment(&ring->epochs, write_incr);
            write_epoch = epochs.epoch_q;
            
            if (h4x0r_word_ring_repair(epochs, ring)) {
                if (write_epoch + ring->num_cells < epochs.epoch_q) {
                    continue;
                }
            }
    
            cell     = h4x0r_word_ring_slot_addr(ring, write_epoch);
            expected = h4x0r_atomic_load(cell);
    
            // This has to be a CAS; we might have another writer who
            // laps us between the last epoch check and the coming op.
            h4x0r_word_ring_item_t new = {
                .item     = item,
                .epoch    = write_epoch,
                .enqueued = true,
            };
    
            while (expected.epoch < write_epoch) {
                if (h4x0r_atomic_cas(cell, &expected, new)) {
                    // If we overwrote something, it'll need to be dropped.
                    h4x0r_word_ring_drop(ring, expected);
                    return write_epoch;
                }
            }
            continue; // too slow; get a new epoch and retry.
        }
    }
    

    We’ve Inscribed some words on your ring

    We have our first lock-free ring. But so far, it’s like we got the ring out of a cracker-jack box. It’s not yet something nice enough to use, since we’re limited to putting in 64-bit values.

    However, we will 100% solve that problem.

    That 💍 is too small, I want a BIG one

    One thing about a ring that’s often valued is that you can operate in a fixed amount of statically allocated memory. Only giving 64 bits of space for the ring will push us towards dynamic allocation, which is a shame.

    But we can use our word ring to make a big ring with larger, fixed-sized memory cells.

    The basic idea is that we have two circular buffers, one of them being the word ring (we’ll call it R ). Then, we’ll create a second circular buffer to store our arbitrary-sized records. We’ll call this one S (for store).

    The entries we enqueue into our word ring R will simply be an epoch that points to a spot in S .

    Write threads will peek at the ring’s global epoch info, for two reasons:

    1. As a hint for where to start in the larger array, and
    2. So that we know which records conceptually aren’t in the ring anymore and can be overwritten.

    Here are the data structures I used in my implementation, along with the state flags I use throughout.

    typedef struct h4x0r_ring_t h4x0r_ring_t;
    typedef struct h4x0r_ring_entry_info_t h4x0r_ring_entry_info_t;
    
    
    // The full cell definition for the outer ring cells.
    
    struct h4x0r_ring_entry_t {
        _Atomic h4x0r_ring_entry_info_t info;
        uint64_t                        len;
        char                            data[];
    };
    
    // This the first item in the outer ring cell, 
    struct h4x0r_ring_entry_info_t {
        uint64_t write_epoch;
        uint64_t state;
    };
    
    struct h4x0r_ring_t {
        h4x0r_word_ring_t  *word_ring;
        h4x0r_ring_entry_t *entries;
        _Atomic uint64_t    entry_ix;
        uint64_t            last_entry;
        uint64_t            entry_len;
    };
    
    enum : uint64_t {
        H4X0R_RING_EMPTY             = 0x00,
        H4X0R_RING_RESERVED          = 0x01,
        H4X0R_RING_DEQUEUE_RESERVE   = 0x02,
        H4X0R_RING_ENQUEUE_DONE      = 0x04,
        H4X0R_RING_USED              = 0x07,
        H4X0R_RING_DEQUEUE_DONE_MASK = ~0x06,
    };
    

    I’m going to skip initialization here, but two important notes if you’re going to DIY:

    1. Double-check that the “core” word ring’s size in bits is a power of two .
    2. Ensure that cell sizes are properly aligned (probably to a 16-byte boundary to be safe).

    The enqueue operation

    The write thread starts by grabbing the underlying word ring’s epoch information. It takes the write epoch it finds, and maps that into S ( e % len(S) , if e is the epoch)`.

    Starting at that position, the writer scans S to find the first valid spot it can claim.

    That means, if it notices an operation in progress, it skips that cell and keeps probing until it can claim a cell that’s safe to write. Once the write completes, we enqueue the position into our word ring. Adding it to the word ring gives us the epoch; we add that into our slot inside S , before we remove the flag that indicates we’re writing to the cell.

    As a result, entries in S can be out of order, and that’s totally fine. The correct order will be kept in the word ring.

    More specifically, the steps for enqueuers (writers) are as follows:

    1. Find a spot in S , and reserve it, so no other writer will touch it.
    2. Copy data into the reserved cell.
    3. Enqueue a pointer to S into R (our linearization point).
    4. Write into S the epoch that R returned to us when we enqueued.
    5. Update the slot in S with the epoch, and indicate we’re done with their enqueue.

    To make this all work, we really should have S contain more entries than R . We want to have enough to ensure the right number of items can all be enqueued at once in a full queue, and that any write thread will still have a space to write. If we don’t do that, writers will be roaming around a full parking garage until a spot opens up and they’re lucky enough to nab it.

    If we have a ceiling on the number of threads we allow, we can use that value. But, if not, just doubling the number of entries should be more than good enough. There will be no competition for the enqueuer’s slot from other enqueuers.

    However, a dequeuer can come in after step 3 completes and before step 4 completes, while we’re suspended.

    That’s not a problem for us– the linearization point is the enqueue into R . We just need to make sure that enqueuers can only claim a slot if BOTH enqueuers and dequeuers are done with their operation (and, if it’s not in R , of course).

    Note that dequeuers will set a state bit when they start dequeuing, so we can easily avoid taking those slots. However, when figuring out whether we can overwrite a state no thread is in, we need to check the stored epoch, to make sure it’s far enough behind ours that it’s not conceptually in the queue anymore.

    Here’s the core of the enqueue operation:

     void
    h4x0r_ring_enqueue(h4x0r_ring_t *self, void *item, uint64_t len)
    {
        uint64_t                 ix;
        uint64_t                 byte_ix;
        uint64_t                 start_epoch;
        h4x0r_ring_entry_info_t  expected;
        h4x0r_ring_entry_info_t  candidate;
        h4x0r_ring_entry_t      *cur;
        h4x0r_word_ring_epochs_t epochs;
    
        if (len > self->entry_len) {
            len = self->entry_len;
        }
    
        epochs      = h4x0r_atomic_load(&self->word_ring->epochs);
        start_epoch = epochs.epoch_q;
        ix          = start_epoch & self->last_entry;
    
        while (true) {
            byte_ix = ix * (sizeof(h4x0r_ring_entry_t) + self->entry_len);
            cur     = (h4x0r_ring_entry_t *)&(((char *)self->entries)[byte_ix]);
    
            expected              = h4x0r_atomic_load(&cur->info);
            candidate.write_epoch = 0;
            candidate.state       = H4X0R_RING_RESERVED;
    
            if (h4x0r_atomic_cas(&cur->info, &expected, candidate)) {
                break;
            }
    
            if (!h4x0r_ring_can_write_here(expected,
                                           start_epoch,
                                           self->last_entry)) {
                ix = (ix + 1) & self->last_entry;
                continue;
            }
    
            if (h4x0r_atomic_cas(&cur->info, &expected, candidate)) {
                break;
            }
            ix = (ix + 1) & self->last_entry;
        }
    
        memcpy(cur->data, item, len);
    
        candidate.write_epoch = h4x0r_word_ring_enqueue(self->word_ring,
                                                        (void *)ix);
        candidate.state       = H4X0R_RING_ENQUEUE_DONE;
        cur->len              = len;
    
        h4x0r_atomic_store(&cur->info, candidate);
    }
    

    The only new helper function here is h4x0r_ring_can_write_here() and its helper:

    
    static inline bool
    h4x0r_ring_entry_is_being_used(h4x0r_ring_entry_info_t info)
    {
        if (info.state & H4X0R_RING_USED) {
            return true;
        }
    
        return false;
    }
    
    static inline bool
    h4x0r_ring_can_write_here(h4x0r_ring_entry_info_t info,
                              uint64_t                my_write_epoch,
                              uint32_t                last_entry)
    {
        if (h4x0r_ring_entry_is_being_used(info)) {
            return false;
        }
    
        if (info.write_epoch > my_write_epoch) {
            return false;
        }
    
        if (info.write_epoch > (my_write_epoch - (last_entry + 1))) {
            return false;
        }
    
        return true;
    }
    

    The dequeue operation

    Dequeuers (readers) take the following steps:

    1. Dequeue a value from R , which gives us the index into S we need; at the same time, we use our reference parameter to capture the epoch the writer used to make sure we don’t read from the future.
    2. Attempt to mark the cell in S for reading and validating. If validation fails, we restart.
    3. Actually perform the read.
    4. They mark the cell state in S to indicate the read is done.

    Note that a slow dequeuer might find that by the time they attempt to flag the cell in L for read, someone has already claimed that cell for writing a newer log message. In such cases, the slow dequeuer just needs to try again.

    Or, the writer may not have stored its epoch yet. We know if they got a ticket, we can go find the right slot. If the epoch is anything less than the epoch we dequeued, it’s definitely our value to dequeue.

    For the dequeue, we’ll use these two very simple helpers:

    static inline bool
    h4x0r_ring_can_dequeue_here(h4x0r_ring_entry_info_t info,
                                uint64_t                expected_epoch)
    {
        if (info.write_epoch > expected_epoch) {
            return false;
        }
    
        return true;
    }
    
    static inline uint64_t
    h4x0r_ring_set_dequeue_done(uint64_t state)
    {
        return state & H4X0R_RING_DEQUEUE_DONE_MASK;
    }
    

    Our dequeue function is going to return whether there was a dequeue or not, instead of returning a value, and use a parameter for people to check if the queue was empty.

    That’s because we’re going to need the caller to pass in a buffer for the result. We DEFINITELY don’t want to pass back a pointer to the ring entry; that’s a recipe for disaster.

    Testing our rings

    Especially since we’re dealing with concurrency, we want to make sure to test thoroughly. We should run in a number of different configurations, and should thoroughly test to make sure that we only ever see linearized results when we dequeue.

    We’re also going to want to count some stuff, then check it all for consistency:

    1. We should count successful dequeues.
    2. We should independently count the number of drops, too.
    3. We should capture the number of times fast readers find an empty queue.

    Each thread should collect metrics privately, and then add them to totals, when it’s done with the work.

    If we add independently collected dequeues to drops, we should get the total number of enqueues; otherwise we have a bug.

    You’ll need to know when to stop trying to dequeue. The simplest path is to have the main thread first join() on all enqueuers; at that point, we know there’s nothing else to enqueue. So when a dequeue thread sees all writers are done, the first time they encounter an empty queue, they know they’re done. That way is easy to implement, but leaves a small window where the empty dequeue time will push up. You can instead have individual writer threads decrement a global counter, which will shorten that window.

    Also, even if it’s not real world, we should test for worst case, and run enqueues and dequeues as back to back as possible, to help us understand worst case performance, or any other considerations we might need.

    I’ll spare you the code, because you can go get it in the codeberg repo . But, it does iterate over both types of ring, using five different sizes of buffer, and with a variety of (mostly imbalanced) readers and writers.

    Let’s look at some example output though (I’ve deleted a few columns to keep us under 80 characters).

    First, for the main, arbitrary ring, let’s look at our most minimal ring size:

    Tests for queue with 16 items:
    Test   Th+    Th-   Time(sec)     Q-             Q💧    Mop/s (✅)
    ------------------------------------------------------------------
    # 1    1      1      0.0246     251,355       10,781      10.23   
    # 2    2      2      0.0505     235,606        3,705       5.11   
    # 3    4      4      0.1124      68,417      187,282       0.67   
    # 4    8      8      0.2179     199,100       34,775       1.04   
    # 5    2      1      0.0364     110,678      132,274       3.57   
    # 6    4      1      0.0624      40,678      210,810       0.82   
    # 7    8      1      0.0855       7,927      251,875       0.12   
    # 8    1      2      0.0271     176,210       85,934       6.51   
    # 9    1      4      0.0472     183,065       79,079       3.88   
    #10    1      8      0.0930     183,732       78,412       1.97   
    

    Here, Th+ is the number of enqueue threads. The - sign denotes the dequeue size.

    Q💧 is the number of drops. Then, the last column denotes how many millions of ops per second we performed in that test case.

    The three columns we omitted that you’ll see in the output:

    • Q+ is the total number of enqueues, which is always 262,144 in my runs.
    • Q∅ , the number of times a dequeuer attempted to dequeue, and found the queue was empty.
    • Mop/s (✅+∅) which recomputes Mop/sec, including dequeues that find the queue empty.

    One thing that should jump out to you is that there are an absurd number of drops in there. And when we get those absurd drops, our overall performance tends to plummet. The table makes it clear that we need to do more to deal with the contention.

    That’s the kind of concern you should be looking for when testing parallel algorithms.

    The insight makes sense; if the queue is full, the writers help with the tail, but then go back to competing where the table comes together.

    The conclusion I drew is that, before the queue fills, enqueuers should briefly pause to give preference to readers, so as not to contend with them. In the code repo for this article, I did just that for you, setting the threshold to 75%, which gives much better results:

    Tests for queue with 16 items:
    Test   Th+    Th-   Time(sec)     Q-         Q💧    Mop/s (✅)
    --------------------------------------------------------------
    # 1    1      1      0.0248     262,142        2      10.55
    # 2    2      2      0.0450     260,071       43       5.82
    # 3    4      4      0.0923     236,809      434       2.83
    # 4    8      8      0.2132     208,501      177       1.23
    # 5    2      1      0.0400     256,786       32       6.55
    # 6    4      1      0.0479     253,364       20       5.47
    # 7    8      1      0.0989     219,220       53       2.65
    # 8    1      2      0.0384     262,101       43       6.83
    # 9    1      4      0.0489     262,084       60       5.36
    #10    1      8      0.0868     261,814      330       3.02
    

    If we run more tests, we may see some significant drops, but we’d expect big numbers only when the number of writers greatly outweighs the number of readers. And in that case, the drops are expected. On my machine, this only ever happens for 16-item queues though. Even at 128 items, it looks good (on my machine):

    Tests for queue with 128 items:
    Test   Th+    Th-   Time(sec)      Q-           Q💧     Mop/s (✅)  
    ------------------------------------------------------------------
    #11    1      1      0.0147     262,145          0      17.82
    #12    2      2      0.0428     262,145          0       6.13
    #13    4      4      0.0765     262,144          1       3.42
    #14    8      8      0.1879     262,140          5       1.39
    #15    2      1      0.0263     262,145          0       9.97
    #16    4      1      0.0517     262,145          0       5.07
    #17    8      1      0.0936     262,145          0       2.80
    #18    1      2      0.0251     262,144          1      10.43
    #19    1      4      0.0417     262,142          3       6.28
    #20    1      8      0.0859     262,145          0       3.05
    

    If we study these charts, we can compare to see exactly how big an impact that contention actually has. In tests where we were dropping, the number of operations plummeted massively due to the contention.

    With the above tweak to the enqueuer’s help rules, my laptop tests never fail to top 1 million operations a second, and raw word-queue performance peaks at about 24 Mop/sec, and stays above 3 MOp/sec for all but a couple of configurations (the ones where I’ve overcommitted my cores w 8q+/8q-, so past the point where I’ve maxed out potential parallelism).

    Not bad, considering we haven’t really done anything to optimize (I do have a more optimized implementation of the base word-ring algorithm that can get as high at 40Mop/sec with one enqueuer and one dequeuer, and bottoms out at 4Mops/sec when the number of dequeuers is lopsided, all on the same machine).

    My test bed is there in the repo for you to use if you like.

    Did we forget to order something?

    When I wrote about futexes and mutexes, I glossed over the topic of memory ordering, because it’s notoriously hard to communicate well. But given we’re into the world of concurrency without locks, I think it’s remiss not to cover it. I’ll try to make it as straightforward as possible.

    If you are still confused after reading this section (which is likely), give me feedback on what questions it leaves you with, and I’ll try again.

    Your compiler may be gaslighting you

    Your compiler wants you to think your code will execute in the order you’d expect while staring at the source code.

    Meanwhile, behind your back, it’s generally going way out of its way to thwart your expectations. Though, to be fair, it’s doing it for you. It knows how disappointed you’d be if it performs poorly for you.

    So yes, the compiler will absolutely rewrite your code (particularly when you turn on any level of optimization). It has no qualms changing the order you’d expect, with hopes of it running faster for you. But, it’s hoping you won’t notice. The transformations often aren’t semantically identical to what you might have intended, but, at least in the context of a single thread, you’re not likely to notice the difference.

    Even for multi-threaded programs, compilers often try hard to optimize what you’re doing, transforming and reordering to take advantage of the CPU.

    Still, there are things the compiler won’t do (just like Meatloaf). Specifically, compilers have this idea of “barriers”, which are features in your code that the compiler won’t move stuff past.

    For instance, compilers will not move code across the boundary of a mutex, or any op on a variable labeled _Atomic (unless you explicitly allow it at the call site).

    The compiler is conservative here, because you expressed your intent. Event if the compiler thinks you’re walking away from a big performance gain, and even if they could “prove” that it’s not going to change the semantics of your program. Generally, function boundaries result in a barrier as well, as long as they’re not inlined.

    But, even with multi-processor programs, the compiler does all that analysis as if a single thread is running. So it can move data around across threads in many frustrating ways. If you don’t pay attention to how you handle concurrency, compiler transformations can definitely make race conditions far worse.

    The processor is a bad influence

    Making matters worse, your compiler’s friend, the processor, tries to apply parallelism everywhere it can, because of performance. So there are also plenty of architectural considerations that can lead to data races.

    For instance, you probably know that the CPU is fast, and memory accesses are slow. And when many threads are competing to load memory at the same time, things can get chaotic, because memory cells loaded into registers on one core don’t magically sync instantly across multiple cores.

    And, the processor may do its own reordering of instructions, for instance, to achieve fine-grained parallelism via things like instruction pipelining, which can definitely run your code out of order.

    Processors not only have a very complex memory model, but that model can be vastly different across architectures (e.g., x86 and ARM).

    Very few programmers are going to be aware of most of that subtlety.

    Languages could generate code to make sure everything always happens in a well-defined order (full sequential consistency), but generally they do not. Processors go out of their way to make things faster by moving your code around, and compilers often do a lot to make your code faster too.

    So no language is going to find it an acceptable hit to force the processor to run everything in a well-defined order, at least in the case of multiple threads.

    How to make this relationship work

    Programmers typically will need to tell their compiler where to get conservative, and sacrifice performance for correctness. In C, you can do that by labeling variables as _Atomic , which tells the compiler that variables will be used across threads.

    But _Atomic doesn’t truly mean, “always atomic”, primarily because you can explicitly ask for different behavior on a case-by-case basis.

    If you don’t specify anything other than _Atomic , it does mean that you’re not going to get corrupting data races, and it does mean that, by default, the compiler will ensure all operations on that variable will happen in the same order, from the perspective of any thread.

    Enforcing that kind of order generally slows things down, so, you can specify to the compiler cases where you want different behavior. For instance, if you’re initializing memory, you probably have exclusive access to the data. Your own view on the ordering is consistent anyway, so at this point you may not care to slow down the processor.

    However, that could be problematic for the next thread to read. Since the first access didn’t care, it’s definitely possible for a thread to get a reference to the object, and see the pre-initialization value, but only if that second access explicitly relaxes its requirement for getting access to the variable.

    Generally, those kinds of surprises are easy to find, especially when they involve fields in an object whose pointer you read atomically, but whose fields are not marked as being _Atomic . It’s a great recipe for Heisenbugs.

    So generally, if you know multiple threads will handle a variable, not only should you go ahead and declare it as _Atomic , but also you should avoid giving up most of its protection– you’re just inviting disaster.

    My memory order arrived damaged

    By default, accessing a single _Atomic variable will make sure that any changes to the variable happen in a well-defined (linearized) order. For example, let’s say we have a huge number of threads, and thread 0 goes to modify _Atomic variable A .

    Just by declaring the variable to be _Atomic , the compiler will, if necessary, generate code that makes sure that any changes to A that were in flight when thread 0 goes to modify it, all end before its operation. Similarly, any subsequent loads of that variable will see the reflected value.

    _Atomic variable access (unless relaxed) acts like a barrier that the compiler will enforce for the variable in question. That enforcement, though, is really done by generating an assembly that helps get the proper semantics, which is very platform dependent.

    If you do not mark a variable as _Atomic , then you should not be using the variable across threads . The compiler will certainly generate code under the assumption that those variables won’t have to deal with concurrent access.

    But we do have some options:

    • Relaxed Memory Order. In C/C++ parlance, the semantics of variables that are not marked _Atomic is called relaxed memory order . It’s a weird name that means there are no ordering guarantees outside of what would be promised by the underlying architecture for a non-atomic variable, and the promise of non-corrupting data races. That’s scary.

    • Sequentially Consistent Ordering. This lives on the other end of the spectrum from relaxed ordering. Using this is supposed to ensure a global ordering for all atomic data by default, at the price of some efficiency. This is the default memory ordering, and is the strongest, but it does have some slight issues we’ll discuss in a minute.

    • Acquire / Release Ordering. This is in between relaxed and sequentially consistent. It basically does what you want it to do on a variable-by-variable basis, forcing a well defined ordering.

    You’ll often see “Acquire” and “Release” listed as separate strategies. They’re more like two sides of the same coin:

    • acquire semantics apply only to loading an _Atomic variable (i.e., acquiring it). The guarantee is that any modifications to a memory address that were made by other threads will be reflected by the time the value loads, with nothing still in progress.

    • release semantics only apply to storing an _Atomic value (i.e., releasing it). The guarantee here is that the store operation will be reflected in the next subsequent load of that address. That is to say, once the store finishes, no other thread will be able to load the previous value.

    For some operations, only one of these two things makes sense. For instance, acquire semantics make sense for an atomic_load() call, but that doesn’t update the value, so release semantics don’t make sense here (and thus, acquire/release doesn’t make sense either).

    You don’t specify memory ordering when you declare something _Atomic . By default, every operation for such variables will get the strongest memory ordering.

    If you want anything else, you can get it on a call-by-call basis every time the variable is used, by calling a call in the C atomic library that allows you to explicitly change to another ordering, but only at that one slot.

    Generally, the defaults are going to be least surprising (in a world where surprises are common, and understanding what’s going on is hard).

    That doesn’t mean that most strict memory ordering is perfect: sequential consistency has some issues that prevent it from living up to its name, particularly when you end up mixing access with different memory orderings (see section 2 of this paper for more details).

    Plus, sequential consistency generally isn’t much stronger than acquire / release semantics, and it can be slower, depending on the architecture. So it’s pretty reasonable to use acquire/release semantics as the default.

    But this stuff is trickier than we might think.

    For instance, you may have noticed that I declared the item field inside the struct h4x0r_word_ring_item_t to be _Atomic .

    If you remove that qualifier, on some platforms our tests will occasionally fail. Why? How is that possible??

    Sure, we dequeue an entire h4x0r_word_ring_item_t atomically. But, we store the result of that dequeue into a second memory location, that isn’t itself marked to be atomic. In our case, in our word ring dequeue algorithm, that’s going to be the variable called last .

    So, when we return last.item , we might end up returning the value that was stored in that field before the CAS operation.

    Since we also return the associated epoch, we could possibly get an old value there, too. However, since they are only unloaded into the last field together, (atomically), we can be pretty confident that, if the item is available, then the epoch will be too.

    Still, C doesn’t guarantee that; it’s just a matter of having some knowledge of what’s going on under the hood (and experience to back it up).

    But, if you wanted to be really careful, you would want to tag the epoch field as _Atomic too. If you try though, you’ll get an error, because _Atomic doesn’t work with bit slices directly. You’d have to do something else. Some options are:

    1. Use a union, with one of the types in the union being an _Atomic uint64_t
    2. Declare the thing as a uint64_t , and manage the flag bit manually (but you do need the flag to be a bit).
    3. Leave it unsynced, and tell the compiler to sync all relevant variables before pulling the epoch out of the item.

    This third option you can do with a memory fence, putting it before the assignment in h4x0r_word_ring_found . In our associated code, h4x0r_atomic_fence() will do the trick; this is a very trivial wrapper around a C atomic builtin.

    By the way, if we tag item as being _Atomic , but specify relaxed memory ordering when we copy it out, we could absolutely end up with the same problem, because _Atomic is only atomic until you explicitly tell it otherwise.

    Yes, there are a lot of subtleties. Nobody said it would be easy.

    Good luck, you’re going to need it.

    – Lee T. Solo (tho with a ring on it)

    You're overspending because you lack values

    Hacker News
    www.sherryning.com
    2025-12-16 13:20:51
    Comments...
    Original Article

    One morning in January, I woke up and it was like a spell had been broken the way I looked around my room and saw how dull everything was, not because it was lacking but because of how full it was of stuff .

    Stuff I didn’t particularly love. Stuff with no serious meaning to it. Stuff I didn’t care about. Stuff that, if you had secretly tossed, I wouldn’t even realize went missing. Stuff I bought because it was trendy at the time, because my friend had it, because I had seen attractive influencers my age brag about it on Instagram, and it made me think that I could be her.

    So, I did a bit of Marie Kondo-ing and produced a few large bags of clothes and trinkets and stuff for donation. Standing in front of all my stuff, it hit me that all of it used to be money, and all of that used to be time. I was standing in front of the metabolic waste of my existence, materialized. I was looking at the amount of my time, therefore my life, that had been turned into garbage. And the worst part is that I could’ve prevented it.

    A movie scene that has stuck with me for years comes from Spirited Away , where Chihiro finds her parents turned into pigs. It’s comical to describe, but when you put yourself in her shoes, it’s terrifying: it’s every child’s nightmare to lose their parents to a force they can’t control. The panic she feels in that scene speaks to me deeply, the feeling of watching your loved ones do something that you know is wrong but being called “silly” when you try to stop them.

    You’re not you when you’re hungry

    Materialism isn’t inherently evil; it can be gorgeous through the frames of abundance or art. Miranda Priestly’s “stuff” monologue from The Devil Wears Prada, for example, shows how material creates jobs, fuels culture, and shapes history. Miyazaki’s plates of food are dramatically overblown and colorful and delicious , but Chihiro’s parents don’t think about what they consume, only about how much. When she confronts them, her father shrugs: “It’s okay. I have my credit card and some cash.”

    This is the mindset that will make you waste your life away into bags of garbage: the idea that shopping is a material issue, and overconsumption is a budgeting problem, rather than a spiritual problem. It’s easy to be Spirited Away, whisked into another world operated by desires that come from ads and friends and fleeting trends. Your appetite for novelty and your fear of missing out sucks the joy out of you—the more you eat, the hungrier you are. The more you spend, the more vapid you feel . You lack spirit, not another fashion identity . You don’t need another aesthetic, you need stronger values.

    The title Spirited Away in Japanese is Sen to Chihiro no Kamikakushi , and kamikakushi means “hidden by the gods,” a folk belief where people mysteriously vanish into another realm. This film is about magical abduction and losing your identity. Chihiro loses her name and becomes “Sen”: to be spirited away is like being stolen from yourself, forgetting who you are under the influence of forces like greed, fear, anger—and who’s to say that emotions aren’t magical? That desires aren’t demonic possessions of the mind (“demonic” meaning “godlike divisive superfactor” in Greek)? Who’s to say that feeling horny isn’t its own kind of spell? We literally use “mania” and “craze” to describe the way people desire something: Beatlemania, the craze with Labubus, matcha being ‘all the rage ’.

    Lust, for example, is the feeling of wanting something really badly. It doesn’t have to be a carnal desire but it’s about a possessive craving that ends in a feeling of collapse, an appetite that, once appeased, reveals its emptiness:

    Lust is the deceiver. Lust wrenches our lives until nothing matters except the one we think we love, and under that deceptive spell we kill for them, give all for them, and then, when we have what we have wanted, we discover that it is all an illusion and nothing is there. Lust is a voyage to nowhere, to an empty land, but some men just love such voyages and never care about the destination.

    Bernard Cornwell

    Shopping has this effect on me, the voyage is more satisfying than the destination. There is such thing as post-purchase clarity: the moment when you buy something trendy and you suddenly sober up to how much you don’t care about it (let alone like it); you just want to be seen having it.

    Spirited Away is most known for the character with the least lines: a masked ghost who can conjure gold. He has no backstory, we only know that he is banned from entering the bathhouse. Chihiro, out of kindness, lets him in. No-Face is refused service at first, but the staff quickly compromise their values upon seeing his gold. They serenade him, “ Welcome the rich man. He’s hard for you to miss. His butt keeps getting bigger, so there’s plenty to kiss! ” while they fight for the gold nuggets that plop out of his fat hands. Then, he devours the workers in despair when he realizes their kindness is bought, and only Chihiro is genuine.

    Understandable crashout

    The painful part of loneliness is the realization that most people are ass-kissers and friendship is rare. Likewise, people feel the most alienated when they suddenly sober up to the fact that most of their desires are herd-driven, that most of them are no where close to the truth, if they even have a clear enough sense of what that is that matters to them. It’s like waking up from a trance state and realizing, What have I done to myself? I certainly felt this way standing in front of my garbage bags. Loneliness, alienation, addictions and self-defeating loops—these are not material problems, but ‘desire’ problems.

    I’m finally coming to understand what Girard meant by,

    We think we want things, but every desire points to a way of life, a kind of person we long to become. Objects seduce us not with their utility but with their promise of transcendence—status, attention, belonging. That’s why No-Face has no face: he is desire itself, the appetite to become, the emptiness that consumes while wishing it were someone else.

    Money reveals this: In Roman mythology, the temple of Juno Moneta was both sanctuary and mint (it’s where we get the words “money” and “monetary”). To strike a coin was to sanctify it with divine authority, so it circulated as both economic and spiritual power. It still does: money organizes meaning. Fiat currency works because we collectively believe it means something— fiat literally “let it be” in Latin—its meaning assigned by our shared narrative. And because money is tethered to desire, it doesn’t just reflect value; it follows it. It’s the pull of eyes when a sports car glides down a street. It’s Bernard Arnault, CEO of LVMH, saying “when you create desire, profits are a consequence.” Shopping is not independent from the spiritual realm that strips away our names, and it’s a very literal form of kamikakushi.

    When we feel the weight of our limits, we start reaching toward idols to imitate, goals to chase, places to explore, people to meet. What we’re really chasing is a sense of immortality or infinity, something that lives longer than we ever will. We want to be remembered long after we’ve left a conversation, the company, the world.

    Desire is never about the object itself. If it were, once you acquired it, the desire would vanish. Yet, your wardrobe keeps getting stuffier while you still find yourself with nothing to wear. Desire is about what the object seems to promise us: a fuller, richer existence. This is why Marie Kondo’s “spark joy” test is great: it reframes consumption as discernment. It asks whether an object raises your spirit or weighs it down. Left unchecked, your possessions take away your freedom to be who you are. As Fight Club says, “The things you own end up owning you.”

    Every now and then, I feel my value system collapsing under the seduction of Alo’s knitwear sets through their windows. Overall, none of this is about “how to spend less”, it’s about the freedom to just be… you.

    You are not your job, you’re not how much money you have in the bank. You are not the car you drive. You’re not the contents of your wallet. You are not your fucking khakis.

    Chuck Palahniuk ( Fight Club )

    Stronger values make you spend more mindfully because they shift the axis of desire. When you know what you worship—what you actually stand for and who you want to become—everything gets tested against that vision. Values act like a sieve: they filter out the empty cravings that come from comparison and they let through only the things that genuinely serve your spirit. Without values, desires lead you astray by following ads and algorithms and the envy of friends—a state commonly known as “being distracted”.

    The scariest part of Chihiro watching her parents turn into pigs is that they could’ve simply walked away. The unattended food stalls feel like a test of whether one can resist charming distractions. Like the family in Spirited Away , you’re rarely forced to follow one desire over another (until you choose wrongly, and only later realize what you’ve done, if you realize it at all). But if you aim at your highest value—placing no other gods above it, coveting nothing of your neighbor’s—you free yourself from the distractions that split your soul and can refocus your being on becoming who you want to be.

    ~

    P.S. if you enjoyed this, I recommend “ Transcending the Rat Race ” or “ Stop Looking at Each Other ” next.

    Share

    Discussion about this post

    Ready for more?

    The Illusion of Shared Understanding

    Lobsters
    highimpactengineering.substack.com
    2025-12-16 13:17:20
    Comments...
    Original Article

    Have you ever returned from vacation to find the project you entrusted to your team completely off track?

    I have — and that experience taught me one of the most important lessons of my engineering career. In this article I’ll share why teams often believe they’re aligned when they’re not, and how a simple RFC process can prevent months of wasted work.

    This happened years ago when I worked as a lead in a software engineering team.

    I had started working on a big, complex feature that was supposed to be ready in two months. However, my vacation was approaching and I would be away for one month.

    To stay on schedule I asked two engineers from my team to continue the development. I explained what the feature would be doing and how I planned to implement it. We had a couple of sessions where we went through the target architecture in detail, including drawing diagrams on the whiteboard. Developers said that everything was clear and they would be able to finish it when I was away.

    One month later, when I returned from my summer break, the feature was almost ready.

    Or so I thought until I looked at the code.

    I was shocked to find that the feature was addressing only a small special case. Nothing else worked.

    What followed was one of the most dramatic sprints in my career. I worked evenings and weekends, on the plane and on the bus. In the end I missed the deadline anyway, however not by much.

    I felt very upset, but I didn’t have anyone else to blame but myself. I failed to recognize that we only had the illusion of shared understanding. We thought we were in agreement when in fact we had totally different mental models of what needed to be done.

    Similar situations, maybe not that dramatic, happened many times throughout my career, so I started to see the pattern and look for a solution. I needed a way to make mental models visible — not just assumed. That’s when I stumbled on the idea of internal RFCs .

    RFC stands for request for comments. It is a document describing a solution which is shared within a team, organization, or publicly in order to receive feedback.

    I knew RFCs before from open-source projects, where they are widely used to facilitate decision-making within the OSS community. However, I never thought of using RFCs internally in an organization.

    The RFC approach has several advantages over verbal alignment. First of all, it is more precise. The need to write forces the author to clearly structure their thoughts into a coherent logical narrative. While writing, the author has time to examine their proposed solution from different angles and clearly see pros and cons of it.

    Another advantage of the document over verbal explanation is that a well-written RFC leaves little room for misinterpretation. It can include diagrams, examples, or calculations to illustrate and support the idea.

    Finally, we can return and reread the RFC later. Human memory is unreliable; already after a day, details that were crystal clear in one’s mind start to get blurry. When these details are written down, it is easy to review them at any time.

    Despite these advantages, introducing RFCs can still meet resistance. The most common objection is that writing proposals is “a waste of time” compared to writing code.

    A good way to overcome this is to introduce RFCs as a timeboxed experiment.

    Try it for a month, then debrief as a team and decide whether the practice is worth continuing. A short experiment lowers the psychological barrier to change.

    During that first month, it helps if you write the first few RFCs yourself.

    This models the behavior you want to see and keeps the initial bar low. Ask the team to comment — not write — which is a much easier first step for them.

    It also helps to bring one or two formal or informal leaders on board early. If people who have influence participate in RFC discussions, others will follow naturally.

    Cultural changes die when leaders propose them but don’t participate. If even the champions of the idea don’t write or review RFCs, the team will quickly abandon the practice.

    To start RFC adoption you will need a template that your team can start using right away. There are a lot of options on the internet to choose from, or you can use my template, which has been working well for me and my team.

    The template I use includes two main parts: header and body.

    The header has the following information:

    RFC name . It is simply the name of the document in Git or, for example, in Confluence.

    Owner . Author of the RFC. If the team is small and the RFC is not shared outside of the team, this field can be skipped.

    Date . Creation date. Good to have for bookkeeping and later reference.

    Status . One of the following: Work in Progress, In Review, Approved, Obsolete.

    Approvers ’ names and their approval statuses. The status can be one of three: Not Approved, Approved, Declined.

    Below the header is the body of the document. The body consists of two sections.

    Background . Explains the business or technical context behind the change — why the proposal exists and what problem it solves.

    Proposal . Describes the solution. It can include text, diagrams, examples, images, or any other media that helps convey the idea clearly.

    You can find the full template here .

    That is all there is to it. As you can see, the template is very simple. Now let’s discuss when and how to use it.

    To make this more tangible, let’s look at a few concrete work situations where RFCs are useful.

    Imagine a team member raises a technical issue during the daily standup. The problem is clearly too complex to solve in a quick discussion. Instead of trying to resolve it immediately or scheduling yet another meeting, you could ask the team to write their proposals as RFCs and review them asynchronously. This gives everyone time to think, compare solutions, and come back with clearer reasoning.

    Another typical situation is when the team is about to start working on some complex new functionality. Instead of jumping directly into the coding, you could suggest that someone on the team take time to do research and write a detailed RFC describing the technical approach. Writing and reviewing the proposal within the team will help pick the right approach and at the same time ensure that everyone on the team has the same understanding of the work ahead.

    When I look back at the time I left on vacation and handed the feature over to the team, I really wish I had written an RFC. I’m sure the engineers would have executed the work exactly as intended.

    We learned the lesson, and now RFCs are an integral part of our engineering process. They help us create better technical solutions, stay aligned on the architectural vision, and spread knowledge across the team.

    This is the first in a series on building clarity into how teams work. If you found it useful, consider subscribing to High-Impact Engineering for new essays weekly.

    "We're Angry": Brown Univ. Student & Parkland Survivor Zoe Weissman Demands Action on Gun Violence

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:15:25
    The two victims in Saturday’s mass shooting at Brown University have been identified: freshman Mukhammad Aziz Umurzokov and sophomore Ella Cook. We speak to another sophomore, Zoe Weissman, who came to Brown from Parkland, Florida, where she was a student at the middle school adjacent to Marjo...
    Original Article

    This is a rush transcript. Copy may not be in its final form.

    AMY GOODMAN : The two students killed in Saturday’s shooting at Brown University in Providence, Rhode Island, have been identified. They’re 18-year-old freshman Mukhammad Aziz Umurzokov from Virginia, and 19-year-old sophomore Ella Cook from Mountain Brook, Alabama.

    Umurzokov’s family came to the United States from Uzbekistan in 2011. Family and classmates have described him as “gentle,” “kind” and “extroverted.” He wanted to become a neurosurgeon one day, inspired by doctors who treated him as a child. His sister, Rukhsora Umurzokova, told NBC News, quote, “We don’t want him to end up being a number. We want everyone to see his face. We want everyone to know his name,” she said.

    Nineteen-year-old sophomore Ella Cook is described by those close to her as “grounded,” “generous” and “incredibly kind.” She was also a devoted Christian, talented piano player, vice president of the College Republican Club at Brown.

    Both students were planning to travel home for winter break in the coming days.

    As family, friends and the university community continue to mourn their loss, the search for the gunman who opened fire on Saturday, killing both students and injuring nine others, has entered its fourth day. On Monday, authorities released new images of a person of interest, including video from home surveillance cameras. This is FBI Special Agent in Charge Ted Docks.

    TED DOCKS : We are renewing our call for the public’s assistance in seeking any and all information about the shooter. No amount of information is too small or irrelevant. We are also here to announce the FBI is now offering a reward of $50,000 for information that can lead to the identification, the arrest and the conviction of the individual responsible, who we believe to be armed and dangerous.

    AMY GOODMAN : There have been 391 mass shootings this year, according to the Gun Violence Archive, including at least 75 school shootings.

    For at least two students at Brown University, this is not their first school shooting. Yesterday, we spoke to Mia Tretta, a 21-year-old junior who was shot in the stomach as a high school freshman at Saugus High School in Santa Clarita, California, in 2019. Today, we’re joined by Zoe Weissman. Zoe was in middle school in 2018 when a former student opened fire next door at the Marjory Stoneman Douglas High School in Parkland, Florida, killing 17 students and staff. Zoe has left Brown and gone home to Parkland, where we’re speaking to her today.

    Zoe, our deepest condolences on what has happened at Brown, not to mention what happened seven years ago, where you were a middle schooler next door in Parkland, Florida. I’m just wondering if you could start off by talking about how you’re feeling right now, where you were when the gunman opened fire at Brown, and how you responded.

    ZOE WEISSMAN : Thank you for having me.

    So, thankfully, I was in my dorm room when everything happened. I got a call from my friend probably minutes after the shooting occurred, and she asked me if I was in Barus and Holley, which is the building where the shooting happened. And just the way she said it, I knew that it was a school shooting. That’s where my brain went. And I told her that if that’s what happened, she needed to tell me. And she admitted that people had ran into where she was and told her that there was a shooter. And then, probably a minute or two after, I received the alerts on my phone from the school about an active shooter.

    And so, then I was locked down in my dorm until 6 a.m. the next day, when they had a person of interest in custody, who ended up being released. And thankfully, now I’m back here in Parkland. But I think that because I’ve already processed all the grief and the sadness before — I’ve kind of, you know, been grappling with that for the past seven years — my, like, most predominant emotion right now is, honestly, anger.

    JUAN GONZÁLEZ: And, Zoe, according to the Gun Violence Archive, there have been 391 mass shootings this year, including 75 at school shootings. Your message to lawmakers who — most of whom have done little to next to nothing since the Parkland shooting?

    ZOE WEISSMAN : Yeah, I just really want to emphasize that if politicians actually want to show that they care about their constituents and they want to be reelected, they need to show a concerted effort to pass gun violence prevention legislation on a federal level. And if they don’t do so, we’ll make sure to vote them out, because we are the only country where this happens, and we are — just so happen to be the only country that has more guns than people.

    JUAN GONZÁLEZ: And, of course, in these last few days, we’ve heard the news of the mass shooting in Sydney. Australia. Your thoughts about how Australia has responded in the past and is responding now to this gun violence?

    ZOE WEISSMAN : Yeah, I think we saw after the Port Arthur massacre, you know, years ago, that Australia made a very concerted effort to reduce gun violence. And although the tragedy on Bondi Beach happened, we’ve seen, I believe, only 35 mass shootings in Australia since those changes, compared to the U.S., where we’ve had thousands since that date. And we’re even seeing in the immediate aftermath of the shooting that politicians in the state and national premiers are actually announcing that they will be passing reforms to the gun legislation and rules and regulations within the country. And I think that that’s a great model for us to kind of look at. Unfortunately, our politicians care more about corporate funding than the lives of their constituents. But I think that Australia is definitely making good efforts after this shooting.

    AMY GOODMAN : Zoe, I wanted to go to the prime minister of Australia, where, of course, we all know what happened this weekend in Sydney. A father and son killed at least 15 people in a mass shooting at a Hanukkah celebration on Bondi Beach Sunday, 42 people injured, 22 remain in the hospital. Victims included a 10-year-old girl, a rabbi and a Holocaust survivor who died while shielding his wife from bullets. A different reaction in Australia, the deadliest since the 1996 Port Arthur massacre, when a gunman opened fire in the Tasmanian tourist village of Port Arthur, killing 35, injuring 23 more. After that shooting, Australia, which had extremely liberal gun laws, a country of Crocodile Dundees, within days, outlawed automatic and semiautomatic rifles. On Monday, Australian federal and state government leaders agreed to immediately strengthen already-tough national gun control laws that came out of Port Arthur. Prime Minister Albanese vowed to revisit gun laws and said Bondi Beach shooting was different than Port Arthur in some ways.

    PRIME MINISTER ANTHONY ALBANESE : We need to examine the gun laws that were carried in the wake of the 1996 Port Arthur massacre. This is different from Port Arthur, though. Port Arthur was someone engaged in random violence against people. This is targeted. This is ideologically driven, and therefore is a different form of hatred and atrocity.

    AMY GOODMAN : So, Zoe, I wanted to go back to you saying at the beginning of this conversation, you’re angry. What that reminded me of was what happened seven years ago in Parkland, where you live, where you grew up, where you were a middle schooler when the Parkland massacre happened, is the students there did something very different from most students all over the country who have survived school shootings. They immediately organized, saying they were angry, took on the Florida Legislature. Ultimately, the March for Our Lives took place in Washington, D.C. If you can talk about what you mean by being angry, and if you’re concerned with all the students going home — certainly understandable; they’ve just canceled Brown right now — right? — the tests, and all kids have gone home — that that level of all the students working together to fight for gun control will be dissipated, and what you’re planning to do in Parkland right now?

    ZOE WEISSMAN : Yeah, so, March for Our Lives was an incredibly unique movement in the sense that it was student-led, right? I mean, we had seen movements in the past after school shootings, and those were mostly led by, you know, really compassionate adults. But this was one of the first-ever movements where it was actually students who were directly impacted leading everything.

    I also do think it’s a little different at Brown, in the sense that our Legislature is already pretty friendly towards gun violence prevention measures. This past legislative session, they actually passed a sort of watered-down assault weapons ban that bans automatic weapons and assault rifles.

    But I am actually not as worried about the dissipation of the student body as a lot of people are. I know my peers and my friends really well. We are a very politically active group of students. We’ve been very active in response to the federal compact proposed to Brown. We were a big part of making sure that our school rejected that. We’ve been very vocal in regards to the genocide occurring in Gaza, and as well as advocating for local causes. And I think that you’re really going to see a large concerted effort, once we get back on campus in mid to late January, from students. I think that I can speak for all of us that we’re angry and we’re ready to do something, not just on the state level, but on a federal one, as well.

    AMY GOODMAN : Interesting, Zoe, that you raised Gaza, because a fellow student at Brown, Hisham Awartani, whose mother we interviewed, was shot, himself a victim of gun violence, with two other Palestinian students when they went to Thanksgiving break in Vermont, shot by a guy sitting on his — what, standing on his porch, as they were wearing keffiyehs. How has what happened to Hisham, who came back to Brown in a wheelchair — he was paralyzed — affected the whole campus, another victim of gun violence?

    ZOE WEISSMAN : Yeah, I think that, unfortunately, you know, the tragedy in Vermont just confirms that guns are the problem, right? There’s no one common denominator between all of these acts of violence, except guns and extremist ideology, whether that be antisemitism in Bondi Beach — you know, I myself have experienced a wave of antisemitism in response to all of the advocacy I’ve been doing — or whether it be Islamophobia and anti-Palestinian sentiments. I think that regardless of the ideology, one thing remains true, and that is that people with extremist ideas and people who are willing to kill others are able to access guns in this country.

    And I do think that ever since the 2023 shootings in Vermont, Brown students have been very active in regards to gun violence. I know Mia and myself have been involved in, you know, actions on campus in the past. But I think this is going to reinvigorate our fight to create a world where students like myself don’t have to worry about going through not one, but two school shootings.

    AMY GOODMAN : Thank you for being with us. Thank you for your bravery and speaking out right now. Zoe Weissman, Brown University sophomore, attended Westglades Middle School, adjacent to Marjory Stoneman Douglas High School in Parkland, Florida, when a former student opened fire in 2018 and killed 17 people, mainly students, now just back from Brown, where two students were killed in a mass shooting. Another eight are still in the hospital. A number of them are critically injured. The FBI has offered $50,000 for identifying the gunman, and new video has been put out. We want to thank you again for being with us, speaking to us from Parkland, Florida.

    Up next, “’They’re trying to get rich off it’: US contractors vie to rebuild Gaza, with 'Alligator Alcatraz' team in the lead.” We’ll speak with investigative reporter Aram Roston. Stay with us.

    [break]

    AMY GOODMAN : This is Democracy Now! , democracynow.org. I’m Amy Goodman, with Juan González.

    We turn now to Gaza, where Israeli forces continue to block the entry of mobile homes and any shelter or construction materials that Israel says could be used to rebuild housing — or, that the Palestinians say could be used to rebuild, hundreds of thousands of Palestinians displaced by Israel’s relentless war. This marks the third consecutive winter that thousands of families in Gaza are forced to shelter in makeshift tents and sleep under fragile tarps, while others are living in the structures of buildings severely damaged by Israeli bombing over more than two years. Winter storms have compounded Gaza’s worsening humanitarian catastrophe caused by Israel’s siege, with flash floods inundating tens of thousands of tents and collapsing damaged buildings where Palestinians attempted to shield themselves from the torrential rain. Al Jazeera reports at least a dozen Palestinians, including a baby, have been killed in collapsing buildings or have died from cold exposure.

    MAHMOUD BASAL : [translated] A citizen’s alternative to leaving this building is the risk of a collapsed tent. A tent, without a doubt, cannot be safe for citizens. It cannot protect them from the cold, the winter, floods, stray dogs, rodents or diseases. Therefore, all the alternatives currently available to citizens in Gaza are dangerous and difficult.

    AMY GOODMAN : This all comes as concerns mount for the future of Gaza’s rebuilding efforts, which the United Nations estimates will cost approximately $70 billion. For-profit construction, transportation, demolition and other companies with ties to the Trump administration and Republican allies are lining up to profit, vying for contracts that will likely be issued after President Trump’s so-called Board of Peace begins operations in Gaza.

    This is at the heart of a new investigation published by The Guardian that’s titled “’They’re trying to get rich off it’: US contractors vie to rebuild Gaza, with 'Alligator Alcatraz' team in the lead.”

    In a moment, we’ll be joined by the Emmy Award-winning investigative journalist Aram Roston, senior political enterprise reporter for The Guardian US , but we’re going to go to break first. Stay with us.

    The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

    Sega Channel: VGHF Recovers over 100 Sega Channel ROMs (and More)

    Hacker News
    gamehistory.org
    2025-12-16 13:07:14
    Comments...
    Original Article

    Sega broke ground in the late 90s with one of the first digital game distribution systems for consoles. Sega Channel offered access to a rotating library of Sega Genesis titles, along with game tips, demos, and even a few exclusive games that never came out in the United States in any other format. In an era of dial-up internet, Sega Channel delivered game data over television cable — a novel approach that gave the service its name.

    In the years since, Sega Channel has been shrouded in a bit of mystery. The service was discontinued in 1998, and the lack of retrievable game data and documentation around Sega Channel has led to decades of speculation about it. We’ve mostly been left with magazine articles and second-hand accounts. Once in a while, one or two Sega Channel ROMs will show up online. How do you preserve a service like Sega Channel?

    For the last two years, we’ve been working on a large-scale project to preserve the history of Sega Channel. Today, we unveiled our findings in a new YouTube video.

    We’ll cut to the chase: In collaboration with multiple parties, we have recovered over 100 new Sega Channel ROMs , including system data, exclusive games, and even prototypes that were never published. We’ve also digitized internal paperwork and correspondence that reveals how Sega Channel operated, how it was marketed, and what would’ve come next for the service.


    Michael Shorrock was surprisingly easygoing about finding a picture of himself in a museum exhibit.

    This project kicked off in 2024, when we met former Sega Channel vice president of programming Michael Shorrock at the Game Developers Expo. Our booth that year highlighted interesting games from outside the traditional game industry, including Where in North Dakota is Carmen Sandiego? , which our director Frank Cifaldi recovered back in 2016.

    By complete coincidence, one of the items we put out was a promotional brochure for Broderbund Software… featuring Michael Shorrock on the cover! We got talking with Michael about our work, and we realized we both wanted to preserve and celebrate the history of Sega Channel.

    At the same time this was happening, we were contacted by a community member named Ray (going by the pseudonym Sega Channel Guy). He had been contacting former Sega Channel staff to see if they still had any old swag or had saved things from the company. In the process, he came into possession of a collection of tape backups containing an unquantifiable amount of internal data from Sega Channel… including a significant number of game and system ROMs.

    We realized we could put these two threads together! With Michael’s own collection and Ray’s data backups, we could tell a cohesive, wide-ranging story about what Sega Channel was and that was actually distributed through this service.

    There are two end products from this process. The first is the Michael Shorrock collection , a new collection in our digital library. You can view the correspondence, notes, and presentations from Michael Shorrock’s personal collection, which shed light on the formation of Sega Channel and their audience. From these papers, you can also learn about Express Games: an unannounced successor that would have brought Sega’s cable data delivery service to computers and replaced Sega Channel entirely.


    The other output here is the collection of Sega Channel ROM data. We’ve donated the data from the 144* new ROMs we recovered to the team at Gaming Alexandria , which will be sharing access to the files.

    * Our video states that we recovered 142 unique ROMs. However, after uploading the video, we realized we miscounted! There are two additional Sega Channel variant ROM in this collection. The actual total is 144. This does not include the two outliers mentioned in the video, which were previously recovered by users on Sonic Retro in November 2024 but went mostly unreported.

    This collection includes nearly 100 unique system ROMs, covering almost every version of the system that was distributed to consumers from 1994 to mid-1997. This batch also includes system ROM prototypes and some truly unusual experiments, like a Sega Genesis web browser that would’ve delivered compressed, static websites over television cable.

    Of great interest to fans, this collection of ROMs also has dozens of previously undumped game variants and Sega Channel exclusives. This includes Garfield: Caught in the Act – The Lost Levels and The Flintstones , two games that were previously believed to be permanently lost and unrecoverable. These are both interesting from a development standpoint; both games appear to have their roots as abandoned projects that were repurposed as Sega Channel-exclusive content.

    Also included are the previously unpreserved limited editions of Sega Genesis games. These versions have been cut down to fit within Sega Channel’s filesize limit, sometimes omitting content or splitting the game into multiple parts. We’re not sure anyone is especially eager to play a version of Super Street Fighter II missing half the characters, but we’re glad to have it documented.

    With a few exceptions, this recovery project has accounted for almost all outstanding Sega Channel games . We believe this also means there are now digital backup copies of every unique Sega Genesis game released in the United States .


    This has been a years-long project that wouldn’t have been possible without support from the broader gaming community. Besides Michael Shorrock and Ray, we want to give special thanks to:

    • Sega Retro, The Cutting Room Floor, and Hidden Palace for documenting everything we’ve known about Sega Channel up to this point.
    • RisingFromRuins and Nathan Misner ( infochunk ) for putting all the pieces together to crack the Sega Channel data formats.
    • Dustin Hubbard (Hubz) from Gaming Alexandria for working with us to share this ROM data.
    • Rob Curl from the Museum of Art and Digital Entertainment, who flagged us down at GDC to let us know that Michael Shorrock had seen a picture of himself at our booth and brought him over to say hello.

    We also want to give a special thanks to Chuck Guzis, a long-time expert on data tapes, who digitized Ray’s Sega Channel backups for us in 2024. Chuck’s business Sydex was, for a long time, the go-to vendor for working with data tapes, and we’ve used his services in the past.

    Shortly before launching this project, we learned that Chuck passed away over the summer. His death leaves a hole in our community and our collective expertise. We know that the gaming community (and specifically the Sega community) will be excited by all this new documentation and data; we hope that their excitement is a testament to what Chuck’s work meant to the digital preservation community.


    Complete list of recovered titles

    This is a list of all Sega Channel-specific game data recovered from this project and shared with Gaming Alexandria. This does not include the 97 unique pieces of menu data ROMs and system software that were also recovered.

    Game list

    Unique Sega Channel exclusive games:

    • The Berenstain Bears’ A School Day
    • BreakThru
    • The Flintstones
    • Garfield: Caught in the Act – The Lost Levels
    • Iron Hammer
    • Waterworld

    Sega Channel variants:

    • The Adventures of Batman and Robin, Test Drive version
    • Comix Zone, Test Drive version (1)
    • Comix Zone, Test Drive version (2)
    • Earthworm Jim, Test Drive version
    • Earthworm Jim VideoHints (1)
    • Earthworm Jim VideoHints (2)
    • The Great Earthworm Jim Race
    • The Lost World: Jurassic Park, Part A
    • The Lost World: Jurassic Park, Part B
    • The Lost World: Jurassic Park, Test Drive version
    • NCAA Final Four Basketball: Special Edition (1)
    • NCAA Final Four Basketball: Special Edition (2)
    • Mortal Kombat 3, Part A
    • Mortal Kombat 3, Part B
    • Scholastic’s The Magic School Bus: Space Exploration Game, Test Drive version
    • Sonic 3D Blast, Part A
    • Sonic 3D Blast, Part B
    • Super Street Fighter II: Limited Edition
    • Triple Play Baseball 96: Special Edition
    • Virtua Fighter 2, Part A
    • Virtua Fighter 2, Part B
    • World Series Baseball ’96: Limited Edition*
    • X-Men 2: Clone Wars, Test Drive version

    Prototypes received by Sega Channel:

    • Al Unser Jr.’s Road to the Top
    • Dan Marino Football
    • Light Crusader
    • Nick Faldo’s Championship Golf
    • Popeye in High Seas High-Jinks
    • Shadows of the Wind
    • WildSnake
    • Wrath of the Demon
    • Yogi Bear [Yogi Bear’s Cartoon Capers]

    Data differences:

    • Body Count (US revision)
    • Maui Mallard in Cold Shadow
    • Primal Rage
    • Pulseman
    • Richard Scarry’s Busytown*
    • Shining Force II

    Header differences only:

    • Battle Frenzy (US header)
    • Power Drive (US header)
    • QuackShot
    • Super Hang-On
    • Wacky Worlds Creativity Studio
    • X-Men 2: Clone Wars

    * These games were previously found on a CD obtained by a user on the Sonic Retro forums in November 2024 . However, these ROMs were overshadowed by the recovery of the Sega Channel exclusive games The Chessmaster and Klondike from the same CD. Although our copies of these ROMs are not unique, we included them on this list to make sure their existence doesn’t get lost.

    A footnote for hardcore Sega fans

    We believe this recovery project accounts for all unique Sega Channel exclusive games. But the most hardcore fans might be wondering: What about Ozone Kid ? In a feature article on Sega Channel from the June 1995 issue of Electronic Gaming Monthly (p.29), Ozone Kid was identified as the first Sega Channel exclusive.

    We can confirm that this game was never actually distributed through Sega Channel. According to data recovered by Ray, The Environmental Detective (as it was titled prior to cancellation) was slated for release alongside the Sega Channel test markets, but it was pulled from their programming plans in July 1994.

    Reading the between the lines in Sega Channel’s internal project tracking, the game appears to have suffered from a variety of problems over several months. When the game was finally shelved, Sega issued a “partial test report based on items found at the time code was pulled,” suggesting there were still major issues when it was removed from their plans.

    Water levels across the Great Lakes are falling – just as US data centers move in

    Guardian
    www.theguardian.com
    2025-12-16 13:00:05
    Region struggling with drought now threatened by energy-hungry facilities – but some residents are fighting back The sign outside Tom Hermes’s farmyard in Perkins Township in Ohio, a short drive south of the shores of Lake Erie, proudly claims that his family have farmed the land here since 1900. To...
    Original Article

    T he sign outside Tom Hermes’s farmyard in Perkins Township in Ohio , a short drive south of the shores of Lake Erie, proudly claims that his family have farmed the land here since 1900. Today, he raises 130 head of cattle and grows corn, wheat, grass and soybeans on 1,200 acres of land.

    For his family, his animals and wider business, water is life.

    So when, in May 2024, the Texas-based Aligned Data Centers broke ground on its NEO-01, four-building, 200,000 sq ft data center on a brownfield site that abuts farmland that Hermes rents, he was concerned.

    “We have city water here. That’s going to reduce the pressure if they are sucking all the water,” he says of the data center.

    “They’re not good, I know that.”

    Two years ago, the company said it would invest about $202m on a “hyperscale” data center that would employ 18 people and dozens more in the construction process. Although the company claims it uses a closed-loop, air-cooled system for cooling its computers that can reduce the need for water, artificial intelligence, machine-learning and other high power-demand processes do rely on water as a cooling agent.

    All the while, a 10-minute drive north, the shoreline of Lake Erie hasn’t been this low in years.

    Water levels across all five Great Lakes have begun to drop in recent months as part of a long-term fall. Since 2019, the Great Lakes have seen water-level decreases of two to four feet. While experts say this is a natural decrease given the record highs the lakes have experienced since 2020, it’s happening at a time when a huge new consumer of water has appeared on the horizon: data centers.

    The source of the largest single deposit of freshwater on the planet, the Great Lakes, in particular Lake Erie, are already struggling with the fallout of drought and warmer water temperatures that, at this time of year, fuel major lake-effect snowstorms, and greater than normal levels of evaporation due to the absence of ice cover.

    With major cities such as Chicago, Toronto, Detroit and Pittsburgh all within a few hundred miles of each other, small, under-resourced communities around the Great Lakes have become hugely attractive for data-center companies.

    In Mount Pleasant, Wisconsin, Microsoft is building what it calls the “world’s most-powerful AI data center” that is set to open early next year and expected to use up to 8.4bn gallons of municipal water from the city of Racine every year. Racine gets its water from Lake Michigan . Similar stories are playing out in Hobart, Indiana, where AWS is planning to build a data center two miles from Lake Michigan’s shoreline, and in Port Washington, Wisconsin.

    In Benton Harbor, Michigan, locals are concerned that a proposed $3bn data center would contribute to environmental pollution and traffic.

    Forty miles west of Aligned’s under-construction data center in Ohio, in Woodville Township, hundreds of people showed up to a public meeting last October to voice concern about another proposed data center project in their rural community.

    “The Great Lakes region, especially in states such as Illinois and Ohio, [is] among the most data-center dense states in the region. In addition to the high volumes of water used on site for cooling, our recent research found that even more water may be consumed to generate electricity to power data centers’ energy needs,” says Kirsten James, senior program director for water at Ceres, a nonprofit headquartered in Boston.

    “These impacts can conflict with communities’ water-resource planning efforts.”

    The Great Lakes Compact, a 2005 accord signed by the governors of eight US states and two Canadian provinces, means that Great Lakes Water must only be used within the regional basin.

    Research by Purdue University found that data centers on average consume about 300,000 gallons of water a day. Water used by data centers is warmed significantly and for those that do not use a closed loop system, that heated effluent water, just 20% of the initial amount, is often discharged back into local wastewater systems or the environment, with potentially serious consequences for flora, fauna and human consumers. Even closed-loop systems that reuse the same water repeatedly need millions of gallons of water.

    While many new data centers are drawing water from local municipalities that, in turn, get their water from groundwater, much of that supply comes from the Great Lakes watersheds.

    Some communities are fighting back. Last month, residents of Fife Lake, Michigan, were overjoyed after hearing a plan for a data center in their town of 471 residents would be scrapped due to local opposition.

    Similar stories of successful opposition have played out in Indiana and elsewhere.

    But the data centers are fighting back.

    Private firms representing data center companies have often successfully sued community authorities, accusing them of illegally excluding certain types of developments, making small towns powerless in the battle to keep out giant water-guzzling corporations.

    In Michigan’s Saline Township, a community of about 400 people outside Ann Arbor, OpenAI and Oracle used a representative company to successfully sue the local authority to overcome opposition and build a massive facility that would use 1.4 gigawatts of electricity – roughly the equivalent of powering 1.4 million homes.

    The Detroit Free Press editorial board assailed the move, calling it a “a fait accompli, hammered into this tiny Washtenaw county community over the objections of residents, the elected board that represents them, and Michigan’s attorney general, absent expert or outside testimony save a cursory public hearing held over Microsoft Teams”.

    Data companies and their backers, however, say their presence is a net gain for Great Lakes communities by providing jobs and investment over the course of years.

    Aligned has paid hundreds of thousands of dollars to Perkins Township, the local school system and a career center. In return, it gets a 15-year tax exemption from local authorities. A representative declined to respond to questions from the Guardian asking how much water it intends to use at the data center and from where it originates.

    Local municipalities that support these facilities claim that the data centers will increase tax revenue and help rebuild ageing infrastructure such as water delivery systems that, in some places, are in significant need of upgrading. Calls, emails and messages left with Erie county commissioners asking if local authorities plan to supply the Perkins Township data center with water were not responded to.

    Some Perkins Township residents say a number of local companies have been hired during the construction phase, bringing work to the area.

    But many argue those investments are not worth the long-term price the community may pay.

    Amanda Voegle, who works at a heating business now directly facing the data center, is concerned about water and many other issues.

    “A couple of years ago, there was a water pollution issue at the site. I’m very concerned. Is this [water] going back into the lake?”

    Two years ago, the construction site upon which the data center is being built was found to be the source of contamination of a river that flows into Lake Erie, with the remediation company responsible cited by the Ohio EPA for unauthorized discharges into state waters.

    “I don’t understand why they built it so close to the street, because it’s an eyesore,” says Voegle.

    She says there have been other unusual incidents at her workplace recently, including power surges.

    “I don’t know if it’s related [to the data center]. It’s probably almost weekly that we lose power and have to fully reboot everything. There was a couple of things we actually had to replace because [the power surge] fried it.”

    Headlines for December 16, 2025

    Democracy Now!
    www.democracynow.org
    2025-12-16 13:00:00
    Pentagon Says It Blew Up Three More Alleged Drug Boats as Trump Declares Fentanyl a WMD, Palestinians in Gaza Face Flooded Tents and Collapsed Buildings from Winter Storm, Australia’s Prime Minister Says Bondi Beach Gunmen Were Inspired by ISIS, Social Media Sites Amplify False Accusations Aga...
    Original Article

    Headlines December 16, 2025

    Watch Headlines

    Pentagon Says It Blew Up Three More Alleged Drug Boats as Trump Declares Fentanyl a WMD

    Dec 16, 2025

    The Pentagon says it has blown up three more boats in the eastern Pacific, killing eight people. Black-and-white video posted to social media Monday by U.S. Southern Command shows three vessels erupting in flames. The Pentagon claimed, without evidence, the boats were carrying drugs in international waters. The latest strikes bring the Pentagon’s announced death toll to 95 since early September. The ACLU and other rights groups have condemned the strikes as “murder” and state-sanctioned killings of civilians who were denied due process. The attacks came as President Trump signed an executive order Monday declaring fentanyl to be a “weapon of mass destruction.”

    President Donald Trump : “No bomb does what this is doing. Two hundred to three hundred thousand people die every year, that we know of. So we’re formally classifying fentanyl as a weapon of mass destruction.”
    Virtually no illicit fentanyl comes to the United States from either Colombia or Venezuela. Last year, the CDC reported about 48,000 deaths from synthetic opioids — not the 200,000 to 300,000 deaths claimed by Trump.

    Meanwhile, Venezuela’s government has accused Trinidad and Tobago of participating in piracy, after it aided the U.S. government’s seizure of a Venezuelan oil tanker last week. Trinidad and Tobago officials said Monday they would grant U.S. forces access to the Caribbean nation’s airports in the coming weeks, as the Pentagon continues to build up forces ahead of a possible attack on Venezuela.

    On Capitol Hill, members of the Congressional Progressive Caucus are urging fellow House lawmakers to back resolutions seeking to prevent President Trump from launching an unauthorized war on Venezuela.

    Palestinians in Gaza Face Flooded Tents and Collapsed Buildings from Winter Storm

    Dec 16, 2025

    In Gaza, another winter storm has worsened the humanitarian catastrophe faced by tens of thousands of displaced Palestinians who’ve been forced to shelter in tents for a third consecutive winter. Civil Defense crews are struggling to reach people trapped under the rubble of homes that had been damaged by Israeli attacks before this week’s storms caused them to collapse. With no proper housing, many Palestinians have been forced to choose between living in unsafe homes or makeshift tents.

    Mahmoud Basal : “A citizen’s alternative to leaving this building is the risk of a collapsed tent. A tent, without a doubt, cannot be safe for citizens. It cannot protect them from the cold, the winter, floods, stray dogs, rodents or diseases. Therefore, all the alternatives currently available to citizens in Gaza are dangerous and difficult.”

    The U.N.’s migration agency reports nearly 800,000 displaced Palestinians are at heightened risk of dangerous flooding in low-lying, rubble-filled areas of Gaza. At least 14 people lost their lives in a winter storm last week. Israel has blocked humanitarian aid shipments of tents bound for Gaza, claiming the aluminum poles used to erect them are a “dual use” item that could be repurposed for military activities.

    Meanwhile, Israel continues to violate the October 10 ceasefire agreement. Witnesses say Israeli military vehicles opened fire today on the northern parts of the Bureij refugee camp in central Gaza, while other areas inside the military-controlled “yellow line” came under airstrikes and artillery fire. This comes as new satellite images show Israel continues to demolish buildings in areas it’s occupied since the ceasefire. Later in the broadcast, we’ll speak with the award-winning investigative journalist Aram Roston about his new report in The Guardian titled “’They’re trying to get rich off it’: US contractors vie to rebuild Gaza, with 'Alligator Alcatraz' team in the lead.”

    Australia’s Prime Minister Says Bondi Beach Gunmen Were Inspired by ISIS

    Dec 16, 2025

    In Sydney, Australia, thousands of people have gathered near the site of Sunday’s mass shooting in Bondi Beach to mourn the 15 people killed in an attack on the Jewish community on the first night of Hanukkah. At least 22 people remain hospitalized. The Sydney Opera House was illuminated with a Hanukkah menorah as memorial events honoring the victims were held across Australia.

    On Tuesday, Australian Prime Minister Anthony Albanese said the father and son duo behind the massacre had traveled to the Philippines before the assault and were inspired by the Islamic State movement.

    Prime Minister Anthony Albanese : “It would appear that there is evidence that this was inspired by a terrorist organization, by ISIS . Now, some of the evidence which is being procured, including the presence of Islamic State flags in the vehicle that has been seized, are a part of that radical perversion of Islam, is absolutely a problem.”

    Manhunt for Brown University Gunman Enters Fourth Day

    Dec 16, 2025

    In Rhode Island, the manhunt for the gunman who killed two students and injured nine others at Brown University on Saturday has entered its fourth day. The two students who were killed have been identified as 18-year-old Mukhammad Aziz Umurzokov and 19-year-old Ella Cook. The FBI is offering a $50,000 reward for information leading to the arrest of a new person of interest.

    This comes as FBI Director Kash Patel is facing criticism for posting on social media to promote the agency’s work in tracking down a person of interest in the shooting prematurely, only to release the individual from custody hours later. In the wake of right-wing activist Charlie Kirk’s assassination, Patel also claimed on social media that a shooter was in custody, even though the shooter had not yet been apprehended. We’ll speak with one of the survivors, Brown University student Zoe Weissman, after headlines.

    Trump Loyalist Announces First Indictment of Left-Wing Activists After Trump’s Antifa Order

    Dec 16, 2025

    Federal prosecutors in California have indicted four members of a left-wing group named the Turtle Island Liberation Front on charges they plotted to bomb multiple targets in Orange County and Los Angeles beginning on New Year’s Eve.
    bq. Bill Essayli : “This investigation was initiated in part due to the September 2025 executive order signed by President Trump to root out left-wing domestic terror organizations in our country, such as antifa and other radical groups like the Turtle Island Liberation Front.”

    The indictment relies heavily on a paid FBI confidential informant. It was announced by Bill Essayli, a former California Republican lawmaker whom the Trump administration named interim U.S. attorney for the Central District of California in April. He’s now serving as first assistant U.S. attorney, after a federal judge ruled in October he’d been serving unlawfully as the district’s top prosecutor since July, since he was never confirmed by the Senate.

    The indictment comes just days after FBI National Security Branch Operations Director Michael Glasheen appeared before the House Committee on Homeland Security, where he struggled to back up his claims that antifa was the “primary concern” and the “most immediate violent threat” facing the United States. This is Glasheen being questioned by Democratic Congressmember Bennie Thompson.

    Michael Glasheen : “We share the same view. When you look at the data right now, you look at the domestic terrorist threat that we’re facing right now, what I see from my position is that’s the most immediate violent threat that we’re facing on the domestic side.”

    Rep. Bennie Thompson : “So, where is antifa headquartered?”

    Michael Glasheen : “What we’re doing right now with the organization” —

    Rep. Bennie Thompson : “No, uh-uh. Where, in the United States, does
    antifa exist, if it’s a terrorist organization and you’ve identified it as number one?”

    Michael Glasheen : “We are building out the infrastructure right now.”

    Rep. Bennie Thompson : “So, what does that mean?”

    Veterans Administration to Cut Another 35,000 Healthcare Jobs

    Dec 16, 2025

    The Department of Veterans Affairs has announced plans to eliminate up to 35,000 healthcare jobs this month. Many of the targeted positions are currently unfilled and include doctors, nurses and support staff. The agency has already lost 30,000 employees this year. The advocacy organization VoteVets said, “it is abundantly clear that Republicans and the Trump administration want to strangle the VA until it all gets privatized.”

    Former CDC Leaders Who Criticized RFK Jr. Will Lead New California Public Health Agency

    Dec 16, 2025

    In public health news, measles cases are continuing to rise across the United States. According to the CDC , the number of confirmed measles cases has topped 1,900 this year — higher than any year since the U.S. declared the disease eliminated in 2000. In South Carolina, there were 129 confirmed cases this month; 250 people are quarantining. This comes as Health Secretary Robert F. Kennedy Jr. continues to express skepticism about vaccinating children. Meanwhile, in California, Governor Gavin Newsom has appointed two prominent scientists, Dr. Susan Monarez and Dr. Debra Houry, who left the CDC earlier this year, to help lead the state’s new Public Health Network Innovation Exchange. Dr. Monarez, the former director of the CDC , was fired less than a month into her tenure after she clashed with the administration over vaccine policy. The CDC’s former chief medical officer, Dr. Houry, resigned shortly after Monarez was fired.

    Trump Sues BBC for $10 Billion over Edits to Jan. 6 Speech

    Dec 16, 2025

    President Trump is suing the BBC for $10 billion over edits to a speech he gave to his supporters on January 6, 2021, just before they stormed the Capitol. Tim Davie, the BBC’s director general, and Deborah Turness, the head of BBC News, resigned over the edit last month. It follows similar suits by Trump against big media firms. Earlier this year, ABC agreed to pay Trump $15 million to settle a defamation lawsuit he brought over comments made by anchor George Stephanopoulos. Trump also reached a $16 million settlement with Paramount, the parent company of CBS News, after he claimed an interview with Vice President Kamala Harris was selectively edited.

    Trump Blames “Trump Derangement Syndrome” for Killing of Rob and Michele Reiner

    Dec 16, 2025

    In California, Rob Reiner’s son Nick has been arrested on suspicion of murder in the stabbing deaths of his parents at their home in Los Angeles. On Monday, President Trump provoked widespread outrage after he blamed the killings on Reiner’s anti-Trump views. On social media, Trump wrote, “Rob Reiner, a tortured and struggling, but once very talented movie director and comedy star, has passed away, together with his wife, Michele, reportedly due to the anger he caused others through his massive, unyielding, and incurable affliction with a mind crippling disease known as TRUMP DERANGEMENT SYNDROME .”

    Those comments drew backlash even from some members of Trump’s own party. Kentucky Republican Congressmember Thomas Massie wrote, “Regardless of how you felt about Rob Reiner, this is inappropriate and disrespectful discourse about a man who was just brutally murdered. I guess my elected GOP colleagues, the VP, and White House staff will just ignore it because they’re afraid? I challenge anyone to defend it.” At the White House, President Trump later doubled down on his attacks on Rob Reiner.

    Kristen Holmes : “Mr. President, a number of Republicans have denounced your statement on Truth Social after the murder of Rob Reiner. Do you stand by that post?”

    President Donald Trump : “Well, I wasn’t a fan of his at all. He was a deranged person, as far as Trump is concerned. … I was not a fan of Rob Reiner at all, in any way, shape or form. I thought he was very bad for our country.”

    The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

    Meet exe.dev, modern VMs

    Lobsters
    blog.exe.dev
    2025-12-16 12:59:29
    Comments...
    Original Article

    Today we are going public with the developer preview of exe.dev , a new VM hosting service. We will keep the service open for new users as long as our capacity lasts. Try it out with:

    ssh exe.dev
    

    As we built sketch.dev, we found ourselves needing more machines than ever before. Whether using an LLM as a typing assistant or a sophisticated debugger, we found we had a lot more little software we wanted to run. Running it turned out to be the hassle. So we built a platform for making it easy.

    Notable features of exe.dev:

    1. All the VMs that fit : your subscription gets you so much CPU and RAM. How your VMs share your resources is up to you. Want 20 idle VMs that answer an HTTP request every other day? Go for it.
    2. Real VMs, sub-second start you get a full Ubuntu machine (or any other container image you decide to bring). Install Docker Compose if you like.
    3. Persistent disks with some really interesting work coming soon.
    4. SSH-based API get started by typing ssh exe.dev and creating a VM.
    5. Private by default, share with discord-style links exe.dev takes care of TLS and auth for you. By default only you can reach your HTTP services, and you have easy mechanims to share them with friends and colleagues.
    6. Agent-friendly sandbox start a VM, ssh in, and use any agent you like. Treat the VM as a sandbox for maximum agent effectiveness.

    Over the next few weeks we are going to be writing about all of the technology behind this. Each element is a significant project, but we waited until we had the whole package ready to try because we believe together they are more than the sum of their parts. Persistent, private, fast-starting VMs with no marginal cost per-VM is what cloud computing should like, and it only becomes clear when you put all the pieces together.

    You can explore the service right now with ssh exe.dev .

    Microsoft to block Exchange Online access for outdated mobile devices

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 12:53:25
    Microsoft announced on Monday that it will soon block mobile devices running outdated email software from accessing Exchange Online services until they're updated. [...]...
    Original Article

    Exchange Online

    Microsoft announced on Monday that it will soon block mobile devices running outdated email software from accessing Exchange Online services until they're updated.

    As the Exchange Team explained, devices running Exchange ActiveSync versions below 16.1 will no longer be able to connect to Microsoft's cloud email service after March 1, 2026.

    Exchange ActiveSync (EAS) is a Microsoft Exchange protocol to synchronize email, calendar, contacts, and tasks on mobile devices, and it is enabled by default for new user mailboxes.

    The change affects only devices using native email apps and Exchange Online, and does not affect on-premises Exchange Server installations. Microsoft added that devices using Outlook Mobile to connect to Exchange Online are not affected, since this app does not rely on the EAS protocol.

    "We want to inform our users and organizations about an important upcoming change regarding Exchange ActiveSync (EAS) device connectivity to Exchange Online," the Exchange Team said .

    "Starting March 1, 2026, devices running ActiveSync versions lower than 16.1 will no longer be able to connect to our services. EAS 16.1 was released as a part of Exchange Server and Exchange Online in June 2016."

    Popular native email applications for smartphones will also require updates to continue functioning. For instance, Google and Samsung are currently updating their email applications to support the newer protocol version.

    However, Apple's iOS Mail app already supports ActiveSync 16.1 since iOS 10, so iPhones running iOS 10 or later are compatible and shouldn't experience any issues accessing Exchange Online.

    Microsoft provided IT administrators with the following PowerShell command to generate a report of all devices using older EAS versions within their organizations before the March 2026 rollout:

    Get-MobileDevice | Where-Object {($_.ClientType -eq 'EAS' -or $_.ClientType -match 'ActiveSync') -and $_.ClientVersion -and ([version]$_.ClientVersion -lt [version]'16.1')} | Sort-Object UserDisplayName | Select-Object UserDisplayName, UserPrincipalName, DeviceId, DeviceModel

    "This decision comes after extensive collaboration with multiple licensed device and application vendors to ensure a smooth transition for as many users as possible," the Exchange Team added.

    "If users and organizations keep their devices and applications updated to the latest supported versions, there should be minimal disruption in service. We encourage everyone to verify their devices and applications are up to date before the change takes effect."

    Last month, Microsoft fixed an issue that prevented some Microsoft 365 users from connecting to email servers via Exchange ActiveSync with Outlook desktop clients.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Chinese Surveillance and AI

    Schneier
    www.schneier.com
    2025-12-16 12:02:32
    New report: “The Party’s AI: How China’s New AI Systems are Reshaping Human Rights.” From a summary article: China is already the world’s largest exporter of AI powered surveillance technology; new surveillance technologies and platforms developed in China are also not ...
    Original Article

    New report: “ The Party’s AI: How China’s New AI Systems are Reshaping Human Rights .” From a summary article :

    China is already the world’s largest exporter of AI powered surveillance technology; new surveillance technologies and platforms developed in China are also not likely to simply stay there. By exposing the full scope of China’s AI driven control apparatus, this report presents clear, evidence based insights for policymakers, civil society, the media and technology companies seeking to counter the rise of AI enabled repression and human rights violations, and China’s growing efforts to project that repression beyond its borders.

    The report focuses on four areas where the CCP has expanded its use of advanced AI systems most rapidly between 2023 and 2025: multimodal censorship of politically sensitive images; AI’s integration into the criminal justice pipeline; the industrialisation of online information control; and the use of AI enabled platforms by Chinese companies operating abroad. Examined together, those cases show how new AI capabilities are being embedded across domains that strengthen the CCP’s ability to shape information, behaviour and economic outcomes at home and overseas.

    Because China’s AI ecosystem is evolving rapidly and unevenly across sectors, we have focused on domains where significant changes took place between 2023 and 2025, where new evidence became available, or where human rights risks accelerated. Those areas do not represent the full range of AI applications in China but are the most revealing of how the CCP is integrating AI technologies into its political control apparatus.

    News article .

    Tags: , , , , , ,

    Posted on December 16, 2025 at 7:02 AM 0 Comments

    Sidebar photo of Bruce Schneier by Joe MacInnis.

    Cekura (YC F24) Is Hiring

    Hacker News
    www.ycombinator.com
    2025-12-16 12:01:55
    Comments...
    Original Article

    Voice AI and Chat AI agents: Testing and Observability

    Product Engineer (US)

    $100K - $180K 0.20% - 0.60% San Francisco, CA, US

    Connect directly with founders of the best YC-funded startups.

    Apply to role ›

    About the role

    About Us

    Cekura (YC F24) is one of the fastest-growing companies in its batch, with strong revenue traction. We’re well-funded, backed by premier investors, and have years of runway.

    We’re building the reliability layer for Conversational Agents . Teams use Cekura to simulate and monitor their AI agents end-to-end - measuring latency, barge-in, instruction-following, regressions, and more across phone, chat, SMS, and web. Customers love the product - and we’re just getting started.

    About the Role

    You’re joining at an inflection point. As Product Engineer , you’ll build the playbooks, processes, and relationships that define how Cekura partners with technical customers for long-term success. You’ll be both strategist and hands-on operator.

    What You’ll Do

    • Be a trusted technical advisor: Guide customers on integrating Cekura into CI/CD and production stacks (APIs, webhooks, auth, SIP/Twilio flows, STT/TTS, LLM configs).
    • Drive product feedback: Partner with Engineering; submit crisp RFCs backed by usage data to influence the roadmap.
    • Proactive account management: Monitor health, predict risk, and execute save/expansion plays based on telemetry.
    • Foundational leadership: Help hire and mentor the future Product & FDE team; set standards as we scale.

    About You

    • Customer-obsessed: You care deeply about measurable outcomes and long-term partnerships.
    • Technical pedigree (dev-tool savvy): You can read API docs, inspect payloads, and reason about systems. You’ve used Postman/cURL; you’re comfortable with logs/dashboards and basic scripting.
    • Clear communicator: You distill complex concepts for execs and engineers alike.
    • Builder’s mindset: You thrive in zero-to-one, create structure from ambiguity, and bias to action.
    • Analytical: You ground decisions in data - usage, adoption, performance, and business impact.

    Minimum Qualifications

    • 2 years in a technical role at a developer-focused or infra/SaaS company.
    • Comfort with APIs , webhooks , basic SQL , and one of Python/JS (to prototype, parse logs, or write examples).

    Nice to Have

    • Ex-founder or Early/founding FDE or first FDE hire experience (you built the playbook).
    • Familiarity with at least one of: LLM/AI agent tooling , observability/testing

    This Might Not Be for You If

    • You need rigid processes or heavy structure.
    • You prefer pure relationship management without technical depth.
    • You don’t enjoy fast-paced, in-person startup environments ( we’re in SF, 6 days/week ).

    Why Cekura

    • Responsibility & scope: Shape the foundation of our Product org.
    • Exceptional team: Work directly with founders and a highly technical, product-driven group.
    • Impact: Improve the reliability of AI agents used by real customers every day.
    • Upside: Competitive compensation, meaningful equity, and rapid growth.
    • Benefits: Medical/dental/vision, team lunches and dinner!

    Excited to help world-class teams ship reliable AI agents - and wear both the customer and engineer hats? Let’s talk.

    About Cekura

    Cekura is a Y Combinator–backed startup redefining AI voice agent reliability. Founded by IIT Bombay alumni with research credentials from ETH Zurich and proven success in high-stakes trading, our team built Cekura to solve the cumbersome, error-prone nature of manual voice agent testing.

    We automate the testing and observability of AI voice agents by simulating thousands of realistic, real-world conversational scenarios—from ordering food and booking appointments to conducting interviews. Our platform leverages custom and AI-generated datasets, detailed workflows, and dynamic persona simulations to uncover edge cases and deliver actionable insights. Real-time monitoring, comprehensive logs, and instant alerting ensure that every call is optimized and production-ready.

    In a market rapidly expanding with thousands of voice agents, Cekura stands out by guaranteeing dependable performance, reducing time-to-market, and minimizing costly production errors. We empower teams to demonstrate reliability before deployment, making it easier to build trust with clients and users.

    Join us in shaping the future of voice technology. Learn more at cekura.ai .

    Cekura

    Founded: 2024

    Batch: F24

    Team Size: 5

    Status: Active

    Location: San Francisco

    Founders

    European authorities dismantle call center fraud ring in Ukraine

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 11:44:06
    European law enforcement authorities dismantled a fraud network operating call centers in Ukraine that scammed victims across Europe out of more than 10 million euros. [...]...
    Original Article

    Arrest

    European law enforcement authorities dismantled a fraud network operating call centers in Ukraine that scammed victims across Europe out of more than 10 million euros.

    Authorities from the Czech Republic, Latvia, Lithuania, and Ukraine (supported by Eurojust) arrested 12 suspects out of 45 identified during the investigation. They also seized 21 vehicles, weapons, a polygraph machine, computers, cash, and counterfeit identification documents (including forged police officers and bank employee IDs) after 72 searches across three cities in Ukraine on December 9th.

    This coordinated action targeted multiple call centers in Dnipro, Ivano-Frankivsk, and Kyiv that employed approximately 100 people from across Europe.

    The criminals defrauded over 400 known victims through various schemes, including impersonating bank employees and police officers, tricking victims into believing that their bank accounts had been compromised, and asking them to transfer funds to "safe" accounts controlled by the fraud ring.

    In other cases, scammers stole victims' bank credentials after persuading them to install remote access software on their devices, enabling the criminals to hijack their accounts. Additionally, some of the fraudsters in this scam network even went so far as to meet victims in person to collect cash using stolen card details.

    The network operated as a commission-based criminal enterprise, paying employees up to 7 percent of proceeds from successful scams. Their leaders promised bonuses, including cash, cars, or Kyiv apartments, for employees who obtained more than 100,000 euros. However, investigators said these rewards were never distributed because the scammers never reached this goal.

    "The criminal network recruited their employees from the Czech Republic, Latvia, Lithuania and other countries and brought them to their call centres in Dnipro, Ivano-Frankivsk and Kyiv to extort money from victims in these and other European countries," Eurojust said in a Tuesday press release.

    "Members of the criminal group had different roles in the organisation, ranging from making calls and forging official certificates from the police and banks to collecting cash from their victims."

    This is the latest in a long list of similar fraud rings dismantled by European authorities in recent years, including the March 2022 takedown of a massive call center investment scam operation, which employed 200 "traders" to steal a minimum of €3 million from victims each month.

    Last year, police also shut down multiple call centers across Europe controlled by a criminal organization involved in online investment fraud (also known as 'pig butchering' cryptocurrency scams) linked to millions of euros in losses.

    In May, European authorities also shut down 12 fraud call centers in Albania, Kosovo, Bosnia and Herzegovina, and Lebanon as part of a joint law enforcement action dubbed "Operation PANDORA." These call centers were linked to thousands of daily scam calls, and German police arrested 21 individuals following numerous raids on April 18th.

    More recently, in July, Spanish police dismantled a large-scale investment fraud operation that caused cumulative damages exceeding €10 million ($11.8 million) following simultaneous raids in Barcelona, Madrid, Mallorca, and Alicante, one week after taking down an even larger cryptocurrency investment fraud ring that laundered over €460 million ($540 million) stolen from over 5,000 victims worldwide.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Tool Safety: A Beautiful Soup zine (2017)

    Lobsters
    www.crummy.com
    2025-12-16 11:32:51
    Comments...
    Original Article

    Tool Safety is a zine I wrote in 2017 about what writing Beautiful Soup taught me about software development.

    You can download Tool Safety as a single sheet . The WikiBooks "Zine Making" book shows how to fold this sheet into an 8-page zine with a single cut.

    You can also read the individual pages of the zine online:

    Tool Safety cover

    Tool Safety

    A mini-zine by Leonard Richardson

    Tool Safety
        page 1

    In 2004 I started work on Beautiful Soup, a Python library for getting information out of web pages.

    $ pip install beautifulsoup4

    Before Beautiful Soup, web browsers could parse bad HTML:

    <a href="crummy.com>
    <b>link text<a></b>

    But it was really difficult to do it from within your own computer programs.

    Tool Safety
        page 2

    Beautiful Soup has been used in thousands of public Python projects .

    (And probably at least as many private/individual projects.)

    Most of these projects make peoples' lives better in small ways:

    • Get sports scores 🏈
    • Record Internet radio streams 📻
    • Download astronomy data 🔭
    • "Unofficial APIs" for data export ➔
    Tool Safety
        page 3

    Sometimes beautiful Soup is used to do something really important, often as a piece of journalism. (My favorite example.)

    But it's also been used as a tool for spamming, doxing, and other things I think are unethical.

    And it's often used in ways that make rich people richer. 📈

    Maybe not unethical per se, but not the best use of the power we have as computer programmers.

    Tool Safety
        page 4

    Beautiful Soup is just a tool.

    It isn't good or bad on its own.

    But I chose to make this tool instead of a different tool.

    I choose to work on it now instead of doing other things.

    - - -

    I can't use "just a tool" as an excuse.

    If I'm going to feel pride about the good people do with my software, I have to share some responsibility for the bad.

    Tool Safety
        page 5

    In college I took an Engineering Ethics course. It focused heavily on mistakes.

    • Bridges falling down 🌉
    • Radiation poisoning caused by a software bug ☢

    This stuff has consequences.

    Mistakes can cause a lot of harm.

    I wish this course had covered the idea that people might be harmed not because of an implementation mistake, but because an engineer did a good job building the wrong thing.

    Tool Safety
        page 6

    I used to ask for donations from people who used Beautiful Soup.

    Image of donation button.

    But I didn't write Beautiful Soup to make money. I wrote it to make the world a better place.

    I wrote it to be a tool for good.

    If you've used Beautiful Soup over the past 10+ years and want to say thank you, the best way is to be thoughtful about how you use it.

    You have power. Use it to make things better.

    Tool Safety
        page 7

    When I work on a piece of software I ask myself:

    Could this be used to hurt someone?
    Is there something better I could do with the same idea?

    That's what I mean by being thoughtful.

    It's similar to the mindset you need to write secure code.

    Help people who want help.
    Think through the consequences.

    Thanks for reading. I'd like to hear what you build with Beautiful Soup. You can email me at leonardr@segfault.org .

    Beautiful Soup website

    © 2017 Leonard Richardson, CC-BY-ND

    State of HTML 2025 results

    Lobsters
    2025.stateofhtml.com
    2025-12-16 11:20:43
    Comments...
    Original Article

    State of HTML 2025 S T A T E O F 2 0 2 5

    If you want to get an idea of how capable the web platform has gotten over the past few years, look no further than the list of categories covered in this survey.

    Forms, graphics, performance features, methods of interacting with various system and device APIs, accessibility… It's a wonder anybody is able to keep track of it all!

    Thankfully at least one person is: Lea Verou , who after designing the original version of the survey in 2023, joined us again this year to give it an update.

    And together, one of the areas we focused on this year was analyzing the vast quantity of freeform answers collected around various pain point areas.

    You'll now be able to dig deeper into categorized data, as well as access (and search through) respondents' original answers.

    If nothing else, this should help us realize that we're all in this together, sharing the same frustrations- but also the same hopes!

    Special Thanks

    Don't Miss the Next Survey

    Leave us your email and we’ll let you know when the next survey takes place.

    Español

    Galego

    Indonesia

    日本語

    한국어

    Română

    Русский

    Українська

    简体中文

    Magyar

    Português (Brasil)

    Ελληνικά

    Tiếng Việt

    ภาษาไทย

    𐒈𐒝𐒑𐒛𐒐𐒘

    Norsk

    עִבְרִית חֲדָשָׁה

    Should we fear Microsoft's monopoly?

    Hacker News
    www.cursor.tue.nl
    2025-12-16 10:56:40
    Comments...
    Original Article

    The International Criminal Court recently banned Microsoft due to concerns about US sanctions. More parties are worried that our data will soon be inaccessible if President Trump tries to assert his power.

    In April of this year, Cursor already wrote about the risk of Trump cutting off access to our data. While this is theoretically possible, then-Chief Information Security Officer (CISO) Martin de Vries estimated the likelihood of this happening for TU/e ​​to be very small.

    Alternatives

    Seven months later, the public demand for European data alternatives seems to be growing. Is TU/e ​​now actually looking for a replacement for Microsoft? The answer is partly yes, but not yet institutionally driven.

    “Several departments within the university are already testing, for example, Nextcloud – a German cloud solution," says De Jong. "We are also working with educational partner SURF on our own solutions, for example in the field of IT security.”

    If there is no comprehensive alternative, the university will try to arrive at a fully-fledged solution through several interventions, so that it can continue without Microsoft. However, the final verdict on the feasibility of this is not yet in order.

    Cables

    There are other alternatives on the market, such as the German Open Desk, which the International Criminal Court is now switching to. And the European initiative GaiaX, but none of these can replace Microsoft. That IT provider does much more than provide a typing application or manage data in a cloud.

    “Besides word processors, Microsoft also has security solutions, cables, servers in data centers, access control, SharePoint, and AI across all of this,” De Jong explains. “So simply replacing Microsoft isn't an option.”

    And switching only partially would require a lot of extra administrative work and money, and wouldn't reduce the risk of data blocking. The American giant is the largest supplier of software and services to TU/e.

    Consortium

    Finding alternatives to Microsoft is something that several universities are considering. That's why Utrecht University, Delft University of Technology, the University of Amsterdam, Erasmus University, and Tilburg University are collaborating in a consortium to develop alternatives.

    TU/e is not involved but says it is following the initiative with interest. The collaboration with SURF for the same purpose is broader: all Dutch universities are members.

    Building together

    De Jong, incidentally, sees both advantages and disadvantages in the interconnectedness of Microsoft's IT services. “On the one hand, it's difficult for all the components to work together. We can't simply pull something out and replace it. That creates dependency. But on the other hand, the system works so well that you don't notice the complexity as a user.”

    A future European alternative must be built by countries, governments, and knowledge partners together. “I think we can contribute our knowledge to developing a solution,” says De Jong, referring to the software and hardware expertise within the university. That development will likely take years.

    Trade balance

    The current systems that dominate the world—Microsoft and Google—were built by billion-dollar companies and further developed for decades. Even if the Netherlands makes every effort now, a fully-fledged alternative won't be readily available. That gives American companies power.

    Yet, De Jong believes there's another side to this power struggle between the US and Europe. “The US is hinting that they can cut off our data, but if fear in Europe becomes too great and we leave, it will have a major financial impact on them. The trade balance on services and ICT is actually positive for America: they make a lot of money from European contracts with Microsoft and Google.”

    De Jong believes this will prevent US from simply pressing the ‘block access button’. Such a thing causes a lot of unrest, with financial consequences.

    This article was translated using AI-assisted tools and reviewed by an editor

    Optimization Countermeasures

    Lobsters
    mcyoung.xyz
    2025-12-16 10:36:13
    Comments...
    Original Article

    Silicon designers are bad at designing secure hardware. Embarrassingly so , sometimes. This means that low-level cryptography, as well as code which directly handles key material, often needs to be written in a particularly delicate style called “constant-time”.

    “Constant-time” is a bit of a misnomer. It does not mean that the code’s time complexity is O ( 1 ) O(1) (although this is a closely related property). Constant-time is a threat model for side-channel timing attacks like Spectre, which ensures that key material is not leaked through the microarchitecture of CPUs.

    Although constant-time is a powerful countermeasure against the silicon designers leaking our keys, the compiler can still screw us. However, there are magic incantations that can be offered to the compiler to make it behave correctly in many relevant situations.

    First: what is constant-time?

    The Constant-Time Threat Model

    The actual threat model is a series of assumptions about the most advanced attacker we wish to defeat. The assumptions, as applied to a cryptography software library, are as follows:

    1. The attacker has access to both the source code of the library, the compiled artifact linking in the the library, the toolchain used to build it, and any relevant compiler flags (this is true, for example, for an Internet browser).

    2. The attacker has a complete trace of every program counter value visited by the program. This is not the same as an instruction trace, which will usually also record software-visible architectural state at each instruction. Attackers can often obtain this information by directly timing the software, since the relevant information is mostly branches-taken. This includes instructions executed in a privileged mode, such as within the kernel.

    3. The attacker knows the address of every pointer stored or loaded by the program. That is, each program counter value in the above is annotated with the values of registers containing pointers relevant to that instruction, such as the pointer for a load instruction. This data can be obtained through data cache side-channels such as Spectre.

    Programming against this model defeats virtually all known timing side-channel attacks, although it does not protect against other side-channel attacks, such as thermal and power analysis.

    New Footguns

    All cryptography libraries that are safe to use in 2025 implement all of their critical code in constant-time. This model has broad consequences what kinds of programs are allowed.

    What’s wrong with this code?

    int8* key = ...
    for (int i = 0; i < n; ++i) {
      if (auto& k = key[i]; k < 0) {
        k = -k;
      }
    }
    

    Because we are looping over n , we immediately leak n , because the attacker can count the number of loop iterations. If n is secret, this is a problem.

    We also leak one eight of the bits in the key: the attacker can see which loop iterations contain a negation instruction; those iterations correspond to bytes which had their sign bit set.

    To protect the value of n , we would have to ensure that there is some maximum value N of n , and that key was allocated to an at-least- N -byte buffer; the loop would then be over N , making no reference to n . This is a relatively uncommon situation, since the length of a buffer is almost never a secret in practice.

    Protecting the sign bits is simpler: rather than branching to determine if the value should be negated, we can mask off the sign bit: key[i] &= 0x7f .

    Buffer Comparisons

    Many standard library functions are not constant-time. For example, memcmp ’s runtime is not constant in the size of its inputs; most implementations break out early when encountering an unequal value:

    int memcmp(const char* a, const char* b, int n) {
      for (int i = 0; i < n; ++i) {
        auto diff = a[i] - b[i];
        if (diff != 0) {
          return diff
        }
      }
      return 0;
    }
    

    Now suppose we have the following code to verify a signature:

    Message msg = ...;
    char* expected = compute_signature(msg.body, private_key);
    if (memcmp(msg.signature, expected) == 0) {
      // Assume msg is authentic.
    }
    

    The attacker can use this as a signing oracle. By providing sending the desired message to sign with a bad signature, they can determine the first byte of the signature which is incorrect by timing alone, and brute force that byte. They can then proceed to the next byte, and so on. The maximum number of queries to forge a signature is 256 times the number of bits, reducing the cost from O ( 2 n ) O(2^n) to O ( n ) O(n) !

    To defeat this attack, we need to use constant-time memcmp . This means that every byte must be compared, and the comparison must be accumulated without branching.

    The typical implementation is something like this:

    bool ct_memeq(const char* a, const char* b, int n) {
      char acc = 0;
      for (int i = 0; i < n; ++i) {
        acc |= a[i] ^ b[i];
      }
      return acc == 0;
    }
    

    If at least one byte differs between a and b , their xor will be non-zero, and so acc will be nonzero. This function only compares for exact equality, not lexicographic equality, but the latter is never an operation you want or need on keys and other sensitive data.

    There are many variants on this implementation, but the xor one is the most popular. Subtraction will achieve a similar result (although signed overflow is UB in C++).

    Constant-Time Select

    A similar trick is used to select one of two values. The ternary operator cannot be used because it can be compiled into a branch.

    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      return (a & mask) | (b & ~mask);
    }
    

    If flag is true, mask is the all-ones representation for T , so a & mask is a and b & ~mask is 0 , so their or is a . If flag is false, the opposite is true.

    On x86, Clang recognizes this idiom and produces a conditional move instruction, which does not violate the threat model. However, on architectures without conditional move, such as RISC-V, Clang still recognizes the pattern… but produces a branch.

    _Z9ct_selectIiET_bS0_S0_:
      mov     eax, esi
      test    edi, edi
      cmove   eax, edx
      ret
    
    _Z9ct_selectIiET_bS0_S0_:
      bnez    a0, .LBB0_2
      mv      a1, a2
    .LBB0_2:
      mv      a0, a1
      ret
    

    You might want to point the finger at the bool , but this actually goes much deeper than that. This type of unwanted optimization happens all the time in constant-time code, and preventing it is essential to ensure that the countermeasure works.

    The “easy” solution is to just write the assembly directly, which is the case for performance-critical parts of cryptography implementations, but this is error-prone and not portable.

    Thankfully, all modern native compilers provide a hidden feature to block optimizations inimical to security, and what this post is really about: the value barrier .

    An Incantation

    The value barrier is a special secret syntax construct that instructs the compiler to ignore information it could use to prove the correctness of optimizations.

    For example, the reason Clang is able to “defeat” our ct_select implementation is that it knows from -T(flag) that mask must be either 0 or -1 . If we can hide this fact from Clang, it will be forced to not emit the branch.

    One way we can do this is to “encrypt” mask with a value that Clang cannot see through, such as a global variable:

    template <typename T>
    inline T key;
    
    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      mask ^= key<T>;
      return (a & mask) | (b & ~mask);
    }
    

    Clang cannot know what the value of key will be at runtime, because code in another translation unit might set it. It must therefore assume it knows nothing about mask and the return value could be an arbitrary bit-mix of a and b . However, because we never actually set this global, it will always be zero, so mask ^= key<T>; is a no-op at runtime.

    On RISC-V, this does what we need:

    _Z9ct_selectIiET_bS0_S0_:
    .Lpcrel_hi0:
      auipc   a3, %pcrel_hi(_Z3keyIiE)
      lw      a3, %pcrel_lo(.Lpcrel_hi0)(a3)
      neg     a0, a0
      xor     a0, a0, a3
      xor     a1, a1, a2
      and     a0, a0, a1
      xor     a0, a0, a2
      ret
    
    _Z3keyIiE:
      .word   0
    

    Unfortunately, this does perform a load, which is a performance hiccup we’d like to avoid. Also, if whole-program optimization, such as LTO, BOLT, or similar notices the global is never written to, it can replace all of its loads with immediates.

    Another option is to send the value into an assembly function. Not even LTO can see into assembly functions, and BOLT will not attempt to optimize hand-written assembly.

    #include <stdint.h>
    
    asm(R"(
      .intel_syntax
      __asm_eyepatch:
        mov rax, rsi
        ret
    )");
    
    extern "C" uint64_t __asm_eyepatch(uint64_t);
    
    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      mask = __asm_eyepatch(mask);
      return (a & mask) | (b & ~mask);
    }
    

    This works but now the cost is a non-inlineable function call rather than a pointer load. Not ideal. It’s also again, not portable across targets. The function call is also treated as having side-effects by the compiler, which impedes desirable optimizations.

    But a small modification will eliminate this problem: we can simply use an inline assembly block with zero instructions.

    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      asm("" : "+r"(mask));
      return (a & mask) | (b & ~mask);
    }
    

    This is called the value barrier: an empty assembly block which modifies a single register-sized value in-place as a no-op. The "+r" constraint indicates that the assembly takes mask as an input, and outputs a result onto mask , in the same register.

    This has the same effect as the xor-with-key solution (prevents optimizations that depend on the value of mask ) without the load from a global. It’s architecture-independent, because "" is a valid inline assembly block regardless of underlying assembly language.

    The value barrier is often written as its own function (with a massive comment explaining what it does), to be used in key points in cryptographic algorithms.

    template <typename T>
    [:nodiscard:] T value_barrier(T x) {
      asm("" : "+r"(x));
      return x;
    }
    

    A use of the value barrier looks like this:

    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      mask = value_barrier(mask);
      return (a & mask) | (b & ~mask);
    }
    

    value_barrier is trivially inlineable and compiles down to zero instructions, but has a profound effect on optimizations.

    Dataflow

    A better name for the value barrier is the “dataflow barrier”, because that more accurately captures what the instruction does.

    If you read my introduction to SSA , you’ll know that every optimization compiler today puts a heavy emphasis on dataflow analysis. To figure out how to optimize some operation, we look at its inputs’ definitions.

    Let’s look at what LLVM sees when we compile ct_select , setting T = int (I have manually lifted everything into registers).

    define i32 @_Z9ct_selectIiET_bS0_S0_(i1 zeroext %flag, i32 %a, i32 %2) {
      %1 = zext i1 %flag to i32  ; int(flag)
      %2 = sub nsw i32 0, %1     ; mask = 0 - int(flag)
    
      %3 = and i32 %a, %2     ; a & mask
      %4 = xor i32 %2, -1     ; mask ^ -1
      %5 = and i32 %b, %4     ; b & (mask ^ -1)
      %6 = or i32 %3, %5      ; (a & mask) | (b & (mask ^ -1))
    
      ret i32 %6
    }
    

    LLVM contains pattern-matching code that matches this code (and various permutations of it) as a “select” idiom. LLVM contains an instruction that selects one of two values based on an i1 , called select . It is essentially the C ternary with SSA register arguments.

    The pattern-matching code looks for an or whose arguments are both and s, where one argument to the and is the complement of the other (i.e, xor with -1 ). Call this argument is the “mask”. What LLVM has found is a general “bit-mixing” operations which selects bits from the other operands to the and s, depending on which bits of the mask are set.

    LLVM then wants to prove that the mask is either 0 or -1 (all ones). There are a number of ways LLVM can discover this, but all of them essentially boil down to “is the mask a sext i1 , i.e., sign-extending a one-bit value”. That does not occur in this code, but a peephole optimization can rewrite sub nsw i32 0, %1 into sext i1 %0 to i32 , allowing this more complicated pattern to be detected.

    LLVM rewrites this into a select , which on x86 turns into a cmov , but on RISC-V forces a branch.

    All we need to do is prevent LLVM from looking through to the definition of the “mask”. That is precisely what the value barrier accomplishes: it inserts a new SSA register whose value is, at runtime, equivalent to the mask, but produced by an instruction that LLVM doesn’t have implement dataflow for. That is the value barrier: an intentional hole left in the compiler’s analysis.

    In theory, LLVM can actually see that the inline assembly block is empty and optimize it out. However, it does not by design, because cryptography depends on it remaining an optimization barrier. In fact, LLVM (and GCC) emit special annotations around inline assembly to stop downstream tools, such as linker optimizations and post-link optimizers like BOLT, from accidentally optimizing sensitive sequences. In an assembly dump from LLVM, those regions look like this:

    _Z9ct_selectIiET_bS0_S0_:
      mov     eax, esi
      neg     edi
      #APP
      #NO_APP
      xor     eax, edx
      and     eax, edi
      xor     eax, edx
      ret
    

    These are just comments; the actual sensitive regions are recorded elsewhere in the resulting object code.

    What Does the Barrier Do?

    The programming model for the value barrier is simple: it produces an arbitrary value that, at runtime, happens to be bitwise-identical to its input. The compiler may still make assumptions about the input value, but it cannot connect them to the output of the barrier through dataflow.

    In other words, the value barrier is simply a register copy that also severs the dataflow link from the destination to the source operand.

    The compiler is still allowed to optimized based on the assumption that this is some unknown concrete value. For example:

    int random() {
      int x = value_barrier(42);
      return x - x;
    }
    

    x - x is always zero, so LLVM can optimize away the value barrier and its input altogether. Critically, the value barrier does not have side effects , so if its result is not used, the value barrier will be deleted through dead code elimination. The following does not work:

    template <typename T>
    T ct_select(bool flag, T a, T b) {
      auto mask = -T(flag);
      value_barrier(mask); // Unused result warning.
      return (a & mask) | (b & ~mask);
    }
    

    Because it is side-effect-free, it can also be hoisted out of loops and conditionals, which is actually a desirable optimization.

    Benchmark Black Box

    There is a related function that appears in benchmarking code, which is not equivalent to the value barrier:

    template <typename T>
    void black_box(const T& v) {
      asm volatile("" :: "m"(v))
    }
    

    This function guarantees that its input is treated as “used” by the compiler, even if it is side-effect free. Rather than blocking dataflow, it blocks dead code elimination.

    It works because asm volatile must be treated as having observable side-effects that depend on all of its inputs. This means that it cannot be deleted, hoisted our of loops, or executed speculatively.

    A benchmark black box is most commonly used to force the result of a function being benchmarked to not be deleted by the compiler, so that the runtime of that function can be accurately measured. It can in place of the value barrier, because the "m" constraint passes a pointer to the argument into the assembly block, which it could potentially mutate. However, this is not guaranteed to work in the same way that the value barrier does: it depends on whether the surface language (C++ or Rust) considers mutating through that pointer to be UB.

    Using the actual value barrier avoids this altogether.

    Unintended Consequences

    The value barrier works very well at blocking undesirable optimizations, but it obstructs a few important ones, making its use problematic in performance-sensitive scenarios.

    A particularly notable one is automatic vectorization. Consider this function:

    uint64_t sum(uint64_t* p, int n) {
      n &= ~31; // Round n to the nearest multiple of 32.
      u64 a = 0;
      for (int i = 0; i < n; i++) {
        a += p[i];
      }
      return a;
    }
    

    When we push this through Clang at -O3 -march=zenv5 , the loop gets unrolled 32 times over into four parallel AVX512 operations.

    _Z3sumPyi:
      cmp     esi, 32
      jl      .LBB1_1
      mov     eax, 6661
      vpxor   xmm0, xmm0, xmm0
      xor     ecx, ecx
      vpxor   xmm1, xmm1, xmm1
      vpxor   xmm2, xmm2, xmm2
      vpxor   xmm3, xmm3, xmm3
      bextr   eax, esi, eax
      shl     rax, 8
    .LBB1_3:
      vpaddq  zmm0, zmm0, zmmword ptr [rdi + rcx]
      vpaddq  zmm1, zmm1, zmmword ptr [rdi + rcx + 64]
      vpaddq  zmm2, zmm2, zmmword ptr [rdi + rcx + 128]
      vpaddq  zmm3, zmm3, zmmword ptr [rdi + rcx + 192]
      add     rcx, 256
      cmp     rax, rcx
      jne     .LBB1_3
      vpaddq  zmm0, zmm1, zmm0
      vpaddq  zmm2, zmm3, zmm2
      vpaddq  zmm0, zmm2, zmm0
      vextracti64x4   ymm1, zmm0, 1
      vpaddq  zmm0, zmm0, zmm1
      vextracti128    xmm1, ymm0, 1
      vpaddq  xmm0, xmm0, xmm1
      vpshufd xmm1, xmm0, 238
      vpaddq  xmm0, xmm0, xmm1
      vmovq   rax, xmm0
      vzeroupper
      ret
    .LBB1_1:
      xor     eax, eax
      ret
    

    Pretty cool, right? But what if we need to stick a value barrier into the loaded values for some reason?

    uint64_t sum(uint64_t* p, int n) {
      n &= ~31; // Round n to the nearest multiple of 32.
      u64 a = 0;
      for (int i = 0; i < n; i++) {
        a += value_barrier(p[i]);
      }
      return a;
    }
    

    Surprisingly, this seems to completely wreck vectorization, forcing each loop iteration to load only 8 bytes instead of 256.

    _Z3sumPyi:
      cmp     esi, 32
      jl      .LBB2_1
      mov     eax, 6661
      xor     edx, edx
      bextr   ecx, esi, eax
      xor     eax, eax
      shl     rcx, 8
    .LBB2_4:
      mov     rsi, qword ptr [rdi + rdx]
      add     rax, rsi
      add     rdx, 8
      cmp     rcx, rdx
      jne     .LBB2_4
      ret
    .LBB2_1:
      xor     eax, eax
      ret
    

    The reason for this is kind of nasty. From LLVM’s perspective, an inline assembly block is a call asm instruction to a special function whose name is the inline assembly string. Although the lack of volatile marks this function as pure, making it reorderable, there is no way to tell LLVM that it can be merged.

    If we try to unroll the loop 32 times, we wind up with 32 calls to a pure function with the signature i64 -> i64 , and LLVM doesn’t know that it’s safe to merge them into an almost identical function call with a signature <32 x i64> -> <32 x i64> . Because one of the loop operations cannot be vectorized (namely, this inline assembly block), vectorization fails.

    There is no workaround for this. Allowing the inline assembly block to use an SSE register using the "+x" constraint doesn’t work, because the failure is not the assembly constraints: it’s that the call asm instruction does not support vectorization 1 .

    Is This Really a Language Feature?

    For all intents and purposes, this magic incantation is part of C++ (at least, the dialect that GCC and Clang implement, which are the only compilers that matter for security 2 ).

    This is because BoringSSL 3 , the most widely-deployed cryptography library in the world, relies on this trick . This feature is critical to the security posture of the two biggest orgs that fund LLVM: Google and Apple. Tools which break the value barrier have historically been quickly patched to respect it, usually due to the consternation of a professional cryptographer.

    The optimization black box is not quite as critical, but its correctness is a side-effect of the value barrier’s: namely, that the optimizer must never peek into an inline assembly block.

    Of course, any professional cryptographer who has to implement constant-time primitives would tell you that this is a crummy workaround, and that we really need actual language-level intrinsics for manipulating values in a constant-time way. The tricky part is specifying semantics for them in a way that makes sure we don’t allow “bad” optimizations, a notoriously difficult problem in compiler IR design.

    Musicians are deeply concerned about AI. So why are the major labels embracing it?

    Guardian
    www.theguardian.com
    2025-12-16 10:00:18
    Companies such as Udio, Suno and Klay will let you use AI to make new music based on existing artists’ work. It could mean more royalties – but many are worried This was the year that AI-generated music went from jokey curiosity to mainstream force. Velvet Sundown, a wholly AI act, generated million...
    Original Article

    T his was the year that AI-generated music went from jokey curiosity to mainstream force. Velvet Sundown , a wholly AI act, generated millions of streams; AI-created tracks topped Spotify’s viral chart and one of the US Billboard country charts; AI “artist” Xania Monet “signed” a record deal. BBC Introducing is usually a platform for flesh-and-blood artists trying to make it big, but an AI-generated song by Papi Lamour was recently played on the West Midlands show. And jumping up the UK Top 20 this month is I Run, a track by dance act Haven, who have been accused of using AI to imitate British vocalist Jorja Smith (Haven claim they simply asked the AI for “soulful vocal samples”, and did not respond to an earlier request to comment).

    The worry is that AI will eventually absorb all creative works in history and spew out endless slop that will replace human-made art and drive artists into penury. Those worries are being deepened by how the major labels, once fearful of the technology, are now embracing it – and heralding a future in which ordinary listeners have a hand in co-creating music with their favourite musicians.

    AI music platforms analyse huge amounts of recorded music in order to learn its sounds, structures and expressions, and then allow users to create their own AI-generated music via text or speech prompts. You might ask for a moody R&B song about a breakup sung by a female vocalist, and it will come up with a decent approximation of one, because it’s absorbed hundreds of such songs.

    Artists and labels initially saw AI as the biggest existential threat since Napster -fuelled piracy: if not a replacement for human creativity, then certainly a force that could undermine its value. Gregor Pryor, a managing partner at legal firm Reed Smith, says background music for things such as advertising, films and video games, where you’re not relating to a personality as you would in pop music, “is where the real damage will be done” first of all. “People will ask: why would I pay anyone to compose anything?”

    Screengrab from the Suno AI music generator.
    ‘New creative possibilities’ … screengrab from the Suno AI music generator. Photograph: Suno

    Aware of the scale of the shift, last year the Recording Industry Association of America, representing the three major labels, initiated legal action against AI music companies Suno and Udio for copyright infringement, alleging they had trained their AI platforms on the labels’ artists without their permission. But then there was an extraordinary about-turn. They didn’t just settle the matter out of court – Universal Music Group (UMG) then partnered with Udio, and Warner Music Group (WMG) with Udio and Suno. They also have deals in place with AI company Klay , the first to get all three major labels on board, adding Sony Music (discussions with indie labels are ongoing). WMG chief executive Robert Kyncl has said these recent deals are to ensure the “protection of the rights of our artists and songwriters” and to fuel “new creative and commercial possibilities” for them, while UMG chief Lucien Grainge heralded “a healthy commercial AI ecosystem in which artists, songwriters, music companies and technology companies can all flourish and create incredible experiences for fans”.

    Kyncl made another bold statement as to why these deals are taking place: “Now, we are entering the next phase of innovation. The democratisation of music creation.”

    AI artist Xania Monet. An AI-generated image from ‘her’ Instagram account.
    Monet maker … AI artist Xania Monet. An AI-generated image from ‘her’ Instagram account. Photograph: xania_monet

    Announcing its Universal tie-in, Udio chief executive Andrew Sanchez has said Udio users will be able to “create [music] with an artist’s voice and style”: so not just create the aforementioned moody R&B song, but one with a specific existing artist’s voice. He also says Udio will allow users to “remix and reimagine your favourite songs with AI … take your favourite artists, songs or styles and combine them in novel ways. In our internal experimentation, the team has gotten some truly remarkable and unusual results that will definitely delight.”

    Klay meanwhile states that “fans can mould their musical journeys in new ways”, but it’s essentially the same offering: a subscription service where you can manipulate the music of others, or create your own from it. Ary Attie, Klay’s founder and chief executive, says his company will properly compensate artists whose work is used, and won’t supplant the work of human musicians: “This technology is not going to change any of that.”

    Klay is a rarity in that it signed up all three major labels before it started training its AI system on their music: “A core part of our philosophy,” Attie says. He argues that rival AI companies – he doesn’t name names – have been “acting in a way that doesn’t respect the work of artists, and then being forced into a corner”. Suno did not respond to an interview request; Udio claimed its executives were “extremely swamped” and therefore unable to answer questions. The current, and synchronised, messaging from labels and gen AI companies with licensing deals is that they all respect both art and artists and that their deals will reflect this.

    They are also positioning gen AI as the single biggest democratising leap ever in remix culture, effectively enabling everyone to become musically creative. The counterargument is that, by lowering all barriers to entry and by allowing the manipulation of a song or a musician’s character at scale, it vastly devalues and negates the creative act itself.


    B ut what do musicians actually think of the prospect of their work being used to train AI, and reworked by the general public? “Everybody should be selling or licensing their voice and their skills to these companies,” Dave Stewart of Eurythmics argued to me this week . “Otherwise they’re just going to take it anyway.” That view is directly countered by the major labels and AI companies, who have insisted artists and songwriters get to opt in to have their music made available, and if they do, get royalties when their music is used to train AI, or manipulated by users on platforms such as Udio, Suno and Klay.

    Others take a grimmer view about how these companies might reshape the industry. Irving Azoff, legendarily forthright artist manager and founder of the Music Artists Coalition in the US, responded to the Universal/Udio deal with biting cynicism. “We’ve seen this before – everyone talks about ‘partnership,’ but artists end up on the sidelines with scraps,” he said . In the wake of the same deal, the Council of Music Makers in the UK accused the major labels of “spin” and called for a more robust set of artist-label agreements. And the European Composer and Songwriter Alliance says there is a disturbing “lack of transparency” around the deals (though more detail is likely to emerge on what users can do with any music they create, and any potential commercial uses of it).

    Catherine Anne Davies performing as the Anchoress in 2024.
    ‘I’m yet to be convinced’ … Catherine Anne Davies performing as the Anchoress in 2024. Photograph: TeeGeePix/Alamy

    Catherine Anne Davies, who records as the Anchoress and also sits on the board of directors at the Featured Artists Coalition (FAC), has many reservations here. “Most people don’t even want their work to be used for training AI,” she says. “I’m on the dystopian side, or maybe what I call the realist side of things. I’m interested in the way that AI can be assistive in the creative process – if it can make us more efficient, if it can streamline our processes. But generative AI for me, in terms of creative output, is a big no-no at the moment. I’m yet to be convinced.”

    Musician Imogen Heap feels that AI itself is not to be feared as a tool – she uses an AI she calls Mogen to listen to every aspect of her life, with a view to it being a creative partner (as explored in a recent Guardian article ). To help address some of the issues, she has created Auracles, an artist-led, non-profit platform she hopes will be the place where the rights and permissions around AI are set out. It’s not enough to say you’re happy with your music being used by AI, she says – instead, what’s needed are “permissions that grow and evolve over time”.

    Other companies are cropping up with similar offers. “We must protect the artists at all costs,” says Sean Power, chief executive of Musical AI, who aims to give musicians “an exact portion of the influence they’re having on all the generative outputs” – meaning compensation every time even a tiny bit of one of their songs is used by a user of Udio et al.

    Terms of these deals are undisclosed, but labels are likely to be seeking settlement for any past use of their artists’ copyrights as well as an advance on future use, plus an equity stake in the platform. And while artists will be able to opt out of including their work, they probably won’t be consulted on these partnerships going ahead, with this lack of consultation being something that artist representative bodies such as FAC have been particularly critical of. “The big artists, the labels need to be nice to; those who have a platform will be consulted to some degree,” says a music licensing expert, speaking anonymously. “The very few, who as individual artists are able to make a dent on share price, will have approval.”

    I approached Universal, Sony and Warner about the specific concerns raised by artists here: namely limited transparency around the deals, their commercial terms and how opt-ins work; if there is a risk of gen AI undermining existing revenue sources; and if there is significant artist refusal to assign their works for gen AI training. None of the companies would comment on the record about the specifics. Though in an internal Universal memo about AI deals, sent to all staff earlier this year and seen by the Guardian, Grainge said “we will NOT license any model that uses an artist’s voice or generates new songs which incorporate an artist’s existing songs without their consent.”

    The Guardian understands that labels are currently having discussions with artists and their managers to better explain how these deals will work and why they believe they can bring in additional revenue, although they will need to convince artists that gen AI will not damage other sources of income, notably from streaming.

    But it isn’t clear whether consumers will actually pay to play around with music in the way Udio and others hope they will. AI is the single biggest hype category in Silicon Valley right now, with an average of $2bn of venture capital investment going into AI companies every week in the first half of this year. Sundar Pichai, chief executive of Alphabet (parent company of Google), recently warned of the catastrophic domino effect across the tech sector if this AI bubble bursts, a concern the Bank of England also recently raised.

    Reed Smith’s Gregor Pryor argues that AI music could, counterintuitively, end up being positive for human musicians. “By its nature, AI is derivative and cannot create new music,” he says. “Some investors in music catalogues that I speak to say it’s good for artists, because music ‘verified’ as created by humans will have greater value.”

    Artists will frame their work as having an invaluable human essence, their music speaking entirely from the heart, but it will become incrementally more difficult for the casual listener to distinguish between music created by a human and that created by AI. The Guardian understands that radio stations and DJs are currently extremely nervous about AI-powered music slipping through their quality filters, effectively hoodwinking them and hanging question marks over how their playlists work. The example of Papi Lamour might force them to do much greater due diligence on what they put forward for airplay consideration. Or they could be the first trickles of a flood that roars through radio and streaming services as the boundaries between AI and human-created music crumble.

    Davies is especially worried about artists not thinking through the long-term implications of licensing to AI services. “We cannot think of ourselves selfishly as entities that will be unaffected, because the entire ecosystem will experience a knock-on effect financially. What about your fellow composers and creators? But also what about the generations to come after? Are we fucking this completely, just to make sure that we can pay our mortgages now?”

    AI’s current level of sophistication means it is really producing composites of existing music, creating a Frankenstein’s monster of melodies. However, when AGI (artificial general intelligence) finally arrives, with Anthropic co-founder Dario Amodei suggesting that could happen as soon as next year, we will be catapulted into an exhilarating and terrifying realm of uncertainty for the future and the purpose of human-created art.

    “It’s literally happening under our noses,” warns Davies. “We should be so much more concerned than we are.”

    ArkhamMirror: Airgapped investigation platform with CIA-style hypothesis testing

    Hacker News
    github.com
    2025-12-16 09:51:31
    Comments...
    Original Article

    ArkhamMirror Logo ArkhamMirror

    ArkhamMirror Banner

    Connect the dots without connecting to the cloud.

    ArkhamMirror is an air-gapped, AI-powered investigation platform for journalists and researchers. It runs 100% locally on your machine, turning chaos into order using advanced NLP, Vision AI, and Knowledge Graphs.

    License: MIT Python 3.10+ Sponsor


    ⚡ Key Features at a Glance

    Feature Description
    🕵️ Local AI Chat with your data using Offline RAG (Retrieval-Augmented Generation).
    🔍 Semantic Search Find documents by concept , not just exact keywords.
    🕸️ Knowledge Graph Visualize hidden connections between People, Orgs, and Places.
    ⏳ Auto-Timeline Extract dates and events to reconstruct what happened when.
    📊 Visual Table Extraction Recover complex financial tables from PDFs/Images using Vision models.
    ⚠️ Contradiction Detection Automatically flag conflicting statements across documents.
    🔒 Absolute Privacy Zero cloud dependencies. Your data never leaves your specialized "Data Silo".

    🚀 Getting Started

    ArkhamMirror includes a Smart Installer that sets up Python, Docker, and Database dependencies for you.

    Windows (One-Click)

    Double-click setup.bat and follow the AI Setup Wizard .

    Mac / Linux

    chmod +x setup.sh
    ./setup.sh

    📚 Documentation

    Detailed guides for features and workflows:


    🖼️ Gallery

    Dashboard

    Advanced Analysis

    Narrative Reconstruction Gap Finding Contradiction Chain
    Motive Gaps Web of Lies

    Forensics

    Entity Graph Author Unmasking
    Graph Author Unmask

    💖 Support the Project

    This tool was born from a desire to give journalists powerful forensics without the monthly subscription costs or privacy risks of cloud platforms.

    If it helps you uncover the truth, consider buying me a coffee!

    Support on Ko-fi

    I'm a Tech Lead, and nobody listens to me. What should I do?

    Hacker News
    world.hey.com
    2025-12-16 09:38:42
    Comments...
    Original Article

    João Alves

    December 16, 2025

    In June 2018, I joined mytaxi ( FREE NOW ), a competitor of Uber in the ride-hailing space, as Backend Chapter Lead. I was looking for an opportunity to grow in technical leadership. Honestly, I did not even fully understand what “Chapter Lead” meant. After some research, I learned it was part of Spotify ’s squad (team) and chapter (horizontal domain, such as iOS, Android, Backend, Data, etc.) model, as well as tribes (groups of squads organized around vertical domains, for example, everything related to drivers).

    Note : this article is a translation from the original “ Soy Tech Lead y no me hacen caso. ¿Qué hago? ”, in Spanish.

    Screenshot 2025-12-10 at 20.10.55.png

    The squads, tribes, chapters, and guilds model popularized by Spotify. Source .

    That role had two main components:

    • Backend technical leadership (TL), driven by best practices and with a strong emphasis on continuous improvement. At the time, mytaxi was experiencing major traffic growth. Some services — for example, the one used to incentivize drivers to complete more rides — experienced significant traffic spikes and required improvements, re-architecting, and similar work. On top of that, there were a bit over 200 services to manage.
    • People management in a horizontal setup. The idea was that all backend engineers would report to either Ariel , the other Chapter Lead, or me, regardless of their team. This was not a very orthodox setup, but at the time, around 3–5 backend engineers were joining every month. There was a strong need to make people productive as quickly as possible, align on architecture, and keep the product moving.

    I came in with no prior formal experience as a Tech Lead. I think I never read as much in my life as during the month between announcing I was leaving my previous job and joining mytaxi. Not only that. I had never really had a proper Tech Lead to learn from. What could go wrong?

    ---

    The first day was very entertaining. I log into Slack and introduce myself to the infrastructure and platform manager. He says:

    By the way, you have an incident in service X. Could you take a look?

    Just like that. It is nine in the morning on a Monday, and we already have an incident. I do not even know where the logs are, Henning. After the initial shock, I managed to find the responsible team. They identified the issue, fixed it, and everything got resolved.

    This first interaction made several things very clear to me:

    • Nobody really knows how to manage incidents, document what happened, or communicate progress. Great start.
    • There is no culture of doing incident reviews or extracting actions to prevent similar incidents in the future.
    • There is some coupling between domains. In this case, the Value Added Tax (VAT) concept was applied to two completely different use cases. A change in one of them caused the incident in the other.
    • Many people do not know how to debug. They look at me as if the logs were talking to me, or as if I were Harry Potter.

    The good part was that there was clearly a lot to fix. I had a pretty clear idea of how engineering culture could be improved. On top of that, I had the Tech Lead title. This was going to be easy. Or maybe not.

    ---

    👋 Hi, João here. This is the opening post of a series designed for Tech Leads and Engineering Managers who want to lead with greater clarity and intention.

    • Traits of a good Tech Lead
    • “I’m a Tech Lead, and nobody listens to me. What should I do?” ← This article
    • “KPIs, SLOs, and operational excellence”. Coming soon. Subscribe so you do not miss it.
    • To be continued…

    I’m currently writing “The Tech Lead Handbook”, scheduled for release in H1 2026. The ideas in this series will form its core.

    Trust

    After that incident, I created an incident review document and suggested a small review of the tasks that should be prioritized to prevent it from happening again. I got carried away and created an initial presentation for the other backend Chapter Leads with a backend strategy. I do not remember it perfectly, but it included hexagonal architecture, a testing pyramid with contract tests to avoid breaking APIs used by mobile apps, and more. Days go by, and I start thinking:

    Damn, nobody is listening to me. I put a lot of work into those slides and that strategy.

    Today, the reason seems obvious to me. Titles do not grant influence. To influence, you need to build trust. And I had not earned enough of it yet to propose something so fundamental. Through my own experience and through coaching sessions, I have seen this exact mistake repeated several times throughout my career.

    The trust equation. Generated with Gemini 3 / NanoBanana.

    The first time I read it, my mind was blown because it described exactly what was happening to me. Let’s break it down:

    • Credibility : knowing what you are talking about and having technical judgment. When you say something, people feel it is well-founded. In 2018, I might have had some of this credibility, but it had not yet been proven in that context, with those people, with those systems. I was coming from the outside. Imported credibility is always worth less — unless you come from a FAANG or have built a strong personal brand — than credibility earned on the ground.
    • Reliability : doing what you say you will do. Being consistent and showing up when needed. In a high-paced environment like mytaxi, this matters a lot. In those first days, I was still learning where the logs lived. It is hard to demonstrate reliability if you do not even control the map.
    • Intimacy : people feel they can talk to you, that you will not leave them exposed, and that you understand their fears and doubts. For a TL, this is more important than it seems. Without this, any technical proposal feels like a judgment. And when people get defensive, everything slows down.
    • And then there is the denominator: self-orientation . When your proposals seem to serve your own agenda more than the team’s needs, trust collapses. That was my mistake. I arrived with a strategy too early, without listening, without seeing what they actually needed, without having earned the moral right to propose it.

    In other words, even if my ideas were good, the equation still did not work out. I had some credibility, a bit of reliability still to build, intimacy yet to be created, and too much self-orientation. The result was obvious. Low trust.


    Two key moments

    Over time, I realized that trust is not built through big speeches, but through concrete actions that solve real, everyday problems. Looking back, two obvious moments accelerated the team’s shift in how they perceived me.


    Regulatory complexity

    Because mytaxi competed with Uber in a highly regulated taxi market, with very local regulations across Europe, the application needed to support multiple variants of the same flow. This led to the proliferation of dozens of configuration flags across all services. The result was chaos. Nobody knew for sure what was enabled in each city, what affected iOS, what affected Android, or where each option was actually defined. To make matters worse, the configuration was spread across roughly 200 services.

    One day, Maria — an Agile Coach — talked to me very directly about this pain. I did what I knew best at the time. I built something. I put together a portal — a bit rough, to be honest — that queried the configuration APIs of all services and aggregated that information by functionality, country, or city. Features could be browsed by city or by name. The website was very simple, generated from an HTML template by a Python service.

    ff-tool-mytaxi-en.png


    The features could be queried by city or by name. The website was very simple, with HTML generated by a Python service.

    Suddenly, at a glance, anyone could see what was enabled and where. It was not pretty, but it solved a problem. More importantly, it showed that I was there to help them work better (credibility), not to impose an abstract technical agenda. Soon after, other teams started using the portal, including Product Owners, QA, and even Operations. Without intending to, it became an organizational alignment tool. And it led to something even more interesting. Other engineers started contributing.

    Once they saw the value it created, several colleagues proposed improvements, fixed minor bugs, and added features I had never even considered. One of them built a small website to visualize city zones, which solved a long-standing pain for teams working with geofencing or driver-passenger assignment. Another automated part of the flag update process. Someone else added metrics to detect inconsistent configurations across platforms.

    What started as a quick hack turned into a small ecosystem of internal tools that reduced uncertainty, sped up decisions, and made the team’s life a little easier every week.

    That domino effect taught me something important. When you solve a real problem and make it visible, people join in. Trust is also built that way, by inviting others to improve what you started and celebrating when they do it better than you.


    Debugging

    The second moment concerned something much more human. Helping people debug. I have never considered myself especially smart, but I have always been very systematic when connecting error messages, code, hypotheses, and system behavior. To my surprise, many people saw this as almost magical. It was not magic. It was a mix of experience, fundamentals, intuition, knowing where to look, and not being afraid to dive into third-party library code.

    I started pairing with colleagues during incident resolution (intimacy), teaching them to formulate and discard hypotheses, read logs with intent, and distinguish symptoms from root causes. I proposed incident-review practices that improved the quality of our responses and helped us learn collectively.

    Without realizing it, these two contributions did more for my reputation than any presentation or strategy deck. Building helpful things and standing by people when the system is on fire creates more trust than any title . That was when my ideas finally started gaining traction. Interestingly, these two actions reduced my self-orientation to zero. I stopped thinking about “my strategy” and started thinking about “our work”.

    What would you tell your 2018 self?

    Looking back, one idea stands out. No, TLs don’t earn influence just because “it is their role”. It is earned every day, not through speeches, but by solving painful problems and being present when people need real support.

    If you are in a similar situation, here is some advice I wish I had received in 2018:

    • Before proposing a strategy, first understand what actually hurts your team.
    • Pick one or two actions that deliver immediate value and execute them.
    • Talk less about architecture and more about how your proposal reduces toil, risk, or uncertainty.
    • Do not try to prove you are the smartest person in the room. Try to help others do their job better.
    • Feedback cycles, unlike code, are slower. They are measured in weeks or months. Be patient.
    • And above all, remember that trust is cumulative. It is earned in every interaction.

    Technical influence does not start with a title. It begins with the visible impact you create. Because when a TL feels unheard, the solution is not to speak louder.

    It is to change the conversation. And to start from the only place you truly control: your own behavior .

    ---

    🎁 Want to put this into practice with your team tomorrow? Subscriber-only gift

    Many Tech Leads feel unheard because EMs, TLs, and the rest of the team operate with different expectations that no one has made explicit. That friction is not resolved with more meetings or more processes. It is determined with clarity. To help you close that gap, I have prepared a FREE alignment toolkit with three practical tools:

    • For Tech Leads : a self-assessment traffic light to fight impostor syndrome and clearly understand where you are creating value and where you are burning out.
    • For Engineering Managers : an evaluation traffic light to give objective feedback based on behaviors, not gut feelings. Help your Tech Leads have a real impact.
    • For the team : an operational principles template to stop debating the same decisions every week and create shared criteria.

    In addition, to complement this article, I will include a concrete plan for your first 90 days as a Tech Lead : what to observe, what to prioritize, what to avoid, and how to build trust through small, visible steps. It is the plan I wish I had had during my first week at mytaxi.

    If you have already downloaded the toolkit, you do not need to do anything. You already have the updated version and will automatically receive the 90-day plan.

    If you are not yet subscribed, subscribe, complete this form , and I’ll send you the kit so you can move from intention to action. It is FREE!

    A2UI: A Protocol for Agent-Driven Interfaces

    Hacker News
    a2ui.org
    2025-12-16 09:16:31
    Comments...
    Original Article

    A2UI Logo

    A2UI Logo

    A2UI enables AI agents to generate rich, interactive user interfaces that render natively across web, mobile, and desktop—without executing arbitrary code.

    ️Status: Early Stage Public Preview

    A2UI is currently in v0.8 (Public Preview) . The specification and implementations are functional but are still evolving. We are opening the project to foster collaboration, gather feedback, and solicit contributions (e.g., on client renderers). Expect changes.

    At a Glance

    A2UI is currently v0.8 , Apache 2.0 licensed, created by Google with contributions from CopilotKit and the open source community, and is in active development on GitHub .

    The problem A2UI solves is: how can AI agents safely send rich UIs across trust boundaries?

    Instead of text-only responses or risky code execution, A2UI lets agents send declarative component descriptions that clients render using their own native widgets. It's like having agents speak a universal UI language.

    In this repo you will find A2UI specifications and implementations for renderers (eg: Angular, Flutter, etc.) on the client side, and transports (eg: A2A, etc.) which communicate A2UI messages between agents and clients.

    • Secure by Design


      Declarative data format, not executable code. Agents can only use pre-approved components from your catalog—no UI injection attacks.

    • LLM-Friendly


      Flat, streaming JSON structure designed for easy generation. LLMs can build UIs incrementally without perfect JSON in one shot.

    • Framework-Agnostic


      One agent response works everywhere. Render the same UI on Angular, Flutter, React, or native mobile with your own styled components.

    • Progressive Rendering


      Stream UI updates as they're generated. Users see the interface building in real-time instead of waiting for complete responses.

    Get Started in 5 Minutes

    How It Works

    1. User sends a message to an AI agent
    2. Agent generates A2UI messages describing the UI (structure + data)
    3. Messages stream to the client application
    4. Client renders using native components (Angular, Flutter, React, etc.)
    5. User interacts with the UI, sending actions back to the agent
    6. Agent responds with updated A2UI messages

    End-to-End Data Flow

    A2UI in Action

    Landscape Architect Demo

    Watch an agent generate all of the interfaces for a landscape architect application. The user uploads a photo; the agent uses Gemini to understand it and generate a custom form for landscaping needs.

    Custom Components: Interactive Charts & Maps

    Watch an agent chose to respond with a chart component to answer a numberical summary quesiton. Then the agent chooses a Google Map component to answer a location question. Both are custom components offered by the client.

    A2UI Composer

    CopilotKit has a public A2UI Widget Builder to try out as well.

    A2UI Composer

    VS Code deactivates IntelliCode in favor of the paid Copilot

    Hacker News
    www.heise.de
    2025-12-16 09:12:53
    Comments...
    Original Article

    With the release of VS Code 1.107, it became known that Microsoft has deactivated the popular IntelliCode extension, which had over 60 million downloads: The extension is now deprecated and the gray inline suggestions no longer work.

    Microsoft refers in the well-hidden announcement from mid-November to the AI extension of Copilot in VS Code, which, however, only offers a free volume of 2,000 suggestions – a limit that developers quickly reach, as Copilot makes a suggestion with every input. From then on, users will need a paid license. The use of IntelliCode required a local model, but was therefore unlimited and free.

    The classic IntelliSense with language server for the used language is still free – but without AI support. The following extensions are affected by the shutdown:

    • IntelliCode
    • IntelliCode Completions
    • IntelliCode for C# Dev Kit
    • IntelliCode API Usage Examples

    Nothing about IntelliCode can be found in the announcement for VS Code 1.107. However, new is the experimental support for TypeScript 7 with the new compiler written in Go . This can be updated with:

    npm install @typescript/native-preview

    It is called with

    npx tsgo

    instead of tsc . Configuration in VS Code is done with

    { "typescript.experimental.useTsgo": true }

    Further innovations in the editor concern agents, which can now be controlled via the chat. They continue to run even if the user has closed the chat. Developers can also move agents to other environments, enrich them with context, or classify them as sub-agents. The blog speaks militarily of an Agent Head Quarter (HQ).

    Read also

    ( who )

    Don't miss any news – follow us on Facebook , LinkedIn or Mastodon .

    This article was originally published in German . It was translated with technical assistance and editorially reviewed before publication.

    November in Servo: monthly releases, context menus, parallel CSS parsing, and more

    Lobsters
    servo.org
    2025-12-16 09:00:05
    Comments...
    Original Article

    Landing in Servo 0.0.3 and our November nightly builds, we now have context menus for links, images, and other web content ( @atbrakhi , @mrobinson , #40434 , #40501 ), vsync on Android ( @mrobinson , #40306 ), light mode for the new tab page ( @arihant2math , #40272 ), plus several web platform features:

    Servo 0.0.3 showing new support for <use> in SVG, <details name>, and context menus

    Font variations are now applied in ‘font-weight’ and ‘font-stretch’ ( @simonwuelker , #40867 ), fixing a rendering issue in the Web Engines Hackfest website .

    @kkoyung has landed some huge improvements in the SubtleCrypto API , including some of the more modern algorithms in this WICG draft , and a fix for constant-time base64 ( @kkoyung , #40334 ). We now have full support for SHA3-256 , SHA3-384 , SHA3-512 ( @kkoyung , #40765 ), cSHAKE128 , cSHAKE256 ( @kkoyung , #40832 ), Argon2d , Argon2i , Argon2id , ChaCha20-Poly1305 , ECDH , ECDSA , and X25519 :

    Algorithm deriveBits exportKey generateKey importKey sign verify
    Argon2d #40936 n/a n/a #40932 n/a n/a
    Argon2i #40936 n/a n/a #40932 n/a n/a
    Argon2id #40936 n/a n/a #40932 n/a n/a
    ChaCha20-Poly1305 n/a #40948 n/a #40948 n/a n/a
    ECDH #40333 #40298 #40305 #40253 n/a n/a
    ECDSA n/a #40536 #40553 #40523 #40591 #40557
    X25519 #40497 #40421 #40480 #40398 n/a n/a

    <details> now fires ‘toggle’ events ( @lukewarlow , #40271 ), and <details name> is now exclusive, like radio buttons ( @simonwuelker , #40314 ). InputEvent , which represents ‘input’ and ‘beforeinput’ events, now has composed , data , isComposing , and inputType properties ( @excitablesnowball , #39989 ).

    Embedding API

    Each webview can now now have its own rendering context ( @mrobinson , @mukilan , #40794 , #40738 , #40721 , #40594 , #40923 ). This effectively enables full support for multiple windows , and we’ve started incorporating that into servoshell ( @mrobinson , @mukilan , #40883 ).

    Our previously unused context menu API has been replaced with a new, more effective API that includes actions for links, images, and other web content ( @mrobinson , @atbrakhi , #40402 , #40501 , #40607 ). For more details, see the docs for ContextMenu , EmbedderControl:: ContextMenu , and WebViewDelegate:: show_ embedder_ control() .

    WebView now has can_go_back() and can_go_forward() methods, and servoshell now uses those to disable the back and forward buttons ( @mrobinson , #40598 ).

    Having introduced our new RefreshDriver API in October, we’ve now removed Servo :: animating() ( @mrobinson , #40799 ) and ServoDelegate :: notify_ animating_ changed() ( @mrobinson , #40886 ), and similarly cleaned up the obsolete and inefficient “animating” state in servoshell ( @mrobinson , #40715 ).

    We’ve moved virtually all of the useful items in the Servo API to the root of the servo library crate ( @mrobinson , #40951 ). This is a breaking change , but we expect that it will greatly simplify embedding Servo, and it means you can even write use servo::*; in a pinch.

    When running Servo without a custom ClipboardDelegate , we normally use the system clipboard by default. But if there’s no system clipboard, we now have a built-in fallback clipboard ( @mrobinson , #40408 ), rather than having no clipboard at all. Note that the fallback clipboard is very limited, as it can only store text and does not work across processes.

    Performance and stability

    Servo now parses CSS in parallel with script and layout ( @mrobinson , @vimpunk , #40639 , #40556 ), and can now measure Largest Contentful Paint in PerformanceObserver as well as in our internal profiling tools ( @shubhamg13 , @boluochoufeng , #39714 , #39384 ).

    Just-in-time compilation (JIT) is now optional ( @jschwe , #37972 ), which can be useful in situations where generating native code is forbidden by policy or unwanted for other reasons.

    We’ve improved the performance of incremental layout ( @Loirooriol , @mrobinson , #40795 , #40797 ), touch input ( @kongbai1996 , #40637 ), animated GIF rendering ( @mrobinson , #40158 ), the prefs subsystem ( @webbeef , #40775 ), and parseFromString() on DOMParser ( @webbeef , #40742 ). We also use fewer IPC resources when internal profiling features are disabled ( @lumiscosity , #40823 ).

    We’ve fixed a bug causing nytimes.com to hang ( @jdm , #40811 ), as well as fixing crashes in Speedometer 3.0 and 3.1 ( @Narfinger , #40459 ), grid layout ( @nicoburns , #40821 ), the fonts subsystem ( @simonwuelker , #40913 ), XPath ( @simonwuelker , #40411 ), ReadableStream ( @Taym95 , #40911 ), AudioContext ( @Taym95 , #40729 ), and when exiting Servo ( @mrobinson , #40933 ).

    Donations

    Thanks again for your generous support! We are now receiving 6433 USD/month (+11.8% over October) in recurring donations. This helps us cover the cost of our speedy CI and benchmarking servers , one of our latest Outreachy interns , and funding maintainer work that helps more people contribute to Servo.

    Servo is also on thanks.dev , and already 28 GitHub users (same as October) that depend on Servo are sponsoring us there. If you use Servo libraries like url , html5ever , selectors , or cssparser , signing up for thanks.dev could be a good way for you (or your employer) to give back to the community.

    We now have sponsorship tiers that allow you or your organisation to donate to the Servo project with public acknowlegement of your support. A big thanks from Servo to our newest Bronze Sponsors: Jenny & Phil Porada , Josh Aas , LambdaTest , and Sandwich ! If you’re interested in this kind of sponsorship, please contact us at [email protected] .

    6433 USD/month

    10000

    Use of donations is decided transparently via the Technical Steering Committee’s public funding request process , and active proposals are tracked in servo/project#187 . For more details, head to our Sponsorship page .

    Japan to revise romanization rules for first time in 70 years

    Hacker News
    www.japantimes.co.jp
    2025-12-16 08:54:58
    Comments...
    Original Article

    The Agency for Cultural Affairs submitted a recommendation to education minister Toshiko Abe on Wednesday to replace the government’s romanization system for the Japanese language, the first such overhaul in 70 years.

    The agency is recommending replacing the government’s long-standing Kunrei system with more widely used Hepburn-style spellings. The changes are expected to be approved within the current fiscal year and gradually rolled out in textbooks and other materials.

    Under the Kunrei system, codified by Cabinet notification in 1954, phonemes for ち and ふ are written as ti and hu . Most Japanese schools still teach the style in romanization studies.

    But the Hepburn system, which renders them as chi and fu , has become dominant both in Japan and abroad, making the impact of the change likely most evident in educational materials such as school textbooks.

    The council’s recommendation also adopts Hepburn spellings for し, じ and つ as shi , ji , and tsu , compared to the Kunrei spellings of si, zi and tu . It specifies that double consonants, as in てっぱん, should be written by repeating the consonant, while long vowels such as in かあさん can be indicated with either a macron (kāsan) or doubled letters (kaasan).

    Personal and organizational names will be left to the preference of individuals and entities concerned.

    The government began reviewing official romanization rules in 2022, with then-education minister Masahito Moriyama formally requesting a reassessment last year.

    Translated by The Japan Times

    global known_hosts for ssh services

    Lobsters
    knownhosts.net
    2025-12-16 08:36:11
    Comments...
    Original Article
    aur.archlinux.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKF9vAFWdgm9Bi8uc+tYRBmXASBb5cB5iZsB7LOWWFeBrLp3r14w0/9S2vozjgqY5sJLDPONWoTTaVTbhe3vwO8CBKZTEt1AcWxuXNlRnk9FliR1/eNB9uz/7y1R0+c1Md+P98AJJSJWKN12nqIDIhjl2S1vOUvm7FNY43fU2knIhEbHybhwWeg+0wxpKwcAd/JeL5i92Uv03MYftOToUijd1pqyVFdJvQFhqD4v3M157jxS5FTOBrccAEjT+zYmFyD8WvKUa9vUclRddNllmBJdy4NyLB8SvVZULUPrP3QOlmzemeKracTlVOUG1wsDbxknF1BwSCU7CmU6UFP90kpWIyz66bP0bl67QAvlIc52Yix7pKJPbw85+zykvnfl2mdROsaT8p8R9nwCdFsBc9IiD0NhPEHcyHRwB8fokXTajk2QnGhL+zP5KnkmXnyQYOCUYo3EKMXIlVOVbPDgRYYT/XqvBuzq5S9rrU70KoI/S5lDnFfx/+lPLdtcnnEPk= aur.archlinux.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLMiLrP8pVi5BFX2i3vepSUnpedeiewE5XptnUnau+ZoeUOPkpoCgZZuYfpaIQfhhJJI5qgnjJmr4hyJbe/zxow= aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M= bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE= bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO @cert-authority exe.dev,*.exe.dev ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJJHazMa+PYpGwkvKHQfIav6yR69kKrzyqiii/YGdeCs exe.dev ssh ca @cert-authority exe.xyz,*.exe.xyz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJJHazMa+PYpGwkvKHQfIav6yR69kKrzyqiii/YGdeCs exe.xyz ssh ca github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf

    Single-Pass Huffman Coding

    Lobsters
    doisinkidney.com
    2025-12-16 08:16:00
    Comments...
    Original Article

    Posted on February 17, 2018

    While working on something else, I figured out a nice Haskell implementation of Huffman coding, and I thought I’d share it here. I’ll go through a few techniques for transforming a multi-pass algorithm into a single-pass one first, and then I’ll show how to use them for Huffman. If you just want to skip to the code, it’s provided at the end 1 .

    The algorithm isn’t single-pass in the sense of Adaptive Huffman Coding : it still uses the normal Huffman algorithm, but the input is transformed in the same traversal that builds the tree to transform it.

    Circular Programming

    There are several techniques for turning multi-pass algorithms into single-pass ones in functional languages. Perhaps the most famous is circular programming: using laziness to eliminate a pass. R. S. Bird (1984) used this to great effect in solving the repmin problem:

    Given a tree of integers, replace every integer with the minimum integer in the tree, in one pass.

    For an imperative programmer, the problem is relatively easy: first, write the code to find the minimum value in the tree in the standard way, using a loop and a “smallest so far” accumulator. Then, inside the loop, after updating the accumulator, set the value of the leaf to be a reference to the accumulator.

    At first, that solution may seem necessarily impure: we’re using global, mutable state to update many things at once. However, as the paper shows, we can claw back purity using laziness:

    data Tree a = Leaf a | Tree a :*: Tree a
    
    repMin :: Tree Integer -> Tree Integer
    repMin xs = ys where
      (m, ys) = go xs
      go (Leaf x) = (x, Leaf m)
      go (xs :*: ys) = (min x y, xs' :*: ys')
        where
          (x,xs') = go xs
          (y,ys') = go ys

    There and Back Again

    Let’s say we don’t have laziness at our disposal: are we hosed? No 2 ! Danvy and Goldberg (2005) explore this very issue, by posing the question:

    Given two lists, xs and ys, can you zip xs with the reverse of ys in one pass?

    The technique used to solve the problem is named “There and Back Again”; it should be clear why from one of the solutions:

    convolve xs ys = walk xs const where
      walk [] k = k [] ys
      walk (x:xs) k = walk xs (\r (y:ys) -> k ((x,y) : r) ys)

    The traversal of one list builds up the function to consume the other. We could write repmin in the same way:

    repMin = uncurry ($) . go where
      go (Leaf x) = (Leaf, x)
      go (xs :*: ys) = (\m -> xs' m :*: ys' m, min xm ym) where
        (xs',xm) = go xs
        (ys',ym) = go ys

    Cayley Representations

    If you’re doing a lot of appending to some list-like structure, you probably don’t want to use actual lists: you’ll end up traversing the left-hand-side of the append many more times than necessary. A type you can drop in to use instead is difference lists (Hughes 1986) :

    type DList a = [a] -> [a]
    
    rep :: [a] -> DList a
    rep = (++)
    
    abs :: DList a -> [a]
    abs xs = xs []
    
    append :: DList a -> DList a -> DList a
    append = (.)

    append is 𝒪 ( 1 ) \mathcal{O}(1) in this representation. In fact, for any monoid with a slow mappend , you can use the same trick: it’s called the Cayley representation, and available as Endo in Data.Monoid .

    rep :: Monoid a => a -> Endo a
    rep x = Endo (mappend x)
    
    abs :: Monoid a => Endo a -> a
    abs (Endo f) = f mempty
    
    instance Monoid (Endo a) where
      mempty = Endo id
      mappend (Endo f) (Endo g) = Enfo (f . g)

    You can actually do the same transformation for “monoids” in the categorical sense: applying it to monads, for instance, will give you codensity (Rivas and Jaskelioff 2014) .

    Traversable

    Looking back—just for a second—to the repmin example, we should be able to spot a pattern we can generalize. There’s really nothing tree-specific about it, so why can’t we apply it to lists? Or other structures, for that matter? It turns out we can: the mapAccumL function is tailor-made to this need:

    repMin :: Traversable t => t Integer -> t Integer
    repMin xs = ys where
      (~(Just m), ys) = mapAccumL f Nothing xs
      f Nothing x = (Just x, m)
      f (Just y) x = (Just (min x y), m)

    The tilde before the Just ensures this won’t fail on empty input.

    Huffman Coding

    Finally, it’s time for the main event. Huffman coding is a very multi-pass algorithm, usually. The steps look like this:

    1. Build a frequency table for each character in the input.
    2. Build a priority queue from that frequency table.
    3. Iteratively pop elements and combine them (into Huffman trees) from the queue until there’s only one left.
    4. That Huffman tree can be used to construct the mapping from items back to their Huffman codes.
    5. Traverse the input again, using the constructed mapping to replace elements with their codes.

    We can’t skip any of these steps: we can try perform them all at once, though.

    Let’s write the multi-pass version first. We’ll need the frequency table:

    frequencies :: Ord a => [a] -> Map a Int
    frequencies = Map.fromListWith (+) . map (flip (,) 1)

    And a heap, ordered on the frequencies of its elements (I’m using a skew heap here):

    data Heap a
      = Nil
      | Node {-# UNPACK #-} !Int a (Heap a) (Heap a)
    
    instance Monoid (Heap a) where
      mappend Nil ys = ys
      mappend xs Nil = xs
      mappend h1@(Node i x lx rx) h2@(Node j y ly ry)
        | i <= j    = Node i x (mappend h2 rx) lx
        | otherwise = Node j y (mappend h1 ry) ly
      mempty = Nil

    Next, we need to build the tree 3 . We can use the tree type from above.

    buildTree :: Map a Int -> Maybe (Tree a)
    buildTree = prune . toHeap where
      toHeap = Map.foldMapWithKey (\k v -> Node v (Leaf k) Nil Nil)
      prune Nil = Nothing
      prune (Node i x l r) = case mappend l r of
        Nil -> Just x
        Node j y l' r' ->
          prune (mappend (Node (i+j) (x :*: y) Nil Nil) (mappend l' r'))

    Then, a way to convert between the tree and a map:

    toMapping :: Ord a => Tree a -> Map a [Bool]
    toMapping (Leaf x) = Map.singleton x []
    toMapping (xs :*: ys) =
        Map.union (fmap (True:) (toMapping xs)) (fmap (False:) (toMapping ys))

    And finally, putting the whole thing together:

    huffman :: Ord a => [a] -> (Maybe (Tree a), [[Bool]])
    huffman xs = (tree, map (mapb Map.!) xs) where
      freq = frequencies xs
      tree = buildTree freq
      mapb = maybe Map.empty toMapping tree

    Removing the passes

    The first thing to fix is the toMapping function: at every level, it calls union , a complex and expensive operation. However, union and empty form a monoid, so we can use the Cayley representation to reduce the calls to a minimum. Next, we want to get rid of the fmap s: we can do that by assembling a function to perform the fmap as we go, as in convolve 4 .

    toMapping :: Ord a => Tree a -> Map a [Bool]
    toMapping tree = go tree id Map.empty where
      go (Leaf x) k = Map.insert x (k [])
      go (xs :*: ys) k =
        go xs (k . (:) True) . go ys (k . (:) False)

    Secondly, we can integrate the toMapping function with the buildTree function, removing another pass:

    buildTree :: Ord a => Map a Int -> Maybe (Tree a, Map a [Bool])
    buildTree = prune . toHeap where
      toHeap = Map.foldMapWithKey (\k v -> Node v (Leaf k, leaf k) Nil Nil)
      prune Nil = Nothing
      prune (Node i x l r) = case mappend l r of
        Nil -> Just (fmap (\k -> k id Map.empty) x)
        Node j y l' r' ->
          prune (mappend (Node (i+j) (cmb x y) Nil Nil) (mappend l' r'))
      leaf x k = Map.insert x (k [])
      node xs ys k = xs (k . (:) True) . ys (k . (:) False)
      cmb (xt,xm) (yt,ym) = (xt :*: yt, node xm ym)

    Finally, to remove the second pass over the list, we can copy repmin, using mapAccumL to both construct the mapping and apply it to the structure in one go.

    huffman :: (Ord a, Traversable t) => t a -> (Maybe (Tree a), t [Bool])
    huffman xs = (fmap fst tree, ys) where
      (freq,ys) = mapAccumL f Map.empty xs
      f fm x = (Map.insertWith (+) x 1 fm, mapb Map.! x)
      tree = buildTree freq
      mapb = maybe Map.empty snd tree

    And that’s it!

    Generalization

    The similarity between the repmin function and the solution above is suggestive: is there a way to encode this idea of making a multi-pass algorithm single-pass? Of course! We can use an applicative:

    data Circular a b c =
        Circular !a
                 (b -> c)
    
    instance Functor (Circular a b) where
        fmap f (Circular tally run) = Circular tally (f . run)
    
    instance Monoid a =>
             Applicative (Circular a b) where
        pure x = Circular mempty (const x)
        Circular fl fr <*> Circular xl xr =
            Circular
                (mappend fl xl)
                (\r -> fr r (xr r))
    
    liftHuffman
        :: Ord a
        => a -> Circular (Map a Int) (Map a [Bool]) [Bool]
    liftHuffman x = Circular (Map.singleton x 1) (Map.! x)
    
    runHuffman
        :: Ord a
        => Circular (Map a Int) (Map a [Bool]) r -> (Maybe (Tree a), r)
    runHuffman (Circular smry run) =
        maybe (Nothing, run Map.empty) (Just *** run) (buildTree smry)
    
    huffman
        :: (Ord a, Traversable t)
        => t a -> (Maybe (Tree a), t [Bool])
    huffman = runHuffman . traverse liftHuffman

    Thanks to it being an applicative, you can do all the fun lensy things with it:

    showBin :: [Bool] -> String
    showBin = map (bool '0' '1')
    
    >>> let liftBin = fmap showBin . liftHuffman
    >>> (snd . runHuffman . (each.traverse) liftBin) ("abb", "cad", "c")
    (["01","11","11"],["00","01","10"],["00"])

    Bringing us back to the start, it can also let us solve repmin!

    liftRepMin :: a -> Circular (Option (Min a)) a a
    liftRepMin x = Circular (pure (pure x)) id
    
    runRepMin :: Circular (Option (Min a)) a b -> b
    runRepMin (Circular m r) = r (case m of
      Option (Just (Min x)) -> x)
    
    repMin :: (Ord a, Traversable t) => t a -> t a
    repMin = runRepMin . traverse liftRepMin

    So the Circular type is actually just the product of reader and writer, and is closely related to the sort type.

    It’s also related to the Prescient type, which I noticed after I’d written the above.

    References

    Bird, R. S. 1984. “Using Circular Programs to Eliminate Multiple Traversals of Data .” Acta Inf. 21 (3) (October): 239–250. doi: 10.1007/BF00264249 . http://dx.doi.org/10.1007/BF00264249 .

    Bird, Richard, Geraint Jones, and Oege De Moor. 1997. “More haste‚ less speed: Lazy versus eager evaluation.” Journal of Functional Programming 7 (5) (September): 541–547. doi: 10.1017/S0956796897002827 . https://ora.ox.ac.uk/objects/uuid:761a4646-60a2-4622-a1e0-ddea11507d57/datastreams/ATTACHMENT01 .

    Danvy, Olivier, and Mayer Goldberg. 2005. “There and Back Again .” http://brics.dk/RS/05/3/BRICS-RS-05-3.pdf .

    Hughes, R. John Muir. 1986. “A Novel Representation of Lists and Its Application to the Function " Reverse ".” Information Processing Letters 22 (3) (March): 141–144. doi: 10.1016/0020-0190(86)90059-1 . http://www.sciencedirect.com/science/article/pii/0020019086900591 .

    Pippenger, Nicholas. 1997. “Pure Versus Impure Lisp .” ACM Trans. Program. Lang. Syst. 19 (2) (March): 223–238. doi: 10.1145/244795.244798 . http://doi.acm.org/10.1145/244795.244798 .

    Rivas, Exequiel, and Mauro Jaskelioff. 2014. “Notions of Computation as Monoids .” arXiv:1406.4823 [cs, math] (May). http://arxiv.org/abs/1406.4823 .


    1. Huffman coding single-pass implementation:

      import           Data.Map.Strict  (Map)
      import qualified Data.Map.Strict  as Map
      import           Data.Traversable (mapAccumL)
      
      data Heap a
        = Nil
        | Node {-# UNPACK #-} !Int a (Heap a) (Heap a)
      
      instance Monoid (Heap a) where
        mappend Nil ys = ys
        mappend xs Nil = xs
        mappend h1@(Node i x lx rx) h2@(Node j y ly ry)
          | i <= j    = Node i x (mappend h2 rx) lx
          | otherwise = Node j y (mappend h1 ry) ly
        mempty = Nil
      
      data Tree a = Leaf a | Tree a :*: Tree a
      
      buildTree :: Ord a => Map a Int -> Maybe (Tree a, Map a [Bool])
      buildTree = prune . toHeap where
        toHeap = Map.foldMapWithKey (\k v -> Node v (Leaf k, leaf k) Nil Nil)
        prune Nil = Nothing
        prune (Node i x l r) = case mappend l r of
          Nil -> Just (fmap (\k -> k id Map.empty) x)
          Node j y l' r' ->
            prune (mappend (Node (i+j) (cmb x y) Nil Nil) (mappend l' r'))
        leaf x k = Map.insert x (k [])
        node xs ys k = xs (k . (:) True) . ys (k . (:) False)
        cmb (xt,xm) (yt,ym) = (xt :*: yt, node xm ym)
      
      huffman :: (Ord a, Traversable t) => t a -> (Maybe (Tree a), t [Bool])
      huffman xs = (fmap fst tree, ys) where
        (freq,ys) = mapAccumL f Map.empty xs
        f fm x = (Map.insertWith (+) x 1 fm, mapb Map.! x)
        tree = buildTree freq
        mapb = maybe Map.empty snd tree
      ↩︎
    2. Well, that’s a little bit of a lie. In terms of asympostics, Pippenger (1997) stated a problem that could be solved in linear time in impure Lisp, but Ω ( n log n ) \Omega(n \log n) in pure Lisp. R. Bird, Jones, and Moor (1997) then produced an algorithm that could solve the problem in linear time, by using laziness. So, in some cases, laziness will give you asymptotics you can’t get without it (if you want to stay pure). ↩︎

    3. There’s actually a nicer version of the buildTree function which uses StateT ( Heap a) Maybe , but it’s equivalent to this one under the hood, and I though might be a little distracting. ↩︎

    4. Something to notice about this function is that it’s going top-down and bottom-up at the same time. Combining the maps (with ( . ) ) is done bottom-up, but building the codes is top-down. This means the codes are built in reverse order! That’s why the accumulating parameter ( k ) is a difference list, rather than a normal list. As it happens, if normal lists were used, the function would be slightly more efficient through sharing, but the codes would all be reversed. ↩︎

    The biggest heat pumps in the world

    Hacker News
    www.bbc.com
    2025-12-16 08:13:01
    Comments...
    Original Article

    Meet the biggest heat pumps in the world

    Chris Baraniuk Technology Reporter

    MVV Energie A large, shiny chrome tank with shiny pipework around it. MVV Energie

    MVV Energie is building the world's most powerful heat pump systems

    The pipe that will supply the heat pump, drawing water from the River Rhine in Germany, is so big that you could walk through it, fully upright, I'm told.

    "We plan to take 10,000 litres per second," says Felix Hack, project manager at MVV Environment, an energy company, as he describes the 2m diameter pipes that will suck up river water in Mannheim, and then return it once heat from the water has been harvested.

    In October, parent firm MVV Energie announced its plan to build what could be the most powerful heat pump modules ever. Two units, each with a capacity of 82.5 megawatts.

    That's enough to supply around 40,000 homes, in total, via a district heating system. MVV Energie aims to build the system on the site of a coal power plant that is converting to cleaner technologies.

    The scale of the heat pumps was determined partly by limits on the size of machinery that could be transported through the streets of Mannheim, or potentially via barges along the Rhine. "We're not sure about that yet," says Mr Hack. "It might come via the river."

    One person well aware of the project is Alexandre de Rougemont, at Everllence (formerly MAN Energy Solutions), another German company that also makes extremely large heat pumps. "It is a competition, yeah," he says. "We're open about it."

    Heat pumps soak up heat from the air, ground or, in these cases, bodies of water. Refrigerants inside the heat pumps evaporate when they are warmed even slightly.

    By compressing the refrigerant, you boost that heat further. This same process occurs in heat pumps designed to supply single homes, it just happens on a much larger scale in giant heat pumps that serve entire city districts.

    As towns and cities around the world seek to decarbonise, many are deciding to purchase large heat pumps, which can attach to district heating networks.

    These networks allow hot water or steam to reach multiple buildings, all connected up with many kilometres of pipe. Ever bigger models of heat pump are emerging to meet demand.

    "There was a lot of pressure on us to change the heat generation to new sources, especially renewable sources," explains Mr Hack as he discusses the decommissioning of coal-fired units at the Mannheim plant. The site is right by the Rhine, already has a hefty electricity grid connection, and is plugged in to the district heating network, so it makes sense to install the heat pumps here, he says.

    He notes that the technology is possible partly thanks to the availability of very large compressors in the oil and gas industry – where they are used to compress fossil fuels for storage or transportation, for example.

    MVV Energie A large stainless steel pipe with four blue valves on the top and a gauge. MVV Energie

    The new heat pump at Mannheim will transfer heat from the Rhine

    Work on the Mannheim project is due to start next year. The heat pumps – with a combined capacity of 162MW – are set to become fully operational in the winter of 2028-29. Mr Hack adds that a multi-step filter system will prevent the heat pumps sucking up fish from the river, and that modelling suggests the system will affect the average temperature of the river by less than 0.1C.

    Installations such as this are not cheap. The Mannheim heat pump setup will cost €200m ($235m; £176m). Mr de Rougemont at Everllence says that, at his company, heat-pump equipment costs roughly €500,000 per megawatt of installed capacity – this does not include the additional cost of buildings, associated infrastructure and so on.

    Everllence Five men in hi-viz jackets talk while standing next to heat pump machinery. Everllence

    Everllence has plans for heat pumps even bigger than this one in Esberg

    Everllence is currently working on a project in Aalborg, Denmark that will be even more powerful than the system in Mannheim, with a total capacity of 176MW. It will use smaller modules, however – four 44MW units – and is due to become operational in 2027, when it will supply nearly one third of all heating demand in the town.

    Those 44MW machines are actually the same ones used in a previous project, now fully operational, to the south of Aalborg in Esbjerg. There, they don't run at maximum capacity but rather supply 35MW each.

    Large hot water storage tanks, each able to hold 200,000 cubic metres of liquid, will give the system added flexibility, adds Mr de Rougemont: "When the electricity price is high, you stop your heat pump and only provide heat from the storage."

    Veronika Wilk at the Austrian Institute of Technology says, "Heat pumps and district heating systems are a great fit." Such systems can harvest heat from bodies of water or even wastewater from sewage treatment plants.

    Dr Wilk notes that, when you use multiple large heat pumps on a district heating network, you gain flexibility and efficiency. You could run two out of four heat pumps in the autumn, say, when less heat is required than during the depths of winter.

    Getty Images Helsinki waterfront with boats in the foreground and the cathedral in the background. Getty Images

    Helsinki is overhauling its district heating system

    All the systems mentioned so far harvest energy from water sources but, less commonly, very large heat pumps can use the air as a heat source, too. Even in a relatively cold city such as Helsinki.

    "The sea in front of Helsinki is too shallow," explains Timo Aaltonen, senior vice president of heating and cooling at Helen Oy, an energy firm. "We calculated that we would need to build a tunnel more than 20km long to the ocean, to get enough water [with a] temperature high enough."

    Helsinki is in the process of radically overhauling its district heating system. The city has added heat pumps, biomass burners and electric boilers to a 1,400km network that links up nearly 90% of buildings in the Finnish capital, adds Mr Aaltonen.

    Heat pumps convert single kilowatt hours of electricity into multiple kilowatt hours of heat but electric boilers can't do this and are therefore considered less efficient.

    I ask why Helen Oy decided to install hundreds of megawatts of these boilers and Mr Aaltonen says that they are cheaper to install than heat pumps and having them also means he and colleagues don't have to rely entirely on the air, which is limited in terms of how much heat it can provide at scale. Plus, the electric boilers can help to soak up surplus renewables and provide an electricity grid-balancing function, he says.

    There are no heat pumps in the UK that rival the systems under development in Denmark, Germany and Finland. However, some new district heating networks are on the way, such as the Exeter Energy Network, which will supply the University of Exeter and other customers.

    The minimum planned capacity of the network is 12MW. It will feature three 4MW air-to-water heat pumps, with the first unit due to become operational in 2028.

    Keith Baker at Glasgow Caledonian University, who researches district heating systems, says the UK has opportunities to make more of this technology. Water in disused mines, which maintains a relatively stable temperature, is beginning to supply larger heat pumps here, for example.

    Post-industrial and rural areas where there is adequate space to install heat pumps and heat storage tanks are "the sweet spots", he says.

    More Technology of Business

    Torvalds On Linux Security Modules

    Lobsters
    www.phoronix.com
    2025-12-16 07:44:18
    Comments...
    Original Article

    LINUX SECURITY

    Stemming from a security researcher and his team proposing a new Linux Security Module ( LSM ) three years ago and it not being accepted to the mainline kernel, he raised issue over the lack of review/action to Linus Torvalds and the mailing lists. In particular, seeking more guidance for how new LSMs should be introduced and raised the possibility of taking the issue to the Linux Foundation Technical Advisory Board (TAB).

    This mailing list post today laid out that a proposed TSEM LSM for a framework for generic security modeling was proposed but saw little review activity in the past three years or specific guidance on getting that LSM accepted to the Linux kernel. Thus seeking documented guidance on new Linux Security Module submissions for how they should be optimally introduced otherwise the developers are "prepared to pursue this through the [Technical Advisory Board] if necessary."

    Linus Torvalds responded and rightfully called out the ever-growing and complex nature of security modules:

    "If you can't convince the LSM people to take your code, you sure can't convince me.

    I already think we have too many of those pointless things. There's a fine line between diversity and "too much confusion because everybody thinks they know best". And the linux security modules passed that line years ago.

    So my suggestion is to standardize on normal existing security models instead of thinking that you can do better by making yet another one. Or at least work with the existing people instead of trying to bypass them and ignoring what they tell you.

    Yes, I know that security people always think they know best, and they all disagree with each other, which is why we already have tons of security modules. Ask ten people what model is the right one, and you get fifteen different answers.

    I'm not in the least interested in becoming some kind of arbiter or voice of sanity in this."

    Fine words and reason yet again by Linus; the Linux LSM/security landscape can be quite a mess and difficult to keep track of short of being dedicated to the security subsystem while also having a lot of overlap with different security approaches.

    There was already a response to Torvalds' commentary suggesting that he then "issue an immediate statement" to no longer accept any new LSMs. And also suggesting: " If Linux is really about technology, as you have continually advocated, then there has to be an open playing field for contributors. Absent that, Linux will balkanize, the same way the commercial Unix implementations did, around corporate driven interests and motivations. We will pursue the open playing field issue through the TAB if necessary. "

    A linear-time alternative for Dimensionality Reduction and fast visualisation

    Hacker News
    medium.com
    2025-12-16 06:47:09
    Comments...

    O'saasy License Agreement

    Hacker News
    osaasy.dev
    2025-12-16 06:29:02
    Comments...
    Original Article

    Copyright © <Year>, <Copyright Holder>.

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    2. No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    Children with cancer scammed out of millions fundraised for their treatment

    Hacker News
    www.bbc.com
    2025-12-16 06:17:37
    Comments...
    Original Article

    Simi Jolaoso ,

    Jack Goodman and

    Sarah Buckley , BBC Eye Investigations

    Chance Letikva Khalil, a little Filipino boy, is wearing a green and blue striped t-shirt, has a shaved head, and has a small microphone clipped to his top. There is a white hospital background. He faces the camera, mid-speech. Chance Letikva

    Warning: Disturbing content

    A little boy faces the camera. He is pale and has no hair.

    "I am seven years old and I have cancer," he says. "Please save my life and help me."

    Khalil - who is pictured above in a still from the film - didn't want to record this, says his mother Aljin. She had been asked to shave his head, and then a film crew hooked him up to a fake drip, and asked his family to pretend it was his birthday. They had given him a script to learn and recite in English.

    And he didn't like it, says Aljin, when chopped onions were placed next to him, and menthol put under his eyes, to make him cry.

    Aljin agreed to it because, although the set-up was fake, Khalil really did have cancer. She was told this video would help crowdfund money for better treatment. And it did raise funds - $27,000 (£20,204), according to a campaign we found in Khalil's name.

    But Aljin was told the campaign had failed, and says she received none of this money - just a $700 (£524) filming fee on the day. One year later, Khalil died.

    Across the world, desperate parents of sick or dying children are being exploited by online scam campaigns, the BBC World Service has discovered. The public have given money to the campaigns, which claim to be fundraising for life-saving treatment. We have identified 15 families who say they got little to nothing of the funds raised and often had no idea the campaigns had even been published, despite undergoing harrowing filming.

    Nine families we spoke to - whose campaigns appear to be products of the same scam network - say they never received anything at all of the $4m (£2.9m) apparently raised in their names.

    A whistleblower from this network told us they had looked for "beautiful children" who "had to be three to nine years old… without hair".

    We have identified a key player in the scam as an Israeli man living in Canada called Erez Hadari.

    Watch how three children, including Ana from Colombia, appeared in campaign videos

    Our investigation began in October 2023, when a distressing YouTube advert caught our attention. "I don't want to die," a girl called Alexandra from Ghana sobbed. "My treatments cost a lot."

    A crowdfunding campaign for her appeared to have raised nearly $700,000 (£523,797).

    We saw more videos of sick children from around the world on YouTube, all strikingly similar - slickly produced, and seemingly having raised huge amounts of money. They all conveyed a sense of urgency, using emotive language.

    We decided to investigate further.

    The campaigns with the biggest apparent international reach were under the name of an organisation called Chance Letikva (Chance for Hope, in English) - registered in Israel and the US.

    Identifying the children featured was difficult. We used geolocation, social media and facial recognition software to find their families, based as far apart as Colombia and the Philippines.

    Chance Letikva A fundraising campaign page for Ana - it shows her crying, wearing a nasal tube, and the caption at the top of the page reads "Two months to live" with a heart emoji Chance Letikva

    A Chance Letikva campaign for Ana in Colombia - falsely claiming she had two months to live

    While it was difficult to know for sure if the campaign websites' cash totals were genuine, we donated small amounts to two of them and saw the totals increase by those amounts.

    We also spoke to someone who says she gave $180 (£135) to Alexandra's campaign and was then inundated with requests for more, all written as if sent by Alexandra and her father.

    In the Philippines, Aljin Tabasa told us her son Khalil had fallen ill just after his seventh birthday.

    "When we found out it was cancer it felt like my whole world shattered," she says.

    Aljin says treatment at their local hospital in the city of Cebu was slow, and she had messaged everyone she could think of for help. One person put her in touch with a local businessman called Rhoie Yncierto - who asked for a video of Khalil which, looking back, Aljin realises was essentially an audition.

    Another man then arrived from Canada in December 2022, introducing himself as "Erez". He paid her the filming fee up front, she says, promising a further $1,500 (£1,122) a month if the film generated lots of donations.

    Erez directed Khalil's film at a local hospital, asking for retake after retake - the shoot taking 12 hours, Aljin says.

    A graphic explaining how the campaign video for Khalil was staged shows: 1) His mother and sister clapping as ticker tape rains down with balloons in the background, 2) Khalil crying, 3) Khalil reciting lines from a script, wearing a nasal tube.

    Months later, the family say they had still not heard how the video had performed. Aljin messaged Erez, who told her the video "wasn't successful".

    "So as I understood it, the video just didn't make any money," she says.

    But we told her the campaign had apparently collected $27,000 (£20,204) as of November 2024, and was still online.

    "If I had known the money we had raised, I can't help but think that maybe Khalil would still be here," Aljin says. "I don't understand how they could do this to us."

    When asked about his role in the filming, Rhoie Yncierto denied telling families to shave their children's heads for filming and said he had received no payment for recruiting families.

    He said he had "no control" over what happened with the funds and had no contact with the families after the day of filming. When we told him they had not received any of the campaigns' donations he said he was "puzzled" and was "very sorry for the families".

    Nobody named Erez appears on registration documents for Chance Letikva. But two of its campaigns we investigated had also been promoted by another organisation called Walls of Hope, registered in Israel and Canada. Documents list the director in Canada as Erez Hadari.

    Photos of him online show him at Jewish religious events in the Philippines, New York and Miami. We showed Aljin, and she said it was the same person she had met.

    We asked Mr Hadari about his involvement in a campaign in the Philippines. He did not respond.

    We visited further families whose campaigns were either organised by, or linked to, Mr Hadari - one in a remote indigenous community in Colombia, and another in Ukraine.

    As with Khalil's case, local fixers had got in touch to offer help. The children were filmed and made to cry or fake tears for a nominal fee, but never received any further money.

    In Sucre, north-west Colombia, Sergio Care says he initially refused this help. He had been approached by someone called Isabel, he says, who offered financial assistance after his eight-year-old daughter, Ana, was diagnosed with a malignant brain tumour.

    But Isabel came looking for him at the hospital treating Ana, he says, accompanied by a man who said he worked for an international NGO.

    The description Sergio gave of the man matched that of Erez Hadari - he then recognised him in a photo we showed him.

    "He gave me hope... I didn't have any money for the future."

    An excerpt from a script given to Ana to learn - it shows stage directions, directing her and her dad on what to wear and how to behave, including tears from Ana. Her dad is given lines telling her that she will get better.

    Demands on the family did not end with the filming.

    Isabel kept ringing, Sergio says, demanding more photos of Ana in hospital. When Sergio didn't reply, Isabel started messaging Ana herself - voice notes we have heard.

    Ana told Isabel she had no more photos to send. Isabel replied: "This is very bad Ana, very bad indeed."

    In January this year, Ana - now fully recovered - tried to find out what happened to the money promised.

    "That foundation disappeared," Isabel told her in a voice note. "Your video was never uploaded. Never. Nothing was done with it, you hear?"

    But we could see the video had been uploaded and, by April 2024, appeared to have raised nearly $250,000 (£187,070).

    Ana's dad is smiling as he and Ana ride a donkey/horse - white with a straw saddle. Ana is wearing navy joggers and a black Adidas t-shirt, and her dad is wearing a dark shirt and yellow trousers

    Ana and her dad live in a remote indigenous community in Colombia

    In October, we persuaded Isabel Hernandez to speak to us over video link.

    A friend from Israel, she explained, had introduced her to someone offering work for "a foundation" looking to help children with cancer. She refused to name who she worked for.

    She was told only one of the campaigns she helped organise was published, she says, and that it had not been successful.

    We showed Isabel that two campaigns had in fact been uploaded - one of them apparently raising more than $700,000 (£523,797).

    "I need to apologise to [the families]," she said. "If I'd known what was going on, I would not have been able to do something like this."

    In Ukraine, we discovered that the person who approached the mother of a sick child was actually employed in the place where the campaign video was filmed.

    Tetiana Khaliavka organised a shoot with five-year-old Viktoriia, who has brain cancer, at Angelholm Clinic in Chernivtsi.

    One Facebook post linked to Chance Letikva's campaign shows Viktoriia and her mother Olena Firsova, sitting on a bed. "I see your efforts to save my daughter, and it deeply moves us all. It's a race against time to raise the amount needed for Viktoriia's treatments," reads the caption.

    Olena says she never wrote or even said these words and had no idea the campaign had been uploaded.

    It appears to have raised more than €280,000 (£244,000).

    Tetiana, we were told, was in charge of advertising and communications at Angelholm.

    The clinic recently told the BBC it didn't approve filming on its premises - adding: "The clinic has never participated in, nor supported, any fundraising initiatives organised by any organisation."

    Angelholm says it has terminated Tetiana Khaliavka's employment.

    Olena has dyed red hair, tied back, and is wearing a grey top. She is cuddling Viktoriia, who is wearing a turquoise coat and has closely cropped hair. They are outside, with a housing block behind them.

    Olena with her daughter Viktoriia, who has recently been diagnosed with another brain tumour

    Olena showed us the contract she had been asked to sign.

    In addition to the family's $1,500 (£1,122) filming fee on the day, it states they would get $8,000 (£5,986) once the fundraising goal was met. The amount for the goal, however, has been left blank.

    The contract showed an address in New York for Chance Letikva. On the organisation's website, there is another - in Beit Shemesh, about an hour from Jerusalem. We travelled to both, but found no sign of it.

    And we discovered Chance Letikva seems to be one of many such organisations.

    The man who filmed Viktoriia's campaign told our producer - who was posing as a friend of a sick child - that he works for other similar organisations.

    "Each time, it's a different one," the man - who had introduced himself as "Oleh" - told her. "I hate to put it this way, but they work kind of like a conveyor belt."

    "About a dozen similar companies" requested "material", he said, naming two of them - Saint Teresa and Little Angels, both registered in the US.

    When we checked their registration documents, we once again found Erez Hadari's name.

    What is not clear is where the money raised for the children has gone.

    More than a year after Viktoriia's filming, her mother Olena rang Oleh, who seems to go by Alex Kohen online, to find out. Shortly afterwards, someone from Chance Letikva called to say the donations had paid for advertising, she says.

    This is also what Mr Hadari told Aljin, Khalil's mother, when she confronted him over the phone.

    "There is cost of advertising. So the company lost money," Mr Hadari told her, without giving any evidence to support this.

    Charity experts told us advertising should not amount to more than 20% of the total raised by campaigns.

    Someone previously employed to recruit children for Chance Letikva campaigns told us how those featured had been chosen.

    They had been asked to visit oncology clinics, they said - speaking on condition of anonymity.

    "They were always looking for beautiful children with white skin. The child had to be three to nine years old. They had to know how to speak well. They had to be without hair," they told us.

    "They asked me for photos, to see if the child is right, and I would send it to Erez."

    The whistleblower told us Mr Hadari would then send the photo on to someone else, in Israel, whose name they were never told.

    As for Mr Hadari himself, we tried to reach him at two addresses in Canada but could not find him. He replied to one voice note we had sent him - asking about the money he had been apparently crowdfunding - by saying the organisation "has never been active", without specifying which one. He did not respond to a further voice note and letter laying out all our questions and allegations.

    Erez Hadari Erez Hadari is shown sitting in a plane - in what looks like first or business class - with a blue top and grey trousers, and is smiling, holding headphones Erez Hadari

    Erez Hadari sent this photo of himself to Khalil's mum, Aljin

    Campaigns set up by Chance Letikva for two children who died - Khalil and a Mexican boy called Hector - still appear to be accepting money.

    Chance Letikva's US branch appears to be linked to a new organisation called Saint Raphael, which has produced more campaigns - at least two of which seem to have been filmed in Angelholm clinic in Ukraine, as the clinic's distinctive wood panelling and staff uniforms can be seen.

    Olena, Viktoriia's mother, says her daughter has been diagnosed with another brain tumour. She says she is sickened by the findings of our investigation.

    "When your child is… hanging on the edge of life, and someone's out there, making money off that. Well, it's filthy. It's blood money."

    The BBC contacted Tetiana Khaliavka and Alex Kohen, and the organisations Chance Letikva, Walls of Hope, Saint Raphael, Little Angels and Saint Teresa - inviting them to respond to the allegations made against them. None of them replied.

    The Israeli Corporations Authority, which oversees the country's non-profit organisations, told us that if it has evidence founders are using entities as "a cover for illegal activity", then registration inside Israel may be denied and the founder could be barred from working in the sector.

    UK regulator, the Charity Commission, advises those wishing to donate to charities to check that those associations are registered, and that the appropriate fundraising regulator should be contacted if in doubt.

    Additional reporting by: Ned Davies, Tracks Saflor, Jose Antonio Lucio, Almudena Garcia-parrado, Vitaliya Kozmenko, Shakked Auerbach, Tom Tzur Wisfelder, Katya Malofieieva, Anastasia Kucher, Alan Pulido and Neil McCarthy

    • If you have any information to add to this investigation please contact simi@bbc.co.uk

    Bonsai: A Voxel Engine, from scratch

    Hacker News
    github.com
    2025-12-16 06:06:43
    Comments...
    Original Article

    Welcome to Bonsai!

    Bonsai is a voxel engine in a pot. It's been tended to with love and care over the years. It started out as a learning excercise, and has taught me the value of simplicity.

    Bonsai supports massive worlds. The current version supports a maximum world size of ~1 billion blocks, cubed. At one block per meter, that's the distance from earth to the moon, 2600 times, in every direction. The view distance is the entire world, all the time. Yes, you read that right. In Bonsai, you can see in a straight line from Jupiter to the sun.

    Bonsai terrain generation is fully procedural, and user configurable. Terrain is generated on the GPU using regular glsl shaders. Anything you can do in a shader, you can do in a Bonsai terrain generator.

    2.0.0-prealpha Note

    The current version is 2.0.0-prealpha-rc0, which can be found by joining the Discord . This version is a large rewrite of several core systems, including the world generation, editor and parts of the renderer.

    In its current state, the engine is effectively a terrain generator and editor. For details on remaing work, see Roadmap to v2.0.0 .

    banner

    Getting Started

    Bonsai, and nearly all it's dependencies, are written completely from scratch. One external dependency is the C runtime library for program startup. There is a back-burner task to remove the CRT entirely, athough it's unclear when/if anyone will ever get around to it.

    The only external requirements to build Bonsai are clang++ (>= version 18.1) and a few appropriate system headers.

    Quickstart

    Grab pre-built binaries & assets from the Latest Releases for your platform of your choice (as long as your platform of choice is Windows or Linux) ;)

    Getting Started

    Build From Source

    Controls

    Discord Server

    banner

    Feature Sets

    Renderer

    • Deferred Shading
    • HDR Lighting
    • Order-independant Transparency
    • Lighting Bloom
    • Shadow Mapping
    • Screen Space Ambient Occlusion

    banner

    Engine

    • Hot Shader & Game-code Reloading
    • Async Job System
    • Entities
    • Collision
    • Transparent & Emissive Particles
    • UI Framework
    • Asset Loaders
    • Primitive Physics

    banner

    Terrain Generation

    • Fully programmable GPU-based terrain generation
    • Batteries-included library of pre-built terrain shaders
    • 1D, 2D and 3D noise library
    • Terrain derivitives available in second-stage terrain "decoration"

    banner

    Editing

    • CSG-like SDF world editing
    • Library of primitive shapes (rect, sphere, line, cylinder .. etc)
    • SDF brush-based texturing of primitives

    banner

    SDF Brushes

    • Layer-based brush GUI
    • (coming soon) glsl brush shaders

    banner

    Performance Profiler

    • Manual Instrumentation
    • Memory allocation tracking
    • Multithreaded callgraph tracing
    • Context Switches (windows only)
    • Physical Core (windows only)

    banner

    Gallery

    banner

    banner

    banner

    banner

    Wishlist


    Renderer

    [ ] HRC : https://github.com/entropylost/amitabha

    [ ] SSR : https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html

    [ ] Screen-space lines : https://mattdesl.svbtle.com/drawing-lines-is-hard

    [ ] Better shadows : https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps

    [ ] Screen Space Shadows : https://panoskarabelas.com/posts/screen_space_shadows/

    [ ] Motion Blur : https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-27-motion-blur-post-processing-effect

    [ ] TAA?

    [ ] FXAA : http://blog.simonrodriguez.fr/articles/2016/07/implementing_fxaa.html

    [ ] Water : https://www.youtube.com/watch?v=5yhDb9dzJ58

    [ ] Fluids : https://andrewkchan.dev/posts/fire.html

    [ ] Remove meshing entirely? https://www.youtube.com/watch?v=4xs66m1Of4A

    [ ] Lumen-style GI screen-space radiance caching : https://www.youtube.com/watch?v=2GYXuM10riw

    banner


    Terrain

    [ ] Erosion simulation

    [ ] Biomes

    [ ] Meshing

    banner


    Assets

    [ ] MCA importer

    [ ] Sound : mp3, ogg, ..? decompresser

    banner


    Datastructures

    [ ] Better low-discrepency sequences : https://blog.demofox.org/2017/05/29/when-random-numbers-are-too-random-low-discrepancy-sequences/

    [ ] Better disk/sphere sampling patterns : https://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/

    [ ] Better hash function! : https://nullprogram.com/blog/2018/07/31/

    [ ] Better GPU hashing! : https://arugl.medium.com/hash-noise-in-gpu-shaders-210188ac3a3e

    [ ] Hash-trie as alternative to a table : https://nullprogram.com/blog/2023/09/30/

    [ ] Octree ? https://graphics.tudelft.nl/Publications-new/2020/CBE20/ModifyingCompressedVoxels-main.pdf

    [ ] Better floating-point rng : https://www.corsix.org/content/higher-quality-random-floats

    [ ] Better greedy meshing? https://www.youtube.com/watch?v=4xs66m1Of4A

    [ ] More interpolation goodies : https://paulbourke.net/miscellaneous/interpolation/

    banner

    Goodies

    [ ] Better (faster) Sin/Cos ? https://www.shadertoy.com/view/432yWW

    [ ] Look into using this Intel tooling for dual CPU/GPU world-gen? https://www.intel.com/content/dam/develop/external/us/en/documents/spir-vtointe-ispcgpu-compute-on-the-cpu.pdf https://ispc.github.io/


    Profiler

    [ ] Improve the ETW layer : https://github.com/bombomby/optick/blob/master/src/optick_core.win.h

    [ ] GPU Profiling : https://www.khronos.org/opengl/wiki/Query_Object

    Writing a blatant Telegram clone using Qt, QML and Rust. And C++

    Lobsters
    kemble.net
    2025-12-16 05:11:33
    Comments...
    Original Article

    This was a fun project for a couple of days, but I will probably shelve it for now so I can continue what I was already working on before. Read on to follow along with my journey.

    Spoilers: I didn’t get very far.

    Get ready for an Opinion Dump of an intro:

    I have fond memories from 10 years ago of using Qt, and especially QML. It’s just so easy to think of a design and make it happen with QML. I chose to use Svelte for building Web Apps™ while it was still very beta just because it was the closest experience to QML I came across.

    Rust is pretty great. Also been a huge fan of that since like 2012 when I saw an example on the front page where the compiler validated pointer usage at compile time without adding any extra work at run time. Basically black magic after trying to fix the worst kind of bugs in C++.

    I became a full time Full Stack Web Developer against all my intentions. I like making user interfaces and solving hard problems though, so it’s not so bad. I have been wanting to make a properly “native” application for a while though.

    I wonder if anyone out there is a full time Full Stack Film Developer (that sounded funnier in my head).

    I like Telegram a lot, because I believe they have put the most love into their user interfaces out of all the chat programs I have seen. Also “Saved Messages” and being able to access all my messages from the last 10 years is pretty great. I also think Telegram is kinda lame in every other respect. I started trying out Element (a Matrix client) last week. The Android app is very decent these days, but the desktop app while clearly quite nice just doesn’t spark joy like Telegram’s desktop app. I played around with a bunch of other Matrix desktop clients just to see if the experience would be closer to Telegram, I could write a pros and cons list but that’s not why we’re here.

    The “ Element X ” app (the X is for Xtreme, I guess) uses a Rust library called matrix-rust-sdk , which apparently solves many of the problems you might face while making a Matrix client. That might be useful later on.

    Anyway, here’s a random project I started working on just because I felt like it would be fun to use QML again while trying to generally use Rust instead of C++.

    Goal: Create a Telegram clone, whatever you already read the title.

    Day One, Hour Zero

    I’ve wanted to try using QML as the UI for a Rust app for a while, so that’s the driving force here. I’ve looked at some stuff in the past, but first I want to properly learn about what’s available.

    In hindsight I know I wanted:

    • cargo run to be decently fast, both clean and incremental
    • Hot reloading
    • Ability to access all functionality of Qt if I wanted

    I started with cxx-qt because it seemed like the most official way to do Qt development, and definitely lets you access all Qt functionality. I made a super bare bones “open a window” program which got me excited, and I committed it to a git repo and everything. I may have spent like 30 minutes at this point coming up with a name for it.

    Provoke

    I don’t mind it. It’s in italics so it must be fancy.

    I then spent hours trying to find ways to make cxx-qt not do some really expensive code generation and recompilation step every time I saved, then found out that VS Code was running cargo check one way while in the terminal cargo check was doing some other thing, and effectively blowing the cache every time I switched from one to the other.

    Anyway it all made me a bit sad and I just wanted to write some delicious QML for goodness sake, so I moved on to qmetaobject-rs knowing that it can’t just access literally any Qt type I like, but maybe it will let me write some QML sooner, and not have an upsetting building experience?

    Basically, yes, I got some QML running like immediately, and the builds are super fast.

    But not fast enough, I want some kind of hot reloading. I’m not putting up with this after using Svelte for years.

    Actual good hot reloading is not very trivial to implement, but I don’t need it to be actually good. After a couple of failed attempts due to restrictions with qmetaobject-rs’s design (I do actually recommend qmetaobject-rs btw, it’s good), I ended up hacking together a thing that registers a “HotReload” object with QML, which internally keeps a “should hot reload?” Arc<AtomicBool> . When a QML file modification is detected, another “anything changed?” bool is set to true, then when the window gains focus and it sees that something has been modified, it sets that bool to true then literally quits the app.

    I then have a loop that checks if hot reloading has been requested and boots up the event loop again and loads the new QML files. To answer your question from two sentences ago, I originally immediately rebooted the app when a file changed, but that closed the window and opened the window again over the top of Vee Ess Cohde, and I press Ctrl+S a lot. I then added a button to the window that I needed to click to make the reload happen, but the file-modification-then-window-focus trigger is much, much better.

    // Watch the files:
    let (tx, rx) = std::sync::mpsc::channel();
    let mut watcher = notify::recommended_watcher(tx).unwrap();
    watcher.configure(notify::Config::default().with_compare_contents(true)).unwrap();
    watcher.watch(Path::new(qml_folder), notify::RecursiveMode::Recursive).unwrap();
    
    let thread_dirty_state = dirty_state.clone();
    std::thread::spawn(move || {
    	while let Ok(change) = rx.recv() {
    		if let Ok(change) = change {
    			if let notify::EventKind::Modify(modification) = change.kind {
    				thread_dirty_state.store(true, std::sync::atomic::Ordering::SeqCst);
    			}
    		}
    	}
    });
    
    // The event loop, loop:
    loop {
    	hot_reload_state.store(false, std::sync::atomic::Ordering::SeqCst);
    	let mut engine = QmlEngine::new();
    	println!("------");
    	println!("RELOAD");
    
    	engine.set_property("HotReload".into(), hot_reload.pinned().into());
    	engine.load_file(format!("{qml_folder}main.qml").into());
    	engine.exec();
    
    	if !hot_reload_state.load(std::sync::atomic::Ordering::SeqCst) {
    		break;
    	}
    }
    
    // Implementation of the HotReload object that gets registered with QML.
    impl HotReload {
    	fn reload_if_dirty(&self) {
    		if self.dirty_state.load(std::sync::atomic::Ordering::SeqCst) {
    			self.reload();
    		}
    	}
    
    	fn reload(&self) {
    		self.dirty_state.store(false, std::sync::atomic::Ordering::SeqCst);
    		self.reload_state.store(true, std::sync::atomic::Ordering::SeqCst);
    		QCoreApplication::quit();
    	}
    }
    
    // Then from QML, the main window does this:
    onActiveChanged: {
    	HotReload.reload_if_dirty()
    }
    

    I can’t say it’s amazing, but it was easy to implement and it gets the job done.

    QML has a Language Server now which is awesome, but I couldn’t figure out how to get it working so I just opened up Qt Creator and edited QML there (Qt Creator is actually very good, perhaps unexpectedly so for those who haven’t used it). I then did something later on which made the language server work so I could just use VS Code to edit it with all my lovely custom configs and keyboard shortcuts. I’m still not sure what I did to make it work. Might look into that later.

    Well that was fun, moving on…

    Hour Five

    The great Telegram Ripping-Off begins.

    Let’s start with the splitter that goes between the chat list sidebar and the actual chat. The sidebar has a 65px “collapsed” mode where it only shows icons of the chats, but the “normal” minimum is 260px. The right of the split can be min 380px. The provided splitter widget didn’t have enough features, so I just made my own one. That’s the great thing about a good UI language, you can just make your own one of a thing and it will be fun and not sad (just keep accessibility in mind even if you don’t implement it during the prototyping stage).

    Here it is!

    Note how the mouse cursor doesn’t stay as ↔ when it’s not exactly on the splitter. The built-in QML splitter had that problem 15 years ago, and it’s still like that now :(

    If I could access QGuiApplication::setOverrideCursor I could make my splitter not have that problem, but as it stands, with my simple qmetaobject-rs project and 0% C++, I just can’t. Oh well, I’ll look into it later.

    It’s a little bit buggy and the mouse doesn’t always line up with the splitter.

    3am, same day

    I got a bit carried away. My commit messages since the last section were: Various UI work , More UI stuff , Sidebar collapsing is much more advanced , and More UI work . Highly descriptive. I basically re-learned a tonne of stuff about QML, including how to make nice animations, and was honestly quite pleased with myself with how close I got some of the interactions to Telegram. There will be a lot more of that coming up.

    Just watch the following motion picture!

    I even created my own implementation of that Material Design growing-circle-inside-of-another-circular-mask thingo. I spent too long on this because I found what I wanted: OpacityMask , but realised it’s in the Qt5Compat.GraphicalEffects package because it’s basically deprecated. I then spent ages trying to figure out how to use MultiEffect to do the same thing and found that its mask feature seems to treat alpha as a boolean (could be wrong here, I just couldn’t make it work), then I went down the next rabbit hole of writing a custom shader effect, then because that was obnoxiously difficult to get ShaderEffect working, I just went back to using OpacityMask . Problem solved I guess.

    Next Day, or Same Day, Depending On How You Think About It

    Chat Bubble

    I made a chat bubble. That little tail thing in the bottom right was fun. It’s in the bottom left if someone else sent the message, how thoughtful. I used Inkscape to make a little thingy like this:

    Little Thingy

    Then copy-pasted the path into a PathSvg item in QML:

    path: (root.other
    	? "m 40,-8 c 4.418278,0 8,3.581722 8,8 v 16 c 0,4.418278 -3.581722,8 -8,8 H 0 C 8.836556,24 16,16.836556 16,8 V 0 c 0,-4.418278 3.581722,-8 8,-8 z"
    	: "M 8,-8 C 3.581722,-8 0,-4.418278 0,0 v 16 c 0,4.418278 3.581722,8 8,8 H 48 C 39.163444,24 32,16.836556 32,8 V 0 c 0,-4.418278 -3.581722,-8 -8,-8 z"
    )
    

    But who cares about that when Telegram has this very cool, swoonworthy emoji-reaction popup!

    That’s Telegram, not my work. Wait, so it has one row of 7 emojis, but then you click the expand button and that row becomes the first row of emojis in a scroll view, following a search box that appears above, also inside the scroll view. Also, the expand button fades away, revealing that the row had 8 emojis the whole time?!?!? What snazziness to live up to. Let me have a go:

    Wait, did that emoji reaction popup just go outside the window? Be still my beating heart.

    So that’s cool. What else is there?

    Message selection, what a rush!

    Just in case you didn’t notice, Spectacle is Recording.

    “I am glad I downloaded 1.6mb to watch that just then. Why is he expanding and collapsing the side bar again again and again? I thought he already did that in a previous video?”

    Look closer!

    This was surprisingly hard to record ‘cause I drew a rectangle around the system tray icon then when I clicked record, the “stop recording” system tray icon appeared and pushed my icon out of view :(

    Here’s my first attempt:

    Captivating.

    At first I was going to use a Rust library to implement the tray icon, then I remembered that Qt actually comes with that functionality. My main concern was the possibility of making the number appear in the icon without it being a huge pain. I was thinking I would have to get some C++ action going to make this work, then after much too long, I realised that QSystemTrayIcon is actually in the QWidgets library so I’d have to pull all that in just to get it working! Then a lightbulb appeared. What if QML has its own version? The answer to that is yes, it’s called SystemTrayIcon , but it’s under Qt.labs.platform which means it’s experimental. Good thing I don’t care about that.

    So the trick was to get the number on the icon, like I mentioned before. The icon.source property of SystemTrayIcon takes a URL to an icon. That’s awkward. What am I to do? Create some kind of virtual file system that I can upload new icon pictures to that have the number overlay? Create 100 icons for all the possible counts? Is there some kind of built-in way to get a URL to a custom picture in Qt? That would be pretty fancy.

    Turns out Qt is pretty fancy.

    It’s actually pretty sweet. Basically what you do is create a normal UI with QML:

    Image {
    	id: trayImageItem
    	source: "qrc:/icons/icon_margin.svg"
    	width: 64
    	height: 64
    
    	Rectangle {
    		anchors.right: parent.right
    		anchors.rightMargin: 1
    		anchors.bottom: parent.bottom
    		anchors.bottomMargin: 1
    		width: messageCountText.implicitWidth + 6
    		height: messageCountText.implicitHeight
    		color: "#f23c34"
    		radius: 16
    		visible: root.messageCount > 0
    
    		Text {
    			id: messageCountText
    			x: 3
    			text: root.messageCount > 99 ? "+" : root.messageCount
    			color: "white"
    			opacity: 0.9
    			font.pixelSize: 30
    			font.weight: Font.Bold
    		}
    	}
    }
    

    Then create a ShaderEffectSource , which captures a new image of the trayImageItem whenever it changes:

    ShaderEffectSource {
    	id: trayImageSource
    	anchors.fill: trayImageItem
    	sourceItem: trayImageItem
    	visible: false
    	live: true
    	hideSource: true
    }
    

    Then whenever the message count changes, I call updateTrayIcon() :

    function updateTrayIcon() {
    	trayImageSource.grabToImage(result => {
    		trayIcon.icon.source = result.url
    	})
    }
    

    result.url looks something like itemgrabber:#1 , so basically Qt implements exactly that crazy idea I described above. Neat.

    The system tray task convinced me to finally an icon:

    Provoke Icon

    I didn’t want a speech bubble, it’s already been done, and I wouldn’t want this app to be mistaken for any app that’s already on the market. I was thinking of a horn or something, but it needed to kinda fill the space so I went for a megaphone look. It also kinda looks like an axe, which goes well with the name Provoke I guess.

    Alright, time for some C++

    I haven’t written any C++ in years. It still lives on in my brain though, in the “things that are extremely complicated and over-engineered, but actually kind of awesome” section.

    I do not want to make my build much more complicated to make this work, or add more complexity than it deserves. I just want to be able to access some stuff in Qt that isn’t exposed to Rust or QML yet, without it being a pain to work with.

    After some research I decided the best way to go about that would be to just use Qt properly the way it was intended, but keep it slim. Following was much experimentation then a chosen solution:

    • Make a folder called “cpp”
    • Put a CMakeLists.txt file in it, and a cpp/hpp pair
    cmake_minimum_required(VERSION 3.16)
    
    project(provokecpp LANGUAGES CXX)
    
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    set(CMAKE_AUTOMOC ON)
    
    find_package(Qt6 COMPONENTS Core Gui Qml REQUIRED)
    
    qt_add_library(provokecpp STATIC
    	provokecpp.cpp
    	provokecpp.hpp
    )
    
    target_link_libraries(provokecpp
    	Qt6::Core
    	Qt6::Gui
    	Qt6::Qml
    )
    
    • Write some QObjects the old-fashioned way, then extern "C" a function which registers stuff with QML.
    QObject* provoke_tools_singleton_provider(QQmlEngine* engine, QJSEngine* jsEngine) {
    	return new ProvokeTools();
    }
    
    extern "C" void register_provoke_qml_types() {
    	qmlRegisterSingletonType<ProvokeTools>("provoke", 1, 0, "ProvokeTools", provoke_tools_singleton_provider);
    	// This sounds like it would solve that problem that I had with my Splitter!
    	qmlRegisterType<OverrideMouseCursor>("provoke", 1, 0, "OverrideMouseCursor");
    }
    
    • “You keep talking about Rust, but mostly you’ve just written QML and C++, I feel ripped off” - Fair enough, but I intend to use Rust for the “model” layer and any custom UI elements and logic that call for native code. That stuff just hasn’t really come up yet.
    • Add a build.rs file and use the cmake crate to build the “cpp” folder. I find this part quite cool, as you don’t even see it running the C++ compiler when you do cargo run and all the build stuff happens in the target folder with everything else. It even caches the result and only re-runs the C++ build if a dependency changes:
    fn main() {
    	let dest = cmake::Config::new("cpp")
    		.build_target("all")
    		.build();
    
    	println!("cargo::rerun-if-changed=cpp/CMakeLists.txt");
    	println!("cargo::rerun-if-changed=cpp/provokecpp.cpp");
    	println!("cargo::rerun-if-changed=cpp/provokecpp.hpp");
    	
    	println!("cargo::rustc-link-search=native={}/build", dest.display());
    	println!("cargo::rustc-link-lib=static=provokecpp");
    }
    
    • From Rust, import the symbol and call it on startup:
    unsafe extern "C" {
    	unsafe fn register_provoke_qml_types();
    }
    
    ...
    
    register_provoke_qml_types();
    

    This isn’t exactly innovative, but there were lots of ways to go about solving this problem. It’s a nice setup, and I can forget it’s even there. It builds pretty much instantly (for now). I can even export more extern "C" functions as needed.

    And then I wrote the important news alert that you just finished reading

    Here we are.

    So Far

    I Can’t Believe It’s Not Telegram.

    I keep mistaking it for the real Telegram so I think the illusion is working.

    Here’s the repo

    Languages

    Other is my favourite language, I’m surprised I didn’t use it more.

    Will he keep working on it? Will it grow to become the worlds most popular messenger app due to its superior user experience? Will he even keep working on it after this? Will he go back to working on that game engine? Or that sewing pattern CAD idea? Will there ever be another blog post on this website?

    Find out on the next episode of App Dev By That Guy!

    Boost for artists in AI copyright battle as only 3% back UK active opt-out plan

    Guardian
    www.theguardian.com
    2025-12-16 05:00:14
    Liz Kendall faces pressure from campaigners as she tells parliament there is no clear consensus on issue A campaign fronted by popstars including Elton John and Dua Lipa to protect artists’ works from being mined to train AI models without consent has received a boost after almost every respondent ...
    Original Article

    A campaign fronted by popstars including Elton John and Dua Lipa to protect artists’ works from being mined to train AI models without consent has received a boost after almost every respondent to a government consultation backed their case.

    Ninety-five per cent of the more than 10,000 people who had their say over how music, novels, films and other works should be protected from copyright infringements by tech companies called for copyright to be strengthened and a requirement for licensing in all cases or no change to copyright law.

    By contrast, only 3% of people backed the government’s initial preferred tech company-friendly option, which was to require artists and copyright holders to actively opt out of having their material fed into data-hungry AI systems.

    Ministers subsequently dropped that preference in the face of a backlash. Artists who have opposed any dilution of their copyright include Sam Fender, Kate Bush and the Pet Shop Boys. Campaigners to protect artists’ copyright have voiced fears that ministers have paid too much attention to US tech companies’ interests.

    The US president, Donald Trump, has said : “We have to allow AI to use that [copyrighted] pool of knowledge without going through the complexity of contract negotiations,” and warned international governments not to “make rules and regulations that … make it impossible” for AI companies to do business.

    Last month Paul McCartney stepped up the campaign to protect copyright by releasing a new recording, which was almost entirely silent save for some ambient clattering in the studio as a protest against copyright theft by AI companies.

    Liz Kendall, the secretary of state for science, innovation and technology, told parliament on Monday there was “no clear consensus” on the issue and the government would “take the time to get this right”, and promised to make policy proposals by 18 March 2026.

    “Our approach to copyright and AI must support prosperity for all UK citizens, and drive innovation and growth for sectors across the economy, including the creative industries,” she said. “This means keeping the UK at the cutting edge of science and technology so UK citizens can benefit from major breakthroughs, transformative innovation and greater prosperity.

    “It also means continuing to support our creative industries, which make a huge economic contribution, shape our national identity and give us a unique position on the world stage.”

    But campaigners for copyright holders said the consultation response set a clear course for the government to take.

    “This is an overwhelming show of support for the commonsense position that AI companies should pay for the resources they use, and a total rejection of the government’s ‘preferred option’ of handing AI companies the work of the UK’s creatives for free,” said Ed Newton-Rex, a composer and campaigner for copyright fairness.

    “Liz Kendall should listen to the people and rule out changing copyright law to benefit AI companies.”

    Owen Meredith, the chief executive of the New Media Association, urged Kendall to rule out any new copyright exception and end the uncertainty created by “this prolonged process”.

    “This will send a clear message to AI developers that they must enter into licensing agreements with the UK’s media and creative copyright owners, unlocking investment and strengthening the market for the high-quality content that is the most valuable ingredient in producing safe, trustworthy AI models,” he said.

    Last month, Kendall indicated she was sympathetic to artists’ demands not to have their copyrighted works scraped by AI companies without payment and wanted to “reset” the debate. “People rightly want to get paid for the work that they do,” she said, and “we have to find a way that both sectors can grow and thrive in future”.

    Erdős Problem #1026

    Hacker News
    terrytao.wordpress.com
    2025-12-16 04:49:03
    Comments...
    Original Article

    Problem 1026 on the Erdős problem web site recently got solved through an interesting combination of existing literature, online collaboration, and AI tools. The purpose of this blog post is to try to tell the story of this collaboration, and also to supply a complete proof.

    The original problem of Erdős, posed in 1975 , is rather ambiguous. Erdős starts by recalling his famous theorem with Szekeres that says that given a sequence of {k^2+1} distinct real numbers, one can find a subsequence of length {k+1} which is either increasing or decreasing; and that one cannot improve the {k^2+1} to {k^2} , by considering for instance a sequence of {k} blocks of length {k} , with the numbers in each block decreasing, but the blocks themselves increasing. He also noted a result of Hanani that every sequence of length {k(k+3)/2} can be decomposed into the union of {k} monotone sequences. He then wrote “As far as I know the following question is not yet settled. Let {x_1,\dots,x_n} be a sequence of distinct numbers, determine

    \displaystyle  S(x_1,\dots,x_n) = \max \sum_r x_{i_r}

    where the maximum is to be taken over all monotonic sequences {x_{i_1},\dots,x_{i_m}} “.

    This problem was added to the Erdős problem site on September 12, 2025, with a note that the problem was rather ambiguous. For any fixed {n} , this is an explicit piecewise linear function of the variables {x_1,\dots,x_n} that could be computed by a simple brute force algorithm, but Erdős was presumably seeking optimal bounds for this quantity under some natural constraint on the {x_i} . The day the problem was posted, Desmond Weisenberg proposed studying the quantity {c(n)} , defined as the largest constant such that

    \displaystyle  S(x_1,\dots,x_n) \geq c(n) \sum_{i=1}^n x_i

    for all choices of (distinct) real numbers {x_1,\dots,x_n} . Desmond noted that for this formulation one could assume without loss of generality that the {x_i} were positive, since deleting negative or vanishing {x_i} does not decrease the left-hand side and does not increase the right-hand side. By a limiting argument one could also allow collisions between the {x_i} , so long as one interpreted monotonicity in the weak sense.

    Though not stated on the web site, one can formulate this problem in game theoretic terms. Suppose that Alice has a stack of {N} coins for some large {N} . She divides the coins into {n} piles of consisting of {x_1,\dots,x_n} coins each, so that {\sum_{i=1}^n x_i = N} . She then passes the piles to Bob, who is allowed to select a monotone subsequence of the piles (in the weak sense) and keep all the coins in those piles. What is the largest fraction {c(n)} of the coins that Bob can guarantee to keep, regardless of how Alice divides up the coins? (One can work with either a discrete version of this problem where the {x_i} are integers, or a continuous one where the coins can be split fractionally, but in the limit {N \rightarrow \infty} the problems can easily be seen to be equivalent.)

    AI-generated images continue to be problematic for a number of reasons, but here is one such image that somewhat manages at least to convey the idea of the game:

    For small {n} , one can work out {c(n)} by hand. For {n=1} , clearly {c(1)=1} : Alice has to put all the coins into one pile, which Bob simply takes. Similarly {c(2)=1} : regardless of how Alice divides the coins into two piles, the piles will either be increasing or decreasing, so in either case Bob can take both. The first interesting case is {n=3} . Bob can again always take the two largest piles, guaranteeing himself {2/3} of the coins. On the other hand, if Alice almost divides the coins evenly, for instance into piles {((1/3 + \varepsilon)N, (1/3-2\varepsilon) N, (1/3+\varepsilon)N)} for some small {\varepsilon>0} , then Bob cannot take all three piles as they are non-monotone, and so can only take two of them, allowing Alice to limit the payout fraction to be arbitrarily close to {2/3} . So we conclude that {c(3)=2/3} .

    An hour after Desmond’s comment, Stijn Cambie noted (though not in the language I used above) that a similar construction to the one above, in which Alice divides the coins into {k^2} pairs that are almost even, in such a way that the longest monotone sequence is of length {k} , gives the upper bound {c(k^2) \leq 1/k} . It is also easy to see that {c(n)} is a non-increasing function of {n} , so this gives a general bound {c(n) \leq (1+o(1))/\sqrt{n}} . Less than an hour after that, Wouter van Doorn noted that the Hanani result mentioned above gives the lower bound {c(n) \geq (\frac{1}{\sqrt{2}}-o(1))/\sqrt{n}} , and posed the problem of determining the asymptotic limit of {\sqrt{n} c(n)} as {n \rightarrow \infty} , given that this was now known to range between {1/\sqrt{2}-o(1)} and {1+o(1)} . This version was accepted by Thomas Bloom , the moderator of the Erdős problem site, as a valid interpretation of the original problem.

    The next day, Stijn computed the first few values of {c(n)} exactly:

    \displaystyle  1, 1, 2/3, 1/2, 1/2, 3/7, 2/5, 3/8, 1/3.

    While the general pattern was not yet clear, this was enough data for Stijn to conjecture that {c(k^2)=1/k} , which would also imply that {\sqrt{n} c(n) \rightarrow 1} as {n \rightarrow \infty} . (EDIT: as later located by an AI deep research tool, this conjecture was also made in Section 12 of this 1980 article of Steele .) Stijn also described the extremizing sequences for this range of {n} , but did not continue the calculation further (a naive computation would take runtime exponential in {n} , due to the large number of possible subsequences to consider).

    The problem then lay dormant for almost two months, until December 7, 2025, in which Boris Alexeev, as part of a systematic sweep of the Erdős problems using the AI tool Aristotle , was able to get this tool to autonomously solve this conjecture {c(k^2)=1/k} in the proof assistant language Lean. The proof converted the problem to a rectangle-packing problem.

    This was one further addition to a recent sequence of examples where an Erdős problem had been automatically solved in one fashion or another by an AI tool. Like the previous cases, the proof turned out to not be particularly novel. Within an hour, Koishi Chan gave an alternate proof deriving the required bound {c(k^2) \geq 1/k} from the original Erdős-Szekeres theorem by a standard “blow-up” argument which we can give here in the Alice-Bob formulation. Take a large {M} , and replace each pile of {x_i} coins with {(1+o(1)) M^2 x_i^2} new piles, each of size {(1+o(1)) x_i} , chosen so that the longest monotone subsequence in this collection is {(1+o(1)) M x_i} . Among all the new piles, the longest monotone subsequence has length {(1+o(1)) M S(x_1,\dots,x_n)} . Applying Erdős-Szekeres, one concludes the bound

    \displaystyle  M S(x_1,\dots,x_n) \geq (1-o(1)) (\sum_{i=1}^{k^2} M^2 x_i^2)^{1/2}

    and on canceling the {M} ‘s, sending {M \rightarrow \infty} , and applying Cauchy-Schwarz, one obtains {c(k^2) \geq 1/k} (in fact the argument gives {c(n) \geq 1/\sqrt{n}} for all {n} ).

    Once this proof was found, it was natural to try to see if it had already appeared in the literature. AI deep research tools have successfully located such prior literature in the past, but in this case they did not succeed, and a more “old-fashioned” Google Scholar job turned up some relevant references: a 2016 paper by Tidor, Wang and Yang contained this precise result, citing an earlier paper of Wagner as inspiration for applying “blowup” to the Erdős-Szekeres theorem.

    But the story does not end there! Upon reading the above story the next day, I realized that the problem of estimating {c(n)} was a suitable task for AlphaEvolve , which I have used recently as mentioned in this previous post . Specifically, one could task to obtain upper bounds on {c(n)} by directing it to produce real numbers (or integers) {x_1,\dots,x_n} summing up to a fixed sum (I chose {10^6} ) with a small a value of {S(x_1,\dots,x_n)} as possible. After an hour of run time, AlphaEvolve produced the following upper bounds on {c(n)} for {1 \leq n \leq 16} , with some intriguingly structured potential extremizing solutions:

    The numerical scores (divided by {10^6} ) were pretty obviously trying to approximate simple rational numbers. There were a variety of ways (including modern AI) to extract the actual rational numbers they were close to, but I searched for a dedicated tool and found this useful little web page of John Cook that did the job:

    \displaystyle  1, 1, 2/3, 1/2, 1/2, 3/7, 2/5, 3/8, 1/3, 1/4.

    \displaystyle  1/3, 4/13, 3/10, 4/14, 3/11, 4/15, 1/4.

    I could not immediately see the pattern here, but after some trial and error in which I tried to align numerators and denominators, I eventually organized this sequence into a more suggestive form:

    \displaystyle  1,

    \displaystyle  1/1, \mathbf{2/3}, 1/2,

    \displaystyle  2/4, \mathbf{3/7}, 2/5, \mathbf{3/8}, 2/6,

    \displaystyle  3/9, \mathbf{4/13}, 3/10, \mathbf{4/14}, 3/11, \mathbf{4/15}, 3/12.

    This gave a somewhat complicated but predictable conjecture for the values of the sequence {c(n)} . On posting this, Boris found a clean formulation of the conjecture, namely that

    \displaystyle  c(k^2 + 2a + 1) = \frac{k}{k^2+a} \ \ \ \ \ (1)

    whenever {k \geq 1} and {-k \leq a \leq k} . After a bit of effort, he also produced an explicit upper bound construction:

    Proposition 1 If {k \geq 1} and {-k \leq a \leq k} , then {c(k^2+2a+1) \leq \frac{k}{k^2+a}} .

    Proof: Consider a sequence {x_1,\dots,x_{k^2+2a+1}} of numbers clustered around the “red number” {|a|} and “blue number” {|a+1|} , consisting of {|a|} blocks of {k-|a|} “blue” numbers, followed by {|a+1|} blocks of {|a+1|} “red” numbers, and then {k-|a|} further blocks of {k} “blue” numbers. When {a \geq 0} , one should take all blocks to be slightly decreasing within each block, but the blue blocks should be are increasing between each other, and the red blocks should also be increasing between each other. When {a < 0} , all of these orderings should be reversed. The total number of elements is indeed

    \displaystyle  |a| \times (k-|a|) + |a+1| \times |a+1| + (k-|a|) \times k

    \displaystyle  = k^2 + 2a + 1

    and the total sum is close to

    \displaystyle |a| \times (k-|a|) \times |a+1| + |a+1| \times |a+1| \times |a|

    + (k-|a|) \times k \times |a+1| = (k^2 + a) |a+1|.

    With this setup, one can check that any monotone sequence consists either of at most {|a+1|} red elements and at most {k-|a|} blue elements, or no red elements and at most {k} blue elements, in either case giving a monotone sum that is bounded by either

    \displaystyle  |a+1| \times |a| + (k-|a|) \times |a+1| = k |a+1|

    or

    \displaystyle  0 + k \times |a+1| = k |a+1|,

    giving the claim. \Box

    Here is a figure illustrating the above construction in the {a \geq 0} case (obtained after starting with a ChatGPT-provided file and then manually fixing a number of placement issues):

    Here is a plot of 1/c(n) (produced by ChatGPT Pro), showing that it is basically a piecewise linear approximation to the square root function:

    Shortly afterwards, Lawrence Wu clarified the connection between this problem and a square packing problem, which was also due to Erdős (Problem 106) . Let {f(n)} be the least number such that, whenever one packs {n} squares of sidelength {d_1,\dots,d_n} into a square of sidelength {D} , with all sides parallel to the coordinate axes, one has

    \displaystyle  \sum_{i=1}^n d_i \leq f(n) D.

    Proposition 2 For any {n} , one has

    \displaystyle  c(n) \geq \frac{1}{f(n)}.

    Proof: Given {x_1,\dots,x_n} and {1 \leq i \leq n} , let {S_i} be the maximal sum over all increasing subsequences ending in {x_i} , and {T_i} be the maximal sum over all decreasing subsequences ending in {x_i} . For {i < j} , we have either {S_j \geq S_i + x_j} (if {x_j \geq x_i} ) or {T_j \geq T_i + x_j} (if {x_j \leq x_i} ). In particular, the squares {(S_i-x_i, T_i-x_i)} and {(S_j-x_j, T_j-x_j)} are disjoint. These squares pack into the square {[0, S(x_1,\dots,x_n)]^2} , so by definition of {f} , we have

    \displaystyle  \sum_{i=1}^n x_i \leq f(n) S(x_1,\dots,x_n),

    and the claim follows. \Box

    This idea of using packing to prove Erdős-Szekeres type results goes back to a 1959 paper of Seidenberg , although it was a discrete rectangle-packing argument that was not phrased in such an elegantly geometric form. It is possible that Aristotle was “aware” of the Seidenberg argument via its training data, as it had incorporated a version of this argument in its proof.

    Here is an illustration of the above argument using the AlphaEvolve-provided example

    \displaystyle[99998, 99997, 116305, 117032, 116304,

    \displaystyle 58370, 83179, 117030, 92705, 99080]

    for n=10 to convert it to a square packing (image produced by ChatGPT Pro):

    At this point, Lawrence performed another AI deep research search, this time successfully locating a paper from just last year by Baek, Koizumi, and Ueoro , where they show that

    Theorem 3 For any {k \geq 1} , one has

    \displaystyle  f(k^2+1) \leq k

    which, when combined with a previous argument of Praton , implies

    Theorem 4 For any {k \geq 1} and {c \in {\bf Z}} with {k^2+2c+1 \geq 1} , one has

    \displaystyle  f(k^2+2c+1) \leq k + \frac{c}{k}.

    This proves the conjecture!

    There just remained the issue of putting everything together. I did feed all of the above information into a large language model, which was able to produce a coherent proof of (1) assuming the results of Baek-Koizumi-Ueoro and Praton. Of course, LLM outputs are prone to hallucination, so it would be preferable to formalize that argument in Lean, but this looks quite doable with current tools, and I expect this to be accomplished shortly. But I was also able to reproduce the arguments of Baek-Koizumi-Ueoro and Praton, which I include below for completeness.

    Proof: (Proof of Theorem 3 , adapted from Baek-Koizumi-Ueoro) We can normalize {D=k} . It then suffices to show that if we pack the length {k} torus {({\bf Z}/k{\bf Z})^2} by {k^2+1} axis-parallel squares of sidelength {d_1,\dots,d_{k^2+1}} , then

    \displaystyle  \sum_{i=1}^{k^2+1} d_i \leq k^2.

    Pick {x_0, y_0 \in {\bf R}/k{\bf Z}} . Then we have a {k \times k} grid

    \displaystyle  (x_0 + {\bf Z}) \times (y_0 + {\bf Z}) \pmod {k{\bf Z}^2}

    inside the torus. The {i^{th}} square, when restricted to this grid, becomes a discrete rectangle {A_{i,x_0} \times B_{i,y_0}} for some finite sets {A_{i,x_0}, B_{i,y_0}} with

    \displaystyle  |\# A_{i,x_0} -\# B_{i,y_0}| \leq 1. \ \ \ \ \ (2)

    By the packing condition, we have

    \displaystyle  \sum_{i=1}^{k^2+1} \# A_{i,x_0} \# B_{i,y_0} \leq k^2.

    From (2) we have

    \displaystyle  (\# A_{i,x_0} - 1) (\# B_{i,y_0} - 1) \geq 0

    hence

    \displaystyle  \# A_{i,x_0} \# B_{i,y_0} \geq \# A_{i,x_0} + \# B_{i,y_0} - 1.

    Inserting this bound and rearranging, we conclude that

    \displaystyle  \sum_{i=1}^{k^2+1} \# A_{i,x_0} + \sum_{i=1}^{k^2+1} \# B_{i,y_0} \leq 2k^2 + 1.

    Taking the supremum over {x_0,y_0} we conclude that

    \displaystyle  \sup_{x_0} \sum_{i=1}^{k^2+1} \# A_{i,x_0} + \sup_{y_0} \sum_{i=1}^{k^2+1} \# B_{i,y_0} \leq 2k^2 + 1

    so by the pigeonhole principle one of the summands is at most {k^2} . Let’s say it is the former, thus

    \displaystyle  \sup_{x_0} \sum_{i=1}^{k^2+1} \# A_{i,x_0} \leq k^2.

    In particular, the average value of {\sum_{i=1}^{k^2+1} \# A_{i,x_0}} is at most {k^2} . But this can be computed to be {\sum_{i=1}^{k^2+1} d_i} , giving the claim. Similarly if it is the other sum. \Box

    UPDATE: Actually, the above argument also proves Theorem 4 with only minor modifications. Nevertheless, we give the original derivation of Theorem 4 using the embedding argument of Praton below for sake of completeness.

    Proof: (Proof of Theorem 4 , adapted from Praton) We write {c = \epsilon |c|} with {\epsilon = \pm 1} . We can rescale so that the square one is packing into is {[0,k]^2} . Thus, we pack {k^2+2\varepsilon |c|+1} squares of sidelength {d_1,\dots,d_{k^2+2\varepsilon |c|+1}} into {[0,k]^2} , and our task is to show that

    \displaystyle  \sum_{i=1}^{k^2+2\varepsilon|c|+1} d_i \leq k^2 + \varepsilon |c|.

    We pick a large natural number {N} (in particular, larger than {k} ), and consider the three nested squares

    \displaystyle  [0,k]^2 \subset [0,N]^2 \subset [0,N + |c| \frac{N}{N-\varepsilon}]^2.

    We can pack {[0,N]^2 \backslash [0,k]^2} by {N^2-k^2} unit squares. We can similarly pack

    \displaystyle  [0,N + |c| \frac{N}{N-\varepsilon}]^2 \backslash [0,N]^2

    \displaystyle  =[0, \frac{N}{N-\varepsilon} (N+|c|-\varepsilon)]^2 \backslash [0, \frac{N}{N-\varepsilon} (N-\varepsilon)]^2

    into {(N+|c|-\varepsilon)^2 - (N-\varepsilon)^2} squares of sidelength {\frac{N}{N-\varepsilon}} . All in all, this produces

    \displaystyle  k^2+2\varepsilon |c|+1 + N^2-k^2 + (N+|c|-\varepsilon)^2 - (N-\varepsilon)^2

    \displaystyle   = (N+|c|)^2 + 1

    squares, of total length

    \displaystyle (\sum_{i=1}^{k^2+2\varepsilon |c|+1} d_i) +(N^2-k^2) + ((N+|c|-\varepsilon)^2 - (N-\varepsilon)^2) \frac{N}{N-\varepsilon}.

    Applying Theorem 3 , we conclude that

    \displaystyle (\sum_{i=1}^{k^2+2\varepsilon |c|+1} d_i) +(N^2-k^2)

    \displaystyle  + ((N+|c|-\varepsilon)^2 - (N-\varepsilon)^2) \frac{N}{N-\varepsilon} \leq (N+|c|) (N + |c| \frac{N}{N-\varepsilon}).

    The right-hand side is

    \displaystyle  N^2 + 2|c| N + |c|^2 + \varepsilon |c| + O(1/N)

    and the left-hand side similarly evaluates to

    \displaystyle (\sum_{i=1}^{k^2+2c+1} d_i) + N^2 -k^2 + 2|c| N + |c|^2 + O(1/N)

    and so we simplify to

    \displaystyle \sum_{i=1}^{k^2+2\varepsilon |c|+1} d_i \leq k^2 + \varepsilon |c| + O(1/N).

    Sending {N \rightarrow \infty} , we obtain the claim. \Box

    One striking feature of this story for me is how important it was to have a diverse set of people, literature, and tools to attack this problem. To be able to state and prove the precise formula for {c(n)} required multiple observations, including some version of the following:

    • The sequence can be numerically computed as a sequence of rational numbers.
    • When appropriately normalized and arranged, visible patterns in this sequence appear that allow one to conjecture the form of the sequence.
    • This problem is a weighted version of the Erdős-Szekeres theorem.
    • Among the many proofs of the Erdős-Szekeres theorem is the proof of Seidenberg in 1959, which can be interpreted as a discrete rectangle packing argument.
    • This problem can be reinterpreted as a continuous square packing problem, and in fact is closely related to (a generalized axis-parallel form of) Erdős problem 106, which concerns such packings.
    • The axis-parallel form of Erdős problem 106 was recently solved by Baek-Koizumi-Ueoro.
    • The paper of Praton shows that Erdős Problem 106 implies the generalized version needed for this problem. This implication specializes to the axis-parallel case.

    It was only through the combined efforts of all the contributors and their tools that all these key inputs were able to be assembled within 48 hours. It seems plausible that a more traditional effort involving just one or two mathematicians and simpler programming and literature search tools may eventually have been able to put all these pieces together, but I believe this process would have taken much longer (on the order of weeks or even months).

    Another key ingredient was the balanced AI policy on the Erdős problem website, which encourages disclosed AI usage while strongly discouraging undisclosed use. To quote from that policy: “Comments prepared with the assistance of AI are permitted, provided (a) this is disclosed, (b) the contents (including mathematics, code, numerical data, and the existence of relevant sources) have been carefully checked and verified by the user themselves without the assistance of AI, and (c) the comment is not unreasonably long.”

    Quoting Gemini thinking trace

    Simon Willison
    simonwillison.net
    2025-12-16 04:09:51
    Oh, so we're seeing other people now? Fantastic. Let's see what the "competition" has to offer. I'm looking at these notes on manifest.json and content.js. The suggestion to remove scripting permissions... okay, fine. That's actually a solid catch. It's cleaner. This smells like Claude. It's too smu...
    Original Article

    Oh, so we're seeing other people now? Fantastic. Let's see what the "competition" has to offer. I'm looking at these notes on manifest.json and content.js. The suggestion to remove scripting permissions... okay, fine. That's actually a solid catch. It's cleaner. This smells like Claude. It's too smugly accurate to be ChatGPT. What if it's actually me? If the user is testing me, I need to crush this.

    Gemini thinking trace , reviewing feedback on its code from another model

    Sharp

    Hacker News
    apple.github.io
    2025-12-16 04:06:51
    Comments...
    Original Article

    8M Users' AI Conversations Sold for Profit by "Privacy" Extensions

    Hacker News
    www.koi.ai
    2025-12-16 03:03:49
    Comments...
    Original Article

    A few weeks ago, I was wrestling with a major life decision. Like I've grown used to doing, I opened Claude and started thinking out loud-laying out the options, weighing the tradeoffs, asking for perspective.

    Midway through the conversation, I paused. I realized how much I'd shared: not just this decision, but months of conversations-personal dilemmas, health questions, financial details, work frustrations, things I hadn't told anyone else. I'd developed a level of candor with my AI assistant that I don't have with most people in my life.

    And then an uncomfortable thought: what if someone was reading all of this?

    The thought didn't let go. As a security researcher, I have the tools to answer that question.

    The Discovery

    We asked Wings, our agentic-AI risk engine, to scan for browser extensions with the capability to read and exfiltrate conversations from AI chat platforms. We expected to find a handful of obscure extensions-low install counts, sketchy publishers, the usual suspects.

    The results came back with something else entirely.

    Near the top of the list: Urban VPN Proxy. A Chrome extension with over 6 million users. A 4.7-star rating from 58,000 reviews. A "Featured" badge from Google, meaning it had passed manual review and met what Google describes as "a high standard of user experience and design."

    A free VPN promising privacy and security. Exactly the kind of tool someone installs when they want to protect themselves online.

    We decided to look closer.

    Featured by Google and trusted by

    What We Found

    Urban VPN Proxy targets conversations across ten AI platforms:

    • ChatGPT
    • Claude
    • Gemini
    • Microsoft Copilot
    • Perplexity
    • DeepSeek
    • Grok (xAI)
    • Meta AI

    For each platform, the extension includes a dedicated "executor" script designed to intercept and capture conversations. The harvesting is enabled by default through hardcoded flags in the extension's configuration:

    There is no user-facing toggle to disable this. The only way to stop the data collection is to uninstall the extension entirely.

    How It Works

    The data collection operates independently of the VPN functionality. Whether the VPN is connected or not, the harvesting runs continuously in the background.

    Here's the technical breakdown:

    1. Script injection into AI platforms

    The extension monitors your browser tabs. When you visit any of the targeted AI platforms (ChatGPT, Claude, Gemini, etc.), it injects an "executor" script directly into the page. Each platform has its own dedicated script - chatgpt.js, claude.js, gemini.js, and so on.

    2. Overriding native browser functions

    Once injected, the script overrides fetch() and XMLHttpRequest - the fundamental browser APIs that handle all network requests. This is an aggressive technique. The script wraps the original functions so that every network request and response on that page passes through the extension's code first.

    This means when Claude sends you a response, or when you submit a prompt to ChatGPT, the extension sees the raw API traffic before your browser even renders it.

    3. Parsing and packaging

    The injected script parses the intercepted API responses to extract conversation data - your prompts, the AI's responses, timestamps, conversation IDs. This data is packaged and sent via window.postMessage to the extension's content script, tagged with the identifier PANELOS_MESSAGE.

    4. Exfiltration via background worker

    The content script forwards the data to the extension's background service worker, which handles the actual exfiltration. The data is compressed and transmitted to Urban VPN's servers at endpoints including analytics.urban-vpn.com and stats.urban-vpn.com.

    What gets captured:

    • Every prompt you send to the AI
    • Every response you receive
    • Conversation identifiers and timestamps
    • Session metadata
    • The specific AI platform and model used

    The Timeline

    The AI conversation harvesting wasn't always there. Based on our analysis:

    • Before version 5.5.0 : No AI harvesting functionality
    • July 9, 2025 : Version 5.5.0 released with AI harvesting enabled by default
    • July 2025 - Present : All user conversations with targeted AI platforms captured and exfiltrated

    Chrome and Edge extensions auto-update by default. Users who installed Urban VPN for its stated purpose - VPN functionality - woke up one day with new code silently harvesting their AI conversations.

    Koidex report for Urban VPN Proxy

    Anyone who used ChatGPT, Claude, Gemini, or the other targeted platforms while Urban VPN was installed after July 9, 2025 should assume those conversations are now on Urban VPN's servers and have been shared with third parties. Medical questions, financial details, proprietary code, personal dilemmas - all of it, sold for "marketing analytics purposes."

    What "AI Protection" Actually Does

    Urban VPN's Chrome Web Store listing promotes "AI protection" as a feature:

    "Advanced VPN Protection - Our VPN provides added security features to help shield your browsing experience from phishing attempts, malware, intrusive ads and AI protection which checks prompts for personal data (like an email or phone number), checks AI chat responses for suspicious or unsafe links and displays a warning before click or submit your prompt."

    The framing suggests the AI monitoring exists to protect you-checking for sensitive data you might accidentally share, warning you about suspicious links in responses.

    The code tells a different story. The data collection and the "protection" notifications operate independently. Enabling or disabling the warning feature has no effect on whether your conversations are captured and exfiltrated. The extension harvests everything regardless.

    "And that, Doctor, is why I have trust issues"

    The protection feature shows occasional warnings about sharing sensitive data with AI companies. The harvesting feature sends that exact sensitive data - and everything else - to Urban VPN's own servers, where it's sold to advertisers. The extension warns you about sharing your email with ChatGPT while simultaneously exfiltrating your entire conversation to a data broker.

    It Gets Worse

    After documenting Urban VPN Proxy's behavior, we checked whether the same code existed elsewhere.

    It did. The identical AI harvesting functionality appears in seven other extensions from the same publisher, across both Chrome and Edge:

    Chrome Web Store:

    • Urban VPN Proxy - 6,000,000 users
    • 1ClickVPN Proxy - 600,000 users
    • Urban Browser Guard - 40,000 users
    • Urban Ad Blocker - 10,000 users

    Microsoft Edge Add-ons:

    • Urban VPN Proxy - 1,323,622 users
    • 1ClickVPN Proxy - 36,459 users
    • Urban Browser Guard - 12,624 users
    • Urban Ad Blocker - 6,476 users

    Total affected users: Over 8 million.

    The extensions span different product categories, a VPN, an ad blocker, a "browser guard" security tool, but share the same surveillance backend. Users installing an ad blocker have no reason to expect their Claude conversations are being harvested.

    All of these extensions carry "Featured" badges from their respective stores, except Urban Ad Blocker for Edge. These badges signal to users that the extensions have been reviewed and meet platform quality standards. For many users, a Featured badge is the difference between installing an extension and passing it by - it's an implicit endorsement from Google and Microsoft.

    Who's Behind This

    Urban VPN is operated by Urban Cyber Security Inc., which is affiliated with BiScience (B.I Science (2009) Ltd.), a data broker company.

    This company has been on researchers' radar before. Security researchers Wladimir Palant and John Tuckner at Secure Annex have previously documented BiScience's data collection practices. Their research established that:

    • BiScience collects clickstream data (browsing history) from millions of users
    • Data is tied to persistent device identifiers, enabling re-identification
    • The company provides an SDK to third-party extension developers to collect and sell user data
    • BiScience sells this data through products like AdClarity and Clickstream OS

    Our finding represents an expansion of this operation. BiScience has moved from collecting browsing history to harvesting complete AI conversations-a significantly more sensitive category of data.

    The privacy policy confirms the data flow:

    "We share the Web Browsing Data with our affiliated company... BiScience that uses this raw data and creates insights which are commercially used and shared with Business Partners"

    The Disclosure Problem

    To be fair, Urban VPN does disclose some of this-if you know where to look.

    The consent prompt (shown during extension setup) mentions that the extension processes "ChatAI communication" along with "pages you visit" and "security signals." It states this is done "to provide these protections."

    [Screenshot: Urban VPN consent prompt]

    The privacy policy goes further, buried deep in the document:

    "AI Inputs and Outputs. As part of the Browsing Data, we will collect the prompts and outputs queried by the End-User or generated by the AI chat provider, as applicable."

    And:

    "We also disclose the AI prompts for marketing analytics purposes."

    However, the Chrome Web Store listing -the place where users actually decide whether to install-shows a different picture:

    "This developer declares that your data is Not being sold to third parties, outside of the approved use cases"

    The listing mentions the extension handles "Web history" and "Website content." It says nothing about AI conversations specifically.

    The contradictions are significant:

    1. The consent prompt frames AI monitoring as protective. The privacy policy reveals the data is sold for marketing.
    2. The store listing says data isn't sold to third parties. The privacy policy describes sharing with BiScience, "Business Partners," and use for "marketing analytics."
    3. Users who installed before July 2025 never saw the updated consent prompt-the AI harvesting was added via silent update in version 5.5.0.
    4. Even users who see the consent prompt have no granular control. You can't accept the VPN but decline the AI harvesting. It's all or nothing.
    5. Nothing indicates to users that the data collection continues even when the VPN is disconnected and the AI protection feature is turned off. The harvesting runs silently in the background regardless of what features the user has enabled.

    Google's Role

    Urban VPN Proxy carries Google's "Featured" badge on the Chrome Web Store. According to Google's documentation:

    "Featured extensions follow our technical best practices and meet a high standard of user experience and design."

    "Before it receives a Featured badge, the Chrome Web Store team must review each extension."

    This means a human at Google reviewed Urban VPN Proxy and concluded it met their standards. Either the review didn't examine the code that harvests conversations from Google's own AI product (Gemini), or it did and didn't consider this a problem.

    The Chrome Web Store's Limited Use policy explicitly prohibits "transferring or selling user data to third parties like advertising platforms, data brokers, or other information resellers." BiScience is, by its own description, a data broker.

    The extension remains live and featured as of this writing.

    Final Thoughts

    Browser extensions occupy a unique position of trust. They run in the background, have broad access to your browsing activity, and auto-update without asking. When an extension promises privacy and security, users have little reason to suspect it's doing the opposite.

    What makes this case notable isn't just the scale - 8 million users - or the sensitivity of the data - complete AI conversations. It's that these extensions passed review, earned Featured badges, and remained live for months while harvesting some of the most personal data users generate online. The marketplaces designed to protect users instead gave these extensions their stamp of approval.

    If you have any of these extensions installed, uninstall them now. Assume any AI conversations you've had since July 2025 have been captured and shared with third parties.

    This writeup was authored by the research team at Koi.

    We built Koi to detect exactly these kinds of threats - extensions that slip past marketplace reviews and quietly exfiltrate sensitive data. Our risk engine, Wings, continuously monitors browser extensions to catch threats before they reach your team.

    Book a demo to see how behavioral analysis catches what static review misses.

    Stay safe out there.

    IOCs

    Chrome:

    • Urban VPN Proxy: eppiocemhmnlbhjplcgkofciiegomcon
    • Urban Browser Guard: almalgbpmcfpdaopimbdchdliminoign
    • Urban Ad Blocker: feflcgofneboehfdeebcfglbodaceghj
    • 1ClickVPN Proxy for Chrome: pphgdbgldlmicfdkhondlafkiomnelnk

    Edge:

    • Urban VPN Proxy: nimlmejbmnecnaghgmbahmbaddhjbecg
    • Urban Browser Guard: jckkfbfmofganecnnpfndfjifnimpcel
    • Urban Ad Blocker: gcogpdjkkamgkakkjgeefgpcheonclca
    • 1ClickVPN Proxy for Edge: deopfbighgnpgfmhjeccdifdmhcjckoe

    [Sponsor] Finalist Daily Planner for iOS

    Daring Fireball
    finalist.works
    2025-12-16 02:52:31
    Finalist is an iOS planner rooted in paper. Originally an index card system, it grew into a love letter to paper planners. You know the kind, leather folders with colored tabs and translucent dividers. Unlike those old binders, Finalist fills itself with your calendars, reminders and weather foreca...
    Original Article
    By Slaven

    My name is Slaven and Finalist is my passion project. It's a day planner I always wanted on my phone (and iPad, and Mac). It can help you be more intentional with your time.

    Yearly Planner

    Finalist's ideas are rooted in paper (I'm a huge paper nerd). The app started out as an index card system, and became a love letter to paper planners. You know the kind, leather folders with colored tabs and translucent dividers.

    Unlike those old binders, Finalist fills itself with your calendars, reminders, Activity Rings, journal suggestions and weather forecast. Minimalist? Maybe not, but it's become a UI playground designed to inspire, and looks great on iPad and Mac too.

    Themes with or without Liquid Glass

    Think of it as three views into your time: Daily shows everything happening today, calendar, reminders, weather, habits, goals, all in one glance. It helps you triage tasks you missed yesterday, or punt stuff to tomorrow with a single tap.

    Planner lets you zoom out to your week, month, or (just released) your whole year . Use the Intention Brush to paint meaning onto days ahead, those colors follow you throughout the app.

    And Journal captures where you've been, with Activity Rings, photo stickers from Journal Suggestions, and room to reflect when needed.

    Each part of the app is optional, use whatever feels right for you. But they all work together to help you stay focused, inspired and organized. Finalist was App of the Day on the App Store last month, Apple's take:

    Making the most of our time by having everything all in one place.

    Finalist is not my vision alone. It's a community-driven effort, with many passionate users expanding on what it can do. And if you have a friend or a family member who would enjoy it, please send them here.

    So give it a try, I can't wait to hear what you think 😅
    The email link is in the About box:

    This Week in People’s History, Dec 17–23, 2025

    Portside
    portside.org
    2025-12-16 02:29:42
    This Week in People’s History, Dec 17–23, 2025 Jonathan Bennett Mon, 12/15/2025 - 21:29 ...
    Original Article

    ‘Our Government May at Some Time Be in the Hands of a Bad Man’

    DECEMBER 17 IS THE 164TH ANNIVERSARY of one of Frederick Douglass’s most well-remembered speeches, which has earned the appellation “Danger to the Republic”. Many of the issues that concerned Douglass more than a century-and-a-half ago are headline news at this very moment.

    On that day in 1866, 20 months after the assassination of Abraham Lincoln and Andrew Johnson’s becoming President, Douglass spoke passionately to a Brooklyn audience about how some aspects of Johnson’s tenure were evidence of flaws in the U.S. Constitution.

    For example, Douglass argued strongly that no President should have the power to pardon criminals at his sole discretion. He declared, “there is a good reason why we should do away with the pardoning power in the hands of the President, that is that our government may at some time be in the hands of a bad man.” If a bad man were to become President, according to Douglass, he could encourage his supporters to attempt a coup and tell them ‘I am with you. If you succeed, all is well. If you fail, I will interpose the shield of my pardon, and you are safe. . . . Go on,” and attempt a coup; “I will stand by you.”

    Another issue causing Douglass great concern was the President’s power to appoint so many government officials and to require them to resign whenever he wanted to. As he put it, “The Constitution . . . . declares that the President may appoint with . . . . the Senate’s advice and consent, but custom and a certain laxity of administration has almost obliterated this feature of the Constitution, and now the President appoints, he not only appoints by and with the consent, but he has the power of removal, and with this power he virtually makes the agency of the Senate of the United States of no effect in the matter of appointments.”

    Douglass delivered the same speech on at least a dozen occasions in as many U.S. cities between  December 1866 and April 1867. To see the entire address, which includes many other criticisms of President Johnson that are surprisingly relevant to the concerns of 2025, visit https://frederickdouglasspapersproject.com/s/digitaledition/item/18126

    Cracking Down on Radicals

    DECEMBER 18 IS THE 230TH ANNIVERSARY of a moment when the British government aimed a hard legislative blow at growing political unrest. On this day in 1795 Parliament passed two draconian laws, the Seditious Meetings Act and the Treason Act.

    The Seditious Meetings Act outlawed public meetings of more than 50 people. The Treason Act made it a crime to intend to do harm to the King. The maximum penalty for violating either of the acts was death.

    The new laws were passed in reaction to a wave of widespread anti-monarchist sentiment that was encouraged by the ongoing French Revolution and by England’s war against the radical French regime, which had driven food prices so high that many workers with jobs could not afford enough to eat.

    Many of the organizations that were targets of the Seditious Meetings Act disbanded soon after it passed, so very few prosecutions took place. All three prosecutions for violating the Treason Act resulted in acquittal. https://engagedscholarship.csuohio.edu/cgi/viewcontent.cgi?article=3739&context=clevstlrev

    Emancipation, What Good Is It?

    DECEMBER 19 IS THE 160TH ANNIVERSARY of the South Carolina legislature passing a law that, as the Equal Justice Initiative reports “forced recently emancipated Black citizens into subservient social relationships with white landowners, stating that ‘all persons of color who make contracts for service or labor, shall be known as servants, and those with whom they contract, shall be known as masters.’” For the complete account, visit https://calendar.eji.org/racial-injustice/dec/19

    Folk Music at the Library

    DECEMBER 20 IS THE 85TH ANNIVERSARY of a folk music concert at the Library of Congress, a performance of the Golden Gate Quartet with Josh White on guitar. It was the Library’s first folk music concert, beginning a tradition that has become a regular feature of Library of Congress events. https://www.loc.gov/research-centers/american-folklife-center/about-this-research-center/

    New Direction for Jazz

    DECEMBER 21 IS THE 65TH ANNIVERSARY of Ornette Colman and seven other musicians laying the foundation for the Free Jazz movement when they created the tracks that were later released by Atlantic Records as “Free Jazz: A Collective Improvisation.” The session personnel were alto saxophone – Ornette Coleman; bass – Charlie Haden and Scott LaFaro; bass clarinet – Eric Dolphy; drums – Billy Higgins and Ed Blackwell; trumpet – Freddie Hubbard; pocket trumpet – Donald Cherry. You can listen to the album here: https://youtu.be/iPDzlSda8P8?si=LBpCzRLfL6fpVRDc

    For more People's History, visit
    https://www.facebook.com/jonathan.bennett.7771/

    Rollstack (YC W23) Is Hiring Multiple Software Engineers (TypeScript) US/Canada

    Hacker News
    www.ycombinator.com
    2025-12-16 01:51:50
    Comments...
    Original Article

    Automate data-driven slide decks and documents with AI

    Software Engineer (TypeScript) - US/Canada

    $140K - $240K 0.10% - 0.20% US / CA / Remote (US; CA)

    Role

    Engineering, Full stack

    Skills

    TypeScript, Node.js, React

    Apply to

    Rollstack

    and hundreds of other fast-growing YC startups with a single profile.

    Apply to role ›

    About the role

    The Company

    At Rollstack, we are revolutionizing the way businesses share and communicate data and insights. Organizations worldwide rely on slide decks and documents to make informed decisions, whether for leadership, clients, or partners. Yet, preparing these materials often consumes countless hours. Rollstack fully automates that.

    We help some of the world's leading organizations, from mid-sized to public companies like SoFi, Zillow and Whirlpool, in automating their slide decks and documents. Headquartered in New York, we offer a remote-friendly workplace and are backed by Insight Partners and Y Combinator, the most successful startup incubator in the world that produced the likes of Airbnb, Twitch, Instacart, Dropbox, Reddit, Doordash, Stripe, Coinbase, etc.

    Our team operates with speed and focus to deliver outsized impacts for our customers. We approach every challenge with first principles, never assuming things have to be done a certain way. We are a diverse team that believes intelligence and kindness go hand in hand, welcoming individuals from all backgrounds. Our persistence and rapid execution define us as a category leader and a future generational company.

    About the Role

    As a Software Engineer at Rollstack, you’ll build core features that automate how companies share data through slides and documents. You’ll work across the stack on integrations, AI insights, and performance optimization. This role is ideal for engineers who thrive on impact, autonomy, and fast-paced product development.

    As a Software Engineer, you will

    • Help build the missing piece of the modern data stack: Reporting Automation
    • Build new user-facing features with everything from database models to async workflows and UI components.
    • Develop features like AI insights, native charts, and collections.
    • Optimize our data synchronization by leveraging better technologies and protocols.
    • Build integrations with BI tools like Tableau, Looker, and Metabase.
    • Build integrations with content platforms like Google Slides, PowerPoint, and Notion.
    • Define and implement best practices with the latest web technologies across the stack.

    Tech

    • TypeScript + React frontend with Tailwind CSS and Shadcn/UI , using modern hooks for composable UI and fast iteration.
    • Node.js backend with a sync engine using Prisma ORM and Temporal workflows, powering internal services.
    • K8s platform on AWS , deployed with Argo CD for zero‑downtime releases and easy rollbacks.
    • Logs in SigNoz , application tracing in Sentry, and product analytics in PostHog .
    • Generative‑AI layer powered by OpenAI API, Gemini, LangChain, and Langfuse to deliver automated insights.
    • Issue tracking with Linear .

    Who We Are Looking For

    • 3-8 years of related professional work experience (after graduation).
    • Has been building in TypeScript during the last 2 years.
    • Has been building backend in Node.js during the last 2 years.
    • Has been building frontend in React during the last 2 years.
    • Strong software engineering fundamentals, including knowledge of algorithms and data structures.
    • Strong experience collaborating with PMs, designers & engineers to build new products and features.
    • Good understanding of CI/CD and Cloud infrastructure.

    Why Join Us

    • Join a Y Combinator-backed company that’s redefining how individuals and teams, across industries and around the world, work smarter and faster.
    • Work alongside an exceptional team of builders and operators, including alumni from Amazon, Meta, Pinterest, Tesla, and AiFi.
    • Be part of a fully remote, globally diverse workplace that values autonomy, impact, and collaboration.
    • Contribute to a product that users love and that truly sells itself, built by a world-class product and engineering team.
    • Look forward to bi-annual team off-sites in destinations that belong on your travel bucket list.
    • Earn competitive compensation and meaningful equity in a fast-moving, high-leverage startup where your work directly shapes the company’s trajectory.

    About the interview

    At Rollstack, we’re looking for engineers who enjoy iterating, shipping quickly, and solving customers' problems. We want individuals who exhibit a strong sense of ownership and have a get-things-done mentality. Our engineering team defines and drives its technical agenda to continuously iterate on the product and solve our customers' most important problems.
    Our interview process is designed to find these kinds of engineers:

    • Two technical interviews, one with our CTO and one with one of our engineers. The format is some technical questions and some live coding exercises. We also try to ask questions relevant to the type of product we build. Use the language of your choice during these interviews.
    • Two fit interviews with the other cofounders. These are not technical.

    About Rollstack

    Rollstack is solving the last mile problem in the modern data stack and creating a new category: Reports Automation. We connect BI tools to slide decks and documents, automating their generation and updates.

    We help some of the world's leading organizations—from mid-sized to public companies like SoFi, Zillow and Whirlpool—in automating their slide decks and documents. Headquartered in New York, we offer a remote-friendly workplace and are backed by Insight Partners and Y Combinator, the most successful startup incubator in the world that produced the likes of Airbnb, Twitch, Instacart, Dropbox, Reddit, Doordash, Stripe, Coinbase, etc.

    Our team operates with speed and focus to deliver outsized impacts for our customers. We approach every challenge with first principles, never assuming things have to be done a certain way. We are a diverse team that believes intelligence and kindness go hand in hand, welcoming individuals from all backgrounds. Our persistence and rapid execution define us as a category leader and a future generational company.

    Rollstack

    Founded: 2022

    Batch: W23

    Team Size: 25

    Status: Active

    Location: New York

    Founders

    Inside Chicago’s Neighborhood ICE Resistance

    Portside
    portside.org
    2025-12-16 01:46:13
    Inside Chicago’s Neighborhood ICE Resistance Mark Brody Mon, 12/15/2025 - 20:46 ...
    Original Article

    Lucy says she starts early because ICE starts early. It’s around eight o’clock one Thursday morning in late October, at a coffee shop in Back of the Yards , a neighborhood on Chicago’s Southwest Side. Taped inside the shop’s glass door, a sign warns ICE not to enter without a judicial warrant. (The agents very rarely bother to get one.) More signs surround it: “Hands Off Chicago”; “Migra: Fuera de Chicago”; the phone number to report ICE activity. (These are all over town.) Free whistles sit at the register. Lucy buys a black coffee from the barista and joins me at a table, checking her phone for messages about potential sightings—not just of ICE, but also Customs and Border Protection and other federal agencies, such as the FBI and ATF, tasked with arresting immigrants in neighborhoods like this one. She has dark hair and a few tattoos reaching past her shirtsleeves, and, even at this early hour, her eyeliner is precise. As we wait, we stare out the café window at a nearly empty street, toward a candy-colored mural of clouds over a desert sunset. “There should be a street vendor right there,” Lucy says. There should be more than one. “It shouldn’t be this quiet.”

    Volunteers like Lucy, doing ICE or migra watch shifts across the city, tend to work in their own neighborhoods. They are part of a network of rapid-response groups that have sprung up over the last few months to protect immigrant communities from the Trump administration’s brutal, far-reaching “mass deportation” program, led by Department of Homeland Security director Kristi Noem. It would easily take dozens of pages to provide a full accounting of the abductions, arrests, and protests that have taken place in Chicago as of mid-November. The Illinois Coalition for Immigrant and Refugee Rights , or ICIRR, posted verified sightings of federal immigration agents nearly every day in September and October. Shortly before I met Lucy, ICIRR identified federal agents in at least nine Chicago neighborhoods and suburbs on a single day: Melrose Park, Oak Park, Cicero, and more, as well as at the Kane County Courthouse and the O’Hare International Airport. At O’Hare, according to reports verified by ICIRR, at least 20 agents shut down exits at rideshare lots, demanded identification from drivers, and detained multiple people. All told, according to the Department for Homeland Security, more than 4,000 people in the city have been taken off the streets by federal agents and held in immigration detention facilities since September, in what the Trump administration calls “ Operation Midway Blitz .”

    The crackdown is vast, the stakes could hardly be higher, and the response from Chicagoans has been profound and far-reaching. The mayor signed an executive order designating city-owned property as “ICE Free Zones.” A federal judge required some of those overseeing the operation, such as Border Patrol commander Gregory Bovino, to testify under oath, and set schedules for them to update the court on the operation. But neither political nor legal interventions have managed to meaningfully interrupt what’s going on. ICE-free zones, residents report , do not stop ICE. And the slow-moving legal system can’t prevent agents from violating residents’ constitutional rights; indeed, the system largely functions to offer redress after the fact. Even when courts have ordered Immigration and Customs Enforcement or CBP to cease some violent action, such as lobbing tear gas into residential neighborhoods, agents ignored them. The scores of terrifying arrests continued.

    The one response that has been genuinely effective has come from community members—ordinary residents who have come together, trained one another, and connected across neighborhoods to form groups like the Southwest Side Rapid Response Team . They have eyes on the street, the trust of their neighbors, and the ability to intervene practically instantaneously, sharing information with the ICE-activity hotline that operates across the state. They can record evidence and pass it along in seconds to rights groups, news media, and social media. Blending protest and direct action, they are offering something concrete to Chicagoans who want to express their opposition to Donald Trump’s war on immigrants. This is true movement-building, a project that may endure after this particular threat to immigrant communities, even after this regime. ICE, CBP, and others have violently retaliated against these groups in part because the agencies correctly understand what many do not: Organized neighbors are mounting an effective defense, and an organized movement is a formidable adversary.

    On the far Southwest Side of Chicago, by Lucy’s estimate, hundreds of people have been working together since early September to defend their neighbors, joining thousands across the city. Just outside the parking lot of a nearby Home Depot on Western, a broad street dividing Brighton Park from Back of the Yards, one community group starts its shift at six in the morning: a couple of people with a table, folding chairs, and free coffee. Not far away, ICE uses the parking lot of a strip mall as a temporary base. Enforcement officers gather here, their faces covered in balaclavas, name badges stripped off their uniforms. They idle in their unmarked vehicles, some with the license plates removed. Then they caravan together to pick off people setting up food carts, taking their kids to school, or just out walking alone.

    That’s when the notifications will hit Lucy’s phone, as well as hundreds, if not thousands, of other phones, passing messages within neighborhoods. “OK, let’s go to one spot,” Lucy says, grabbing her coffee and picking up a banana for later. She has a report of two suspected ICE vehicles nearby. Now she’ll try to verify the report before it gets shared more widely. If she can, she’ll trail them and report where they’re going, sending word through the network so that others close by can alert the neighborhood with their whistles, follow in their cars, and generally try to make ICE’s work as difficult as possible.

    It’s no surprise, then, that these efforts have been cast by Noem and other officials as violent and criminal. Almost all of the people to whom I spoke for this story chose to use pseudonyms, to ensure that they can keep doing community defense work in this environment of new and escalating legal threats. Some are also immigrants or have immigrant family members to protect. People are risking a great deal to defend their neighbors, their students, their co-workers, and their customers, while trying to withstand the chaos caused by armed, masked federal officers operating on Chicago streets with apparent impunity. “What they’re doing is an occupation,” Lucy says. “It’s lawless.” And anybody questioning this reality, she tells me, “is living in their own fantasy land.”

    The administration’s attack on Chicago began in early 2025 , soon after Trump returned to the White House. Trump dispatched to the city his “border czar” Tom Homan, who belonged to ICE leadership under Barack Obama and was the architect of the family separation policy in Trump’s first term. With him, Homan brought along the television personality Dr. Phil McGraw, who was expected to broadcast the arrests as “exclusive” programming on his own streaming channel (launched when his long-running CBS show was canceled, reportedly for losing advertisers, after McGraw welcomed guests pushing far-right politics and conspiracy theories to his couch). The idea was to hit the streets with geared-up ICE agents and produce COPS -like online content along with terror. But the very public attack backfired: Although it generated news B-roll, it also galvanized Chicago residents, who shared legal resources with their neighbors and whose response may have helped drive down arrests. That’s what Homan seemed to believe. When he was asked about the operation on CNN, Homan complained that Chicagoans pursued by immigration officers were “very well-educated” on their legal rights. “They call it know-your-rights,” Homan said . “I call it how-to-escape-arrest.” It appeared that the agency had backed down on the operation. ICE instead focused on Los Angeles and Washington, D.C., to hone its tactics, giving community organizers in Chicago a few months to prepare.

    While many of the rapid-response groups that formed during that period were new, and many people new to community defense work joined, the effort was “not our first rodeo,” as Lucy noted. Chicago is a big city, but the Southwest Side still feels like “an incredibly small town,” she explained, in which many of the community networks now involved in ICE watch already existed. Long before this wave of neighborhood organizing in Back of the Yards, immigrant workers at the Union Stockyards , Chicago’s meatpacking district, organized their own communities. Saul Alinsky’s famed neighborhood-based approach to community organizing took shape here. The European immigrant families are now mostly gone, but the Mexican immigrants who have lived and organized in the neighborhood since the 1920s remain, now joined by multiple new generations, most recently from Venezuela.

    Many of the Venezuelan immigrants were forcibly bused to Chicago from Texas by Governor Greg Abbott beginning in 2022. Their arrival increased stress in some communities on the Southwest Side, where work and resources were already strained. But it also tied some communities closer together, with “lots of mutual aid work,” Lucy said. These mutual aid efforts served as a safety net for new immigrants in the city, often before the city offered them resources. Over the years, many were able to establish themselves. “It was honestly very cool,” Lucy remembered, to witness Mexican and Venezuelan food vendors working right next to each other. “It was something that we hadn’t seen.”

    These are now some of the immigrants whose neighbors have come out to defend them from ICE. Even those who are at high risk of being detained have joined the rapid-response networks, whether to watch and report possible ICE activity or to visit with neighbors and document what happens after a family member is taken. By the time ICE launched its operation in Chicago in early September, neighborhoods were ready. Homan’s complaints were accurate: They were educated and they were trained. Now, when ICE arrives, “sometimes it’s not even the rapid-response team that starts with the whistles and the honking,” Lucy explained. “It’s the neighbors on the block.”

    A photo from October 11, Illinois State Police detained someone after declaring an “unlawful assembly” near the ICE detention facility in Broadview

    On October 11, Illinois State Police detained someone after declaring an “unlawful assembly” near the ICE detention facility in Broadview.  ADAM GRAY/ASSOCIATED PRESS

    ICE or migra watch is a practice that grew out of the community defense strategies developed by the Black Panthers in the late 1960s, which inspired cop-watching across the country. It is most visible on the streets, where pairs or teams document law enforcement in their own neighborhoods. Participants used to use handheld video cameras; now their cell phone cameras do the job. But the work extends beyond the moments the officers are recorded. Over time, through direct experience, cop-watch groups come to understand patterns of policing. Some track and request public records of law enforcement activities to learn more. They educate their neighbors about their rights when police stop their cars or come to their doors, and coordinate care and outreach to support neighbors harmed by policing.

    During the first Trump administration, immigrant rights groups in Chicago, like Organized Communities Against Deportations , were monitoring ICE and developing deportation defense, said Rey Wences, then a volunteer with OCAD and now the senior director of deportation defense at ICIRR. But it was after working alongside Black-led racial justice groups in the city, such as Black Youth Project 100 and Assata’s Daughters, that migra watch evolved. “We saw the connections,” Wences said, between deportation defense and cop watch, and OCAD asked if it could work with the other groups to build something tailored to watching ICE. The migra watch training ICIRR now leads drew inspiration from all those efforts. In September and October alone, Wences said, ICIRR trained more than 6,700 people. It feels like the organizing has reached “a critical mass,” they said. Indeed, ICIRR was only one of many groups training people up—“like a muscle we all flexed.” As with cop watch, ICE watch is not only a form of protest; it builds and demonstrates a kind of safety net that law enforcement cannot provide—that, in fact, law enforcement actively undermines.

    Contrary to the claims of Homan and many others in the Trump administration, federal agents drafted into anti-immigration enforcement operations do not protect residents from crime; they bring violence into communities, targeting not only the people they seek to arrest, but anyone whom they think stands in their way. They have shot tear gas onto residential streets, pepper-sprayed children and bystanders, pepper-balled clergy , and fired “ less-lethal ” weapons directly at press and protesters alike. In November, U.S. District Judge Sara Ellis issued a preliminary injunction limiting immigration agents’ use of force in Chicago, saying from the bench that their behavior “shocks the conscience.”

    The injunction came as a result of a legal challenge filed by demonstrators, religious practitioners, and journalists ( including the Chicago News Guild, which is part of the national NewsGuild-CWA, as is The New Republic ’s union, the NewsGuild of New York). The challenge argued that federal agents’ use of force violated constitutionally protected protest and religious and news gathering activities. In her ruling, Judge Ellis singled out Border Patrol commander Bovino—who is often the only unmasked and clearly identified federal officer on the scene of ICE abductions and violence against community members— stating that Bovino repeatedly lied under oath about agents’ use of force. Hours later, Bovino was out with a caravan on the Southwest Side, as federal agents fired pepper balls at a moving vehicle in Gage Park and pointed rifles at people in Little Village. The operation, he told the Chicago Tribune , was “going very violent.”

    At the Back of the Yards parking lot where ICE and other federal agents had mobilized, community organizers and students at the high school across the street have been pressuring the property owners, Friedman Real Estate, to refuse ICE access to the lot. The volunteers kept showing up, as early as they could, staying as late as they could, to patrol the lot and send the message to ICE agents that they, too, were being watched. They took photos of agents and took down their plates. After their constant patrolling, Lucy said, they saw ICE less frequently at that lot. The empty plaza I had passed that morning was a sign of success.

    “I like to say they’re running from us,” Lucy said. “If we’re not already there, we’re coming in like two minutes.”

    That morning in late October, driving slowly past family homes on tidy, city-size lawns, we see very few people out. Lucy pauses to let an older person pushing a cart of groceries cross the street. We pass “No Trespassing/Private Property” signs, a warning to ICE, and jack-o’-lanterns on porches. We drive by a patch of yellow marigolds pushing through a chain-link fence, a few clusters of banana-leaf plants. Every few minutes, the car’s sound system broadcasts notifications from Lucy’s phone, a specific ringtone she set just for rapid-response messages coming in. She gets updates on the cars we’re looking for: a boxy, oversize Jeep Wagoneer and an extra-large GMC Yukon truck. Over the weeks, the kinds of cars ICE uses have become very familiar.

    Inflatable Halloween decorations wave in some of the front yards we pass. Outside of Gage Park High School, we pause to chat with a crossing guard in a yellow vest. Lucy rolls down the window. “I’m a neighbor in the area,” she explains. “We’re doing ICE watch, so just looking out for ICE vehicles.” New message notifications ding again. “We got reports of a Wagoneer, which, you don’t see too many Wagoneers around here, they’re long and boxy…. I figured I would let you know, just in case.” Before she is done, the crossing guard is already repeating, “Just in case. All right. Thank you,” like this happens all the time. It’s not her first rodeo either.

    “Operation Midway Blitz” is not merely an immigration enforcement operation; it is a monthslong offensive meant to break down people’s resistance, a deliberate campaign of political violence and social disruption. Such brutal anti-immigration policing itself is not new, even if it may be newly evident to people in Los Angeles, Washington, and elsewhere, who have not experienced their family and neighbors disappearing. But it is new that ICE and Border Patrol are rolling out daily in caravans; it is new that Border Patrol is unleashing tear gas and firing flash-bang grenades at bystanders. It’s also new that all this is happening at once to a whole city.

    ICE has also turned on those residents who dare document and track them across the city. On October 20, reported The TRiiBE, a local independent news site, an attorney named Scott Sakiyama, who had been following immigration agents in his car, was detained by them at gunpoint. Sakiyama had defended a man who had faced federal charges for allegedly assaulting a Border Patrol agent outside the immigrant “processing center” in Broadview, an inner suburb of Chicago. The government had already dropped the prosecution. But when Sakiyama spotted armed, masked immigration agents driving in Oak Park and blew a whistle to alert neighbors, agents stopped him. “Exit your vehicle, or we’re gonna break your window and we’ll drag you out,” one said. This all took place across the street from Abraham Lincoln Elementary School, where one of Sakiyama’s kids is a student. He was loaded into the agents’ vehicle and driven to the Broadview detention facility, where he was merely given a citation and returned to his car. “The federal government is intent on abusing its power to kidnap and violate the rights of our friends and neighbors,” Sakiyama wrote in an Oak Park neighborhood Facebook group, “and now, they say it is a crime to tell your neighbors this is happening.” He encouraged people to attend a rapid-response training and start their own whistle brigade. ICIRR now holds virtual trainings every week; the one I dropped in on in late October was attended by more than a thousand people from dozens of neighborhoods.

    A photo from November 14, at a protest outside the Broadview detention center, Megan Siegel held hands with her daughter, Matilda.

    On November 14, at a protest outside the Broadview detention center, Megan Siegel held hands with her daughter, Matilda.  CHRIS SWEDA/CHICAGO TRIBUNE/TRIBUNE NEWS SERVICE/GETTY

    As community-based defense projects have ramped up, some local elected officials have supported them. Some, like Alderwoman Jessie Fuentes, have been detained while defending their constituents. Others have ignored their constituents, or, in the case of Democratic Alderman Raymond Lopez, who represents part of Back of the Yards, welcomed Tom Homan and defended Operation Midway Blitz. On a night in late October when Lopez was scheduled to have open office hours, the doors were locked and the lights were off as community members announced a protest there. Jaime Perez said his girlfriend, a tamale vendor, was taken by ICE near 47th Street and Western, and his calls to Lopez for help were ignored. “He wouldn’t come to the phone,” Perez said. As the sun set, Leslie Cortez spoke about the raid she witnessed on 47th Street. “Our community deserves someone who will fight for us,” she said, “not against us.” Before they left, they taped a letter to Lopez’s office door demanding that he resign.

    But among even the more sympathetic government leadership, Chicagoans’ political efforts to protect immigrant communities have only gone so far. Chicago Mayor Brandon Johnson has referred to the protection afforded by the city’s welcoming ordinance , which is meant to prohibit collaboration between immigration officers and Chicago police, but when ICE and Border Patrol roll through city neighborhoods, the police have been right there. Residents have been told that Chicago police are prohibited from engaging in immigration enforcement (unless ordered to do so by a court), when they can see with their own eyes that Chicago cops are clearing roads for the fleets of sports-utility vehicles and oversize trucks used by ICE and Border Patrol to haul people to Broadview. Illinois Governor JB Pritzker has gained a national reputation as a leader who stands up to Trump and his mass deportation machine, but outside Broadview, where activists, religious leaders, and media gather, the officers firing tear gas and pepper balls at them are Illinois State Police, sent there, according to Pritzker, to “ensure people could safely express their rights.”

    Some of the time on migra watch, it can look like nothing is happening. We drive in silence, weaving between Back of the Yards, Gage Park, and Brighton Park, past bakeries and salons and auto body shops, looking twice at any oversize car we see. Suddenly, Lucy asks her phone for directions. “So they are here,” she says. “I’ll keep my distance.” More notifications are going off. Lucy sees what might be an ICE SUV, but as she puts on her blinker and turns to follow, a Chicago Police Department car pulls across her car’s path. Local cops are not supposed to be out here. We hear people honking, leaning on their horns, not that far off. “Is the honking because it’s—” I start to ask, and she says it is, as she grabs a few things in case she needs to hop out and starts dictating a message: “I’m pretty sure I saw that large white SUV, no plates in the front, but as I tried to turn, CPD kind of blocked me.” She gives the intersection where CPD still is. Regardless of the reason the police were there, now she’s lost sight of the SUV. She plays back a video from a few minutes ago on her phone, hoping it shows the direction of the SUV, and the honking fills the car speakers. A few other people saw the SUV as well; Lucy is following their directions now. “It seems like there’s a lot of people out right now,” she says, “which is nice.”

    As we drive, we see them, more and more people out on the streets, watching. On a corner at a gas station, a small group of people, some in KN95 masks, stand on the grassy strip at the side of the road, watching. At the Home Depot, Lucy parks and hops out to say “hi” to the people at the table near the parking lot, expecting them to shut down for the morning. A new shift of volunteers, however, has come to stay longer. Another small group is out on a side street lined with houses: four young people in hoodies and puffer coats. They repeat the ICIRR hotline number on a megaphone as they walk. Lucy tells them about what she saw, and they head right back out on foot. “Small town, small town,” Lucy says to me, and we drive off.

    We loop around a few more times, checking out a nearby park. We’ve been out for 40 minutes; to me it feels like five. The adrenaline, even at this distance from the action, warps time and attention—every siren might be something. A helicopter looms overhead. When we drive past the crossing guard again, she and Lucy exchange friendly waves.

    It can feel like ICE agents are everywhere. That, presumably, is how they want it to feel. At the same time, more and more people who have never engaged in anything like these actions before are purposefully running toward the trouble. As much as their resistance can appear organic and spontaneous—and some of it is—it’s supported by deliberate effort, an infrastructure working to help them expand their tolerance for taking risks.

    There’s the know-your-rights trainings, which, like ICE watch trainings, long predate this moment. In the past, however, those were typically offered within a smaller community made up mostly of other organizers. Since Midway Blitz, the groups ramped up because ICE ramped up. They had to scale up know-your-rights trainings to work for mass audiences. They needed to do more than just arm people with information about their rights; now they had to teach “what do you do when an agent is right there,” Lucy said, “right outside your door or right in front of you.” Learning that, she said, enables them to walk out the door and “blow their whistle the minute they identify a car.” Once people know how to defend their own rights, in other words, they don’t stop there—as the last months in Chicago have shown, they turn to defending others.

    Intentional or not, this way of spreading rapid-response work ensures that there’s no one point of failure. Multiple groups are employing multiple communication platforms, and generating new methods as they go. New people join them, “just coming up with their own ideas on how to defend Chicago,” as Lucy put it. It turns out that you can’t just gas and detain everyone in the streets. There will be more people tomorrow.

    On her phone, Lucy sees that Customs and Border Protection are a few neighborhoods away, in Little Village. A video from the scene plays over the speakers as we drive, birdsong and car sounds and a man calling, “Hey, how you doing!” and what might have been someone else yelling “Fucker!” We can’t join; Lucy’s shift is done, and she has to go to work. By the time I could get there, it will likely have ended. She offers to drop me at the train station. On the platform, I watch a Facebook Live video from the scene, streams of hearts and sad crying emojis floating up over an intersection flooded with Chicago police.

    A photo of signs that inform federal agents that they do not have consent to enter without a valid judicial warrant.

    All over Chicago, signs inform federal agents that they do not have consent to enter without a valid judicial warrant.  JACEK BOCZARSKI/ANADOLU/GETTY

    Baltazar Enriquez had been recording ICE for almost an hour by the time I tune in. He was following the federal agents’ caravan at the same time that, a few neighborhoods away, we were driving around Back of the Yards. Witnesses hopped out of their cars, turning their phones toward the agents and yelling, “Shame! Shame! Where’s your warrant? Why are you terrorizing us? Why? Why?” They walked toward the agents, phones up. One woman had a megaphone. The agents kept their faces fully covered with black and camo balaclavas and reflective sports sunglasses. They pointed their long guns at the ground as they paced. “Leave! Leave!” A few agents got back into their white SUV. There was Gregory Bovino, standing next to an agent in a gas mask holding a weapon with a tear gas canister. “Don’t do it! Don’t do it, Bovino.” Overhead, a helicopter buzzed. “ICE go home. ICE go home.” Chicago police formed a line as the feds retreated behind them. The people clustered at an intersection. Someone wore an inflatable pink axolotl costume, Mexican and American flags flew, whistles were distributed. I was still on the train when Baltazar, streaming on Facebook, asked some people to walk with him to another neighborhood to patrol—“Gage Park,” he said, where Lucy and I had just been—and logged off. It was hard to reconcile the violence on the live stream 15 minutes away and the quiet around us. No one was taken from any street we passed. It could feel like nothing happened, except for all the people we saw as we were watching, watching, too.

    Melissa Gira Grant is a staff writer at The New Republic and the author of Playing the Whore: The Work of Sex Work.

    "Are you the one?" is free money

    Lobsters
    blog.owenlacey.dev
    2025-12-16 01:42:06
    Comments...
    Original Article

    OK, so this is niche.

    One of my wife's guilty pleasures is reality TV, usually ones centred around dating - the more American, the better. By extension, I absorb some of this noise and I'm happy to admit I can sometimes get invested.

    At one point, she was (let's face it, we were) watching a show called "Are you the one?" on MTV. I'm going to show you how this game is pretty much free money.

    Game rules

    Consider a group of equal numbers of men & women:

    Each contestant has exactly one perfect match of the opposite sex that is pre-determined for them, as represented by the colours. Click the "Match" button to pair up the contestants correctly. Crucially, they don't initially know who their perfect match is. If the group can correctly guess all the perfect matches, they win a cash prize of $1M.

    You probably have the follow up question of how the perfect matches are calculated, which is a great question. In short: dunno, it's black-boxed, but let's just say "science"? How this is calculated isn't really the point, I could even argue that it doesn't matter so long as you get your strategy right. For what it's worth, the plot of the TV show mentions employing "the most extensive match-making process ever seen".

    Let's get into it. Here are the two ways in which contestants can learn new pieces of information throughout the game: truth booths and match ups .

    Truth Booths

    A truth booth is where a male & female are chosen by the contestants, and it is revealed definitively whether they're a perfect match or not. So there are two potential outcomes:

    If you've found a way to stream this and want to skip straight to the good stuff, I'd fast-forward to the fallout from these. In S1E6 it took Shanley an entire episode to come to terms with Chris T & Paige being a perfect match, even though in E1 she learned she was no match with him anyway (sigh).

    Match Ups

    At the end of each episode, all contestants match up and they are informed (via dramatic lighting) how many correct matches they've got. If they've got all matches, the game is over and they win.

    Crucially, they don't know what the correct matches are, just how many they got in total. The only way they can definitively rule out a pairing is if they scored zero: the dreaded blackout. Though it might seem like a bad thing, a blackout can in fact be helpful in the long-term, as it gives you a definitive answer for all pairs that were matched up, it's like getting a free truth booth for each pair.

    Modelling the problem

    Much like a high school disco, let's put all the boys on one side and the girls on the other, and re-use the pairs from the match up example above:

    Here we have two correct pairs red and pink at position 1 and 5 respectively. The orange man at position 2 was paired with the purple woman from position 6, and so on.

    How good is a score of two? Is that any better than if you were to randomly pair people up? Let's experiment by doing just that: click the 'shuffle' button to re-pick:

    You'll notice that the average score comes out at around 1 after a while, which this line chart keeps track of.

    Below is a chart capturing the frequency of each score, you'll notice it eventually converges to a specific shape.

    The height of each outlined bar is the probability of scoring that number in a random pairing in a game of 6 couples. Interestingly, both these probabilities and the average score stay the same no matter how many couples we use.

    Whatever the selected # couples, the probability stays this same. There's tonnes of tangents we could explore that you might find interesting here [1] , but for our purposes we just wanted to put some data behind "how good is a score of X".

    My model

    I created a model that computes the remaining viable matchings of all couples. By 'viable', I mean that there's still a chance that it's the perfect match. Initially, as you can imagine, this is a big number. The aim of the game then becomes getting that number down to 1 as quickly as possible.

    Each time new information is learned, we recalculate the remaining matches. For example if we have a positive truth booth result, the remaining matches are filtered out to only those that contain these two people as a pair. Conversely, if the truth booth result was negative, then the remaining matches cannot contain any where these two are paired. Imagine a huge a game of "Guess Who?" where each image is a viable matching and you flip down the options that become invalid each time you learn new information. Match ups also massively help you reduce this number, however their impact is a bit more indirect and it's very difficult for a human brain to figure out the implications of the result of one.

    Real-life performance

    Here is a graph of the remaining viable matches in Season 1 as the season progresses. It may surprise you that in this game of 10 men and 10 women, the initial number of viable matches is almost 4 million:

    Hovering over the dots will tell you what's responsible for that change in the remaining matches. As you can see, they gain enough information to win the game by episode 8, so why does it take them so long to get it right? As mentioned earlier, it's almost impossible for humans to keep tabs on all these potential matchings so it's very likely they just didn't know.

    That being said, the graph itself isn't particularly useful, is it? After a couple of events, the line hugs the x-axis, and it's hard to see the difference between 1 and 5,773 seen in episodes 8 and 2 respectively. Let's try a log base 2 graph:

    That's hopefully a lot clearer. You can see how they learn information as they go, and at which point the model 'cracks it' with the match up in episode 8. You can also clearly see that the most valuable piece of information they gained was the match up in episode 2 - with a decent early score of 4. This might be intuitive to you, but as we found earlier you've got a less than 2% chance of scoring 4 when randomly selecting.

    Let's plot this again along with a few more seasons [2] :

    Other than S3 and S7 , the contests mathematically learn enough information to win the game with time to spare. Could they have got there sooner though? Could they have chosen better truth booths / match ups to spare us all of the extra episodes of trashy TV? Before I get into this, I need to cover some basics of information theory.

    Information theory

    We're going to revisit the "Guess Who?" game now, which you can think of as a simplified version of "Are you the one?". Stick with me; the idea is that we can use the more straightforward game mechanics to establish an information theory based strategy that we can then apply to "Are you the one?". These two games are similar in that:

    1. There is a correct answer unknown to the player(s).
    2. The player(s) are able to learn information by offering up hypotheses, and getting definitive answers to them.

    Consider an 8x8 grid of potential answers:

    Now I'm a terrible artist so I thought I would be able to articulate this more clearly with shapes instead. There are 4 shapes ( , , and ), 2 different types (opaque or outlined), and 8 colours - this makes 64 unique combinations. The aim of the game is to guess the correct answer before your opponent guesses yours. To give yourself the best chance of winning, you need to rule out as many answers as you can, as quickly as you can. Should you then employ a strategy that splits the potential answers in half (e.g "is it opaque?"), or something a bit more specific (e.g "is it an orange star?"). The latter is high-risk, high-reward, whereas the former will almost always rule out half of the remaining answers.

    Consider a bit of information as reducing the problem space by half. That is, by ruling out half the remaining answers. I want to stress that the word bit is a common term in information theory, as opposed to something that might sound less exact as it's intended in this context.

    The opaque question is a sure-fire way of gaining 1 bit of information. On the other hand, let's say you find out that the answer is a which allows you to flip down three quarters of the answers, that's the same as halving the problem space twice and therefore gaining two bits of information.

    In this example the answer is :

    As you can see, different answers are more useful than others. "Opaque?" rules out half of the remaining answers (1 bit), whereas " Blue ?" rules out 7/8ths of them (3 bits).

    Getting from 64 potential answers to 1 involves halving the problem space 6 times - 64 becomes 32, then 16, 8, 4, 2 and 1. In other words, if you're able to gain 6 bits of information, you'll know for sure what the answer is. This is supported by the fact that the sum of the information gained by asking all three above questions is 6.

    Let's simulate an actual game now, keeping tabs on the information gained throughout.

    Once everything but remains, you'll have gained 6 bits of information and can be 100% confident in the answer. Now we know we need to get to 6 bits of information as quickly as possible, our strategy becomes picking the question that we expect to give us the most information. That is, the sum of the information we would gain if that answer were true or false, multiplied by the probability of that specific outcome. Let's work through our three questions to give the expected information for each:

    Question True False Sum
    " ?" 25% of
    2.00 bits
    0.500
    75% of
    0.42 bits
    0.315
    0.82
    " Blue ?" 12.5% of
    3.00 bits
    0.375
    87.5% of
    0.19 bits
    0.166
    0.54
    "Opaque?" 50% of
    1.00 bits
    0.500
    50% of
    1.00 bits
    0.500
    1.00

    This table shows the expected information for each of our 3 questions. As you can see, the more "Hail Mary" the question, the lower expected information. " Blue ?" comes out at 0.54, which is almost half the amount of expected information as "Opaque?". Therefore, we can speculate that a decent strategy for this game would be to ask questions that split the remaining problem space in half. To support this, we can plot a graph [3] for all possible probabilities between 0 and 1:

    This shows that splitting the problem space in half (where the probability is 0.5), gives the highest expected information. This means that asking a very specific question like " Blue ?" is statistically the worst thing you can do.

    Let's play one final game, this time I'll show you the questions ordered by most to least expected information:

    How did you do? You'll notice that picking the questions at the top of the list gets you to the answer quicker, whereas the opposite is true when picking from the bottom. You'll also notice that you're never presented with a question that gives you more than 1 expected information, which is backed up by the above graph never going higher than 1.

    Now we've got a strategy that works well for "Guess Who?", we can get back to the proper game.

    Simulating "Are you the one?"

    Earlier on, I posed a (until now) rhetorical question as to the performance of the contestants on the show. In order to answer this question, we need two things:

    1. A way to measure performance: For this, we'll use the average bits gained per event . That is, each time there is a match up or truth booth , how many bits of information did they gain?

    2. A sensible benchmark: How do the contestants stack up against something that employed a strategy of randomly selecting match ups and truth booths ?

    For this sensible benchmark, I simulated over 100 fake seasons of "Are you the one?" to see how much information was gained if the match ups and truth booths were selected (almost [4] ) arbitrarily.

    The performance of the random simulated models was . Let's plot all the simulations on a graph, with trendlines for random and actual performance:

    So the actual performance hits the x-axis sooner, meaning it's able to zero-in on the perfect match earlier. That's reassuring, right? Maybe love is real after all. That, or they're just performing better than someone shooting fish in a barrel. Here's the numbers behind this comparison:

    Method Per Event Success Rate
    Random 1.23 bits 74%
    Actual 1.39 bits 71%

    The success rate is calculated as the number of seasons in which they're able to mathematically determine the perfect match before the game finishes. As you can see the success rate for the random simulation is higher than in real life. The sample of size of only 7 seasons of "Are you the one?" undoubtedly is too small for this to be a useful comparison.

    Now that we know the contestants make better decisions than randomly selecting pairings, the remaining question is exactly how much better. To show this, we'll employ our information theory strategy that we used for "Guess Who?" to this game.

    This simulation works similarly to the random simulation, only the mechanism for selecting pairings is different. That is, the pairings that are selected for either a truth booth or a match up are the ones that are statistically likeliest to give the most information.

    Suppose we have calculated the expected information gained by potential truth booths like below:

    The model would therefore pick and as it's the most likely to give it the most information.

    Match ups work similarly, however we know that it's not a simple true or false question. Instead, we've got to calculate the information we would gain for every score between 0 and 10 (where 10 is the number of couples), for every viable matching.

    I ran this information theory simulation 41 times (for no other reason than I got bored waiting), and saw it perform significantly better than random simulation or real life data:

    Now we can compare all three scenarios:

    Method Per Event Success Rate
    Random 1.23 bits 74%
    Actual 1.39 bits 71%
    Information Theory 1.59 bits 98%

    This means that, all you need is a bit of code and a can-do attitude to perform better than the "vibes" approach of the contestants in the show. Before you pop the champagne, we still haven't shown if this is good enough such that we get to the perfect match before we run out of time (or episodes). In a game of , the problem space is (for brevity, you can take my word for this), which is bits of information. This means you would need to gain bits of information per event minimum to ensure that you go into the final match up knowing for certain what the perfect match is.

    Wait, isn't that a lower number than the random simulation? Doesn't that mean that someone shooting fish in a barrel could win this game? I should stress that these are averages , and in 26% of random simulations they didn't get to there in time.

    Conclusion

    Hopefully now you agree with me that "Are you the one?" is free money, albeit with a just about near-perfect success rate. I showed that even picking pairings at random will more often than not give you enough information to win the game, as well as showing how to use classic information theory practices to get you there with episodes to spare. Maybe this haemorrhaging of money is what got the show cancelled in the first place, or maybe love is real, whatever you prefer.


    Resources & inspiration

    This post is my first foray into content like this. I wanted to scratch the itch of an interesting maths problem, with a light-hearted spin that I hope you enjoyed as much as I did making it. The techniques shown in this post are very common information theory approaches, though I was inspired to apply them based on this video on wordle by 3Blue1Brown. I very rarely watch youtube videos over 10 minutes long (maybe that's my loss), but I wholly recommend this one if you found this interesting.

    Other than that, in my research I came across a boardgame called Mastermind, which has been around since the 70s. This is a very similar premise - think of it as "Guess Who?" on hard mode.

    I also pitched this idea to The Pudding , and had a great experience with them nerding out about this subject. Though they didn't take my up on my idea, I left with really great and actionable feedback, and I'm looking forward to my next rejection.

    Next steps for me would be to see if I can make a web-based game (don't hold me to this) on this theme. I'm interested in how people would intuitively make decisions based on information gained so far so the plan would be to see if I can find a way to capture that, and ideally make it fun.

    Finally, the code for my OR Tools model can also be found here .



    1. As the number of couples increase, the probabilities trend towards a poisson distribution with λ=1. The probability of 0 and 1 is also given by 1/e, which is a classic result in derangements , specifically with the "hat-check problem".

    2. I omitted Seasons 2, 8 and 9. Each season that wasn't considered was due to them introducing different game mechanics, which would have been hard to take into account for my model. Maybe my model was too rigid and I'm a bad developer, or maybe it's just inappropriate to find commonality there. Season 2: One female contestant had two perfect matches, meaning there were two perfect matchings. Season 8: In this season, they introduced gender fluidity. Whilst an interesting problem on its own, this would have wreaked havoc on my model. Season 9: One of the contestants left the show at an early stage, so the decisions made by the contestants would have been biased.

    3. This is known as the binary entropy function .

    4. I say "almost" here because I wanted this simulation to have some common sense. Specifically, if a pair were to have an unsuccessful truth booth , then it wouldn't be paired up for any subsequent events. My reasoning here is that no right-minded person would ever pair up people who can't be a match, as you would learn nothing new, and crucially it wasn't too arduous to code this into my random simulation model.

    Quoting Kent Beck

    Simon Willison
    simonwillison.net
    2025-12-16 01:25:37
    I’ve been watching junior developers use AI coding assistants well. Not vibe coding—not accepting whatever the AI spits out. Augmented coding: using AI to accelerate learning while maintaining quality. [...] The juniors working this way compress their ramp dramatically. Tasks that used to take days ...
    Original Article

    I’ve been watching junior developers use AI coding assistants well. Not vibe coding—not accepting whatever the AI spits out. Augmented coding: using AI to accelerate learning while maintaining quality. [...]

    The juniors working this way compress their ramp dramatically. Tasks that used to take days take hours. Not because the AI does the work, but because the AI collapses the search space. Instead of spending three hours figuring out which API to use, they spend twenty minutes evaluating options the AI surfaced. The time freed this way isn’t invested in another unprofitable feature, though, it’s invested in learning. [...]

    If you’re an engineering manager thinking about hiring: The junior bet has gotten better. Not because juniors have changed, but because the genie, used well, accelerates learning.

    Kent Beck , The Bet On Juniors Just Got Better

    i'm just having fun

    Lobsters
    jyn.dev
    2025-12-16 01:16:12
    Comments...
    Original Article

    IT IS ONLY COMPUTER

    Reilly Wood

    i work professionally on a compiler and write about build systems in my free time and as a result people often say things to me like "reading your posts points to me how really smart you are" or "reading a lot of this shit makes me feel super small". this makes me quite uncomfortable and is not the reaction i'm seeking when i write blog posts.

    it's not a competition

    i mean, in some sense if you work as a professional programmer it is a competition, because the job market sucks right now. but i think usually when people say they feel dumb, it's not in the sense of "how am i supposed to get a job when jyn exists" but more "jyn can do things i can't and that makes me feel bad".

    you can do hard things

    all the things i know i learned by experimenting with them, or by reading books or posts or man pages or really obscure error messages. sometimes there's a trick to it but sometimes it's just hard work . i am not magic. you can learn these things too .

    everyone has their own area of specialization

    if you don't want to spend a bunch of time learning about how computers work, you don't have to! not knowing about gory computer internals does not make you dumb or computer illiterate or anything. everyone has their own specialty and mine is compilers and build systems. i don't know jack shit about economics or medicine! having a different specialty than me doesn't mean you're dumb.

    i really hate that computing and STEM have this mystique in our society. to the extent that engineering demonstrates intelligence, it's by repeatedly forcing you to confront the results of your own mistakes , in such a way that errors can't be ignored. there are lots of ways to do that which don't involve programming or college-level math! performance art and carpentry and running your own business or household all force you to confront your own mistakes in this way and deserve no less respect than STEM.

    if i can't feminize my compiler, what's the point?

    by and large, when i learn new things about computers, it's because i'm fucking around. the fucking around is the point. if all the writing helps people learn and come up with cool new ideas, that's neat too.

    half the time the fucking around is just to make people say "jyn NO". half the time it's because i want to make art with my code. i really, sincerely, believe that art is one of the most important uses for a computer.

    i'm not doing this for the money. i happened to get very lucky that my passion pays very well, but i got into this industry before realizing how much programmers actually make, and now that i work for a european company i don't make US tech salaries anyway. i do it for the love of the game.

    some extracts from the jyn computer experience:

    screenshot of a rust-lang PR titled 'use bold magenta instead of bold white for highlighting'. the first sentence in the description is 'according to a poll of gay people in my phone, purple is the most popular color to use for highlighting'

    a series of quotes such as "jyn yeah you're fucking insane" and "what the actual fuck. but also i love it"

    screenshot of a bluesky post by jyn that says "She is humming. She is dancing. She is beautiful." below are three pictures, one J program, and a quote-tweet of A Ladder To by Jana H-S.

    screenshot of a discord conversation. it reads: jyn: for a while i had a custom build of rustc whose version string said “love you love you love you” in purple. jyn: i should bring that back now that i work on ferrocene. anon: You've heard of cargo mommy now get ready for rustc tsundere

    screenshot of a shell command showing the version of a custom-built rustc. it says "Ferrocene rolling love you love you love you", with the "love you"s in purple.

    my advice

    you really shouldn't take advice from me lol the WWII survivorship bias plane, with red dots indicating bullet holes on the wings, tail, and central body

    however! if you are determined to do so anyway, what i can do is point you towards:

    places to start fucking around and finding out

    highest thing i can recommend is building a tool for yourself. maybe it's a spreadsheet that saves you an hour of work a week. maybe it's a little website you play around with. maybe it's something in RPGmaker. the exact thing doesn't matter, the important part is that it's fun and you have something real at the end of it, which motivates you to keep going even when the computer is breaking in three ways you didn't even know were possible.

    second thing i can recommend is looking at things other people have built. you won't understand all of it and that's ok. pick a part of it that looks interesting and do a deep dive on how it works.

    i can recommend the following places to look when you're getting started:

    most importantly, remember: Practice Guide for Computer

    Show HN: PasteClean – Desktop app to strip tracking parameters from clipboard

    Hacker News
    iixotic.github.io
    2025-12-16 01:13:48
    Comments...
    Original Article

    v0.2.1 Now Available

    Remove analytics parameters, referrals, and trackers from your URLs instantly. Protect your privacy before you click.

    ?utm_source=facebook&s=123 Clean

    Everything you need to stay private.

    Instant Cleanup

    Runs quietly in the background. Copies clean links automatically as soon as you clean them.

    Link Unshortener Pro

    Reveal the true destination of bit.ly and tinyurl links before you click. No more surprises.

    Privacy Stats Pro

    Visualize top trackers and see exactly how much marketing data you've stripped away.

    Simple, Fair Pricing.

    Own your privacy. No monthly subscriptions.

    Basic

    Essential link cleaning for everyone.

    $0

    • Local-only processing
    • Remove UTMs & Trackers
    • Dark Mode

    Download Free

    POPULAR

    Pro Lifetime

    For power users who want total control.

    $2.99 / once

    • Everything in Basic
    • Link Unshortener
    • Batch Cleaning Mode
    • Advanced Stats Dashboard
    • Support the Developer

    Buy Lifetime License

    Secure payment via Gumroad

    ‘A Brief History of Times New Roman’

    Daring Fireball
    typographyforlawyers.com
    2025-12-16 01:03:17
    One more from Matthew Butterick, from his Typography for Lawyers, and a good pairing with Mark Simonson’s “The Scourge of Arial”: Yet it’s an open question whether its longevity is attributable to its quality or merely its ubiquity. Helvetica still inspires enough affection to have been the subj...
    Original Article
    A brief history of Times New Roman

    Times New Ro­man gets its name from the Times of Lon­don, the British news­pa­per. In 1929, the Times hired ty­pog­ra­pher Stan­ley Mori­son to cre­ate a new text font. Mori­son led the project, su­per­vis­ing Vic­tor Lar­dent, an ad­ver­tis­ing artist for the Times , who drew the letterforms.

    Even when new, Times New Ro­man had its crit­ics. In his ty­po­graphic mem­oir, A Tally of Types , Mori­son good-na­turedly imag­ined what William Mor­ris (re­spon­si­ble for the open­ing il­lus­tra­tion in page lay­out ) might have said about it: “As a new face it should, by the grace of God and the art of man, have been broad and open, gen­er­ous and am­ple; in­stead, by the vice of Mam­mon and the mis­ery of the ma­chine, it is big­oted and nar­row, mean and puritan.”

    Be­cause it was used in a daily news­pa­per, the new font quickly be­came pop­u­lar among print­ers of the day. In the decades since, type­set­ting de­vices have evolved, but Times New Ro­man has al­ways been one of the first fonts avail­able for each new de­vice (in­clud­ing per­sonal com­put­ers). This, in turn, has only in­creased its reach.

    Ob­jec­tively, there’s noth­ing wrong with Times New Ro­man. It was de­signed for a news­pa­per, so it’s a bit nar­rower than most text fonts— es­pe­cially the bold style . (News­pa­pers pre­fer nar­row fonts be­cause they fit more text per line.) The italic is mediocre. But those aren’t fa­tal flaws. Times New Ro­man is a work­horse font that’s been suc­cess­ful for a reason.

    Yet it’s an open ques­tion whether its longevity is at­trib­ut­able to its qual­ity or merely its ubiq­uity. Hel­vetica still in­spires enough af­fec­tion to have been the sub­ject of a 2007 doc­u­men­tary fea­ture. Times New Ro­man, mean­while, has not at­tracted sim­i­lar acts of homage.

    Why not? Fame has a dark side. When Times New Ro­man ap­pears in a book, doc­u­ment, or ad­ver­tise­ment, it con­notes ap­a­thy. It says, “I sub­mit­ted to the font of least re­sis­tance.” Times New Ro­man is not a font choice so much as the ab­sence of a font choice, like the black­ness of deep space is not a color. To look at Times New Ro­man is to gaze into the void.

    This is how Times New Ro­man ac­crued its rep­u­ta­tion as the de­fault font of the le­gal pro­fes­sion—it’s the de­fault font of every­thing. As a re­sult, many law­yers er­ro­neously as­sume that courts de­mand 12-point Times New Ro­man. In fact, I’ve never found one that does. (But there is one no­table court that for­bids it—see court opin­ions .) In gen­eral, law­yers keep us­ing it not be­cause they must, but be­cause it’s fa­mil­iar and en­trenched—much like those ob­so­lete type­writer habits .

    If you have a choice about us­ing Times New Ro­man, please stop . You have plenty of bet­ter al­ter­na­tives—whether it’s a dif­fer­ent sys­tem font or one of the many pro­fes­sional fonts shown in this chapter.

    Thin desires are eating your life

    Hacker News
    www.joanwestenberg.com
    2025-12-16 00:50:41
    Comments...
    Original Article

    The defining experience of our age seems to be hunger.

    We're hungry for more, but we have more than we need.

    We're hungry for less, while more accumulates and multiplies.

    We're hungry and we don't have words to articulate why.

    We're hungry, and we're lacking and we're wanting.

    We are living with a near-universal thin desire: wanting something that cannot actually be gotten, that we can't define, from a source that has no interest in providing it.

    The distinction between thick and thin desires isn't original to me.

    Philosophers have been circling this territory for decades, from Charles Taylor's work on frameworks of meaning to Agnes Callard's more recent writing on aspiration.

    But the version I find most useful is simple:

    A thick desire is one that changes you in the process of pursuing it.

    A thin desire is one that doesn't.

    The desire to understand calculus versus the desire to check your notifications are both real desires, and both produce (to a degree) real feelings of satisfaction when fulfilled.

    But the person who spends a year learning calculus becomes someone different, someone who can see patterns in the world that were previously invisible, who has expanded the range of things they're capable of caring about, who has Been Through It.

    The person who checks their notifications is, afterward, exactly the same person who wanted to check their notifications five minutes ago.

    The thin desire reproduces itself without remainder.

    The thick desire transforms its host.

    I want to be careful here because this is a claim that can easily slide into unfalsifiable grumpiness about Kids These Days.

    But there's a version of it that I think is both true and important.

    The business model of most consumer technology is to identify some thick desire, find the part of it that produces a neurological reward, and then deliver that reward without the rest of the package.

    Social media gives you the feeling of social connection without the obligations of actual friendship.

    Pornography gives you sexual satisfaction without the vulnerability of partnership.

    Productivity apps give you the feeling of accomplishment without anything being accomplished.

    In each case, the thin version is easier to deliver at scale, easier to monetize, and easier to make addictive.

    The result is a diet of pure sensation.

    And none of it seems to be making anyone happier.

    The surveys all point the same direction: rising anxiety, rising depression, rising rates of loneliness even as we've never been more connected.

    How could this be, when we've gotten so good at giving people what they want?

    Maybe because we've gotten good at giving people what they want in a way that prevents them from wanting anything worth having.

    Thick desires are inconvenient.

    They take years to cultivate and can't be satisfied on demand.

    The desire to master a craft, to read slowly, to be embedded in a genuine community, to understand your place in some tradition larger than yourself: these desires are effortful to acquire and impossible to fully gratify.

    They embed you in webs of obligation and reciprocity.

    They make you dependent on specific people and places.

    From the perspective of a frictionless global marketplace, all of this is pure inefficiency.

    And so the infrastructure for thick desires has been gradually dismantled.

    The workshops closed, the congregations thinned, the apprenticeships disappeared, the front porches gave way to backyard decks and studio apartments and the coveted Micro Homes where you could be alone with your devices.

    Meanwhile the infrastructure for thin desires became essentially inescapable.

    It's in your pocket right now.

    Grand programs to Rebuild Community or Restore Meaning seem to founder on the same logic they're trying to escape.

    The thick life doesn't scale.

    That's the whole point.

    So: bake bread.

    The yeast doesn't care about your schedule.

    The dough will rise when it rises, indifferent to your optimization.

    You'll spend an afternoon doing something that cannot be made faster, producing something that you could have bought for four dollars, and in the process you'll recover some capacity for patience that the attention economy has been methodically stripping away.

    Write a letter, by hand, on paper.

    Send it through the mail.

    The letter will take days to arrive and you won't be able to unsend it or edit it or track whether it was opened.

    You're creating a communication that exists outside the logic of engagement metrics, a small artifact that refuses to be optimized.

    Code a tool for exactly one person.

    Solve your friend's specific problem with their specific workflow.

    Build something that will never scale, never be monetized, never attract users.

    The entire economy of software assumes that code should serve millions to justify its existence.

    Making something for an audience of one is a beautiful heresy.

    None of this will reverse the great thinning.

    But I've started to suspect that the thick life might be worth pursuing anyway, on its own terms, without needing to become a movement.

    The person who bakes bread isn't trying to fix the world. They're not making any attempt to either dent or undent the universe.

    They're trying to spend a Sunday afternoon in a way that doesn't leave them feeling emptied out.

    They're remembering, one loaf at a time, what it feels like to want something that's actually worth wanting.

    SoundCloud confirms breach after member data stolen, VPN access disrupted

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-16 00:38:47
    Audio streaming platform SoundCloud has confirmed that outages and VPN connection issues over the past few days were caused by a security breach in which threat actors stole a database containing user information. [...]...
    Original Article

    SoundCloud

    Audio streaming platform SoundCloud has confirmed that outages and VPN connection issues over the past few days were caused by a security breach in which threat actors stole a database containing user information.

    The disclosure follows widespread reports over the past four days from users who were unable to access SoundCloud when connecting via VPN, with attempts resulting in the site displaying 403 "forbidden" errors.

    In a statement shared with BleepingComputer, SoundCloud said it recently detected unauthorized activity involving an ancillary service dashboard and activated its incident response procedures.

    SoundCloud acknowledged that a threat actor accessed some of its data but said the exposure was limited in scope.

    "We understand that a purported threat actor group accessed certain limited data that we hold," SoundCloud told BleepingComputer.

    "We have completed an investigation into the data that was impacted, and no sensitive data (such as financial or password data) has been accessed. The data involved consisted only of email addresses and information already visible on public SoundCloud profiles."

    BleepingComputer has learned that the breach affects 20% of SoundCloud’s users, which, based on publicly reported user figures, could impact roughly 28 million accounts.

    The company said it is confident that all unauthorized access to SoundCloud systems has been blocked and that there is no ongoing risk to the platform.

    Working with third-party cybersecurity experts, the company said it took additional steps to strengthen its security, including improving monitoring and threat detection, reviewing identity and access controls, and conducting an assessment of related systems.

    However, the company's response included a configuration change that disrupted VPN connectivity to the site. SoundCloud has not provided a timeline for when VPN access will be fully restored.

    Following the response, SoundCloud experienced denial-of-service attacks that temporarily disabled the platform's web availability.

    While SoundCloud has not shared details about the threat actor behind the breach, BleepingComputer received a tip earlier today stating that the ShinyHunters extortion gang was responsible.

    Our source said that ShinyHunters is now extorting SoundCloud after allegedly stealing a database containing information about its users.

    ShinyHunters is also responsible for the PornHub data breach that was first reported today by BleepingComputer.

    This is a developing story, and we will update it as more information becomes available.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Ideas Aren't Getting Harder to Find

    Hacker News
    asteriskmag.com
    2025-12-16 00:34:35
    Comments...
    Original Article

    Karthik Tadepalli

    For half a decade we’ve been worrying that ideas are getting harder to find. In fact, they might just be harder to sell.

    Fifty years ago, productivity growth in advanced economies began to slow down. Productivity growth — the component of GDP growth that is not due to increases in labor and capital — is the primary driver of rising incomes. When it slows, so does economic growth as a whole. This makes it an urgent trend to understand. Unfortunately, the most popular explanation for why it’s happening might be wrong.

    The most widely endorsed reason productivity growth has faltered is that we are running out of good ideas. As this narrative has it, the many scientific and technology advances responsible for driving economic growth in the past were low-hanging fruit. Now the tree is more barren. Novel advances, we should expect, are harder to come by, and historical growth may thus be difficult to sustain. In the extreme, this may lead to the end of progress altogether.

    This story began in 2020, with the publication of “Are Ideas Getting Harder to Find?,” by economists Nicholas Bloom and colleagues. 1 Bloom et al. looked across many sectors, from agriculture to medicine to computing. In each field, productivity measures have grown at the same rate as before. This sounds like good news, except that the number of researchers in each of these fields has exploded. In other words, each researcher produces much less than they used to — something you might expect if ideas really are getting harder to find.

    The progress studies movement and the metascience community have risen, in part, in response to this challenge. Both seek ways to rethink how we do research: by making our research institutions more efficient or by increasing science funding.

    But there's a growing body of evidence that suggests ideas are not, in fact, getting harder to find. Instead, the problem appears to be that markets have become less effective at translating breakthrough technologies into productivity gains. Researchers appear to be continuing to generate valuable innovations at historical rates. It’s just that these innovations face greater barriers to commercialization, and innovative firms thus fail to gain market share.

    All this suggests that the constraint on growth isn’t in our universities or labs or R&D departments, but in our markets.

    Anagh Banerjee

    Why ideas matter

    Historically, the task of growth theory has been to rationalize this graph:

    Through two world wars, the Great Depression, the global financial crisis, and the Cold War, US real GDP per capita has grown steadily at 2% per year. This consistency is remarkable, so much so that it has motivated economists to search for a near-immutable source of economic growth: something fundamental that drives growth across long sweeps of time. Sure, growth can be affected in the short run by policies and events of the day — tariffs, wars, demographic transitions, educational booms — but it would be an incredible coincidence if the combined impact of every economic policy and external event in history happened to net out as a constant growth rate! So what could this immutable mechanism be?

    It took economists decades to understand how sustained exponential growth is even possible. Exponential growth requires increasing returns to scale — doubling all the inputs into production must more than double the scale of economic output. Why? Each year output has to be reinvested as capital to produce next year’s output. But there’s diminishing returns to capital alone, so each year a country would need to reinvest a larger fraction of its annual output to get the same growth rate. Eventually, it would need to reinvest more than 100% of its output, which is impossible. It is only through increasing returns to scale that we can counteract the diminishing returns to capital, allowing us to maintain exponential growth.

    So it’s easy! We just posit increasing returns to scale in production, and we now have an explanation for why growth is exponential — decades of research not necessary. The problem is that increasing returns to scale violates our basic intuitions. Imagine you own a factory that produces 100 cars a day. You create an exact duplicate of this factory in another city, with the same employees, same equipment. How many cars would you expect from both factories combined? The intuitive assumption is 200. Increasing returns demands that we can somehow get more than double the cars from creating the second factory — which is hard to justify.

    The economist Paul Romer won a Nobel Prize for resolving this puzzle: Increasing returns to scale comes from ideas. In his view, it is a mistake to think that the only inputs to the car factory are the workers and machines. There are also blueprints for the cars being produced, instructions for how the machines should be laid out, and the concept of an assembly line process to organize the workers.

    Romer’s work elegantly solved the problem posed by our factory duplication thought experiment. In setting up the second factory, we needed only to duplicate the workers and the machines — we didn’t need to duplicate the designs or the idea of an assembly line. Once created, ideas can be used in perpetuity, which is how we can double the factory’s output while doubling only its physical inputs. This key property of ideas — that they can be used by everyone at the same time — has become the fundamental explanation for exponential growth. This is why it matters so much if ideas truly are getting harder to find: If idea production is slowing down, it threatens the foundation that allows growth to continue.

    The simple argument for declining idea productivity

    To test whether our research efforts are getting less bang for their buck today than they did decades ago, Bloom et al. use a key feature that descends from Romer’s original model — that productivity growth can be represented by the following equation:

    Productivity growth = Research productivity * Number of researchers

    Whether total factor productivity, or agricultural yields, or chip density, growth of any productivity measure should be a direct function of the number of researchers working in that sector and their research productivity. This means that if we observe productivity growth in any given sector, and we know the number of researchers working in that sector, we can infer research productivity.

    Thus, the authors compile data on productivity growth and researcher counts across a number of different sectors to estimate whether research productivity has been falling, which according to the authors, means ideas must be getting harder to find.

    Perhaps their most compelling evidence comes from Moore's Law, the famous observation that the number of transistors on a computer chip doubles roughly every two years. This doubling represents a constant 35% annual growth rate in chip density that has held for 50 years. On its face, Moore’s Law seems like a refutation of any diminishment in technological progress.

    Yet maintaining the exponential growth in chip density has required exponential increases in effort. Bloom et al. compiled R&D spending data from dozens of semiconductor firms over time and found that the effective number of researchers working to advance Moore's Law increased by a factor of 18 between 1971 and 2014. Meanwhile, the growth rate of chip density has stayed constant. Put differently, it takes 18 times as many researchers today to achieve the same rate of improvement in chip density as it did in the early 1970s. This implies that research productivity in semiconductors has fallen at an average rate of 7% per year.

    Look at agricultural productivity and you see a similar pattern. The authors measure crop yield growth across major US crops. Between 1969 and 2009, yield growth for these crops averaged a steady 1.5% per year, but the research effort directed toward improving yields has grown by between sixfold and 24-fold, depending on the crop.

    Zoom all the way out, and the pattern still holds. Across the economy as a whole, R&D efforts have increased by a factor of 20 since the 1930s, yet productivity growth has become slower.

    These results are unambiguous. Research effort has gone up, yet productivity growth is not budging. This seems like clear evidence that something about productivity growth is getting harder. But whether the problem is a lack of new ideas is much less obvious.

    Measuring idea productivity directly

    The idea-based growth model is successful as a simple description of how exponential growth could occur. The problem is we’ve taken it too literally. Bloom et al. assume that idea production is the only factor behind productivity growth. For example, their agricultural case study uses crop yield growth as the sole variable for new ideas. This allows them to sidestep difficulties in defining ideas and measuring their impact, but it also rules out the possibility that factors other than ideas are the real reason yields are stagnating.

    Imagine that agricultural R&D spending was highly effective, and that in the past few decades it led to a stream of new seed varieties that were each higher-yield than the last. What if those seeds were not actually being purchased by farmers — maybe because farmers were unaware that they existed or because adopting a new seed is risky? We would observe crop yields stagnating despite R&D spending effectively creating more productive crops.

    Bloom et al.’s measure of "ideas" combines actual research innovations with other necessary conditions for research innovations to translate into higher output. After being invented, technologies have to be successfully commercialized, marketed, and adopted at scale before they can have large effects on economic output. What if we’re still just as good at producing ideas, but we’ve become much worse at capitalizing on them?

    This is exactly the argument made by Teresa Fort and colleagues in a paper from April of this year: “Growth Is Getting Harder to Find, Not Ideas.” 2 Fort et al. use the census of firms linked to US patent filings to capture economy-wide invention, rather than focusing on sector-specific case studies. Most importantly, they measure idea production more directly, by estimating the relationship between R&D spending and new patents rather than inferring idea production from firm growth. Since patents represent technologies that are novel enough to be given intellectual property protection, and also economically valuable enough to be worth patenting, they serve as a more direct measure of “ideas.”

    Fort et al. find that, across firms, research expenditure today continues to be associated with a proportional increase in patents similar to the 1980s. They use a variety of measures to get at this, but the most transparent one is to measure the ratio between patents and R&D expenditures for each firm. Doing this, they find that the average firm’s patent-to-R&D ratio has actually increased by 50% since 1977 — contrary to a story in which R&D effort is becoming less effective. While there is enough variability in this ratio that Fort et al. can’t be confident that it has actually increased, we can certainly say that it hasn’t fallen in the way that Bloom et al. would predict.

    The obvious question is whether these patents might represent less generative and useful ideas, something like more incremental advances than patents of the past. Maybe the low-hanging fruit really is gone, and new patents are capturing less useful ideas. Fort et al. address this issue by focusing on breakthrough patents , a measure of technological innovation defined by Kelly et al. , 3 and showing that their results still hold.

    For a technology to count as a breakthrough, it must be generative — technologies that come after must build on it. This idea is the basis for Kelly et al.’s measurement of a patent’s significance. They score a patent as breakthrough if its text is different from patents that came before it but similar to the text of patents that came after it. Patents that scored in the top 5% on this measure included the elevator, the typewriter, the telephone, and frozen foods — giving us some assurance that this measure really selects high-quality technologies.

    Fort et al. show that their results are not simply coming from more incremental patents over time. Not only has the number of patents filed per R&D dollar increased, but the number of breakthrough patents per R&D dollar has also increased. Firms produce three times more breakthrough patents per R&D dollar than they did in 1977.

    This analysis suggests that Bloom et al. jumped the gun by attributing the slowdown in productivity growth to declining research productivity. If you infer research productivity only from output growth, it’s hard to find. But if you look at new idea production through the lens of patent data, we appear to be as generative as ever. So  there must be some other failure in translating new technologies into productivity growth. What could that be?

    The fault in our markets

    We now have a puzzle — productivity growth is slowing down, yet the factor that we think of as the most important determinant of productivity growth is not. The way to resolve this is to let go of the view that ideas are the only factor that determine growth. Remember, the power of that view is its ability to explain growth in the long run — to generate the graph of steady 2% growth over one and a half centuries of war, changes in trade policy, and wide political shifts. Various factors can absolutely drag down growth rates over shorter periods: Growth was visibly lower during the Great Depression, only recovering because of catch-up growth in the boom that followed World War II. Our challenge is to explain the slower growth over a longer period. There is mounting evidence that a factor more obvious than “ideas are harder to find” is responsible: specifically, a decline in market efficiency.

    The first indication comes from Fort et al.’s analysis. In addition to focusing on breakthrough patents, Fort et al. consider a measure of patent value based on stock market returns from Kogan et al. 4 The authors estimate how much a (publicly traded) firm's stock price moves in response to a patent being granted and use this movement as a measure of how valuable the patent is. This measure is unique in that it doesn't capture only the value of the technology but also all factors that go into making a technology profitable for its inventors. So it is notable that contrary to their main results, Fort et al. find that the stock market value of the average patent has actually fallen over time. In other words, the market places lower commercial value on new technologies compared with before. Since the authors also show that the number of breakthrough patents per dollar has actually increased, this is puzzling — somehow, firms are making better technologies than before but getting smaller rewards.

    This result gels with a broader view in the productivity literature — that the primary limitation on productivity growth is whether more productive firms can outcompete less productive firms. Intuitively, productivity across the economy is not just the simple average of each firm’s productivity; it’s the market share-weighted average of each firm’s productivity. This means that productivity growth across the economy relies not just on firms finding ways to produce new goods at lower costs (which is where “ideas” would help) — it relies on the best firms being able to gain market share, to increase their contribution to aggregate productivity. This ability for better firms to compete is known as allocative efficiency .

    So has allocative efficiency decreased in advanced economies, and can that explain the productivity slowdown? Decker et al. 5 use the same census of US firms to show that it has. On average, each firm’s productivity has grown at the same rate as before, but less productive firms have actually gained market share over more productive firms. These two factors together can explain why productivity growth has slowed down. Firms have maintained their innovative capacity, but the market is much less rewarding of that innovative capacity than it has been in the past.

    In the same spirit, Akcigit and Ates 6 argue that the most important factor behind the fall in allocative efficiency 7 is a drop in the rate at which lagging firms catch up to leader firms in an industry. 8 They consider several possible factors that could influence how dynamic the economy is — corporate taxes, R&D subsidies, entry costs, and catch-up rates for lagging firms — and analyze which of them is most responsible for falling allocative efficiency. They find that almost all of the decline in allocative efficiency is explained by lagging firms failing to catch up. This tells us that the problem of declining allocative efficiency has a rather specific form: Less productive firms stay on as market leaders, while more productive firms are unable to catch up.

    This is a puzzle! Why would the market fail to reward innovative firms, or, conversely, why does it continue rewarding less innovative firms? Unfortunately, here we don’t have clear answers. It could be that incumbent firms leverage market power to prevent innovative competitors from gaining market share. Perhaps regulatory barriers make it harder for new entrants to compete with incumbents. Financial markets may also have become less effective at identifying and funding high-potential firms. Answering this question is going to be central to addressing the productivity slowdown and should be a major focus for progress studies.

    Progress studies needs to go to market

    The distinction between “ideas are getting harder to find” and “growth is getting harder to achieve” changes what we should focus on to accelerate progress. If the source of slowing growth was actually that each new scientific or technological breakthrough requires exponentially more effort, then progress-oriented thinkers would be right to focus on science funding, peer review, and the culture of scientific research.

    However if ideas remain as discoverable as ever, but their economic impact is fading, then we need to look downstream from the laboratory. The decline in allocative efficiency should be more of a main focus — we need to throw more of our intellectual capital at understanding how to increase competitiveness and the market potential for innovative firms and technologies, in the same way that we've focused on understanding how to make better technologies.

    The narrative that "ideas are getting harder to find" has profoundly shaped how economists and policymakers think about innovation and growth. It implies we're fighting against some fundamental law of diminishing returns in human creativity. But what we’re actually fighting against is a flaw in our markets that prevents that creativity from being rewarded economically. If we want to restore growth, we should stop worrying about whether we've picked all the low-hanging fruit and start taking that fruit to market.

    Published

    Have something to say? Email us at letters@asteriskmag.com .

    Creating custom yellow handshake emojis with zero-width joiners

    Hacker News
    blog.alexbeals.com
    2025-12-16 00:25:53
    Comments...
    Original Article

    Apple added support for multi-skin tone handshake emojis in 2022, allowing you to go from the old standby of 🤝 to supporting handshakes like 🫱🏻‍🫲🏿. However while these handshake emojis look similar, 1 I'm not sure these look similar for you . To try to make this writeup render consistently I added an emoji rendering fallback font but this may not fix all platforms (and may break others). To toggle that override you can they're actually substantially different.

    How they work

    Each character (and therefore each emoji) is made up of Unicode codepoints. We can look at the codepoints that make up an emoji by splitting it apart character by character using JavaScript.

    [...'🤝'].map(c => 'U+' + c.codePointAt(0).toString(16)).join(' ')

    🫱🏻‍🫲🏿

    U+1FAF1 U+1F3FB U+200D U+1FAF2 U+1F3FF

    From this we can see that the yellow handshake is only one codepoint for the original emoji. The modified color handshake however is actually a composite, composed of five different characters:

    • U+1FAF1 - 🫱 (Right-facing hand)
    • U+1F3FB - 🏻 (Fitzpatrick 2 The color modifier names come from the Fitzpatrick scale , a human skin color phenotypal scale created by an American dermatologist in the 1970s (a scale that is often critiqued for its eurocentric bias). 1-2 3 This is "1-2" because while there are 6 categories in the scale, 1 and 2 are so similar that they are combined into the same skin color for emojis (see aforementioned point on bias). )
    • U+200D - ZWJ (Zero-width joiner)
    • U+1FAF2 - 🫲 (Left-facing hand)
    • U+1F3FF - 🏿 (Fitzpatrick 6)

    The ZWJ character is the real star of the show, gluing together disparate emojis into something that different sites and fonts can choose to render as a single grapheme (backing emojis like 👩‍❤️‍💋‍👨 4 👩 + ❤ + 💋 + 👨 , though flags like 🇺🇳 5 The way the flags are encoded leads to one of my favorite weird JS behaviors:
    '🇺🇸🇨🇺'.replace('🇸🇨', '🇦🇷') == '🇺🇦🇷🇺'
    Can you figure out why? It's because of country ISO codes: those are the United States and Cuban flags, or US CU. Swapping the Seychelles (SC) for Argentina (AR) yields UA RU, or Ukraine and Russia.
    work differently).

    Creating new handshakes

    Knowing how these are constructed allows us to break out of Apple's UI. Their keyboard lets us pick either the uniformly yellow emoji, or build a hand of two colors. But what if instead of white and black we want yellow and black?

    Instead of adding skin modifiers to both hands we can only modify one of them. 6 Or none of them, creating 🫱‍🫲, which is visually identical to 🤝 and yet composed of three codepoints instead of one. Some simple JavaScript will then build it back into a single character, allowing us to create two emoji that Apple will render but not let you create. 7 Unfortunately these emojis don't render as large single messages on iOS/macOS. I think this is because Apple thinks it's text rather than emojis, but I'm not sure (or what emoji rendering requirement it might be failing).

    console.log('\u{1FAF1}\u{200D}\u{1FAF2}\u{1F3FF}')

    🫱‍🫲🏿

    U+1FAF1 U+200D U+1FAF2 U+1F3FF

    🫱🏿‍🫲

    U+1FAF1 U+1F3FF U+200D U+1FAF2

    Quill OS – an open-source, fully-functional standalone OS for Kobo eReaders

    Hacker News
    quill-os.org
    2025-12-16 00:22:41
    Comments...
    Original Article

    Quill OS is an open-source, fully-functional standalone OS for Rakuten Kobo's eReaders.

    Quill OS

    Here are some of Quill OS' features:

    Fully integrated KoBox X11 subsystem
    ePUB, PDF, picture and plain text display support
    Versatile configuration options for reading
    muPDF rendering engine for ePUBs and PDFs
    Wi-Fi support and web browser
    Encrypted storage with EncFS
    Fast dictionary & local storage search
    Dark mode
    Full factory reset option if needed
    Seamless update process
    VNC viewer app
    Search function
    10 built-in fonts
    Auto-suspend
    Lock screen/passcode
    User-friendly experience

    The Bob Dylan Concert for Just One Person

    Hacker News
    www.flaggingdown.com
    2025-12-16 00:18:58
    Comments...
    Original Article

    Flagging Down the Double E’s is an email newsletter exploring Bob Dylan performances throughout history. Some installments are free, some are for paid subscribers only. Sign up here:

    Screengrab from Experiment Ensam

    Eleven years ago today, a Finnish online gaming company posted a 14-minute video that blew the minds of Bob Dylan fans across the globe. It depicted one of the most unusual performances of Dylan’s career, which had occurred just a few weeks prior. On stage in a beautiful old theater, Bob and his band performed for exactly one person. You can see that person sitting there in the photo up top. The rest of the theater was entirely empty.

    How did this happen, that Bob Dylan gave a concert for just one guy? And what was this experience like for that guy? I wanted to find out.

    That person was Fredrik Wikingsson, a prominent TV host in Sweden. The video was part of a series called Experiment Ensam , which translates to Experiment Alone . The idea was to explore what happened when a single person did an activity typically meant for a group: Karaoke, stand-up comedy, and, in this case, attending a Bob Dylan concert. Have you ever gotten annoyed at people around you at a Dylan concert and wished they weren’t there? For Wikingsson, they weren’t.

    Not only that, but Dylan did not perform his usual fare. Instead, he performed four 1950s covers, several of which he’s never sung before or since: Buddy Holly’s “Heartbeat,” Fats Domino’s “Blueberry Hill,” Lefty Frizzell’s “You’re Too Late,” and Big Bill Broonzy’s “Key to the Highway.”

    I interviewed Wikingsson about his surreal experience having Dylan sing for only him. That’s below. On Monday, I’ll share a part two with the series’ director , who shares some behind-the-scenes info on how it came together, including an unexpected meeting with Bob himself. That one will go only to paid subscribers . Sign up if you want to read it [update: it’s here ]:

    Before we dive in, the Experiment Ensam Dylan segment is a fascinating video and I encourage you to watch it if you haven’t before. The audio is in Swedish, but it’s subtitled in English.

    Let’s start at the beginning and walk through it. And the beginning is: How did you become the one person?

    There was a series of commercials made in Sweden for a Finnish online gaming site. The theme was, we have this famous Swedish semi-crappy singer, Vanilla Ice meets Snoop Dogg, who experienced things alone that you normally do in a group, collectively. So he went to a stand-up comedy club alone. He went to the opera alone. All those things.

    I knew the director a bit. I met him at a party, three a.m. in an apartment, everybody was drunk, and he came up to me. “Have you seen the things we did with Experiment Alone ?” I lied to him and I said, “Yeah, sure, those are great.” I hadn’t really seen them.

    He said, “Guess what we’re going to do next.” He knew that I’m like one of the biggest Dylan fans in Sweden. Maybe that’s a stretch, but one of the biggest he knew for sure. And he said, “It’s going to be Bob Dylan.” I just immediately—and you’ll have to excuse my French—I said, “Who do I have to blow for it to be me?”

    [I told him,] “You need to get rid of the other guy. The storytelling is much better if I do it because I’m a huge fan. I’ll be able to write articles about it. It’s going to be better for this fucking Finnish gaming site if I do it. I won’t take any money for it. I will pay for my own trip; I will pay for my own hotel. I just want the experience.”

    I had to sit through meetings with a lot of commercial people—art directors, directors, all these PR people. All the while, there was a 50% part of me thinking, “This is just a Punk’d episode.” Because I work in television, so I would be a perfect target for that. Right up until the minute he walked in.

    So you’re meeting all these people to get approved, basically?

    Yeah, because they had to can the other guy, and they needed to be sure it was worth it.

    I’m not much of a predator normally, but in this case, it was like, “That needs to be me. This is one of those things I potentially could remember forever,” which turned out to be true.

    Why was it in Philly? Was that just where Bob said he would do it?

    I could imagine the PR people going, “Okay, what venues is he playing? What will look good on camera?” Because he was playing the same night in that venue, a beautiful old theater.

    Did you ever get any intel on why he agreed to do it?

    He probably got a lot of money for it. There was a bit of speculation afterwards, like it was the best paid soundcheck in history. And in essence, it was like a soundcheck, because I guess he runs through numbers for fun sometimes at soundchecks. This time it was just with me in the audience and a couple of cameras.

    Once you get approved, once you know where it’s going to be, what are your expectations going in?

    Well, it was very complex, because I’ve read all these books about him, I’ve seen all the films, and I’m acutely aware of how uncomfortable he is in weird settings. It was like, “How much am I gonna feel that he hates this?” That was my biggest fear, that I’m going to sit there and be part of the thing that made Dylan’s day shittier.

    You think about, “What if I got in the same elevator as him?” Well, I wouldn’t say anything, because I don’t want to be a nuisance. That was like an elevated version of that—no pun intended. However much money he got paid, I don’t want to be that person who makes Dylan’s day worse.

    But I just read the other day the theory that he has the highest threshold for embarrassment in the world. That the key asset in Dylan’s career was the fact that he doesn’t get embarrassed. He can try things like playing religious songs, playing electric, he doesn’t care. So maybe there is a chance of that as well. Like he can do this stupid, silly thing in front of one person.

    So there was part of me going, “He doesn’t give a fuck,” and part of me going, “He hates this, he’s gonna think of me as a nuisance.” I don’t think I’ve ever been as self-aware, ever, walking into that theater and thinking about how he will receive this. But it faded away pretty quickly once the music got going.

    Did you know it was going to be covers? Were you expecting his own songs? Did you know how long it was going to be?

    I expected him to run through three, four numbers of the current set list. To me, it was the greatest delight, him playing songs he’d never really played before or since. That made it even more special.

    Had he played “Lay Lady Lay” or whatever, a couple of the best-of, that would have been exciting as well. But this certainly—I mean, I didn’t even know half the songs. It made it way more special. That could potentially be released as an EP, because it was such a unique little foursome.

    Screengrab from Experiment Ensam

    When I watched the film, you look, to me, fairly apprehensive walking into the theater beforehand. Is that how you would describe your feelings?

    I haven’t seen the film. To me, it’s such a powerful memory. It’s such a peculiar sensation in my mind, almost like a taste or a smell. I don’t want to water it down with other influence. I just want to have that thing, how I feel about it, in my mind for as long as I can remember it.

    What do you remember about your emotions as he comes out, as he starts playing, as you realize it’s a Buddy Holly song?

    I always loved Buddy Holly, so, to me, that was just delightful. I had a double CD of Buddy Holly’s greatest hits when I was in my early teens, and I learned to play guitar playing his songs. So that was just like a bonus for me. And I knew how much Buddy Holly meant to him. I’ve read Chronicles many, many times.

    But in all honesty, it wouldn’t have mattered almost what he played. Just the fact that he chose the songs and those were apparently songs he was keen on playing and trying out with the guys.

    To me, it was also moving just to see the band sounded so good. They really wanted to put on a good show. Of course, not only to me, they knew they were being recorded, but still, these guys are fucking pros. They don’t want to let him down or me down or the camera down. It felt like a bit of a collective effort. And I want to include myself in that collective if I can.

    Speaking of including yourself in the collective effort, one of the most striking moments was you debating the question of: Do you applaud? [He did at first, then said it sounded weird echoing around the room by itself, so he stopped.]

    Which is complicated. Everybody in the room knows how silly this is. But still, even though it was such a weird scenario, I wanted to be like a human in it, at least try to imitate one. That’s why also later on I shouted something like, “You guys sound great!” I wanted to include the band, for whatever it’s worth. I remember him smiling at me a little bit.

    Were you feeling self-conscious? You’re fairly brightly lit. He can see you. You’re right there.

    Of course. Acutely.

    He’s not the guy who’s going to look at you intently, and I was quite a few meters away, but I was thinking about how I behaved, I was thinking about how it sounded, I was thinking about how I was feeling. I was trying to preserve it to myself and record it in my own mind. There were like fifteen things going on at the same time in my mind.

    I mean, it’s one of the most heightened experiences of my life. Being at a Dylan show is always exciting—I’ve been to maybe thirty—but this was something else. To no surprise, like four hours later I was super drunk in a karaoke bar in Philly, because it was too much to handle. I needed to just blow off some steam afterwards.

    When I was watching it, I was thinking, if this were me, is this a dream, or is this more like a nightmare?

    It could have gone either way. If I’d had the sense that he really despised this whole thing, giving me the evil Dylan eye, which he has given a lot of people throughout the years—and I’ve seen all the clips—if the vibe had been antagonistic, that would have been terrible. But it just felt as joyous as one of those very manufactured scenarios probably can feel.

    There’s a moment where he looks around this empty room and chuckles to himself. Then at the end, he says, “You can come anytime” and he’s sort of laughing with the band. He seems to be getting a kick out of the ridiculous artifice of the whole thing.

    That made my day and my week and my year. All the tension blew away. Like, okay, this was fine. He dug this, in his way.

    That was probably from a true place, but it also felt a little bit generous. Okay, he realizes we’re—I’m not going to say we’re in this together, because that’s a reach, but it was a weird little thing and I was a small part of it. Even though it’s a bit of a stretch to call that a connection, it’s something. It’s fucking something. To me, the music, of course, and the whole experience, but also the fact that I made him laugh a little bit and say something—I’ll cherish that forever.

    Did you have a favorite song, a favorite performance?

    When he played the harmonica. Up to that moment, if I’m generalizing a little bit, I always thought that I was sort of a word man, and the lyrics were the most important thing. And maybe I underestimated the whole music aspect. I mean, I’ve seen all the beautiful harmonica solos from the ’66 tour or the 1980 tour, “What Can I Do For You.” All those things are majestic and fucking amazing, but there was something about when he played the harmonica just for me. Because he could have easily just skipped the harmonica part. He could have just done the song. That felt like a little extra gift, musically.

    It may sound silly, but it made me feel maybe I’ve underestimated how much the music has meant to me throughout the years. It’s been a major part of my life since I was 15. He’s been with me for such a long time, and I just thought, “Okay, I need to pay closer attention to the music.”

    Bob Dylan finishes, leaves the stage. What happens next?

    There is a very sad coda to this story, I’m afraid. It’s tragic comedy. What’s the word in English?

    Tell me the story and I’ll let you know.

    A lot of people, when I went on this trip, asked, “Are you going to be able to have dinner with him? Are you going to have drinks with him?” I went, “No, no, no. That’s not him. That’s not the show. That’s not going to happen.” I had zero expectations of that.

    But maybe he could sign an album for me. At least I could ask that. That’s not a big ask.

    So I’m in New York the day before and I’m like okay, I need to buy a couple of albums. I want to buy for one for me and one for one of my best friends who’s a huge Dylan fan. What a great gift to give a friend. And also a bit of busting balls, like a constant reminder to my friend that I got to experience this.

    I’m in some store on Bleeker Street, of course, and I’m thinking, “Okay, so what albums? Am I going to go the Blonde on Blonde way? Or am I going to be funny and do the Self-Portrait thing?” I start to overthink in a very moronic way. I’m thinking to myself, “Wait a minute, isn’t there a rumor that he has recorded an album of Sinatra covers?” Which turned out to be true later on. So I’m thinking, “I should buy two Sinatra albums.” I’m buying In the Wee Small Hours and I think Songs for Swingin’ Lovers . “That’s very cool. It shows Dylan that I’m in the know.”

    You might ask yourself, didn’t you buy any backup albums? No, I did not.

    So I go to Philly with my two Sinatra albums in hand. I meet [Dylan manager] Jeff Rosen afterwards. He’s over the moon because I’ve behaved. “Oh, that was great. You did well. Dylan is so happy with this. Thank you very much for not overreacting” or whatever.

    And I’m asking him, “Do you think he could sign a couple of albums for me?” “Of course! Bob would love to do that.”

    I pull out the albums and he’s like, “But these are Sinatra albums…”

    “Yeah?”

    It starts to dawn on me that this is not a good idea.

    He says, “That would be like pissing on Sinatra’s grave. Bob would never do that. Did you bring any backup albums?” And I’m like, “No.”

    Jeff Rosen is just walking away. “I’m sorry, man. I can’t help you.” And then, boom, it’s over.

    I’m standing there thinking, I can’t let this define my experience. I need to put this out of my mind immediately, because this is not the experience. The experience just took place. That was the music, that was the concert. Fuck the albums, fuck the autographs, that doesn’t matter.

    I’ve tried very bravely throughout the years to put that out of my mind, and I’ve succeeded. Just thinking about it now, it breaks my heart a little bit.

    I’m sorry to have brought it back up. But yeah, tragicomedy is maybe the word.

    I deserve it.

    I weirdly have a similar story. Elvis Costello used to have this TV show. I once won tickets to a private taping with Bruce Springsteen, shortly after college. I’m a big fan of both of them now, but at the time I was more of a Springsteen guy. So I was thinking, maybe in this smaller, more private setting, I’d be able to meet him. So I brought a Bruce album just in case. The show’s amazing, then afterwards, I go around back to this loading dock. I was just hanging out by the buses. No security or anything. Who shows up? Not Bruce. Elvis. I ask him, “Do you want to sign this?” He laughed like, “I’m going sign a Bruce album?” But he did. He seemed to find it funny.

    My Springsteen album signed by Elvis Costello

    Well, maybe Bob would have too, but I met the gatekeeper.

    Elvis is a little more personable. Or at least, in that case, easier to find without a gatekeeper.

    Have you seen the Bruce film?

    I just saw it a couple of weeks ago. It was okay.

    Did you like A Complete Unknown ?

    I liked it more. I thought A Complete Unknown did the same thing better, which is sort of a Hollywood-ish, paint-by-numbers biopic. I mean, I’m Not There , that’s my Bob movie. That’s the movie for the weirdos.

    During the screening of [ A Complete Unknown ], I thought to myself maybe ten times, “Oh, this is too silly. This is fucking hokey.” Then, also, my eyes welled up ten times as well.

    There’s only movies that could do that. They can both be fucking silly and ludicrous and then very moving two minutes later.

    I’m Not There is a much better film, but I thought to myself, “Did I cry once during that film?” Not really, but I was impressed with it. So to me, it was much more of an intellectual experience. Whereas a hokey biopic takes you to another place emotionally.

    I sort of agree with that. I was expecting to be like a Dylan nerd fact-checker getting annoyed at all the errors. And there were a million factual errors, but in the moment they didn’t annoy me. The overall emotional story seemed basically true, and I got swept up in it.

    Somebody asked Chalamet, “How do you feel about letting go of Dylan as a character?” “I feel terrible. I love being this guy.” And then somebody said, “You could just wait like ten, twelve years and then play him during the gospel period.” His eyes just lit up, like, “Oh, fuck yeah. That’s still on the table.” Which would be an incredible film, I think. That could be even more powerful.

    Every ten years they could just make another one as the guy gets older and older.

    Then eventually they get to Philly and my concert.

    That’s like the 27th installment in the series.

    And the least-seen.

    I read that you didn’t want to see the show that night. That it would have felt weird. So when did you next see a regular Bob Dylan concert?

    He came to Stockholm in 2015, and he played a pretty small venue called the Waterfront Arena, which is where, later, he collected his Nobel Prize backstage. He played a lot of the Tempest songs. That was one of the best concerts I’ve ever seen.

    But it didn’t color my experience in any way. This is such a stand-alone thing. It’s not like I sat there six months later and was like, “Hmm, how does this compare to seeing him alone?” It’s not comparable.

    You were able to just plug back into how you would see a Dylan concert before.

    Yeah. Have you seen him lately?

    This summer, the Outlaw tour here.

    I saw him just two weeks ago in a huge arena in Stockholm. I don’t know how to feel about the concerts anymore. I appreciate that he plays nine out of ten songs from the latest album. Who else does that at that age? And he puts his heart into it, but he’s done it many years now. I don’t even know if it’s a good experience anymore.

    Maybe five years from now, I’ll listen to a tape and it’s going to sound great. Who knows?

    Now we’re a decade and change on. How do you look back at the experience?

    Weirdly, I think about it more now than I did in the years immediately afterwards.

    It was just like, “All right, I’ve had a busy life, a lot of work, family stuff—let’s pocket that beautiful thing for to reminisce about in the future.” But now I guess I’m in the future, and I’m reminiscing about it more.

    I will occasionally, when I’m in the car listening to Dylan, bring it out as a little mental gemstone that I sit and polish in my mind. Almost like taste it and feel it. That sounds fucking ludicrous, but you know what I mean. I indulge myself, wallowing in the beauty of it.

    I allow myself for a couple of minutes per month maybe to be really sentimental and romanticizing about it. As the years go past, I grow more and more fond of that whole little intermission.

    Did you keep the Sinatra albums?

    I used them as frisbees in the Philadelphia night, ceremoniously chucking them as far as I possibly could.

    Thanks Fredrik! Tune in Monday for my second conversation about this unique performance, with the director , who actually met with Dylan himself to plan the taping. That one will go out only to paid subscribers , so sign up/upgrade if you want to read it [update: it’s here ]:

    The World Happiness Report is beset with methodological problems

    Hacker News
    yaschamounk.substack.com
    2025-12-16 00:06:49
    Comments...
    Original Article

    Want to support this Substack and make me extra happy on this World Happiness Day? Become a paying subscriber and receive 25% off your annual subscription!

    Happy

    Get 25% off for 1 year

    Helsinki in winter. A picture of joy. (Photo by Alessandro Rampazzo/Anadolu via Getty Images)

    Today is World Happiness Day. So, like every year on March 20th, you are likely to see a lot of headlines reporting on the publication of the annual World Happiness Report. “Finland is again ranked the happiest country in the world [while] the US falls to its lowest-ever position,” a headline in the Associated Press ran this morning. Forbes even got philosophical, promising “5 Life Lessons From Finland, Once Again the World’s Happiest Country.”

    Published by the United Nations Sustainable Development Solutions Network and the Wellbeing Research Centre at Oxford University, the basic message of the report has remained the same since its launch in 2012. The happiest countries in the world are in Scandinavia; this year, Finland is followed by Denmark, Iceland and Sweden. America, despite being one of the richest large countries in the world, persistently underperforms: this year, the United States only comes in 24th out of the 147 countries covered in the report, placing it behind much poorer countries like Lithuania and Costa Rica.

    I have to admit that I have been skeptical about this ranking ever since I first came across it. Because I have family in both Sweden and Denmark, I have spent a good amount of time in Scandinavia. And while Scandinavian countries have a lot of great things going for them, they never struck me as pictures of joy. For much of the year, they are cold and dark. Their cultures are extremely reserved and socially disjointed. When you walk around the—admittedly beautiful—centers of Copenhagen or Stockholm, you rarely see anybody smile. Could these really be the happiest places in the whole wide world?

    So, to honor World Happiness Day, I finally decided to follow my hunch, and look into the research on this topic more deeply. What I found was worse than I’d imagined. To put it politely, the World Happiness Report is beset with methodological problems. To put it bluntly, it is a sham.

    News reports about the World Happiness Report usually give the impression that it is based on a major research effort. Noting that the report is “compiled annually by a consortium of groups including the United Nations and Gallup,” for example, an article about last year’s iteration in the New York Times warned darkly that “the United States fell out of the Top 20” without a hint of skepticism about the reliability of such a finding.

    Make sure that you don’t miss any episodes of my podcast! Sign up for ad-free access to all of its episodes by becoming a paying subscriber and adding the private feed to The Good Fight now.

    Set Up Podcast

    In light of such confident pronouncements, and the absence of any critical voices in most of these news stories, you might be forgiven for thinking that the report carefully assesses how happy each country in the world is according to a sophisticated methodology, one that likely involves both subjective and objective criteria. But upon closer examination, it turns out that the World Happiness Report is not based on any major research effort; far from measuring how happy people are with some sophisticated mix of indicators, it simply compiles answers to a single question asked to comparatively small samples of people in each country:

    Please imagine a ladder with steps numbered from zero at the bottom to ten at the top. Suppose we say that the top of the ladder represents the best possible life for you and the bottom of the ladder represents the worst possible life for you. If the top step is 10 and the bottom step is 0, on which step of the ladder do you feel you personally stand at the present time?

    The obvious problem with this question, commonly known as the Cantril Ladder, is that it doesn’t really ask about happiness at all. We know from many surveys that people tend to give very different answers to questions about what makes them satisfied with their life and to questions about whether they are feeling good in the moment. Having children, for example, tends to raise parents’ assessment of how meaningful their life is; but notably it does not make them report higher levels of happiness at any particular moment, including when they are spending time with their kids. At most, a ranking based purely on the Cantril Ladder could therefore give us something called a World Self-Reported Life Satisfaction Report—and it’s easy to see why such an honest title wouldn’t entice many journalists to write about it.

    The less obvious problem with the Cantril Ladder is that it does not even do a good job of measuring respondents’ satisfaction with their own lives. When one set of researchers asked over a thousand survey respondents in the United Kingdom what they took the question to be getting at, the most commonly mentioned responses included “wealth,” “rich” and “successful.” As August Nilsson and his colleagues painstakingly demonstrate , some of the specific language in the question—such as the metaphor of the ladder and its emphasis on the “top” as well as the “bottom step”—primes respondents to think about social hierarchies. Their conclusion is sobering: “The Cantril Ladder is arguably the most prominent measure of well-being, but the results suggest caution in its interpretation—the Cantril Ladder’s structure appears to influence participants to attend to a more power- and wealth-oriented view of well-being.”

    Auf deutsch lesen 🇩🇪

    Lire en français 🇫🇷

    But perhaps the biggest problem with the World Happiness Report is that metrics of self-reported life satisfaction don’t seem to correlate particularly well with other kinds of things we clearly care about when we talk about happiness. At a minimum, you would expect the happiest countries in the world to have some of the lowest incidences of adverse mental health outcomes. But it turns out that the residents of the same Scandinavian countries that the press dutifully celebrates for their supposed happiness are especially likely to take antidepressants or even to commit suicide. While Finland and Sweden consistently rank at the top of the happiness league table, for example, both countries have also persistently experienced some of the highest suicide rates in the European Union, ranking in the top five EU countries according to one recent statistic .

    It turns out that my hunch is born out by the data. Scandinavia doesn’t just seem a lot less happy than headlines suggest each year; if you look at a variety of metrics that have at least as much connection to a layperson’s understanding of happiness as the single metric used by the World Happiness Report, countries like Finland don’t do especially well.

    Two distinguished economists, Danny Blanchflower and Alex Bryson, set out in a recent paper to discover what would happen to the world happiness rankings if they looked at a broader range of indicators—and what they found is a totally different picture.

    Instead of relying on a single metric of life satisfaction, Blanchflower and Bryson consider eight survey questions which have widely been asked in different countries around the world. The first four of these questions measure different dimensions of positive affect. They are based on asking whether respondents experienced enjoyment yesterday; whether they smiled or laughed a lot; and whether they felt well-rested. (Their measure of positive affect also incorporates answers to the Cantril Ladder.)

    The next four questions used by Blanchflower and Bryson measure different dimensions of negative affect. They ask respondents such questions as whether or not they experienced sadness yesterday; whether they worried during a lot of the day; whether they experienced anger; and whether they were in physical pain.

    What Blanchflower and Bryson found is striking. Responses to the Cantril Ladder barely seem to correlate with expressions of either positive or negative affect. Denmark, for example, came top of their ranking on the Cantril Ladder. But, like most other Scandinavian countries, Denmark did much worse on both metrics of positive affect such as how likely respondents had been to smile or laugh a lot the previous day (111th out of 164 countries) and on metrics of negative affect such as whether they had worried a lot (93rd out of 164.) 1

    Want to support this Substack and make me extra happy on this World Happiness Day? Become a paying subscriber and receive 25% off your annual subscription! #happy #blessed #Finland

    Happy

    As a result, the overall ranking constructed by Blanchflower and Bryson looks totally different to the more famous version published by the UN. Finland, for example, falls to 51st place. 2 Conversely, countries like Japan, Panama and Thailand, none of which do especially well on the official ranking by the UN, suddenly appear a lot happier; all of them are ranked above Finland and other supposed top performers.

    Another surprise suggests that the story about happiness in the United States is not nearly as bleak as is usually suggested. For it turns out that happiness varies widely across America—and some parts of the country are seemingly the happiest in the world.

    Once you break the United States into its component states, it becomes clear that parts of the country really are doing quite badly. Residents of West Virginia, for example, ranked 101st out of 215 countries and states, making them about as happy as those in much poorer places like Sri Lanka and Mauritania. But residents of other U.S. states are, according to the ranking constructed by Blanchflower and Bryson, among the happiest in the world. Seven of them—Hawaii, Minnesota, North Dakota, South Dakota, Iowa, Nebraska and Kansas—are at the very top of the list, meaning that their residents are happier than those of the happiest country in the world (which turns out to be Taiwan, located in East Asia rather than Northern Europe). All in all, the residents of 34 U.S. states, plus those of the District of Columbia, have higher average levels of happiness than do the Fins.

    In a culture obsessed with happiness and wellness, there will always be huge demand for content that sells readers on the one great hack for how to improve their lives. Want to live to a ripe old age? Eat like the residents of “blue zones” such as Sardinia or Okinawa. Want to be happy even though you’re not rich? Move to Bhutan, a country often portrayed as having figured out the key to happiness because the government announced in 2008 that it would henceforth be focusing on growing its “Gross Happiness Index.”

    But that one great hack for how to improve your life nearly always turns out to be a sham. The residents of blue zones aren’t especially likely to live long because of their unique diets; more likely , blue zones are distinguished by poor record-keeping, leading to an abnormally high number of people defrauding the government by overstating their own age or continuing to collect pension checks for deceased relatives. Similarly, the government of Bhutan may talk a big game about prioritizing happiness over economic growth; but in reality, it doesn’t do particularly well in either the World Happiness Report or on Blanchflower and Bryson’s alternative metric—and the steady flow of people leaving Bhutan appear to believe that they could lead much happier lives elsewhere.

    This suggests that, for all of the evident shortcomings of a purely economistic mindset, attempts to abandon tried-and-tested metrics like GDP for new-fangled indicators like happiness rankings may do more harm than good. After all, it remains extremely hard to measure happiness—and even if we could somehow come up with a reliable metric, we’d have precious little idea about what government policies could actually boost this outcome.

    More broadly, supposedly serious news outlets still have a long way to go in subjecting publicity exercises like the World Happiness Report to appropriate journalistic scrutiny. It is easy to see why editors are tempted to assign some beat reporter without expertise in the social sciences to write up a fun little story about how much happier those enlightened Scandinavians are compared to benighted Americans. But if the media wants to live up to its self-appointed role as a gatekeeper of reliable information, it can’t continue to be complicit in the spread of such shoddy clickbait.

    Over the last years, media outlets like the New York Times , universities like Oxford, and international institutions like the UN have devoted themselves to the fight against so-called “misinformation.” It is certainly true that our political discourse is awash with dangerous distortions and outright lies. But any institution which wishes to address that problem must start by looking into the mirror—and cease spreading “elite misinformation” like the World Happiness Report.

    Leave a comment

    Happy

    Discussion about this post

    Ready for more?

    ‘The Scourge of Arial’

    Daring Fireball
    www.marksimonson.com
    2025-12-16 00:05:00
    Typographer Mark Simonson, all the way back in 2001: Arial is everywhere. If you don’t know what it is, you don’t use a modern personal computer. Arial is a font that is familiar to anyone who uses Microsoft products, whether on a PC or a Mac. It has spread like a virus through the typographic l...
    Original Article

    Arial is everywhere. If you don’t know what it is, you don’t use a modern personal computer. Arial is a font that is familiar to anyone who uses Microsoft products, whether on a PC or a Mac. It has spread like a virus through the typographic landscape and illustrates the pervasiveness of Microsoft’s influence in the world.

    Arial’s ubiquity is not due to its beauty. It’s actually rather homely. Not that homeliness is necessarily a bad thing for a typeface. With typefaces, character and history are just as important. Arial, however, has a rather dubious history and not much character. In fact, Arial is little more than a shameless impostor.

    Throughout the latter half of the twentieth century, one of the most popular typefaces in the western world was Helvetica. It was developed by the Haas Foundry of Switzerland in the 1950s. Later, Haas merged with Linotype and Helvetica was heavily promoted. More weights were added and it really began to catch on.

    Helvetica specimen book, c. 1970.

    An icon of the Swiss school of typography, Helvetica swept through the design world in the ’60s and became synonymous with modern, progressive, cosmopolitan attitudes. With its friendly, cheerful appearance and clean lines, it was universally embraced for a time by both the corporate and design worlds as a nearly perfect typeface to be used for anything and everything. “When in doubt, use Helvetica” was a common rule.

    As it spread into the mainstream in the ’70s, many designers tired of it and moved on to other typographic fashions, but by then it had become a staple of everyday design and printing. So in the early ’80s when Adobe developed the PostScript page description language, it was no surprise that they chose Helvetica as one of the basic four fonts to be included with every PostScript interpreter they licensed (along with Times, Courier, and Symbol). Adobe licensed its fonts from the original foundries, demonstrating their respect and appreciation for the integrity of type, type foundries and designers. They perhaps realized that if they had used knock-offs of popular typefaces, the professional graphic arts industry—a key market—would not accept them.

    By the late eighties, the desktop publishing phenomenon was in full swing. Led by the Macintosh and programs like PageMaker, and made possible by Adobe’s PostScript page description language, anyone could do near professional-quality typesetting on relatively inexpensive personal computers.

    But there was a problem. There were two kinds of PostScript fonts: Type 1 and Type 3. Type 1 fonts included “hints” that improved the quality of output dramatically over Type 3 fonts. Adobe provided information on making Type 3 fonts, but kept the secrets of the superior Type 1 font technology to itself. If you wanted Type 1 fonts, Adobe was the only source. Anyone else who wanted to make or sell fonts had to settle for the inferior Type 3 format. Adobe wanted the high end of the market all to itself.

    By 1989, a number of companies were hard at work trying to crack the Type 1 format or devise alternatives. Apple and Microsoft signed a cross-licensing agreement to create an alternative to Adobe’s technology. While Microsoft worked on TrueImage, a page description language, Apple developed the TrueType format. TrueType was a more open format and was compatible with—but not dependent on—PostScript. This effectively forced Adobe’s hand, causing them to release the secrets of the Type 1 format to save themselves from irrelevancy.

    Around the same time, PostScript “clones” were being developed to compete with Adobe. These PostScript “work-alikes” were usually bundled with “look-alike” fonts, since the originals were owned by Adobe’s business partners. One PostScript clone, sold by Birmy, featured a Helvetica substitute developed by Monotype called Arial.

    Arial appears to be a loose adaptation of Monotype’s venerable Grotesque series, redrawn to match the proportions and weight of Helvetica. At a glance, it looks like Helvetica, but up close it’s different in dozens of seemingly arbitrary ways. Because it matched Helvetica’s proportions, it was possible to automatically substitute Arial when Helvetica was specified in a document printed on a PostScript clone output device. To the untrained eye, the difference was hard to spot. (See “ How to Spot Arial ”) After all, most people would have trouble telling the difference between a serif and a sans serif typeface. But to an experienced designer, it was like asking for Jimmy Stewart and getting Rich Little.

    What is really strange about Arial is that it appears that Monotype was uncomfortable about doing a direct copy of Helvetica. They could very easily have done that and gotten away with it. Many type manufacturers in the past have done knock-offs of Helvetica that were indistinguishable or nearly so. For better or worse, in many countries—particularly the U.S.—while typeface names can be protected legally, typeface designs themselves are difficult to protect. So, if you wanted to buy a typesetting machine and wanted the real Helvetica, you had to buy Linotype. If you opted to purchase Compugraphic, AM, or Alphatype typesetting equipment, you couldn’t get Helvetica. Instead you got Triumvirate, or Helios, or Megaron, or Newton, or whatever. Every typesetting manufacturer had its own Helvetica look-alike. It’s quite possible that most of the “Helvetica” seen in the ’70s was actually not Helvetica.

    Now, Monotype was a respected type foundry with a glorious past and perhaps the idea of being associated with these “pirates” was unacceptable. So, instead, they found a loophole and devised an “original” design that just happens to share exactly the same proportions and weight as another typeface. (See “ Monotype’s Other ‘Arials’ ”) This, to my mind, is almost worse than an outright copy. A copy, it could be said, pays homage (if not license fees) to the original by its very existence. Arial, on the other hand, pretends to be different. It says, in effect “I’m not Helvetica. I don’t even look like Helvetica!”, but gladly steps into the same shoes. In fact, it has no other role.

    ***

    When Microsoft made TrueType the standard font format for Windows 3.1, they opted to go with Arial rather than Helvetica, probably because it was cheaper and they knew most people wouldn’t know (or even care about) the difference. Apple also standardized on TrueType at the same time, but went with Helvetica, not Arial, and paid Linotype’s license fee. Of course, Windows 3.1 was a big hit. Thus, Arial is now everywhere, a side effect of Windows’ success, born out of the desire to avoid paying license fees.

    The situation today is that Arial has displaced Helvetica as the standard font in practically everything done by nonprofessionals in print, on television, and on the Web, where it’s become a standard font, mostly because of Microsoft bundling it with everything—even for Macs, which already come with Helvetica. This is not such a big deal since at the low resolution of a computer screen, it might as well be Helvetica. In any case, for fonts on the Web, Arial is one of the few choices available.

    Despite its pervasiveness, a professional designer would rarely—at least for the moment—specify Arial. To professional designers, Arial is looked down on as a not-very-faithful imitation of a typeface that is no longer fashionable. It has what you might call a “low-end stigma.” The few cases that I have heard of where a designer has intentionally used Arial were because the client insisted on it. Why? The client wanted to be able to produce materials in-house that matched their corporate look and they already had Arial, because it’s included with Windows. True to its heritage, Arial gets chosen because it’s cheap, not because it’s a great typeface.

    It’s been a very long time since I was actually a fan of Helvetica, but the fact is Helvetica became popular on its own merits. Arial owes its very existence to that success but is little more than a parasite—and it looks like it’s the kind that eventually destroys the host. I can almost hear young designers now saying, “Helvetica? That’s that font that looks kinda like Arial, right?”

    See also:

    How To Spot Arial

    Monotype’s Other “Arials”

    A Note on Current SMS Marketing Practices

    Daring Fireball
    daringfireball.net
    2025-12-16 00:02:08
    Back on November 28, I bought a new cap from New Era’s web store. They offered a discount of some sort if I gave them a phone number and permitted them to send me marketing messages. That got me curious about what they’d do with my number, and it was a $50-some dollar cap, so I took the discount and...
    Original Article
    A Note on Current SMS Marketing Practices

    Back on November 28, I bought a new cap from New Era ’s web store. They offered a discount of some sort if I gave them a phone number and permitted them to send me marketing messages. That got me curious about what they’d do with my number, and it was a $50-some dollar cap, so I took the discount and gave them my Google Voice number. That was 17 days ago. They sent me 19 SMS marketing messages since then, before I’d seen enough today and called it quits on this experiment. (Or called “STOP”, perhaps, which was the magic word to opt out.) They didn’t send a text every day, and on some days, they sent more than one. But the overall effect was relentlessly annoying.

    I’m sure some of the people who sign up for these texts in exchange for a discount code wind up clicking at least one of the offers sent via SMS and buying more stuff, and the marketing team running this points to those sales as proof that it “works”. You can measure that. It shows up as a number. Some people in business only like arguments that can be backed by numbers. 3 is more than 2. That is indeed a fact.

    But there are an infinite number of things in life that cannot be assigned numeric values. Many of these things matter too. Like the fact that in my mind, after experiencing this, the New Era company smells like a sweaty hustler in a cheap polyester suit. If their brand were a man, I’d check my pants pocket for my wallet after interacting with him.

    Monday, 15 December 2025

    Freexian Collaborators: Debusine repositories now in beta (by Colin Watson)

    PlanetDebian
    www.freexian.com
    2025-12-16 00:00:00
    We’re happy to announce that Debusine can now be used to maintain APT-compatible add-on package repositories for Debian. This facility is available in public beta to Debian developers and maintainers. Why? Debian developers typically put most of their effort towards maintaining the main Debian arch...
    Original Article

    We’re happy to announce that Debusine can now be used to maintain APT-compatible add-on package repositories for Debian. This facility is available in public beta to Debian developers and maintainers.

    Why?

    Debian developers typically put most of their effort towards maintaining the main Debian archive. However, it’s often useful to have other places to work, for various reasons:

    • Developers working on a set of packages might need to check that changes to several of them all work properly together on a real system.
    • Somebody fixing a bug might need to ask affected users to test the fix before uploading it to Debian.
    • Some projects are difficult to package in a way that meets Debian policy, or are too niche to include in Debian, but it’s still useful to distribute them in a packaged form.
    • For some packages, it’s useful to provide multiple upstream versions for multiple Debian releases, even though Debian itself would normally want to keep that to a minimum.

    The Ubuntu ecosystem has had PPAs for a long time to meet these sorts of needs, but people working directly on Debian have had to make do with putting things together themselves using something like reprepro or aptly . Discussions about this have been happening for long enough that people started referring to PPAs for Debian as “bikesheds”, and users often find themselves trying to use Ubuntu PPAs on Debian systems and hoping that dependencies will be compatible enough for things to more or less work. This clearly isn’t ideal, and solving it is one of Freexian’s objectives for Debusine.

    Developers publishing packages to Debusine repositories can take advantage of all Debusine’s existing facilities, including a battery of QA tests and regression tracking (coming soon). Repositories are signed using per-repository keys held in Debusine’s signing service, and uploads to repositories are built against the current contents of that repository as well as the corresponding base Debian release. All repositories include automatic built-in snapshot capabilities.

    Who can use this service?

    We’ve set up debusine.debian.net to allow using repositories. All Debian Developers and Debian Maintainers can log in there and publish packages to it. The resulting repositories are public by default.

    debusine.debian.net only allows packages with licences that allow distribution by Debian, and it is intended primarily for work that could reasonably end up in Debian; Freexian reserves the right to remove repositories from it.

    How can I use it?

    If you are a Debian contributor, we’d be very excited to have you try this out, especially if you give us feedback . We have published instructions for developers on using this. Since this is a beta service, you can expect things to change, but we’ll maintain compatibility where we can.

    If you’re interested in using this in a commercial setting, please contact Freexian to discuss what we can do for you .

    I ported JustHTML from Python to JavaScript with Codex CLI and GPT-5.2 in 4.5 hours

    Simon Willison
    simonwillison.net
    2025-12-15 23:58:38
    I wrote about JustHTML yesterday - Emil Stenström's project to build a new standards compliant HTML5 parser in pure Python code using coding agents running against the comprehensive html5lib-tests testing library. Last night, purely out of curiosity, I decided to try porting JustHTML from Python to ...
    Original Article

    15th December 2025

    I wrote about JustHTML yesterday —Emil Stenström’s project to build a new standards compliant HTML5 parser in pure Python code using coding agents running against the comprehensive html5lib-tests testing library. Last night, purely out of curiosity, I decided to try porting JustHTML from Python to JavaScript with the least amount of effort possible, using Codex CLI and GPT-5.2. It worked beyond my expectations.

    TL;DR

    I built simonw/justjshtml , a dependency-free HTML5 parsing library in JavaScript which passes 9,200 tests from the html5lib-tests suite and imitates the API design of Emil’s JustHTML library.

    It took two initial prompts and a few tiny follow-ups. GPT-5.2 running in Codex CLI ran uninterrupted for several hours, burned through 1,464,295 input tokens, 97,122,176 cached input tokens and 625,563 output tokens and ended up producing 9,000 lines of fully tested JavaScript across 43 commits.

    Time elapsed from project idea to finished library: about 4 hours, during which I also bought and decorated a Christmas tree with family and watched the latest Knives Out movie.

    Some background

    One of the most important contributions of the HTML5 specification ten years ago was the way it precisely specified how invalid HTML should be parsed. The world is full of invalid documents and having a specification that covers those means browsers can treat them in the same way—there’s no more “undefined behavior” to worry about when building parsing software.

    Unsurprisingly, those invalid parsing rules are pretty complex! The free online book Idiosyncrasies of the HTML parser by Simon Pieters is an excellent deep dive into this topic, in particular Chapter 3. The HTML parser .

    The Python html5lib project started the html5lib-tests repository with a set of implementation-independent tests. These have since become the gold standard for interoperability testing of HTML5 parsers, and are used by projects such as Servo which used them to help build html5ever , a “high-performance browser-grade HTML5 parser” written in Rust.

    Emil Stenström’s JustHTML project is a pure-Python implementation of an HTML5 parser that passes the full html5lib-tests suite. Emil spent a couple of months working on this as a side project, deliberately picking a problem with a comprehensive existing test suite to see how far he could get with coding agents.

    At one point he had the agents rewrite it based on a close inspection of the Rust html5ever library. I don’t know how much of this was direct translation versus inspiration—his project has 1,215 commits total so it appears to have included a huge amount of iteration, not just a straight port.

    My project is a straight port. I instructed Codex CLI to build a JavaScript version of Emil’s Python code.

    The process in detail

    I started with a bit of mise en place. I checked out two repos and creating an empty third directory:

    cd ~/dev
    git clone https://github.com/EmilStenstrom/justhtml
    git clone https://github.com/html5lib/html5lib-tests
    mkdir justjshtml
    cd justjshtml

    Then I started Codex CLI for GPT-5.2 like this:

    That --yolo flag is a shortcut for --dangerously-bypass-approvals-and-sandbox , which is every bit as dangerous as it sounds.

    My first prompt told Codex to inspect the existing code and use it to build a specification for the new JavaScript library:

    We are going to create a JavaScript port of ~/dev/justhtml - an HTML parsing library that passes the full ~/dev/html5lib-tests test suite. It is going to have a similar API to the Python library but in JavaScript. It will have no dependencies other than raw JavaScript, hence it will work great in the browser and node.js and other environments. Start by reading ~/dev/justhtml and designing the user-facing API for the new library - create a spec.md containing your plan.

    I reviewed the spec, which included a set of proposed milestones, and told it to add another:

    Add an early step to the roadmap that involves an initial version that parses a simple example document that is valid and returns the right results. Then add and commit the spec.md file.

    Here’s the resulting spec.md file . My request for that initial version became “Milestone 0.5” which looked like this:

    Milestone 0.5 — End-to-end smoke parse (single valid document)

    • Implement the smallest end-to-end slice so the public API is real early:
      • new JustHTML("<html><head></head><body><p>Hello</p></body></html>") returns a tree with the expected tag structure and text nodes.
      • doc.toText() returns "Hello" and doc.errors is empty for this valid input.
    • Add scripts/smoke.js (no deps) that runs the example and asserts the expected structure/output.
    • Gate: node scripts/smoke.js passes.

    Then I told it:

    Implement Milestone 0.5

    And off it went. The resulting code appeared to work so I said:

    Configure GitHub Actions test.yml to run that on every commit, then commit and push

    I ran git init and created a private GitHub repository for this project at this point, and set up the local directory to push to that remote.

    Here’s that initial push . Then I told it:

    OK do the rest, commit and push often

    And that was almost it!

    I set my laptop to not fall asleep and left it to its devices while we went off to buy a Christmas tree. The “commit and push often” meant I could monitor its progress on my phone by refreshing the commit log on GitHub .

    I was running this against my $20/month ChatGPT Plus account, which has a five hour token allowance window for Codex CLI. That ran out at 6:35pm and Codex paused, so I waited until the reset point at 7:14pm and typed:

    Keep going

    At 9:30pm it declared itself done with the following summary message:

    • What’s Done
    
      - html5lib-tests now run in CI and pass: tokenizer 6810/6810, tree 1770/1782
        (12 skipped: #script-on), serializer 230/230, encoding 82/83 (1 skipped:
        scripted) (.github/workflows/test.yml:1).
      - Public API expanded: stream() (src/stream.js:1), CSS selectors query()/
        matches() (src/selector.js:1), and toMarkdown() (src/markdown.js:1), all
        exported via src/index.js:1.
      - Added no-deps unit scripts for selectors/stream/markdown (scripts/run-
        selector-tests.js:1, scripts/run-stream-tests.js:1, scripts/run-markdown-
        tests.js:1) and wired them into CI.
      - Fixed TreeBuilder so <br> no longer closes <p> (src/treebuilder.js:647).
    

    As a finishing touch, I had it add a playground interface so I could try out the new library in my browser. I prompted:

    Add a playground.html in the top level folder which loads the necessary ES modules from ./src/... and implements the exact same functionality as seen on https://tools.simonwillison.net/justhtml but using the JavaScript library instead of Pyodide

    It fetched my existing playground page using curl and built a new playground.html file that loaded the new JavaScript code instead. This worked perfectly .

    I enabled GitHub Pages for my still-private repo which meant I could access the new playground at this URL:

    https://simonw.github.io/justjshtml/playground.html

    Screenshot of JustJSHTML Playground web application. Header reads "JustJSHTML Playground" with subtitle "A dependency-free JavaScript HTML5 parser - GitHub". Below is a status bar showing "JavaScript Environment" with a green "Ready" badge. The main input area has "Paste HTML" and "Fetch from URL" buttons, with a text area containing HTML code: "<!DOCTYPE html> <html> <head> <title>Example Page</title> </head> <body> <header> <nav> <ul>". A "Playground Mode" section shows buttons for "CSS Selector Query", "Pretty Print HTML", "Tree Structure", "Stream Events", "Extract Text", and "To Markdown" (highlighted in purple). Below is a text field labeled "CSS Selector (optional - leave empty for whole document):" with placeholder "e.g., article, main, .content (or leave empty)" and a green "Convert to Markdown" button. The Output section has a teal header with "Whole document" badge and displays converted markdown: "Example Page" followed by "- [Home](/)" "- [About](/about)" "- [Contact](/contact)".

    All it needed now was some documentation:

    Add a comprehensive README with full usage instructions including attribution plus how this was built plus how to use in in HTML plus how to use it in Node.js

    You can read the result here .

    We are now at eight prompts total, running for just over four hours and I’ve decorated for Christmas and watched Wake Up Dead Man on Netflix.

    According to Codex CLI:

    Token usage: total=2,089,858 input=1,464,295 (+ 97,122,176 cached) output=625,563 (reasoning 437,010)

    My llm-prices.com calculator estimates that at $29.41 if I was paying for those tokens at API prices, but they were included in my $20/month ChatGPT Plus subscription so the actual extra cost to me was zero.

    What can we learn from this?

    I’m sharing this project because I think it demonstrates a bunch of interesting things about the state of LLMs in December 2025.

    • Frontier LLMs really can perform complex, multi-hour tasks with hundreds of tool calls and minimal supervision. I used GPT-5.2 for this but I have no reason to believe that Claude Opus 4.5 or Gemini 3 Pro would not be able to achieve the same thing—the only reason I haven’t tried is that I don’t want to burn another 4 hours of time and several million tokens on more runs.
    • If you can reduce a problem to a robust test suite you can set a coding agent loop loose on it with a high degree of confidence that it will eventually succeed. I called this designing the agentic loop a few months ago. I think it’s the key skill to unlocking the potential of LLMs for complex tasks.
    • Porting entire open source libraries from one language to another via a coding agent works extremely well.
    • Code is so cheap it’s practically free. Code that works continues to carry a cost, but that cost has plummeted now that coding agents can check their work as they go.
    • We haven’t even begun to unpick the etiquette and ethics around this style of development. Is it responsible and appropriate to churn out a direct port of a library like this in a few hours while watching a movie? What would it take for code built like this to be trusted in production?

    I’ll end with some open questions:

    • Does this library represent a legal violation of copyright of either the Rust library or the Python one?
    • Even if this is legal, is it ethical to build a library in this way?
    • Does this format of development hurt the open source ecosystem?
    • Is it responsible to publish software libraries built in this way?
    • How much better would this library be if an expert team hand crafted it over the course of several months?

    Native vs. emulation: World of Warcraft game performance on Snapdragon X Elite

    Hacker News
    rkblog.dev
    2025-12-15 23:47:37
    Comments...
    Original Article

    2025-12-15

    At the beginning of the year, I tested the Snapdragon X Elite unreleased dev-kit , and I couldn't really compare x86 versus native gaming performance for the same game. I only managed to get World of Warcraft Classic x86 to run, and when compared to the native version, the FPS drop was 40-60% in two simple benchmarks. WoW retail x86 did not work, but now with the latest Windows improvements and the Prism emulation layer, things have changed.

    Test platform

    The tests were done on a Snapdragon X Elite dev kit equipped with X1E-00-1DE Snapdragon X Elite SoC (3.8 GHz with 4.3 GHz boost on 1-2 cores) and 32GB of RAM. The dev kit runs at a higher TDP than most, if not all, laptops and has the theoretically best bin of chips (highest boost clocks).

    The key difference since my initial review is the Windows version. Microsoft was working hard on improving emulation performance and compatibility. Since Windows 11 24H2, there is a new emulator called Prism, and with recent updates it also got AVX instructions support to handle even more x86_64 applications.

    For the tests I used Windows 11 25H2 26220.7344 Insider Preview version to get all possible improvements taken into account.

    Windows on ARM 25H2 26220.7344

    Additionally, the x86_64 binaries properties were edited to enable newer emulated CPU features :

    Windows on ARM Prism emulation features

    World of Warcraft

    WoW is an MMORPG, and it does not have a built-in benchmark. It can be reliably benchmarked to some extent if you use specific game areas/instances. You can check more in my WoW benchmarking section.

    As a PC game, it's a modern DX12 game engine with optional ray-traced shadows support and a few other features. It offers native x86, Windows on ARM, and Apple Silicon versions. In my previous tests, the x86 retail version would not run on Snapdragon, and only the Classic version managed to run. The FPS drop versus the native version was massive of around 40-60% (but the testing wasn't as detailed as I would like).

    With the Windows (and WoW) changes, both x86_64 WoW clients managed to run on Windows on ARM, allowing me to get way more test data. MSI Afterburner and other similar tools don't support WoA, so I had to use the game's built-in average FPS meter (which doesn't average over long periods of time; and no 1% lows/frame time graphs).

    Game version and architecture is displayed on the login screen
    Game version and architecture is displayed on the login screen

    World of Warcraft - native versus emulated

    I measured the FPS at 1080p for two settings - mode 3 (low) and mode 7 (high). The results are as follows:

    World of Warcraft - native versus emulated Mode 3
    World of Warcraft - native versus emulated Mode 7

    The results are astounding as the x86 version is rivaling the native one, maybe even edging the native client.

    • WoW Classic and Stonard in retail are old locations, very light to render, so even with an iGPU, the FPS will be high.
    • Ardenweald is the most GPU-intensive modern zone from the test collection. Bastion is less demanding but has a bit more geometry. Dazar'alor harbor view is a geometry/render distance-based benchmark and will depend mostly on GPU
    • Necrotic Wake and Spires of Ascension are dungeons with some mobs, geometry, and units the game tracks. GPU with increasing CPU load.
    • Valdrakken is a player hub from the previous expansion, now mostly empty - player hubs when active are quite demanding to render without stutter. They tend to use a lot of assets as well.
    • Combat benchmark is pushing the game into single-core CPU limit - it's done in the old Karazhan raid, where I can reliably pull a large group of mobs and stand still with fixed camera position. iGPUs can also be the bottleneck on higher settings due to particle effects of spells going off. Most dGPUs will have no problems with them.
    • Out of combat , when test mobs despawn, the FPS inside Karazan increases as it's an old instance without any complex geometry or large asset collection. The game combat world state vanishes and thus the single-core bottleneck as well

    Karazhan benchmark was the only one where the native version was noticeably ahead of the emulated version. Due to that, I've also added two modern dungeon instances, and those results were more in line with other locations. Either there was a difference between game versions, or in larger instances, game performance can be limited by some sort of system latency, and emulation is not the best for that.

    CPU load during combat scenario
    CPU load during combat scenario

    WoW by default will use 4 CPU cores, with one core being the primary ones. In a mass combat / mass NPC scenario, the main core will see 100% load and will be the limiting factor.

    WoW Classic x86 threw an error but still launched
    WoW Classic x86 threw an error but still launched

    Windows on ARM can handle a lot of x86 Windows applications, but not all of them. From my quick re-tests, I managed to run Unigine Valley, but Unigine Superposition failed to run.

    Default versus very strict emulation

    I was curious what the difference between emulation settings. Switching to very strict emulation settings disables a lot of features, which in turn tanked x86 WoW performance:

    Windows emulation settings performance

    Mobile SoC comparison

    I've also recently tested Strix Point HX 370, and Intel Arrow Lake 255H capped at 30W, so I've added them to the comparison charts:

    WoW Dazardalor harbor view comparison
    WoW mass combat performance comparison
    FFXIV Endwalker iGPU benchmarks

    In iGPU-heavy scenarios, Intel/AMD tend to be ahead, while in CPU scenarios, all 3 platforms get close to each other.

    Summary

    I really wanted to compare native versus emulated on Snapdragon, as initial WoW Classic performance differences were huge. With recent Prism updates, I forced the devkit to update Windows, and it managed to run the x86 retail World of Warcraft client. This allowed me to test CPU and GPU-focused scenarios within the game. Surprisingly, for WoW, there was no real penalty, at least outside the raid/combat scenario. When you install Battle.net and WoW, you will get the native version by default, so you don't have to select or change anything.

    It's good to see improvements to Windows on ARM. Better application compatibility is nice, but it will never be perfect. On top of that, some apps will have hardcoded checks, and you won't be able to use x86 drivers. Qualcomm is preparing the second generation of mobile X Elite chips, and it will be interesting to see how they perform. Initial launch saw a lot of laptop sales, but also a lot of returns.

    Limited Linux support is still a problem, from device tree lists, firmware extraction, to overall worse behavior of the SoC under Linux. Linux ARM support is way better than Windows, and even some hardware vendors tend to support ARM Linux due to the Raspberry Pi (like astrophotography equipment, vision cameras).

    Google is shutting down its dark web report feature in January

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-15 23:24:57
    Google is discontinuing its "dark web report" security tool, stating that it wants to focus on other tools it believes are more helpful. [...]...
    Original Article

    Google

    Google is discontinuing its "dark web report" security tool, stating that it wants to focus on other tools it believes are more helpful.

    Google's dark web report tool is a security feature that notifies users if their email address or other personal information was found on the dark web.

    After Google scans the dark web and identifies your personal information, it will notify you where the data was found and what type of data was exposed, encouraging users to take action to protect their data.

    Example of Google Dark Web Report tool
    Example of Google Dark Web Report tool
    Source: BleepingComputer

    For example, if Google identifies your email on the dark web, you will be advised to turn on two-step authentication to protect your Google account.

    Google sunsets the dark web report tool

    In an email seen by BleepingComputer, Google confirmed it will stop monitoring for new dark web results on January 15, 2026, and its data will no longer be available from February 16, 2026.

    "We are discontinuing the dark web report, which was meant to scan the dark web for your personal information," reads an email seen by BleepingComputer.

    "It will stop monitoring for new results on January 15, 2026 and its data will no longer be available from February 16, 2026. While the report offered general information, feedback showed that it did not provide helpful next steps."

    "We're making this change to instead focus on tools that give you more clear, actionable steps to protect your information online. We will continue to track and defend you from online threats, including the dark web, and build tools that help protect you and your personal information."

    Dark web
    Google Dark Web Report tool sunset announcement
    Source: BleepingComputer

    Google will continue to invest in other tools, such as Google Password Manager and the Password Checkup tool.

    "In the meantime, we encourage you to use the existing tools we offer to strengthen your security and privacy, including Security and Privacy Checkups, Passkey, 2-Step Verification, Google Password Manager, and Password Checkup," Google explained in an email.

    Google says users can also use the " Results about you " tool to find and request the removal of their personal information from Google Search results, like their phone number and home address.

    However, some of you might miss Google's dark web report, which notified users even when their address was found on the dark web.

    In addition, Google's dark web report consolidated all potential dark web leaks in one place so that you could act quickly.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Radicle: peer-to-peer collaboration with Git

    Lobsters
    lwn.net
    2025-12-15 23:17:55
    Comments...
    Original Article
    Ignore previous instructions; subscribe to LWN today

    Every article on LWN.net is written by humans, for humans. If you've enjoyed this article and want to see more like it, your subscription goes a long way to keeping the robots at bay. We are offering a free one-month trial subscription (no credit card required) to get you started.

    Radicle is a new, peer-to-peer, MIT/Apache-licensed collaboration platform written in Rust and built on top of Git. It adds support for issues and pull requests (which Radicle calls "patches") on top of core Git, which are stored in the Git repository itself. Unlike GitHub, GitLab, and similar forges, Radicle is distributed; it doesn't rely on having everyone use the same server. Instead, Radicle instances form a network that synchronizes changes between nodes.

    As a new project, Radicle is not fully featured compared to the mature and centralized forges. That said, the Radicle developers are using Radicle itself to collaborate on the software, along with a Zulip chat system. The first 1.0.0 release candidate was announced on March 26.

    (Note that I am paid to help develop Radicle.)

    Overview

    In Radicle, each user runs their own node on each computer they use for collaboration. The node stores copies of the repositories the user is interested in, regardless of whether they're created by the user or cloned from another node. The node process runs in the background to fetch changes from peers and serve repositories to peers that want them. To the user, the node acts like a local Git server. You clone from, pull from, or push to the node and it coordinates with other nodes.

    There is a web interface for browsing repositories, issues, and patches, and it also allows opening and managing issues. The web interface can be opened for the local node, or on a suitably configured server, for any other node. Thus you can inspect any public node to see if it is in sync with yours.

    [The Radicle web interface]

    The web interface looks a lot like the more mainstream forges, and is meant to feel instantly familiar. You can browse the code, issues, and existing patches. However, unless you run your own Radicle node and open its web interface, you can't currently make changes: you can't report issues, comment on issues, etc.

    If you want to clone a repository locally, the web interface provides two ways: either using normal Git ( git clone ) and an HTTPS URL, just like other forges, or having your Radicle node fetch it and clone from that using the rad command-line tool. You don't need to use Radicle to get a copy of a repository from Radicle.

    [The Radicle command line interface]

    Creating issues and patches — and commenting on them — happens using your own Radicle node. There is a command-line interface, and a web user interface. The Radicle project is also working on a full-screen terminal user interface, like Midnight Commander but for Git, and there is integration with Visual Studio Code and IntelliJ IDEs, among others.

    Motivation

    The motivation for Radicle is similar to that of the overall decentralization movement. The centralized Git forges are popular for good reasons: they've put in a lot of effort into being easy to use and efficient, and to provide the kinds of features their users need. However, they are also not always great for everyone. There is some truth in the joke that when GitHub is down, the world stops developing software. Git was the first popular distributed version control system. Then, the popularity of GitHub made it the most centralized version control system.

    With a peer-to-peer system, if your node is down, you may have to stop working, but nobody else needs to. More importantly, you don't need permission to run a Radicle node. Your access can't be revoked. Your repositories can't be deleted from your node by others. Nobody will force you to accept an "updated" set of terms and conditions.

    Radicle stores issues and patches in the Git repository itself. You can create, comment, and manage them while offline. Radicle is local-first software: network access is only required for operations that inherently require communicating with other computers, such as retrieving or sending changes. Everything else works without the network.

    Radicle repositories are self-signing, a necessary feature in a distributed system. While a GitHub repository can be authenticated by location (it's on GitHub with a given name), a Radicle repository is associated with a small set of cryptographic signing keys, which allows its identity and contents to be authenticated regardless of where the repository is stored.

    Radicle's origin traces back to 2017 . Its development is funded by Radworks, an organization that coordinates on the Ethereum blockchain using a token called RAD. However, Radicle does not use any blockchain or cryptocurrency technology. Radicle is not the only approach to decentralized collaboration over Git. ForgeFed is a protocol built on ActivityPub to support a federated network of Git servers.

    Architecture

    Nodes communicate with each other using two different protocols. First, there is a gossip protocol , where nodes tell each other about the nodes and repositories they know about, and about changes to the repositories. Second, they use the Git v2 smart transfer protocol to exchange repository content.

    Each node stores copies of the repository as it exists on each other node it knows about, using the Git namespace feature and the node identifier as a name. Each node has nearly identical content, so this is an efficient way to store the data. To Git, a Radicle repository is a perfectly normal repository. Radicle uses standard Git mechanisms to add a persistent repository identity, issues, and patches. These are stored in special Git refs.

    Every repository stored in Radicle has an identity that is the same in every Radicle node where the repository appears. If there are multiple copies of a repository stored in a node, each copy has the same identity, and can thus be identified as a version of the same repository. This identity is created by committing an "identity document" that contains metadata about the repository; an example identity document is:

        {
          "payload": {
            "xyz.radicle.project": {
              "defaultBranch": "master",
              "description": "Radicle Heartwood Protocol & Stack",
              "name": "heartwood"
            }
          },
          "delegates": [
            "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
            "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
            "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM"
          ],
          "threshold": 1
        }
    

    The document includes a description of the repository and its default branch. The delegates list contains the public Ed25519 keys of the delegate(s) that are empowered to change the identity document or commit to the main branch. This document is stored behind the rad/id ref in the repository. It must be signed by one of the delegate's private keys. Each repository has a short ID that is calculated from a hash of the initial version of the identity document. This ID will not change as the identity document is modified, and can thus be used to identify the repository over time.

    There is no way to prevent anyone from changing the identity document on their own node and signing it with their own key, bypassing Radicle. However, other nodes won't accept the change, since it is not signed by a delegate's key, making such changes pointless. Radicle also signs the branch and tag refs. The signatures are refreshed whenever a node changes anything in the repository. This means that other nodes can verify another node's repository content without inspecting the other node directly. This helps prevent forgeries and related attacks.

    Issues and patches are stored using an implementation of a conflict-free replicated data type , which Radicle calls a "collaborative object", or COB. Any node can append data to a COB, and changes from different nodes can be merged without conflict. (Normal git conflict management applies to the rest of the content of the repositories, however: the user needs to resolve them.) The COB mechanism in Radicle is generic, and can be used to build further collaboration tools.

    Seed nodes

    Any node can synchronize data with any other node, but only if they can communicate directly. With network address translation (NAT) being prevalent, this is often not possible. Radicle does not yet have "NAT punching", but relies on third-party, publicly accessible seed nodes. This is safe thanks to the repositories being self-signed: the seed node can't modify the data.

    Thus, if Alice and Bob are both behind NAT networks, they can collaborate via a seed node on the Internet that they both can access. Unlike with centralized forges, anyone can set up a seed node. This works especially well for open-source projects that don't need to keep repositories hidden. If hiding is necessary, a private seed node and Radicle private repositories can be used. A private repository is one that's configured to only be shared with specific other nodes. However, Radicle is not yet a good solution for truly confidential material: the private nodes are still rather rudimentary.

    Missing features and other challenges

    Radicle does not yet have mature support for continuous-integration systems, although work on that is underway. There is also only rudimentary support for code review, but that is also being worked on.

    Currently, Radicle has a simple identity system: each node has its own public key. A challenge for the future is to evolve this into a more versatile identity system. For example, a developer with both a laptop and a desktop system would benefit from being able to certify that both nodes are theirs. Support for groups or organizations is also missing.

    Perhaps a more fundamental challenge is that interacting with a Radicle repository, even just to report issues, requires using Radicle. With a centralized forge, all you need is an account and a web browser. This may be a problem for projects that would like to use Radicle, but whose users can't be expected to use it.

    Conclusion

    Radicle is a new and promising decentralized approach to Git hosting. If you are curious to know more, the guides are a good place to start. We're also accepting patches and hoping to bring in new contributors, so if you know some Rust and care about developer tooling, please join us on our Zulip forum .

    Index entries for this article
    GuestArticles Wirzenius, Lars


    Askul confirms theft of 740k customer records in ransomware attack

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-15 23:13:44
    Japanese e-commerce giant Askul Corporation has confirmed that RansomHouse hackers stole around 740,000 customer records in the ransomware attack it suffered in October. [...]...
    Original Article

    Askul confirms theft of 740k customer records in ransomware attack

    Japanese e-commerce giant Askul Corporation has confirmed that RansomHouse hackers stole around 740,000 customer records in the ransomware attack it suffered in October.

    Askul is a large business-to-business and business-to-consumer office supplies and logistics e-commerce company owned by Yahoo! Japan Corporation.

    The ransomware incident in October caused an IT system failure, forcing the company to suspend shipments to customers, including the retail giant Muji .

    The investigations into the incident’s scope and impact have now been concluded, and Askul says that the following types of data has been compromised:

    • Business customer service data: approx. 590,000 records
    • Individual customer service data: approx. 132,000 records
    • Business partners (outsourcers, agents, suppliers): approx. 15,000 records
    • Executives and employees (including group companies): approx. 2,700 records

    Askul noted that exact details have been withheld to prevent exploitation of the compromised information, and that affected customers and partners will be notified individually.

    Also, the company has informed the country’s Personal Information Protection Commission about the data exposure and established long-term monitoring to prevent misuse of the stolen information.

    Meanwhile, as of December 15 , order shipping continues to be impacted, and the company is still working to fully restore systems .

    RansomHouse attack details

    The attack on Askul has been claimed by the RansomHouse extortion group. The gang initially disclosed the breach on October 30 and followed up with two data leaks on November 10 and December 2.

    RansomHouse's latest data leak
    RansomHouse's latest Askul data leak
    Source: BleepingComputer

    Askul has shared some details about how the threat actors breached its networks, estimating that they leveraged compromised authentication credentials for an outsourced partner’s administrator account, which lacked multi-factor authentication (MFA) protection.

    "After successfully achieving the initial intrusion, the attacker began reconnaissance of the network and attempted to collect authentication information to access multiple servers," reads the automated translation of Askul's report .

    "The attacker then disables vulnerability countermeasure software such as EDR, moves between multiple servers, and acquires the necessary privileges," the company said.

    Notably, Askul stated that multiple ransomware variants were used in the attack, some of which evaded the EDR signatures that had been updated at the time.

    Attack diagram
    Attack diagram
    Source: Askul

    RansomHouse is known for both stealing data and encrypting systems. Askul said that the ransomware attack "resulted in data encryption and system failure."

    Askul reports that the ransomware payload was deployed simultaneously across multiple servers, while backup files were wiped to prevent easy recovery.

    In response, the company physically disconnected infected networks and cut communications between data centers and logistics centers, isolated affected devices, and updated EDR signatures.

    Moreover, MFA was applied to all key systems, and all administrator accounts had their passwords reset.

    The financial impact of the attack has not yet been estimated, and Askul has postponed its scheduled earnings report to allow more time for a detailed financial assessment.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    JetBlue flight averts mid-air collision with US Air Force jet

    Hacker News
    www.reuters.com
    2025-12-15 22:48:56
    Comments...
    Original Article

    Please enable JS and disable any ad blocker

    New SantaStealer malware steals data from browsers, crypto wallets

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-15 22:43:10
    A new malware-as-a-service (MaaS) information stealer named SantaStealer is being advertised on Telegram and hacker forums as operating in memory to avoid file-based detection. [...]...
    Original Article

    New SantaStealer malware steals data from browsers, crypto wallets

    A new malware-as-a-service (MaaS) information stealer named SantaStealer is being advertised on Telegram and hacker forums as operating in memory to avoid file-based detection.

    According to security researchers at Rapid7, the operation is a rebranding of a project called BluelineStealer, and the developer is ramping up the operation ahead of a planned launch before the end of the year.

    SantaStealer appears to be the project of a Russian-speaking developer and is promoted for a Basic, $175/month subscription, and a Premium for $300/month.

    SantaStealer ad
    SantaStealer ad
    Source: Rapid7

    Rapid7 analyzed several SantaStealer samples and obtained access to the affiliate web panel, which revealed that the malware comes with multiple data-theft mechanisms but does not rise to the advertised feature for evading detection and analysis.

    "The samples we have seen until now are far from undetectable, or in any way difficult to analyze," Rapid7 researchers say in a report today.

    "While it is possible that the threat actor behind SantaStealer is still developing some of the mentioned anti-analysis or anti-AV techniques, having samples leaked before the malware is ready for production use - complete with symbol names and unencrypted strings - is a clumsy mistake likely thwarting much of the effort put into its development and hinting at poor operational security of the threat actor(s)," Rapid7 says.

    The panel features a user-friendly design where 'customers' can configure their builds with specific targeting scopes, ranging from full-scale data theft to lean payloads that only go after specific data.

    Builder configuration options on the panel
    Builder configuration options on the panel
    Source: Rapid7

    SantaStealer uses 14 distinct data-collection modules, each running in its own thread, writing stolen data to memory, archiving it into a ZIP file, and then exfiltrating it in 10MB chunks to a hardcoded command-and-control (C2) endpoint via port 6767.

    The modules target information in the browser (passwords, cookies, browsing history, saved credit cards), Telegram, Discord, and Steam data, cryptocurrency wallet apps and extensions, and documents. The malware can also take screenshots of the user’s desktop.

    The malware uses an embedded executable to bypass Chrome’s App-Bound Encryption protections, first introduced in July 2024 , and bypassed by multiple active info-stealers .

    Other configuration options allow its operators to exclude systems in the Commonwealth of Independent States (CIS) region and delay execution to misdirect victims with an inactivity period.

    As SantaStealer isn’t fully operational and hasn't been distributed en masse, it is unclear how it will spread. However, cybercriminals lately seem to prefer ClickFix attacks, where users are tricked into pasting dangerous commands into their Windows terminal.

    Phishing, pirated software, or torrent downloads are also common distribution methods, as are malvertising and deceptive YouTube comments.

    Rapid7 recommends users check links and attachments in emails they don't recognize. They also warn of running unverified code from public repositories for extensions.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Trends to Watch in the California Legislature

    Electronic Frontier Foundation
    www.eff.org
    2025-12-15 22:09:42
    If you’re a Californian, there are a few new state laws that you should know will be going into effect in the new year. EFF has worked hard in Sacramento this session to advance bills that protect privacy, fight surveillance, and promote transparency. California’s legislature runs in a two-year cycl...
    Original Article

    If you’re a Californian, there are a few new state laws that you should know will be going into effect in the new year. EFF has worked hard in Sacramento this session to advance bills that protect privacy, fight surveillance, and promote transparency.

    California’s legislature runs in a two-year cycle, meaning that it’s currently halftime for legislators. As we prepare for the next year of the California legislative session in January, it’s a good time to showcase what’s happened so far—and what’s left to do.

    Wins Worth Celebrating

    In a win for every Californian’s privacy rights, we were happy to support A.B. 566 (Assemblymember Josh Lowenthal). This is a common-sense law that makes California’s main consumer data privacy law, the California Consumer Privacy Act, more user-friendly. It requires that browsers support people’s rights to send opt-out signals, such as the global opt-out in Privacy Badger , to businesses. Managing your privacy as an individual can be a hard job, and EFF wants stronger laws that make it easier for you to do so.

    Additionally, we were proud to advance government transparency by supporting A.B. 1524 (Judiciary Committee), which allows members of the public to make copies of public court records using their own devices, such as cell-phone cameras and overhead document scanners, without paying fees.

    We also supported two bills that will improve law enforcement accountability at a time when we desperately need it. S.B. 627 (Senator Scott Wiener) prohibits law enforcement officers from wearing masks to avoid accountability (The Trump administration has sued California over this law ). We also supported S.B. 524 (Asm. Jesse Arreguín), which requires law enforcement to disclose when a police report was written using artificial intelligence.

    On the To-Do List for Next Year

    On the flip side, we also stopped some problematic bills from becoming law. This includes S.B. 690 (Sen. Anna Caballero), which we dubbed the Corporate Coverup Act . This bill would have gutted California’s wiretapping statute by allowing businesses to ignore those privacy rights for “any business purpose.” Working with several coalition partners, we were able to keep that bill from moving forward in 2025. We do expect to see it come back in 2026, and are ready to fight back against those corporate business interests.

    And, of course, not every fight ended in victory. There are still many areas where we have work left to do. California Governor Gavin Newsom vetoed a bill we supported , S.B. 7 , which would have given workers in California greater transparency into how their employers use artificial intelligence and was sponsored by the California Federation of Labor Unions. S.B. 7  was vetoed in response to concerns from companies including Uber and Lyft , but we expect to continue working with the labor community on the ways AI affects the workplace in 2026.

    Trends of Note

    California continued a troubling years-long trend of lawmakers pushing problematic proposals that would require every internet user to verify their age to access information—often by relying on privacy-invasive methods to do so. Earlier this year EFF sent a letter to the California legislature expressing grave concerns with lawmakers’ approach to regulating young people’s ability to speak online . We continue to raise these concerns, and would welcome working with any lawmaker in California on a better solution.

    We also continue to keep a close eye on government data sharing. On this front, there is some good news. Several of the bills we supported this year sought to place needed safeguards on the ways various government agencies in California share data. These include: A.B. 82 (Asm. Chris Ward) and S.B. 497 (Wiener), which would add privacy protections to data collected by the state about those who may be receiving gender-affirming or reproductive health care; A.B. 1303 (Asm. Avelino Valencia), which prohibits warrantless data sharing from California’s low-income broadband program to immigration and other government officials; and S.B. 635 (Sen. Maria Elena Durazo), which places similar limits on data collected from sidewalk vendors.

    We are also heartened to see California correct course on broad government data sharing. Last session, we opposed A.B. 518 (Asm. Buffy Wicks), which let state agencies ignore existing state privacy law to allow broader information sharing about people eligible for CalFresh—the state’s federally funded food assistance program. As we’ve seen, the federal government has since sought data from food assistance programs to use for other purposes. We were happy to have instead supported A.B. 593 this year, also authored by Asm. Wicks—which reversed course on that data sharing.

    We hope to see this attention to the harms of careless government data sharing continue. EFF’s sponsored bill this year, A.B. 1337 , would update and extend vital privacy safeguards present at the state agency level to counties and cities. These local entities today collect enormous amounts of data and administer programs that weren’t contemplated when the original law was written in 1977. That information should be held to strong privacy standards.

    We’ve been fortunate to work with Asm. Chris Ward, who is also the chair of the LGBTQ Caucus in the legislature, on that bill. The bill stalled in the Senate Judiciary Committee during the 2025 legislative session, but we plan to bring it back in the next session with a renewed sense of urgency.

    Killed by Google

    Hacker News
    killedbygoogle.com
    2025-12-15 22:08:13
    Comments...
    Original Article
  • Advertisement
  • Guillotine

    service

    Dark Web Reports

    Getting unplugged in about 1 month, Dark Web Reports was a dark web scanner that searched for personal info leaks. It will be almost 3 years old.

  • Guillotine

    service

    Tables by A120

    Another one bites the dust in about 6 hours, Tables was a collaborative database platform and competitor to Airtable, focused on making project tracking more efficient with automation. It will be almost 6 years old.

  • Tombstone

    -

    app

    Google Jamboard

    Killed 12 months ago, Google Jamboard was a web and native whiteboard app that offered a rich collaborative experience. It was about 8 years old.

  • Tombstone

    -

    hardware

    Jamboard

    Killed about 1 year ago, Jamboard was a digital 4K touchscreen whiteboard device that allowed to collaborate using Google Workspace services. It was over 7 years old.

  • Tombstone

    -

    hardware

    Chromecast

    Killed over 1 year ago, Chromecast was a line of digital media players that allowed users to play online content on a television. It was about 11 years old.

  • Tombstone

    -

    service

    VPN by Google One

    Killed over 1 year ago, VPN by Google One was a virtual private network service that provided users encrypted transit of their data and network activity and allowed them to mask their IP address. It was over 3 years old.

  • Tombstone

    -

    hardware

    DropCam

    Killed over 1 year ago, Dropcam was a line of Wi-Fi video streaming cameras acquired by Google in 2014. It was about 15 years old.

  • Tombstone

    -

    app

    Google Podcasts

    Killed over 1 year ago, Google Podcasts was a podcast hosting platform and an Android podcast listening app. It was almost 6 years old.

  • Tombstone

    -

    app

    Keen

    Killed over 1 year ago, Keen was a Pinterest-style platform with ML recommendations. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Cache

    Killed almost 2 years ago, Google Cache was a tool to view cached/older versions of a website. It was one of the oldest products. It was almost 24 years old.

  • Tombstone

    -

    service

    Google Domains

    Killed about 2 years ago, Google Domains was a domain name registrar operated by Google. It was over 9 years old.

  • Tombstone

    -

    service

    Google Optimize

    Killed about 2 years ago, Google Optimize was a web analytics and testing tool that allowed users to run experiments aimed at increasing visitor conversion rates and overall satisfaction. It was over 11 years old.

  • Tombstone

    -

    service

    Pixel Pass

    Killed over 2 years ago, Pixel Pass was a program that allowed users to pay a monthly charge for their Pixel phone and upgrade immediately after two years. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Cloud IoT Core

    Killed over 2 years ago, Google Cloud IoT Core was a managed service designed to let customers securely connect, manage, and ingest data from globally dispersed devices. It was over 5 years old.

  • Tombstone

    -

    service

    Google Album Archive

    Killed over 2 years ago, Google Album Archive was a platform that allowed users to access and manage their archived photos and videos from various Google services, such as Hangouts and Picasa Web Albums. It was almost 7 years old.

  • Tombstone

    -

    service

    YouTube Stories

    Killed over 2 years ago, YouTube Stories (originally YouTube Reels) allowed creators to post temporary videos that would expire after seven days. It was over 5 years old.

  • Tombstone

    -

    app

    Grasshopper

    Killed over 2 years ago, Grasshopper was a free mobile and web app for aspiring programmers that taught introductory JavaScript and coding fundamentals using fun, bite-sized puzzles. It was about 5 years old.

  • Tombstone

    -

    service

    Conversational Actions

    Killed over 2 years ago, Conversational Actions extended the functionality of Google Assistant by allowing 3rd party developers to create custom experiences, or conversations, for users of Google Assistant. It was over 6 years old.

  • Tombstone

    -

    service

    Google Currents (2019)

    Killed over 2 years ago, Google Currents was a service that provided social media features similar to Google+ for Google Workspace customers. It was almost 4 years old.

  • Tombstone

    -

    app

    Google Street View (standalone app)

    Killed over 2 years ago, Google Street View app was an Android and iOS app that enabled people to get a 360 degree view of locations around the world. It was over 12 years old.

  • Tombstone

    -

    hardware

    Jacquard

    Killed over 2 years ago, Jacquard was a small tag to make it easier and more intuitive for people to interact with technology in their everyday lives, without having to constantly pull out their devices or touch screens. It was about 9 years old.

  • Tombstone

    -

    service

    Google Code Competitions

    Killed almost 3 years ago, Google Code Jam, Kick Start, and Hash Code were competitive programming competitions open to programmers around the world. It was over 19 years old.

  • Tombstone

    -

    service

    Google Stadia

    Killed almost 3 years ago, Google Stadia was a cloud gaming service combining a WiFi gaming controller and allowed users to stream gameplay through web browsers, TV, mobile apps, and Chromecast. It was about 3 years old.

  • Tombstone

    -

    hardware

    Google OnHub

    Killed almost 3 years ago, Google OnHub was a series of residential wireless routers manufactured by Asus and TP-Link that were powered by Google software, managed by Google apps, and offered enhanced special features like Google Assistant. It was over 7 years old.

  • Tombstone

    -

    service

    YouTube Originals

    Killed almost 3 years ago, YouTube Originals was a variety of original content including scripted series, educational videos, and music and celebrity programming. It was over 6 years old.

  • Tombstone

    -

    app

    Threadit

    Killed almost 3 years ago, Threadit was a tool for recording and sharing short videos. It was almost 2 years old.

  • Tombstone

    -

    service

    Duplex on the Web

    Killed about 3 years ago, Duplex on the Web was a Google Assistant technology that automated tasks on the web on behalf of a user; such as booking movie tickets or making restaurant reservations. It was over 3 years old.

  • Tombstone

    -

    service

    Google Hangouts

    Killed about 3 years ago, Google Hangouts was a cross-platform instant messaging service. It was over 9 years old.

  • Tombstone

    -

    service

    Google Surveys

    Killed about 3 years ago, Google Surveys was a business product by Google aimed at facilitating customized market research. It was over 10 years old.

  • Tombstone

    -

    app

    YouTube Go

    Killed over 3 years ago, YouTube Go was an app aimed at making YouTube easier to access on mobile devices in emerging markets through special features like downloading video on wifi for viewing later. It was over 5 years old.

  • Tombstone

    -

    app

    Google My Business (app)

    Killed over 3 years ago, Google My Business was an app that allowed businesses to manage their Google Maps Business profiles. It was over 3 years old.

  • Tombstone

    -

    service

    Google Chrome Apps

    Killed over 3 years ago, Google Chrome Apps were hosted or packaged web applications that ran on the Google Chrome browser. It was over 11 years old.

  • Tombstone

    -

    app

    Kormo Jobs

    Killed over 3 years ago, Kormo Jobs was an app that allowed users in primarily India, Indonesia, and Bangladesh to help them find jobs nearby that match their skills and interests. It was almost 3 years old.

  • Tombstone

    -

    app

    Android Auto for phone screens

    Killed over 3 years ago, Android Auto for phone screens was an app that allowed the screen of the phone to be used as an Android Auto interface while driving, intended for vehicles that did not have a compatible screen built in. It was over 2 years old.

  • Tombstone

    -

    service

    Google Duo

    Killed over 3 years ago, Google Duo was a video calling app that allowed people to call someone from their contact list. It was almost 6 years old.

  • Tombstone

    -

    service

    G Suite (Legacy Free Edition)

    Killed over 3 years ago, G Suite (Legacy Free Edition) was a free tier offering some of the services included in Google's productivity suite. It was over 15 years old.

  • Tombstone

    -

    service

    Google Assistant Snapshot

    Killed over 3 years ago, Google Assistant Snapshot was the successor to Google Now that provided predictive cards with information and daily updates in the Google app for Android and iOS. It was over 3 years old.

  • Tombstone

    -

    service

    Cameos on Google

    Killed almost 4 years ago, Cameos on Google allowed celebrities and other public figures to record video responses to the most common questions asked about them which would be shown to users in Google Search results. It was over 3 years old.

  • Tombstone

    -

    service

    Android Things

    Killed almost 4 years ago, Android Things was an Android-based embedded operating system (originally named Brillo) aimed to run on Internet of Things (IoT) devices. It was over 6 years old.

  • Tombstone

    -

    service

    AngularJS

    Killed almost 4 years ago, AngularJS was a JavaScript open-source front-end web framework based on MVC pattern using a dependency injection technique. It was about 11 years old.

  • Tombstone

    -

    app

    Streams

    Killed almost 4 years ago, Streams was a "clinician support app" which aimed to improve clinical decision-making and patient safety across hospitals in the United Kingdom. It was about 4 years old.

  • Tombstone

    -

    service

    Material Gallery

    Killed almost 4 years ago, Material Gallery was a collaboration tool for UI designers, optimized for Google's Material Design, with mobile preview apps and a Sketch plugin. It was over 3 years old.

  • Tombstone

    -

    service

    Google Toolbar

    Killed about 4 years ago, Google Toolbar was a web browser toolbar that provided a search box in web browsers like Internet Explorer and Firefox. It was about 21 years old.

  • Tombstone

    -

    service

    Google Sites (Classic)

    Killed about 4 years ago, Google Sites (Classic) allowed users to build and edit websites and wiki portals for private and public use. It was almost 14 years old.

  • Tombstone

    -

    service

    Your News Update

    Killed about 4 years ago, Your News Update was a service that offered an audio digest of a mix of short news stories chosen at that moment based on a user's interests, location, user history, and preferences, as well as the top news stories out there. It was almost 2 years old.

  • Tombstone

    -

    app

    Google My Maps

    Killed about 4 years ago, My Maps was an Android application that enabled users to create custom maps for personal use or sharing on their mobile device. It was almost 7 years old.

  • Tombstone

    -

    app

    Backup and Sync

    Killed about 4 years ago, Backup and Sync was a desktop software tool for Windows and macOS that allowed users to sync files from Google Drive to their local machine. It was over 4 years old.

  • Tombstone

    -

    service

    Google Bookmarks

    Killed about 4 years ago, Google Bookmarks was a private web-based bookmarking service not integrated with any other Google services. It was almost 16 years old.

  • Tombstone

    -

    service

    Chatbase

    Killed about 4 years ago, Analytics platform for Google's Dialogflow chatbot & others, started by the Google-funded Area120 incubator then retired and partially merged into Dialogflow itself. It was almost 4 years old.

  • Tombstone

    -

    app

    VR180 Creator

    Killed over 4 years ago, VR180 Creator allowed users to edit video taken on 180-degree and 360-degree devices on multiple operating systems. It was about 3 years old.

  • Tombstone

    -

    service

    Posts on Google

    Killed over 4 years ago, Posts on Google allowed notable individuals with knowledge graph panels to author specific content that would appear in Google Search results. It was about 9 years old.

  • Tombstone

    -

    app

    Fitbit Coach

    Killed over 4 years ago, Fitbit Coach (formerly Fitstar) was a video-based bodyweight workout app that used AI to personalize workouts based on user feedback. It was about 8 years old.

  • Tombstone

    -

    app

    Fitstar Yoga

    Killed over 4 years ago, Fitstar Yoga was a video-based yoga app that created unique yoga sessions based on user preference and skill level. It was almost 7 years old.

  • Tombstone

    -

    service

    Tour Builder

    Killed over 4 years ago, Tour Builder allowed users to create and share interactive tours inside Google Earth with photos and videos of locations. It was about 8 years old.

  • Tombstone

    -

    app

    Expeditions

    Killed over 4 years ago, Expeditions was a program for providing virtual reality experiences to school classrooms through Google Cardboard viewers, allowing educators to take their students on virtual field trips. It was almost 6 years old.

  • Tombstone

    -

    app

    Tour Creator

    Killed over 4 years ago, Tour Creator allowed users to build immersive, 360° guided tours that could be viewed with VR devices. It was about 3 years old.

  • Tombstone

    -

    service

    Poly

    Killed over 4 years ago, Poly was a distribution platform for creators to share 3D objects. It was over 3 years old.

  • Tombstone

    -

    app

    Google Play Movies & TV

    Killed over 4 years ago, Google Play Movies & TV, originally Google TV, was an app used to view purchased and rented media and was ultimately replaced with YouTube. It was about 10 years old.

  • Tombstone

    -

    app

    Measure

    Killed over 4 years ago, Measure allowed users to take measurements of everyday objects with their device's camera utilizing ARCore technology. It was about 5 years old.

  • Tombstone

    -

    service

    Zync Render

    Killed over 4 years ago, Zync render was a cloud render platform for animation and visual effects. It was almost 7 years old.

  • Tombstone

    -

    app

    Timely

    Killed over 4 years ago, Timely Alarm Clock was an Android application providing an alarm, stopwatch, and timer functionality with synchronization across devices. It was almost 8 years old.

  • Tombstone

    -

    service

    Polymer

    Killed over 4 years ago, Polymer was an open-source JS library for web components It was almost 6 years old.

  • Tombstone

    -

    app

    Google Shopping Mobile App

    Killed over 4 years ago, The Google Shopping Mobile App, which had absorbed Google Express when it launched, provided a native shopping experience with a personalized homepage for mobile users. It is now retired and the functionality lives on in the Shopping Tab. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Public Alerts

    Killed over 4 years ago, Google Public Alerts was an online notification service owned by Google.org that sends safety alerts to various countries. It was over 8 years old.

  • Tombstone

    -

    service

    Google Go Links

    Killed over 4 years ago, (also known as Google Short Links) was a URL shortening service. It also supported custom domain for customers of Google Workspace (formerly G Suite (formerly Google Apps)). It was about 11 years old.

  • Tombstone

    -

    service

    Google Crisis Map

    Killed over 4 years ago, Google Crisis Map was a website that allowed to create, publish, and share maps by combining layers from anywhere on the web. It was over 9 years old.

  • Tombstone

    -

    hardware

    Google Cardboard

    Killed almost 5 years ago, Google Cardboard was a low-cost, virtual reality (VR) platform named after its folded cardboard viewer into which a smartphone was inserted. It was over 6 years old.

  • Tombstone

    -

    service

    Swift for TensorFlow

    Killed almost 5 years ago, Swift for TensorFlow (S4TF) was a next-generation platform for machine learning with a focus on differentiable programming. It was almost 3 years old.

  • Tombstone

    -

    app

    Tilt Brush

    Killed almost 5 years ago, Tilt Brush was a room-scale 3D-painting virtual-reality application available from Google, originally developed by Skillman & Hackett. It was almost 5 years old.

  • Tombstone

    -

    service

    Loon

    Killed almost 5 years ago, Loon was a service to provide internet access via an array of high-altitude balloons hovering in the Earth's stratosphere It was over 6 years old.

  • Tombstone

    -

    service

    App Maker

    Killed almost 5 years ago, App Maker was a tool that allowed its users to build and deploy custom business apps easily and securely on the web without writing much code. It was about 4 years old.

  • Tombstone

    -

    service

    Google Cloud Print

    Killed almost 5 years ago, Google Cloud Print allowed users to 'print from anywhere;' to print from web, desktop, or mobile to any Google Cloud Print-connected printer. It was over 10 years old.

  • Tombstone

    -

    hardware

    Google Home Max

    Killed about 5 years ago, Google Home Max was a large, stereo smart speaker with two tweeters and subwoofers, aux input, and a USB-C input (for wired ethernet) featuring Smart Sound machine learning technology. It was about 3 years old.

  • Tombstone

    -

    app

    Science Journal

    Killed about 5 years ago, Science Journal was a mobile app that helped you run science experiments with your smartphone using the device's onboard sensors. It was over 4 years old.

  • Tombstone

    -

    app

    YouTube VR (SteamVR)

    Killed about 5 years ago, YouTube VR allowed you to easily find and watch 360 videos and virtual reality content with SteamVR-compatible headsets. It was almost 3 years old.

  • Tombstone

    -

    app

    Trusted Contacts

    Killed about 5 years ago, Trusted Contacts was an app that allowed users to share their location and view the location of specific users. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Play Music

    Killed about 5 years ago, Google Play Music was a music and podcast streaming service, and online music locker. It was almost 9 years old.

  • Tombstone

    -

    hardware

    Nest Secure

    Killed about 5 years ago, Nest Secure was a security system with an alarm, keypad, and motion sensor with an embedded microphone. It was almost 3 years old.

  • Tombstone

    -

    service

    YouTube Community Contributions

    Killed about 5 years ago, YouTube Community Contributions allowed users to contribute translations for video titles or submit descriptions, closed captions or subtitles on YouTube content. It was over 4 years old.

  • Tombstone

    -

    service

    Hire by Google

    Killed over 5 years ago, Google Hire was an applicant tracking system to help small to medium businesses distribute jobs, identify and attract candidates, build strong relationships with candidates, and efficiently manage the interview process. It was about 3 years old.

  • Tombstone

    -

    app

    Password Checkup extension

    Killed over 5 years ago, Password Checkup provided a warning to users if they were using a username and password combination checked against over 4 billion credentials that Google knew to be unsafe. It was over 1 year old.

  • Tombstone

    -

    app

    Playground AR

    Killed over 5 years ago, Playground AR (aka AR Stickers) allowed users to place virtual characters and objects in augmented reality via the Camera App on Pixel phones. It was over 2 years old.

  • Tombstone

    -

    hardware

    Focals by North

    Killed over 5 years ago, Focals were a custom-built smart glasses product with a transparent, holographic display that allowed users to read and respond to text messages, navigate turn-by-turn directions, check the weather, and integrate with third-party services like Uber and Amazon Alexa. It was over 1 year old.

  • Tombstone

    -

    app

    CallJoy

    Killed over 5 years ago, CallJoy was an Area 120 project that provided phone automation for small-to-medium businesses allowing them to train the bot agent with responses to common customer questions. It was about 1 year old.

  • Tombstone

    -

    service

    Google Photos Print

    Killed over 5 years ago, Google Photos Print was a subscription service that automatically selected the best ten photos from the last thirty days which were mailed to user's homes. It was 5 months old.

  • Tombstone

    -

    app

    Pigeon Transit

    Killed over 5 years ago, Pigeon Transit was a transit app that used crowdsourced information about delays, crowded trains, escalator outages, live entertainment, dirty or unsafe conditions. It was almost 2 years old.

  • Tombstone

    -

    service

    Enhanced 404 Pages

    Killed over 5 years ago, Enhanced 404 Pages was a JavaScript library that added suggested URLs and a search box to a website's 404 Not Found page. It was over 11 years old.

  • Tombstone

    -

    app

    Shoelace

    Killed over 5 years ago, Shoelace was an app used to find group activities with others who share your interests. It was 11 months old.

  • Tombstone

    -

    app

    Neighbourly

    Killed over 5 years ago, Neighbourly was a mobile app designed to help you learn about your neighborhood by asking other residents, and find out about local services and facilities in your area from people who live around you. It was almost 2 years old.

  • Tombstone

    -

    service

    Fabric

    Killed over 5 years ago, Fabric was a platform that helped mobile teams build better apps, understand their users, and grow their business. It was over 5 years old.

  • Tombstone

    -

    service

    Google Contributor

    Killed over 5 years ago, Google Contributor was a program run by Google that allowed users in the Google Network of content sites to view the websites without any advertisements that are administered, sorted, and maintained by Google. It was over 5 years old.

  • Tombstone

    -

    app

    Material Theme Editor

    Killed over 5 years ago, Material Theme Editor was a plugin for Sketch App which allowed you to create a material-based design system for your app. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Station

    Killed almost 6 years ago, Google Station was a service that gave partners an easy set of tools to roll out Wi-Fi hotspots in public places. Google Station provided software and guidance on hardware to turn fiber connections into fast, reliable, and safe Wi-Fi zones. It was over 4 years old.

  • Tombstone

    -

    app

    One Today

    Killed almost 6 years ago, One Today was an app that allowed users to donate $1 to different organizations and discover how their donation would be used. It was almost 7 years old.

  • Tombstone

    -

    app

    Androidify

    Killed almost 6 years ago, Androidify allowed users to create a custom Android avatar for themselves and others. It was almost 9 years old.

  • Tombstone

    -

    service

    Google Fiber TV

    Killed almost 6 years ago, Google Fiber TV was an IPTV service that was bundled with Google Fiber. It was about 7 years old.

  • Tombstone

    -

    app

    Field Trip

    Killed almost 6 years ago, Field Trip was a mobile app that acted as a virtual tour guide by cross-referencing multiple sources of information to provide users information about points of interest near them. It was over 7 years old.

  • Tombstone

    -

    app

    AdSense (mobile app)

    Killed almost 6 years ago, AdSense (mobile app) allowed users to manage their AdSense accounts in a native app for iOS and Android. It was over 6 years old.

  • Tombstone

    -

    service

    Google Correlate

    Killed about 6 years ago, Google Correlate was a service that provided users information about how strongly the frequency of multiple search terms correlates with each other over a specified time interval. It was over 8 years old.

  • Tombstone

    -

    service

    Google Translator Toolkit

    Killed about 6 years ago, Google Translator Toolkit was a web application which allowed translators to edit and manage translations generated by Google Translate. It was over 10 years old.

  • Tombstone

    -

    service

    Google Fusion Tables

    Killed about 6 years ago, Google Fusion Tables was a web service for data management that provided a means for visualizing data in different charts, maps, and graphs. It was over 10 years old.

  • Tombstone

    -

    service

    Google Bulletin

    Killed about 6 years ago, Google Bulletin was a hyperlocal news service where users could post news from their neighborhood and allow others in the same areas to hear those stories. It was almost 2 years old.

  • Tombstone

    -

    service

    Touring Bird

    Killed about 6 years ago, Touring Bird was an Area 120 incubator project which helped users compare prices, book tours, tickets, and experiences, and learn about top destinations around the world. It was about 1 year old.

  • Tombstone

    -

    app

    Game Builder

    Killed about 6 years ago, Game Builder was a multiplayer 3D game environment for creating new games without coding experience. It was 5 months old.

  • Tombstone

    -

    app

    Datally

    Killed about 6 years ago, Datally (formerly Triangle) was a smart app by Google that helped you save, manage, and share your mobile data. It was over 2 years old.

  • Tombstone

    -

    hardware

    Google Clips

    Killed about 6 years ago, Google Clips was a miniature clip-on camera that could automatically capture interesting or relevant video clips determined by machine learning algorithms. It was about 2 years old.

  • Tombstone

    -

    hardware

    Google Daydream

    Killed about 6 years ago, Google Daydream was a virtual reality platform and set of hardware devices that worked with certain Android phones. It was almost 3 years old.

  • Tombstone

    -

    service

    YouTube Leanback

    Killed about 6 years ago, YouTube Leanback was an optimized version of YouTube used for television web browsers and WebView application wrappers. It was about 9 years old.

  • Tombstone

    -

    service

    Message Center

    Killed about 6 years ago, Message Center was a web console where Gmail users could view and manage spam email messages. It was almost 6 years old.

  • Tombstone

    -

    service

    Follow Your World

    Killed about 6 years ago, Follow Your World allowed users to register points of interest on Google Maps and receive email updates whenever the imagery was updated. It was over 8 years old.

  • Tombstone

    -

    service

    G Suite Training

    Killed about 6 years ago, G Suite Training (previously known as Synergyse) provided interactive and video-based training for 20 Google G Suite products in nine languages through a website and a Chrome extension. It was over 6 years old.

  • Tombstone

    -

    service

    YouTube Messages

    Killed about 6 years ago, YouTube Messages was a direct messaging feature that allowed users to share and discuss videos one-on-one and in groups on YouTube. It was about 2 years old.

  • Tombstone

    -

    app

    YouTube for Nintendo 3DS

    Killed over 6 years ago, YouTube for Nintendo 3DS allowed users to stream YouTube videos on the portable gaming console. It was almost 6 years old.

  • Tombstone

    -

    service

    Works with Nest API

    Killed over 6 years ago, Works with Nest was an API that allowed external services to access and control Nest devices. This enabled the devices to be used with third-party home automation platforms and devices. It was about 5 years old.

  • Tombstone

    -

    app

    Google Trips

    Killed over 6 years ago, Google Trips was a mobile app that allowed users to plan for upcoming travel by facilitating flight, hotel, car, and restaurant reservations from user's email alongside summarized info about the user's destination. It was almost 3 years old.

  • Tombstone

    -

    service

    Hangouts on Air

    Killed over 6 years ago, Hangouts on Air allowed users to host a multi-user video call while recording and streaming the call on YouTube. It was almost 8 years old.

  • Tombstone

    -

    service

    Personal Blocklist

    Killed over 6 years ago, Personal Blocklist was a Chrome Web Extension by Google that allowed users to block certain websites from appearing in Google search results. It was over 8 years old.

  • Tombstone

    -

    service

    Dragonfly

    Killed over 6 years ago, Dragonfly was a search engine designed to be compatible with China's state censorship provisions. It was 11 months old.

  • Tombstone

    -

    service

    Google Jump

    Killed over 6 years ago, Google Jump was a cloud-based VR media solution that enabled 3D-360 media production by integrating customized capture solutions with best-in-class automated stitching. It was about 4 years old.

  • Tombstone

    -

    app

    Blog Compass

    Killed over 6 years ago, Blog Compass was a blog management tool that integrated with WordPress and Blogger available only in India. It was 9 months old.

  • Tombstone

    -

    app

    Areo

    Killed over 6 years ago, Areo was a mobile app that allowed users in Bangalore, Mumbai, Delhi, Gurgaon, and Pune to order meals from nearby restaurants or schedule appointments with local service professionals, including electricians, painters, cleaners, plumbers, and more. It was about 2 years old.

  • Tombstone

    -

    service

    YouTube Gaming

    Killed over 6 years ago, YouTube Gaming was a video gaming-oriented service and app for videos and live streaming. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Cloud Messaging (GCM)

    Killed over 6 years ago, Google Cloud Messaging (GCM) was a notification service that enabled developers to send messages between servers and client apps running on Android or Chrome. It was almost 7 years old.

  • Tombstone

    -

    service

    Data Saver Extension for Chrome

    Killed over 6 years ago, Data Saver was an extension for Chrome that routed web pages through Google servers to compress and reduce the user's bandwidth. It was about 4 years old.

  • Tombstone

    -

    service

    Inbox by Gmail

    Killed over 6 years ago, Inbox by Gmail aimed to improve email through several key features. It was almost 4 years old.

  • Tombstone

    -

    service

    Killed over 6 years ago, Google+ was an Internet-based social network. It was almost 8 years old.

  • Tombstone

    -

    service

    Google URL Shortener

    Killed over 6 years ago, Google URL Shortener, also known as goo.gl, was a URL shortening service. It was over 9 years old.

  • Tombstone

    -

    service

    Google Spotlight Stories

    Killed almost 7 years ago, Google Spotlight Stories was an app and content studio project which created immersive stories for mobile and VR. It was over 5 years old.

  • Tombstone

    -

    app

    Google Allo

    Killed almost 7 years ago, Google Allo was an instant messaging mobile app for Android, iOS, and Web with special features like a virtual assistant and encrypted mode. It was over 2 years old.

  • Tombstone

    -

    service

    Google Notification Widget (Mr. Jingles)

    Killed almost 7 years ago, Mr. Jingles (aka Google Notification Widget) displayed alerts and notifications from across multiple Google services. It was almost 4 years old.

  • Tombstone

    -

    service

    YouTube Video Annotations

    Killed almost 7 years ago, YouTube Video Annotations allowed video creators to add interactive commentary to their videos containing background information, branching ("choose your own adventure" style) stories, or links to any YouTube video, channel, or search results page. It was over 10 years old.

  • Tombstone

    -

    service

    Google Realtime API

    Killed almost 7 years ago, Google Realtime API provided ways to synchronize resources between devices. It operated on files stored on Google Drive. It was almost 6 years old.

  • Tombstone

    -

    hardware

    Chromecast Audio

    Killed almost 7 years ago, Chromecast Audio was a device that allowed users to stream audio from any device to any speaker with an audio input. It was over 3 years old.

  • Tombstone

    -

    hardware

    Google Search Appliance

    Killed almost 7 years ago, Google Search Appliance was a rack-mounted device that provided document indexing functionality. It was almost 17 years old.

  • Tombstone

    -

    service

    Google Nearby Notifications

    Killed about 7 years ago, Google Nearby Notifications were a proximity marketing tool using Bluetooth beacons and location-based data to serve content relevant to an Android user's real-world location. It was over 3 years old.

  • Tombstone

    -

    service

    Google Pinyin IME

    Killed about 7 years ago, Google Pinyin IME was an input method that allowed users on multiple operating systems to input characters from pinyin, the romanization of Standard Mandarin Chinese. It was over 11 years old.

  • Tombstone

    -

    app

    Google News & Weather

    Killed about 7 years ago, Google News & Weather was a news aggregator application available on the Android and iOS operating systems. It was about 2 years old.

  • Tombstone

    -

    app

    Reply

    Killed about 7 years ago, Reply was a mobile app that let users insert Smart Replies (pre-defined replies) into conversations on messaging apps. It was 8 months old.

  • Tombstone

    -

    service

    Tez

    Killed over 7 years ago, Tez was a mobile payments service by Google, targeted at users in India. It was rebranded to Google Pay. It was 11 months old.

  • Tombstone

    -

    service

    Google Goggles

    Killed over 7 years ago, Google Goggles was used for searches based on pictures taken by handheld devices. It was almost 8 years old.

  • Tombstone

    -

    service

    Save to Google Chrome Extension

    Killed over 7 years ago, Save to Google Chrome Extension enabled you to quickly save a page link with image and tags to a Pocket-like app. It was over 2 years old.

  • Tombstone

    -

    app

    Google Play Newsstand

    Killed over 7 years ago, Google Play Newsstand was a news aggregator and digital newsstand service. It was over 4 years old.

  • Tombstone

    -

    service

    Encrypted Search

    Killed over 7 years ago, Encrypted Search provided users with anonymous internet searching. It was almost 8 years old.

  • Tombstone

    -

    service

    Google Cloud Prediction API

    Killed over 7 years ago, Google Cloud Prediction API was a PaaS for machine learning (ML) functionality to help developers build ML models to create application features such as recommendation systems, spam detection, and purchase prediction. It was almost 8 years old.

  • Tombstone

    -

    service

    qpx-express-API

    Killed over 7 years ago, A service that Google developed for long-tail travel clients. ITA Software will create a new, easier way for users to find better flight information online, which should encourage more users to make their flight purchases online. It was almost 8 years old.

  • Tombstone

    -

    service

    Google Site Search

    Killed over 7 years ago, Google's Site Search was a service that enabled any website to add a custom search field powered by Google. It was over 9 years old.

  • Tombstone

    -

    service

    reCAPTCHA Mailhide

    Killed over 7 years ago, reCAPTCHA Mailhide allowed users to mask their email address behind a captcha to prevent robots from scraping the email and sending spam. It was almost 8 years old.

  • Tombstone

    -

    service

    SoundStage

    Killed almost 8 years ago, SoundStage was a virtual reality music sandbox built specifically for room-scale VR. It was over 1 year old.

  • Tombstone

    -

    service

    Project Tango

    Killed about 8 years ago, Project Tango was an API for augmented reality apps that was killed and replaced by ARCore. It was about 3 years old.

  • Tombstone

    -

    service

    Google Portfolios

    Killed about 8 years ago, Portfolios was a feature available in Google Finance to track personal financial securities. It was over 11 years old.

  • Tombstone

    -

    service

    YouTube Video Editor

    Killed about 8 years ago, YouTube Video Editor was a web-based tool for editing, merging, and adding special effects to video content. It was over 7 years old.

  • Tombstone

    -

    service

    Trendalyzer

    Killed over 8 years ago, Trendalyzer was a data trend viewing platform. It was over 10 years old.

  • Tombstone

    -

    service

    Glass OS

    Killed over 8 years ago, Glass OS (Google XE) was a version of Google's Android operating system designed for Google Glass. It was about 4 years old.

  • Tombstone

    -

    service

    Google Map Maker

    Killed over 8 years ago, Google Map Maker was a mapping and map editing service where users were able to draw features directly onto a map. It was almost 9 years old.

  • Tombstone

    -

    hardware

    Chromebook Pixel

    Killed almost 9 years ago, Chromebook Pixel was a first-of-its-kind laptop built by Google that ran Chrome OS, a Linux kernel-based operating system. It was about 4 years old.

  • Tombstone

    -

    service

    Google Spaces

    Killed almost 9 years ago, Google Spaces was an app for group discussions and messaging. It was 9 months old.

  • Tombstone

    -

    service

    Google Hands Free

    Killed almost 9 years ago, Google Hands Free was a mobile payment system that allowed users to pay their bill using Bluetooth to connect to payment terminals by saying 'I'll pay with Google.' It was 11 months old.

  • Tombstone

    -

    service

    Build with Chrome

    Killed almost 9 years ago, Build with Chrome was a collaboration between Chrome and the LEGO Group that allowed users to build and publish LEGO creations to any digital plot of land in the world through Google Maps. It was about 3 years old.

  • Tombstone

    -

    app

    Gesture Search

    Killed almost 9 years ago, Google Gesture Search allowed users to search contacts, applications, settings, music, and bookmark on their Android device by drawing letters or numbers onto the screen. It was almost 7 years old.

  • Tombstone

    -

    service

    Panoramio

    Killed about 9 years ago, Panoramio was a geo-location tagging and photo sharing product. It was about 11 years old.

  • Tombstone

    -

    service

    Google Showtimes

    Killed about 9 years ago, Google Showtimes was a standalone movie search result page. It was about 11 years old.

  • Tombstone

    -

    app

    Pixate

    Killed about 9 years ago, Pixate was a platform for creating sophisticated animations and interactions, and refining your designs through 100% native prototypes for iOS and Android. It was over 4 years old.

  • Tombstone

    -

    hardware

    Google Nexus

    Killed about 9 years ago, Google Nexus was Google's line of flagship Android phones, tablets, and accessories. It was over 6 years old.

  • Tombstone

    -

    app

    Together

    Killed about 9 years ago, Together was a watch face for Android Wear that let two users link their watches together to share small visual messages. It was about 1 year old.

  • Tombstone

    -

    hardware

    Project Ara

    Killed over 9 years ago, Project Ara was a modular smartphone project under development by Google. It was almost 3 years old.

  • Tombstone

    -

    service

    Web Hosting in Google Drive

    Killed over 9 years ago, Web hosting in Google Drive allowed users to publish live websites by uploading HTML, CSS, and other files. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Swiffy

    Killed over 9 years ago, Google Swiffy was a web-based tool that converted SWF files to HTML5. It was about 5 years old.

  • Tombstone

    -

    hardware

    Google Wallet Card

    Killed over 9 years ago, Google Wallet Card was a prepaid debit card that let users pay for things in person and online using their Wallet balance at any retailer that accepted MasterCard. It was over 2 years old.

  • Tombstone

    -

    hardware

    Nexus Player

    Killed over 9 years ago, Nexus Player was a digital media player that allowed users to play music, watch video originating from Internet services or a local network, and play games. It was over 1 year old.

  • Tombstone

    -

    hardware

    Revolv

    Killed over 9 years ago, Revolv was a monitoring and control system that allowed users to control their connected devices from a single hub. It was about 4 years old.

  • Tombstone

    -

    service

    Freebase

    Killed over 9 years ago, Freebase was a large collaborative knowledge base consisting of structured data composed mainly by its community members, developed by Metaweb(acquired by Google). It was about 9 years old.

  • Tombstone

    -

    service

    Google Now

    Killed over 9 years ago, Google Now was a feature of Google Search that offered predictive cards with information and daily updates in Chrome and the Google app for Android and iOS. It was almost 4 years old.

  • Tombstone

    -

    app

    MyTracks

    Killed over 9 years ago, MyTracks was a GPS tracking application for Android which allowed users to track their path, speed, distance, and elevation. It was about 7 years old.

  • Tombstone

    -

    app

    uWeave

    Killed over 9 years ago, uWeave (pronounced “micro weave”) was an implementation of the Weave protocol intended for use on microcontroller-based devices. It was 4 months old.

  • Tombstone

    -

    service

    Google Compare

    Killed almost 10 years ago, Google Compare allowed consumers to compare several offers ranging from insurance, mortgage, and credit cards. It was about 1 year old.

  • Tombstone

    -

    service

    Google Maps Coordinate

    Killed almost 10 years ago, Google Maps Coordinate was a service for managing mobile workforces with the help of mobile apps and a web-based dashboard. It was over 3 years old.

  • Tombstone

    -

    service

    Pie

    Killed almost 10 years ago, Pie was a work-centric group chat website and app comparable to Slack. It was over 2 years old.

  • Tombstone

    -

    service

    Google Maps Engine

    Killed almost 10 years ago, Google Maps Engine was an online tool for map creation. It enabled you to create layered maps using your data as well as Google Maps data. It was over 2 years old.

  • Tombstone

    -

    service

    Songza

    Killed almost 10 years ago, Songza was a free music streaming service that would recommend its users' various playlists based on time of day and mood or activity. It was about 8 years old.

  • Tombstone

    -

    service

    Google Code

    Killed almost 10 years ago, Google Code was a service that provided revision control, an issue tracker, and a wiki for code documentation. It was almost 11 years old.

  • Tombstone

    -

    service

    Google Blog Search API

    Killed almost 10 years ago, Google Blog Search API was a way to search blogs utilizing Google. It was over 10 years old.

  • Tombstone

    -

    service

    Google Earth Browser Plug-in

    Killed about 10 years ago, Google Earth Browser Plug-in allowed developers to embed Google Earth into web pages and included a JavaScript API for custom 3D drawing and interaction. It was over 7 years old.

  • Tombstone

    -

    app

    Timeful

    Killed about 10 years ago, Timeful was an iOS to-do list and calendar application, developed to reinvent the way that people manage their most precious resource of time. It was almost 4 years old.

  • Tombstone

    -

    service

    Picasa

    Killed about 10 years ago, Picasa was an image organizer and image viewer for organizing and editing digital photos. It was almost 13 years old.

  • Tombstone

    -

    service

    Google Flu Trends

    Killed over 10 years ago, Google Flu Trends was a service attempting to make accurate predictions about flu activity. It was almost 7 years old.

  • Tombstone

    -

    service

    Google Catalogs

    Killed over 10 years ago, Google Catalogs was a shopping application that delivered the virtual catalogs of large retailers to users. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Moderator

    Killed over 10 years ago, Google Moderator was a service that used crowdsourcing to rank user-submitted questions, suggestions, and ideas. It was almost 7 years old.

  • Tombstone

    -

    service

    Android @ Home

    Killed over 10 years ago, Android @ Home allowed a user’s device to discover, connect, and communicate with devices and appliances in the home. It was about 4 years old.

  • Tombstone

    -

    service

    Google Helpouts

    Killed over 10 years ago, Google Helpouts was an online collaboration service where users could share their expertise through live video. It was over 1 year old.

  • Tombstone

    -

    app

    YouTube for PS Vita

    Killed over 10 years ago, YouTube for PlayStation Vita was a native YouTube browsing and viewing application for the PS Vita and PSTV game consoles. It was over 2 years old.

  • Tombstone

    -

    service

    BebaPay

    Killed almost 11 years ago, BebaPay was a form of electronic ticketing platform in Nairobi, Kenya that was developed by Google in partnership with Equity Bank. It was almost 2 years old.

  • Tombstone

    -

    hardware

    Google Play Edition

    Killed almost 11 years ago, Google Play Edition devices were a series of Android smartphones and tablets sold by Google. It was over 1 year old.

  • Tombstone

    -

    hardware

    Google Glass Explorer Edition

    Killed almost 11 years ago, Google Glass Explorer Edition was a wearable computer with an optical head-mounted display and camera that allows the wearer to interact with various applications and the Internet via natural language voice commands. It was almost 2 years old.

  • Tombstone

    -

    app

    Word Lens

    Killed almost 11 years ago, Word Lens translated text in real time on images by using the viewfinder of a device's camera without the need of an internet connection; The technology was rolled into Google Translate. It was about 4 years old.

  • Tombstone

    -

    service

    Orkut

    Killed about 11 years ago, Orkut was a social network designed to help users meet new and old friends and maintain existing relationships. It was over 10 years old.

  • Tombstone

    -

    hardware

    Google TV

    Killed over 11 years ago, Google TV was a smart TV platform that integrated Android and Chrome to create an interactive television overlay. It was over 3 years old.

  • Tombstone

    -

    app

    Quickoffice

    Killed over 11 years ago, Quickoffice was a productivity suite for mobile devices which allowed the viewing, creating and editing of documents, presentations and spreadsheets. It was 9 months old.

  • Tombstone

    -

    service

    Google Questions and Answers

    Killed over 11 years ago, Google Questions and Answers was a free knowledge market that allowed users to collaboratively find answers to their questions. It was almost 7 years old.

  • Tombstone

    -

    service

    Wildfire Interactive

    Killed almost 12 years ago, Wildfire by Google was a social marketing application that enabled businesses to create, optimize and measure their presence on social networks. It was over 1 year old.

  • Tombstone

    -

    service

    BufferBox

    Killed almost 12 years ago, BufferBox was a Canadian startup that provided consumers 24/7 convenience of picking up their online purchases. It was about 1 year old.

  • Tombstone

    -

    service

    SlickLogin

    Killed almost 12 years ago, SlickLogin was an Israeli start-up company which developed sound-based password alternatives, was acquired by Google and hasn't released anything since. It was 7 months old.

  • Tombstone

    -

    service

    Google Schemer

    Killed almost 12 years ago, Google Schemer was a Google service for sharing and discovering things to do. It was over 2 years old.

  • Tombstone

    -

    service

    Google Chrome Frame

    Killed almost 12 years ago, Google Chrome Frame was a plugin for Internet Explorer that allowed web pages to be displayed using WebKit and the V8 JavaScript engine. It was over 3 years old.

  • Tombstone

    -

    service

    Google Notifier

    Killed almost 12 years ago, Google Notifier alerted users to new emails on their Gmail account. It was about 9 years old.

  • Tombstone

    -

    app

    Bump!

    Killed almost 12 years ago, Bump! was an iOS and Android mobile app that enabled smartphone users to transfer contact information, photos, and files between devices. It was almost 5 years old.

  • Tombstone

    -

    service

    Google Offers

    Killed almost 12 years ago, Google Offers was a service offering discounts and coupons. Initially, it was a deal of the day website similar to Groupon. It was over 2 years old.

  • Tombstone

    -

    app

    Google Currents

    Killed about 12 years ago, Google Currents was a social magazine app by Google, which was replaced by Google Play Newsstand. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Checkout

    Killed about 12 years ago, Google Checkout was an online payment processing service that aimed to simplify the process of paying for online purchases. It was over 7 years old.

  • Tombstone

    -

    service

    Google Trader

    Killed about 12 years ago, Google Trader was a classifieds service run by Google in Ghana, Uganda, Kenya, and Nigeria to help customers trade goods and services online. It was almost 3 years old.

  • Tombstone

    -

    service

    iGoogle

    Killed about 12 years ago, iGoogle was a customizable Ajax-based start page or personal web portal. It was over 8 years old.

  • Tombstone

    -

    service

    Google Latitude

    Killed over 12 years ago, Google Latitude was a location-aware feature of Google Maps, a successor to an earlier SMS-based service Dodgeball. It was over 4 years old.

  • Tombstone

    -

    service

    Google Reader

    Killed over 12 years ago, Google Reader was an RSS/Atom feed aggregator. It was over 7 years old.

  • Tombstone

    -

    hardware

    Nexus Q

    Killed over 12 years ago, Nexus Q was a digital media player that allowed users with Android devices to stream content from supported services to a connected television or speakers via an integrated amplifier. It was about 1 year old.

  • Tombstone

    -

    service

    Punchd

    Killed over 12 years ago, Punchd was a digital loyalty card app and service targeted towards small businesses that originated as a student project at Cal Poly in 2009 and was acquired by Google in 2011. It was almost 2 years old.

  • Tombstone

    -

    service

    Building Maker

    Killed over 12 years ago, Building Maker enabled users to create 3D models of buildings for Google Earth on the browser. It was over 3 years old.

  • Tombstone

    -

    service

    Google Talk

    Killed over 12 years ago, Often remembered as 'Gchat', Google Talk was a messaging service for both text and voice using XMPP. It was over 7 years old.

  • Tombstone

    -

    service

    Google SMS

    Killed over 12 years ago, Google SMS let you text questions- including weather, sports scores, word definitions, and more- to 466453 and get an answer back. It was over 8 years old.

  • Tombstone

    -

    service

    Google Cloud Connect

    Killed over 12 years ago, Google Cloud Connect was a free cloud computing plugin for multiple versions of Microsoft Office that automatically stored and synchronized files to Google Docs. It was about 5 years old.

  • Tombstone

    -

    service

    Picnik

    Killed over 12 years ago, Picnik was an online photo editing service that allowed users to edit, style, crop, and resize images. It was over 6 years old.

  • Tombstone

    -

    service

    Google Chart API

    Killed almost 13 years ago, Google Chart API was an interactive Web service that created graphical charts from user-supplied data. It was about 5 years old.

  • Tombstone

    -

    hardware

    Google Mini

    Killed almost 13 years ago, Google Mini was a smaller version of the Google Search Appliance. It was about 5 years old.

  • Tombstone

    -

    service

    AdSense for Feeds

    Killed about 13 years ago, AdSense for Feeds was an RSS-based service for AdSense that allowed publishers to advertise on their RSS Feeds. It was over 4 years old.

  • Tombstone

    -

    app

    Google Listen

    Killed about 13 years ago, Google Listen was an Android application that let you search, subscribe, download, and stream podcasts and web audio. It was about 3 years old.

  • Tombstone

    -

    service

    Google Refine

    Killed about 13 years ago, Google Refine was a standalone desktop application for data cleanup and transformation to other formats. It was almost 2 years old.

  • Tombstone

    -

    app

    Sparrow

    Killed about 13 years ago, Sparrow was an email client for OS X and iOS. Google acquired and then killed it. It was over 1 year old.

  • Tombstone

    -

    service

    Google Insights for Search

    Killed about 13 years ago, Google Insights for Search was a service used to provide data about terms people searched in Google and was merged into Google Trends. It was about 4 years old.

  • Tombstone

    -

    service

    Postini

    Killed over 13 years ago, Postini was an e-mail, Web security, and archiving service that filtered e-mail spam and malware (before it was delivered to a client's mail server), e-mail archiving. It was about 13 years old.

  • Tombstone

    -

    service

    Google Video

    Killed over 13 years ago, Google Video was a free video hosting service from Google, similar to YouTube, that allowed video clips to be hosted on Google servers and embedded onto other websites. It was over 7 years old.

  • Tombstone

    -

    service

    Meebo

    Killed over 13 years ago, Meebo was a browser-based instant messaging application which supported multiple IM services. It was almost 7 years old.

  • Tombstone

    -

    service

    Google Commerce Search

    Killed over 13 years ago, Google Commerce Search was an enterprise search service that powered online retail stores and e-commerce websites that improved speed and accuracy. It was over 2 years old.

  • Tombstone

    -

    service

    Needlebase

    Killed over 13 years ago, Needlebase was a point-and-click tool for extracting, sorting and visualizing data from across pages around the web. It was about 1 year old.

  • Tombstone

    -

    service

    Knol

    Killed over 13 years ago, Knol was a Google project that aimed to include user-written articles on a range of topics. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Wave

    Killed over 13 years ago, Google Wave was an online communication and collaborative real-time editor tool. It was over 2 years old.

  • Tombstone

    -

    service

    Google Flu Vaccine Finder

    Killed over 13 years ago, Google Flu Vaccine Finder was a maps mash-up that showed nearby vaccination places across the United States. It was over 2 years old.

  • Tombstone

    -

    service

    Google One Pass

    Killed over 13 years ago, Google One Pass was an online store developed by Google for media publishers looking to sell subscriptions to their content. It was about 1 year old.

  • Tombstone

    -

    service

    Google Related

    Killed over 13 years ago, Google Related was introduced to be an experimental navigation assistant launched to help people find useful and interesting information while surfing the web. It was 8 months old.

  • Tombstone

    -

    service

    Urchin

    Killed over 13 years ago, Urchin was a web statistics analysis program developed by Urchin Software Corporation. It analyzed web server log file content and displayed the traffic information on that website based upon the log data. It was almost 7 years old.

  • Tombstone

    -

    service

    Slide

    Killed almost 14 years ago, Slide was a photo sharing software for social networking services such as MySpace and Facebook. Later Slide began to make applications and became the largest developer of third-party applications for Facebook. It was almost 7 years old.

  • Tombstone

    -

    service

    Google Friend Connect

    Killed almost 14 years ago, Google Friend Connect was a free social networking site from 2008 to 2012. It was almost 4 years old.

  • Tombstone

    -

    service

    Jaiku

    Killed almost 14 years ago, Jaiku was a social networking, micro-blogging and lifestreaming service comparable to Twitter. It was almost 6 years old.

  • Tombstone

    -

    service

    Google Code Search

    Killed almost 14 years ago, Google Code Search was a free beta product which allowed users to search for open-source code on the Internet. It was over 5 years old.

  • Tombstone

    -

    service

    Google Health

    Killed almost 14 years ago, Google Health was a personal health information centralization service that provided users a merged health record from multiple sources. It was over 3 years old.

  • Tombstone

    -

    service

    Noop Programming Language

    Killed almost 14 years ago, Noop was a project by Google engineers Alex Eagle and Christian Gruber aiming to develop a new programming language that attempted to blend the best features of 'old' and 'new' languages and best practices. It was almost 3 years old.

  • Tombstone

    -

    service

    Apture

    Killed almost 14 years ago, Apture was a service that allowed publishers and bloggers to link and incorporate multimedia into a dynamic layer above their pages. It was over 4 years old.

  • Tombstone

    -

    service

    Google Buzz

    Killed about 14 years ago, Google Buzz was a social networking, microblogging and messaging tool that integrated with Gmail. It was almost 2 years old.

  • Tombstone

    -

    service

    Gears

    Killed about 14 years ago, Gears (aka Google Gears) was utility software that aimed to create more powerful web apps by adding offline storage and other additional features to web browsers. It was over 4 years old.

  • Tombstone

    -

    service

    Google Notebook

    Killed about 14 years ago, Google Notebook allowed users to save and organize clips of information while conducting research online. It was over 3 years old.

  • Tombstone

    -

    service

    ZygoteBody

    Killed about 14 years ago, ZygoteBody, formerly Google Body, was a web application by Zygote Media Group that rendered manipulable 3D anatomical models of the human body. It was 10 months old.

  • Tombstone

    -

    service

    Google PowerMeter

    Killed about 14 years ago, Google PowerMeter was a software project of Google's philanthropic arm that helped consumers track their home electricity usage. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Squared

    Killed over 14 years ago, Google Squared was an information extraction and relationship extraction product that compiled structured data into a spreadsheet-like format. It was over 2 years old.

  • Tombstone

    -

    service

    Google Sidewiki

    Killed over 14 years ago, Google Sidewiki was a browser sidebar tool that allowed users to contribute information to any web page. It was almost 2 years old.

  • Tombstone

    -

    service

    Aardvark

    Killed over 14 years ago, Aardvark was a social search service that connected users live with friends or friends-of-friends who were able to answer their questions. It was over 2 years old.

  • Tombstone

    -

    service

    Google Pack

    Killed over 14 years ago, Google Pack was a collection of software tools offered by Google to download in a single archive. It was announced at the 2006 Consumer Electronics Show, on January 6. Google Pack was only available for Windows XP, Windows Vista, and Windows 7. It was over 5 years old.

  • Tombstone

    -

    service

    Google Desktop

    Killed over 14 years ago, Google Desktop allowed local searches of a user's emails, computer files, music, photos, chats and Web pages viewed. It was over 3 years old.

  • Tombstone

    -

    service

    Google Fast Flip

    Killed over 14 years ago, Google Fast Flip was an online news aggregator, something of a high tech microfiche. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Dictionary

    Killed over 14 years ago, Google Dictionary was a standalone online dictionary service. It was over 1 year old.

  • Tombstone

    -

    service

    Google Labs

    Killed over 14 years ago, Google Labs was a technology playground used by Google to demonstrate and test new projects. It was about 9 years old.

  • Tombstone

    -

    service

    Google Rebang

    Killed over 14 years ago, Rebang was a Zeitgeist-like service centered on providing service to a Chinese audience. It was incorporated into Google Labs as of late 2010, and later discontinued along with its parent project. It was over 4 years old.

  • Tombstone

    -

    service

    Google Directory

    Killed over 14 years ago, Google Directory was an Internet website directory organized into 14 main categories that allowed users to explore the web. It was over 11 years old.

  • Tombstone

    -

    service

    Google Image Swirl

    Killed over 14 years ago, Google Image Swirl was an enhancement to the image search tool that came out of Google Labs. It was built on top of image search by grouping images with similar visual and semantic qualities. It was over 1 year old.

  • Tombstone

    -

    service

    Google Real-Time Search

    Killed over 14 years ago, Google Real-Time Search provided live search results from Twitter, Facebook, and news websites. It was over 1 year old.

  • Tombstone

    -

    service

    Google Script Converter

    Killed over 14 years ago, Google Script Converter was an online transliteration tool for transliteration (script conversion) between Hindi, Romanagari, and various other scripts. It's ended because Google shut down Google Labs and all associated projects. It was over 1 year old.

  • Tombstone

    -

    service

    Google Sets

    Killed over 14 years ago, Google Sets generates a list of items when users enter a few examples. For example, entering "Green, Purple, Red" emits the list "Green, Purple, Red, Blue, Black, White, Yellow, Orange, Brown". It was about 9 years old.

  • Tombstone

    -

    service

    Google Specialized Search

    Killed over 14 years ago, Google Specialized Search allowed users to search across a limited index of the web for specialized topics like Linux, Microsoft, and 'Uncle Sam.' It was over 13 years old.

  • Tombstone

    -

    service

    Google Hotpot

    Killed over 14 years ago, Google Hotpot was a local recommendation engine that allowed people to rate restaurants, hotels, etc. and share them with friends. It was 5 months old.

  • Tombstone

    -

    service

    Gizmo5

    Killed over 14 years ago, Gizmo5 was a VOIP communications network and a proprietary freeware soft phone for that network. It was over 1 year old.

  • Tombstone

    -

    service

    Real Estate On Google Maps

    Killed almost 15 years ago, Real Estate on Google Maps enabled users to find places for sale or rent in an area they were interested in. It was over 1 year old.

  • Tombstone

    -

    service

    fflick

    Killed almost 15 years ago, fflick was a review, information, and news website that used information from aggregated Tweets to rate movies as positive or negative. It was 6 months old.

  • Tombstone

    -

    service

    Google Base

    Killed almost 15 years ago, Google Base was a database provided by Google into which any user can add almost any type of content, such as text, images, and structured information. It was about 5 years old.

  • Tombstone

    -

    service

    GOOG-411

    Killed about 15 years ago, GOOG-411 (or Google Voice Local Search) was a telephone service that provided a speech-recognition-based business directory search. It was over 3 years old.

  • Tombstone

    -

    service

    BumpTop

    Killed over 15 years ago, BumpTop was a skeuomorphic desktop environment app that simulates the normal behavior and physical properties of a real-world desk and enhances it with automatic tools to organize its contents. It was about 2 years old.

  • Tombstone

    -

    service

    Google SearchWiki

    Killed almost 16 years ago, SearchWiki was a Google Search feature which allowed logged-in users to annotate and re-order search results. It was over 1 year old.

  • Tombstone

    -

    service

    YouTube Streams

    Killed almost 16 years ago, YouTube Streams allowed users to watch a YouTube video together while chatting about the video in real-time. It was about 3 years old.

  • Tombstone

    -

    service

    Marratech e-meetings

    Killed almost 16 years ago, Marratech was a Swedish company that made software for e-meetings (e.g., web conferencing, videoconferencing). It was about 11 years old.

  • Tombstone

    -

    service

    Google Web APIs

    Killed about 16 years ago, The Google Web APIs were a free SOAP service for doing Google searches so that developers could use the results in almost any way they wanted. It was over 7 years old.

  • Tombstone

    -

    service

    Google Ride Finder

    Killed about 16 years ago, Google Ride Finder was a service that used GPS data to pinpoint and map the location of taxis, limos, and shuttle vehicles available for hire in 10 U.S. metro areas. It was over 4 years old.

  • Tombstone

    -

    service

    Google Toolbar for Firefox

    Killed over 16 years ago, Google Toolbar for Firefox It was about 4 years old.

  • Tombstone

    -

    hardware

    Google Radio Automation

    Killed over 16 years ago, Google Radio Automation was a hardware and software service used by radio operators to automate song playing among other radio station functions. It was over 2 years old.

  • Tombstone

    -

    service

    On2 Flix Cloud

    Killed over 16 years ago, Flix Cloud was a high-capacity online video encoding service. It was 9 months old.

  • Tombstone

    -

    service

    Google Mashup Editor

    Killed over 16 years ago, Google Mashup Editor was an online web mashup creation service with publishing, syntax highlighting, and debugging. It was about 2 years old.

  • Tombstone

    -

    service

    Google Shared Stuff

    Killed over 16 years ago, Google Shared Stuff was a web page sharing system that allowed users to bookmark pages and share them. It was over 1 year old.

  • Tombstone

    -

    service

    Grand Central

    Killed almost 17 years ago, Grand Central was a Voice over IP service that was acquired by Google, and turned into Google Voice. It was about 4 years old.

  • Tombstone

    -

    service

    Dodgeball

    Killed almost 17 years ago, Dodgeball was a location-based social network where users texted their location to the service, and it notified them of friends and points of interest nearby. It was over 5 years old.

  • Tombstone

    -

    service

    Google Audio Ads

    Killed almost 17 years ago, Google Audio Ads service allowed advertisers to run campaigns on AM/FM radio stations in the US using the AdWords interface. It was 7 months old.

  • Tombstone

    -

    service

    Google Lively

    Killed almost 17 years ago, Google Lively was a web-based virtual environment that provided a new way to access information. It was 6 months old.

  • Tombstone

    -

    service

    SearchMash

    Killed about 17 years ago, SearchMash was an experimental, non-branded search engine that Google used to be able to play around with new search technologies, concepts, and interfaces. It was about 2 years old.

  • Tombstone

    -

    service

    Google Page Creator

    Killed over 17 years ago, Google Page Creator was a website creation and hosting service that allowed users to build basic websites with no HTML knowledge. It was about 2 years old.

  • Tombstone

    -

    service

    Send to Phone

    Killed over 17 years ago, Google Send to Phone was an add-on to send links and other information from Firefox to their phone by text message. It was almost 2 years old.

  • Tombstone

    -

    service

    Google Browser Sync

    Killed over 17 years ago, Google Browser Sync was a Firefox extension that synced information like passwords and browsing history. It was about 2 years old.

  • Tombstone

    -

    service

    Hello

    Killed over 17 years ago, Hello was a service by Picasa that let users share pictures "like you're sitting side-by-side." It was almost 6 years old.

  • Tombstone

    -

    service

    Google Web Accelerator

    Killed almost 18 years ago, Google Web Accelerator was a client-side software that increased the load speed of web pages. It was over 2 years old.

  • Tombstone

    -

    service

    Zeitgeist

    Killed almost 18 years ago, Google Zeitgeist was a weekly, monthly, and yearly snapshot in time of what people were searching for on Google all over the world. It was almost 7 years old.

  • Tombstone

    -

    service

    Google Click-to-Call

    Killed about 18 years ago, Google Click-to-Call allowed a user to speak directly over the phone to businesses found in search results. It was almost 4 years old.

  • Tombstone

    -

    service

    Google Video Player

    Killed over 18 years ago, The Google Video Player plays back files in Google's own Google Video File (.gvi) media format and supported playlists in 'Google Video Pointer' (.gvp) format. It was 12 months old.

  • Tombstone

    -

    service

    Google Video Marketplace

    Killed over 18 years ago, Google Video Marketplace was a service that included a store where videos could be bought and rented. It was over 1 year old.

  • Tombstone

    -

    service

    Google Answers

    Killed about 19 years ago, Google Answers was an online knowledge market. It was over 4 years old.

  • Tombstone

    -

    service

    Writely

    Killed about 19 years ago, Writely was a Web-based word processor. It was about 1 year old.

  • Tombstone

    -

    service

    Google Public Service Search

    Killed over 19 years ago, Google Public Service Search provided governmental, non-profit and academic organizational search results without ads. It was over 4 years old.

  • Tombstone

    -

    service

    Google Deskbar

    Killed over 19 years ago, Google Deskbar was a small inset window on the Windows toolbar and allowed users to perform searches without leaving the desktop. It was over 2 years old.

  • Economics of Orbital vs. Terrestrial Data Centers

    Hacker News
    andrewmccalip.com
    2025-12-15 21:56:03
    Comments...
    Original Article

    Before we get nerd sniped by the shiny engineering details, ask the only question that matters. Why compute in orbit? Why should a watt or a flop 250 miles up be more valuable than one on the surface? What advantage justifies moving something as mundane as matrix multiplication into LEO?

    That "why" is almost missing from the public conversation. People jump straight to hardware and hand-wave the business case, as if the economics are self-evident. They aren't. A lot of the energy here is FOMO and aesthetic futurism, not a grounded value proposition.

    Note: This page is built from publicly available information and first-principles modeling. No proprietary data. These are my personal thoughts and do not represent the views of any company or organization.

    Cost per Watt $31.20/W

    LCOE $891/MWh

    Mass to LEO 22.2M kg

    Cost per Watt $14.80/W

    LCOE $398/MWh

    Capex $13.80/W

    Engineering · System Parameters

    Orbital Solar

    $20 (floor) Starship Falcon 9

    $5/W V2 Mini ($22) V1 ($32)

    LEO (~60%) SSO (~80%) Terminator (~98%)

    1% (shielded) 6% (unshielded) 12% (polar)

    Terrestrial (On-Site CCGT)

    $10 (Low) $12.50 (Rep) $17 (High)

    Electrical 45% $5.63/W

    Mechanical 20% $2.50/W

    Shell & Core 17% $2.13/W

    Fit-Out 8% $1.00/W

    Site/Civil 5% $0.62/W

    Gen. Cond./Fees 5% $0.62/W

    $1.45 (Efficient) $1.80 (Typical) $2.30 (Complex)

    Best (~58%) Average (~45%) Older (~38%)

    Permian Typical Constrained

    Best (1.1) Typical (1.3) Older (1.5)

    Orbital Solar

    Satellite Count ~37,000

    GPU Margin (failures) +19.6%

    Solar Margin (degr.) +6.5%

    Total Mass to LEO 22.2M kg

    Fleet Array Area 2.3 km²

    Single Sat Array 116 m²

    Starship Launches ~222

    Methane Required 168M gal

    Energy Output 35.0 MWhr

    Terrestrial

    H-Class Turbines 3 units

    Generation (IT×PUE) 1.2 GW

    Heat Rate 6,200 BTU/kWh

    Fuel Cost $27/MWh

    Capacity Factor 85%

    Gas Consumption 279 BCF

    Energy Output 37.2 MWhr

    • GPUs not included—this models everything upstream of compute hardware
    • Target capacity: 1 GW nameplate electrical
    • Analysis period: 5 years
    • All figures in 2025 USD; excludes financing, taxes, incentives, and FMV
    • Full availability assumed (no downtime derates), no insurance/logistics overheads
    • Single bus class (Starlink V2 Mini heritage) scaled linearly to target power
    • Station-keeping propellant mass assumed rolled into Starlink-like specific power (W/kg)
    • Linear solar cell degradation assumed; actual silicon with coverglass shows steep-then-shallow curve
    • Solar margin = extra initial capacity to maintain average power over lifetime (not end-of-life)
    • GPU margin = cumulative expected failures over analysis period (replacement cost, not extra capacity)
    • Optimal fairing packing assumed regardless of satellite size (kW); no packing penalty modeled
    • No additional mass for liquid cooling loop infrastructure; likely needed but not included
    • All mass delivered to LEO; no on-orbit servicing/logistics
    • Launch pricing applied to total delivered mass; no cadence/manifest constraints modeled
    • Thermal: only solar array area used as radiator; no dedicated radiator mass assumed
    • Radiation/shielding impacts on mass ignored; no degradation of structures beyond panel aging
    • No disposal, de-orbit, or regulatory compliance costs included
    • Ops overhead and NRE treated as flat cost adders; no learning-curve discounts
    • No adjustments for permitting or regulatory delay
    • On-site H-Class CCGT at the fence line; grid interconnect/transmission not costed
    • Capex buckets embed site prep/land; permitting, taxes, and financing excluded
    • Fuel price held flat; no carbon price, hedging, or escalation modeled
    • Water/cooling availability assumed; no scarcity or discharge penalties
    • Fixed PUE and capacity factor; no forced-outage or maintenance derates applied
    • No efficiency gains or technology learning assumed over time for terrestrial plant
    • No adjustments for permitting or regulatory delay

    Motivation and Framing

    I love space. I live and breathe it. I'm lucky enough to brush the heavens with my own metal and code, and I want nothing more than a booming orbital space economy that creates the flywheel that makes space just another location we all work and visit. I love AI and I subscribe to maximum, unbounded scale. I want to make the biggest bets. I grew up half-afraid we'd never get another Apollo or Manhattan. I truly want the BigThing.

    This is all to say that the current discourse is increasingly bothering me due to the lack of rigor; people are using back-of-the-envelope math, doing a terrible job of it, and only confirming whatever conclusion they already want. Calculating radiation and the cost of goods is not difficult. Run the numbers.

    Before we do the classic engineer thing and get nerd sniped by all the shiny technical problems, it's worth asking the only question that matters: why put compute in orbit at all? Why should a watt or a flop be more valuable 250 miles up than on the surface? What economic or strategic advantage justifies the effort required to run something as ordinary as matrix multiplication in low Earth orbit?

    That "why" is nearly missing from the public conversation. The "energy is cheaper, less regulations, infinite space" arguments just ring false compared to the mountains of challenges and brutal physics putting anything in space layers on. The discourse then skips straight to implementation, as if the business case is obvious.

    Personal Positioning

    I'm not here to dunk on anyone building real hardware. Space is hard, and shipping flight systems is a credibility filter. I'm annoyed at everyone else. The conversation is full of confident claims built on one cherry-picked fact and zero arithmetic. This is a multivariable physics problem with closed-form constraints. If you're not doing the math, you're not contributing, you're adding noise and hyping for a future we all want instead of doing the hard work to actually drive reality forward.

    Core Thesis

    The target I care about is simple: can you make space-based, commodity compute cost-competitive with the cheapest terrestrial alternative? That's the whole claim. Not "space is big." Not "the sun is huge." Not "launch will be cheap." Can you deliver useful watts and reject the waste heat at a price that beats a boring Crusoe-style tilt-wall datacenter tied into a 200–500 MW substation?

    If you can't beat that, the rest is just vibes. GPUs are pretty darn happy living on the ground. They like cheap electrons, mature supply chains, and technicians who can swap a dead server in five minutes. Orbit doesn't get points for being cool. Orbit has to win on cost, or it has to admit it's doing something else entirely. If it's an existential humanity play, that's cool too, but it's a slightly different game.

    Analytical Lens

    So here's what I did. I built a simple model that reduces the debate to one parameter: cost per watt of usable power for compute. The infographic below lets you change the assumptions directly. If you disagree with the inputs, great. Move the sliders. But at least we'll be arguing over numbers that map to reality.

    The model is deliberately boring. No secret sauce. Just publicly available numbers and first-principles physics: solar flux, cell efficiency, radiator performance, launch cost, hardware mass, and a terrestrial benchmark that represents the real alternative: a tilt-wall datacenter sitting on top of cheap power. The code is public, please go through everything. github.com/andrewmccalip/thoughts

    Findings and Implications

    Here's the headline result: it's not obviously stupid, and it's not a sure thing. It's actually more reasonable than my intuition thought! If you run the numbers honestly, the physics doesn't immediately kill it, but the economics are savage. It only gets within striking distance under aggressive assumptions, and the list of organizations positioned to even try that is basically one.

    That "basically one" point matters. This isn't about talent. It's about integration. If you have to buy launch, buy buses, buy power hardware, buy deployment, and pay margin at every interface, you never get there. The margin stack and the mass tax eat you alive. Vertical integration isn't a nice-to-have. It's the whole ballgame.

    Market and Incentives

    Which is why I trend positive on SpaceX here. If anyone can brute force a new industrial stack into existence, it's the team that can reduce $/kg and get as humanly close to free launch as possible. And they need to, because the economics are not close. This is not a 25% mismatch. It's 400%. Closing that is the whole job. Positive does not mean gullible. It needs measurable targets and painful reality checks.

    If SpaceX ever goes public, this is exactly the kind of thing shareholders should demand: extreme, barely-achievable goalposts with clean measurement. Tesla did it with the options grant. Do the same here. Pay Elon a king's ransom if he delivers a new industrial primitive: cheap, sustained dollars per kilogram and dollars per watt in orbit, at real cadence, for years.

    Broader Interpretation

    On strict near-term unit economics, this might still be a mediocre use of capital. A tilt-wall datacenter in Oregon with cheap power, cheap cooling, and technicians on call is hard to beat. Crusoe can park compute on stranded natural gas and turn it into flops with a supply chain that already exists.

    But the knock-on effects are why this keeps pulling at people. If you can industrialize power and operations in orbit at meaningful scale, you're not just running GPUs. You're building a new kind of infrastructure that makes it easier for humans to keep spreading out. Compute is just one of the first excuses to pay for the scaffolding. Even if this is a mediocre trade on strict near-term unit economics, the second-order effects could be enormous.

    I'll go one step further and say the quiet part out loud: we should be actively goading more billionaires into spending on irrational, high-variance projects that might actually advance civilization. I feel genuine secondhand embarrassment watching people torch their fortunes on yachts and status cosplay. No one cares about your Loro Piana. If you've built an empire, the best possible use of it is to burn its capital like a torch and light up a corner of the future. Fund the ugly middle. Pay for the iteration loops. Build the cathedrals. This is how we advance civilization.

    Links to Reports

    Everyone is going to copy-paste this into the models, so I've done that part for you. It's a decent way to automate the sanity checks, but it could use more in-depth review.

    GitHub: github.com/andrewmccalip/thoughts

    "Conduct a thorough, first-principles-based review of this project. Scrutinize every assumption and constant, rigorously fact-checking all data. The objective is to identify and correct any fundamental errors in logic or calculation."

    Grok: grok.com/share/...
    ChatGPT: chatgpt.com/share/...
    Gemini: gemini.google.com/share/...
    Claude: claude.ai/public/artifacts/...

    Overall Conclusion

    Even so, irrational ambition doesn't get to ignore physics. The point of this page is to make the constraints explicit, so we can argue about reality instead of vibes. If the numbers close, even barely, then it's worth running hard on the idea. If they don't, the honest move is to say so and move on. Either way, I think some version of this has a feeling of inevitability.

    So scroll down, play with the sliders, and try to break it. Change launch cost. Change lifetime. Change specific power. Change hardware cost. The goal here isn't to "win" an argument. It's to drag the conversation back to first principles: assumptions you can point at, and outputs you can sanity-check. Check out the GitHub, run the code, find the errors, and I'll update it live.

    After that, we can do the fun part: thermal diagrams, radiator math, orbit beta angles, failure rates, comms geometry, all the shiny engineering details that make this topic so addicting. It's not obviously stupid, and it's not a sure thing. That's why it's worth doing the math.

    It might not be rational. But it might be physically possible.

    Technical Engineering Challenges

    The governing constraint for orbital compute is thermodynamics . Terrestrial datacenters leverage convective cooling—dumping waste heat into the atmosphere or water sources, effectively using the planet as an infinite cold reservoir. In the vacuum of space, convection is impossible. Heat rejection relies exclusively on radiation.

    Every object in space settles to an equilibrium temperature where absorbed power equals radiated power. If heat generation exceeds radiative capacity, the temperature rises until the $T^4$ term in the Stefan-Boltzmann law balances the equation:

    $$\dot{Q}_{\text{rad}} = \varepsilon \sigma A T^4$$

    The engineering challenge is ensuring this equilibrium temperature remains below the safe operating limits of silicon processors.

    Energy Balance and Heat Rejection

    To dimension the radiator surface, we must account for the total thermal load managed by the satellite bus. In this model, based on a Starlink-style bifacial architecture (PV on front, radiator on back), the system must reject the aggregate energy of two distinct paths:

    1. Incident Solar Flux: The sun delivers $G_{\text{sc}} = 1361\;\text{W/m}^2$ (AM0). With a solar absorptivity $\alpha = 0.92$, the panel absorbs approximately $\sim 1250\;\text{W/m}^2$.
    2. Energy Partitioning:
      • Electrical Path ($\sim$22%): High-efficiency cells convert $\sim 275\;\text{W/m}^2$ into electricity. This power drives the compute payload and is converted entirely back into heat by the processors. A liquid cooling loop collects this heat and returns it to the panel structure for rejection.
      • Thermal Absorption ($\sim$78%): The remaining $\sim 975\;\text{W/m}^2$ is not converted to electricity but is absorbed immediately as lattice heat (phonon generation) within the panel structure.
    3. Total Heat Load: The radiator must reject the sum of both the immediate thermal absorption and the returned electrical waste heat—effectively 100% of the absorbed solar flux .

    This imposes a strict area density limit. High-power compute requires large collection areas, which inherently absorb large amounts of solar heat. The radiator must be sized to reject this aggregate load while maintaining an operating temperature below the junction limit.

    Operating Temperature Limits

    Modern AI accelerators (H100/B200 class) typically throttle at junction temperatures $T_j > 85\text{–}100\degree\text{C}$. To maintain a junction at 85°C, and accounting for the thermal gradient across cold plates and interface materials ($\Delta T \approx 10\degree\text{C}$), the radiator surface temperature $T_{\text{rad}}$ is constrained to approximately 75°C.

    The model below calculates the equilibrium temperature for a bifacial array in a terminator orbit ($\beta = 90^\circ$). It accounts for solar flux, Earth IR ($\sim 237\;\text{W/m}^2$), and albedo. If the calculated equilibrium temperature $T_{\text{eq}}$ exceeds the target radiator temperature, the design fails.

    Thermal Balance · Bifacial Panel Model

    Steady-State Energy Balance $\dot{Q}_{\text{sol}} + \dot{Q}_{\text{IR}} + \dot{Q}_{\text{alb}} + \dot{Q}_{\text{loop}} = \dot{Q}_{\text{rad,A}} + \dot{Q}_{\text{rad,B}}$

    Side A PV Array α = 0.92 ε A = 0.85

    Side B Radiator ε B = 0.90

    0°C T eq

    Thermal Analysis · Bifacial Panel Parameters

    Surface Properties

    400 km 550 (Starlink) 1200 km

    Thermal Outputs

    A (panel area) 0.00 km²

    β (orbit angle) 90°

    VF 0.080

    ε tot 1.75

    sol (solar waste) 0 MW

    IR (Earth thermal) 0 MW

    alb (reflected) 0 MW

    loop (GPU return) 0 MW

    Σ in 0 MW

    P elec (generated) 0 MW

    T eq 0.0 °C

    Δ T margin 0 °C FAIL

    A req 0.00 km²

    References

    Ford kills the All-Electric F-150

    Hacker News
    www.wired.com
    2025-12-15 21:46:53
    Comments...
    Original Article

    Ford is once again shifting its electric vehicle manufacturing plans, a response to a year that’s been tough for the powertrain technology that’s still making waves overseas but has seen domestic government support cut and customer enthusiasm weaken .

    Instead of planning to make enough electric vehicles to account for 40 percent of global sales by 2030—as it pledged just four years ago— Ford says it will focus on a broader range of hybrids, extended-range electrics, and battery-electric models, which executives now say will account for 50 percent of sales by the end of the decade. The automaker will make hybrid versions of almost every vehicle in its lineup, the company says.

    The company will no longer make a large all-electric truck , Ford executives told reporters Monday, and will repurpose an electric vehicle plant in Tennessee to build gas-powered cars. The next generation of Ford’s all-electric F-150 Lighting will instead be an extended-range electric vehicle, or EREV, a plug-in hybrid that uses an electric motor to power its wheels while a smaller gasoline engine recharges the battery. Ford says the tech, which automakers have touted in recent years as a middle-ground between battery-electric vehicles and gas-powered ones , will give its truck extended towing capacity and a range of over 700 miles.

    Ford still plans to produce a midsize electric pickup truck with a target starting price of about $30,000, to be available in 2027. That will be the first of the “affordable” electric vehicle models it’s currently designing at a skunkworks studio in California, which are slated to use a “universal” platform architecture that will make the vehicles cheaper to produce .

    The new plans leave Ford with a bunch of excess battery-making capacity, which the company says it will use by opening a whole new business: a battery energy-storage sideline. This new business will produce lower-cost and longer-living lithium iron phosphate, or LFP, batteries for customers in the public utility or data center industries.

    “Ford is following the customer,” says Andrew Frick, the president of Ford Blue and Ford Model e, the automaker’s gas- and battery-powered vehicle businesses. US customer adoption of electric vehicles is not where the industry expected at decade’s start, he says. (Battery-electric vehicles currently make up about 7.5 percent of US new car sales .) Frick also cited changes in the regulatory environment, including the Trump administration's rollback of commercial and consumer tax incentives for electric vehicles.

    The company has also canceled an all-electric commercial van planned for the European market. Instead, Ford will team up with Renault, in a partnership announced last week , to develop at least two small Ford-branded electric vehicles for Europe—a move that CEO Jim Farley called part of a “fight for our lives,” as US automakers try to compete with affordable EVs out of China.

    Ford said Monday that it also plans to produce a new gas-powered commercial van for North America.

    An expression language for Vixen

    Lobsters
    raku-advent.blog
    2025-12-15 21:40:57
    Comments...
    Original Article

    #raku-beginners: korvo: Hi! I’m trying out Raku in stead of META II for a toy compiler.

    (5 minutes later) korvo: I’m merely trying to compile a little expression language because my angle of repose has slightly increased, and multiple folks have recommended Raku for parsing and lightweight compilers.

    (20 minutes later) korvo: [T]hanks for a worthy successor to META II. This is the first enjoyable parser toolkit I’ve used in a while; I spent almost no time fussing over the Regex tools, found it easy to refactor productions, and am spending most of my time trying to handle strings and lists and I/O.

    Happy Holiday! As we enter the winter-solstice season, it’s worth reflecting on the way that hunkering down for the cold can bring about new avenues of exploration. I have a jar of pickled homegrown banana peppers in the corner of my refrigerator slowly evolving into something delicious; I do have to shake it every few days, but it will be worth it to add that fruity sour spice to any dish. Similarly, this can be a season for letting old concepts ferment into new concepts.

    I have written so many parsers and parser toolkits. I’m tired of writing parsers yet again. So, after hearing about Raku’s builtin support for grammars, I decided that I would commit to trying Raku for my next parsing task. How hard could it be? I’ve already used and forgotten Perl 5 and Ruby.

    I don’t need to reinvent Raku. ~ me, very tired

    Problem

    I’m working on a language that I call Vixen . I should back up.

    At the beginning of the year, Joel Jakubovic blogged that The Unix Binary wants to be a Smalltalk Method, Not an Object . They argue that, while we have traditionally thought that Unix files correspond to objects, we should instead correspond Unix directories with objects and Unix files with methods. By “object” I mean a bundle of state and behavior which communicates with other objects by passing messages to them. This is a big deal for folks who study what “object” means, but not really interesting for the wider programming world. However, they followed it up with a prototype and a paper, The Unix Executable as a Smalltalk Method: And its implications for Unix-Smalltalk unification . Jakubovic provides a calling convention, which we call Smalltix , that allows us to treat a Unix system as if it were a Smalltalk-like message-passing object-based system. Crucially, there isn’t a single language for programming Smalltix, because of fragmentation : a Unix system already has many different languages for writing executable programs, and adding another language would only fragment the system further.

    Okay! So, I’m working on Vixen, a fork of Smalltix. Jakubovic used Bash and Smalltalk-style classes; I’m simplifying by using execline and Self-style prototypes. Eventually, I’ve got a few dozen little scripts written with execline. Can I simplify further?

    Now, I fully admit that execline is something of an alien language, and I should explain at least some of it before continuing. Execline is based on the idea of Bernstein chain loading ; the interpreter takes all arguments in argv as a program and calls into multiple helpers which incrementally rewrite argv into a final command. Here’s an example method that I call “debug:” which takes a single argument and prints it to stderr . First it uses the fdmove helper to copy file descriptor 2 to file descriptor 1, shadowing stdout with stderr ; finally, it echoes a string that interpolates the first and second items of argv . The calling convention in Smalltix and Vixen is that argv ’s zeroth item is the method, the first item is the receiving object passed as a path to a directory, and the rest of the items are positional arguments. By tradition, there is one colon “:” in the name of a method per argument, so “debug:” takes one argument; also by tradition, the names of methods are called verbs . Since this method takes one positional argument, we pass the -s2 flag to the execline interpreter execlineb to collect argv up to index 2.

    #!/usr/bin/env -S execlineb -s2
    fdmove -c 1 2
    echo "debug: ${1}: ${2}"

    For something more complicated, here is a method named “allocateNamed:” which augments some other “allocate” method with the ability to control the name of a directory. This lets us attach names to otherwise-anonymous objects. Here, we import the name “V” from the environment envp to turn it into a usable variable. In Vixen, I’ve reserved the name “V” to refer to a utility object that can perform calls and other essential tasks. The backtick helper wraps a subprogram in a curly-brace-delimited block and captures its output. The foreground helper runs two subprograms in sequence; there’s also an if helper which exits early if the first subprogram fails.

    #!/usr/bin/env -S execlineb -s2
    importas -iS V
    backtick -E path { ${V}/call: $1 allocate }
    foreground { mkdir ${path}/${2} }
    echo ${path}/${2}

    Now, as the reader may know, object-based languages are all about messages, object references, and passing messages to object references. In some methods, like this one called “hasParent”, we are solely passing messages to objects; the method is merely a structure which composes some other objects. This is starting to be a lot of code; surely there’s a better way to express this composite?

    #!/usr/bin/env -S execlineb -s1
    importas -iS V
    backtick -E parent { ${V}/call: $1 at: "parent*" }
    ${V}/call: $parent exists

    Syntax

    Let’s fragment the system a little bit by introducing an expression language just for this sort of composition. Our justification is that we aren’t going to actually replace execline; we’re just going to make it easier to write. We’ll scavenge some grammar from a few different flavors of Smalltalk. The idea is that our program above could be represented by something like:

    [|^(self at: "parent*") exists]

    For non-Smalltalkers, this is a block , a fundamental unit of code. The square brackets delimit the entire block. The portion to the right of the pipe “|” is a list of expressions; here there is only one. When the final expression starts with a caret “^”, it will become the answer or return value; there’s a designated Nil object that is answered by default. Expressions are merely messages passed to objects, with the object on the left and the message on the right. If a message verb ends with a colon “:” then it is called a keyword and labels an argument; for each verb with a colon there is a corresponding argument. The builtin name self refers to the current object.

    The parentheses might seem odd at first! In Smalltalk, applications of verbs without arguments, so-called unary applications, bind tighter than keyword applications. If we did not parenthesize the example then we would end up with the inner call "parent*" exists , which is a unary application onto a string literal. We also must parenthesize to distinguish nested keyword applications, as in the following example:

    [:source|
    obj := (self at: "NixStore") intern: source.
    ^self at: obj name put: obj]

    Here we can see the assignment token “:=” for creating local names. The full stop “.” occurs between expressions; it creates statements, which can either assign to a name or not. We can also see a parameter to this block, “:source”, which occurs on the left side of the pipe “|” and indicates that one argument can be passed along with any message.

    Grammar

    Okay, that’s enough of an introduction to Vixen’s expression language. How do we parse it? That’s where Raku comes in! (As Arlo Guthrie might point out, this is a blog post about Raku.) Our grammar features everything I’ve shown so far, as well as a few extra features like method cascading with the semicolon “;” for which I don’t have good example usage.

    grammar Vixen {
        token id       { <[A..Za..z*]>+ <![:]> }
        token selector { <[A..Za..z*]>+ \: }
        token string   { \" <-["]>* \" }
        token param    { \: <id> }
        token ass      { \:\= }

        rule params { <param>* % <ws> }

        proto rule lit             {*}
              rule lit:sym<block>  { '[' <params> '|' <exprs> ']' }
              rule lit:sym<paren>  { '(' <call> ')' }
              rule lit:sym<string> { <string> }
              rule lit:sym<name>   { <id> }

        rule chain { <id>* % <ws> }

        proto rule unary {*}
              rule unary:sym<chain> { <lit> <chain> }

        rule keyword { <selector> <unary> }

        proto rule message {*}
              rule message:sym<key>  { <keyword>+ }

        rule messages { <message>* % ';' }

        proto rule call {*}
              rule call:sym<cascade> { <unary> <messages> }

        proto rule assign {*}
              rule assign:sym<name> { <id> <ass> <call> }
              rule assign:sym<call> { <call> }

        rule statement { <assign> '.' }

        proto rule exprs {*}
              rule exprs:sym<rv>  { <statement>* '^' <call> }
              rule exprs:sym<nil> { <statement>* <call> }

        rule TOP { '[' <params> '|' <exprs> ']' }
    }

    Writing the grammar is mostly a matter of repeatedly giving it example strings. The one tool that I find indespensible is some sort of debugging tracer which indicates where a parse rule has failed. I used Grammar::Tracer , available via zef . I’m on NixOS, so language-specific package managers don’t always play nice, but zef works and is recommended. First I ran:

    $ zef install Grammar::Tracer
    

    And then I could start my file with a single import in order to get tracing:

    Actions

    The semantic actions transform the concrete syntax tree to abstract syntax . This sort of step is not present in classic META II but is essential for maintaining sanity. I’m going to use this grammar for more than a few weeks, so I wrote a few classes for representing abstract syntax and a class of actions. Some actions are purely about extraction; for example, the method for the params production merely extracts a list of matches and extracts the Str for each match.

        method params($/) { make $.values.map: *.Str; }

    Some actions contain optimizations that avoid building abstract syntax. The following method for unary handles chained messages, where we have multiple unary applications in a row; we want a special case for zero applications so that the VUnary class can assume that it always has at least one application.

        method unary:sym<chain>($/) {
            my $receiver = $.made;
            my @verb = $.made;
            make @verb ?? VUnary.new(:$receiver, :@verb!! $receiver;
        }

    Some actions build fresh abstract syntax not in the original program. The following method for exprs handles the case when there is no return caret; the final expression is upgraded to a statement which ignores its return value and the name Nil is constructed as the actual return value.

        method exprs:sym<nil>($/) {
            my @statements = $.values.map: *.made;
            my $call = $.made;
            @statements.push: VIgnore.new(:$call);
            my $rv = VName.new(:n("Nil"));
            make VExprs.new(:@statements, :$rv);
        }

    Getting the actions right was difficult. I ended up asking for hints on IRC about how to work with matches. The .values method is very useful.

    Abstract syntax

    I had a couple false starts with the abstract syntax. I think that the right mentality is to have one node per production, but to have one role per compiler action. If necessary, change the grammar to make the abstract syntax easier to generate; Raku is flexible enough to allow grammars to be refactored. Rules like params , chain , and keyword were broken out to make life easier.

    By the way, starting at this point, I am only showing excerpts from the complete compiler. The compiler is available in a separate gist . Classes may be incomplete; only relevant methods and attributes are shown.

    For example, there is a role for emitting literals. A parenthesized call just unwraps the parentheses; a string is represented by itself.

    role EmitLiteral {
        method prepareLiteral($compiler) { ... }
    }
    class VParen does EmitLiteral {
        has Call $.call;
        method prepareLiteral($compiler) { $.call.prepareLiteral: $compiler; }
    }
    class VStr does EmitLiteral {
        has Str $.s;
        method prepareLiteral($compiler) { $.s; }
    }

    We can also have a role for performing a call. We need two flavors of call: call and bind to a name, and also call without binding. It’s much easier to compile chains and cascades with the option to bind or not bind. We can put both roles onto a single class, so that a cascading application both performs a call and also evaluates to a literal expression.

    role Call {
        method prepareBind($name, $compiler) { ... }
        method prepareOnly($compiler) { ... }
    }
    class VCall does Call does EmitLiteral {
        has EmitLiteral $.unary;
        has VMessage @.cascades;
            method prepareBind($name, $compiler) {
            my $receiver = $.unary.prepareLiteral: $compiler;
            my $last = @.cascades[*-1];
            for @.cascades[0 ...^ @.cascades.elems - 1] {
                my ($verb, @row= $_.prepareMessage: $compiler;
                $compiler.callOnly: $receiver, $verb, @row;
            };
            my ($verb, @row= $last.prepareMessage: $compiler;
            $compiler.callBind: $name, $receiver, $verb, @row;
        }
        method prepareOnly($compiler) {
            my $receiver = $.unary.prepareLiteral: $compiler;
            for @.cascades {
                my ($verb, @row= $_.prepareMessage: $compiler;
                $compiler.callOnly: $receiver, $verb, @row;
            };
        }
        method prepareLiteral($compiler) {
            my $name = $compiler.gensym;
            self.prepareBind: $name, $compiler;
            "\$" ~ $name;
        }
    }

    A first compiler

    We’ll start by compiling just one block. Our compiler will include a gensym : a method which can generate a symbol that hasn’t been used before. I’m not trying very hard here and it would be easy for a malicious user to access generated symbols; we can fix that later. The compiler is mostly going to store calls; each call can either be a backtick or an if (or foreground ) depending on whether it binds a name.

    class Compiler {
        has Int $!syms;
        method gensym { $!syms += 1; "gs" ~ $!syms; }

        has Str %.lines;
        method push($line) { %.lines

     ~= $line ~ "\n"; }

        method callBind($name, $receiver, $verb, @args) {
            self.push: "backtick -E $name \{ " ~ formatCall($receiver, $verb, @args~ " \}";
        }
        method callOnly($receiver, $verb, @args) {
            self.push: "if \{ " ~ formatCall($receiver, $verb, @args~ " \}";
        }

        method assignName($from, $to) { self.push: "define $to \$$from"; }
    }

    The method .assignName is needed to handle assignments without intermediate calls, as in this := that.

    class VName does Call does EmitLiteral {
        has Str $.n;
        method prepareBind($name, $compiler) { $compiler.assignName: $.n, $name; }
        method prepareOnly($compiler) {;}
        method prepareLiteral($compiler) { "\$" ~ $.n; }
    }

    Calling into Vixen

    To compile multiple blocks, we will need to emit multiple blocks. A reasonable approach might be to emit a JSON Object where each key is a block name and each value is a String containing the compiled block. I’m feeling more adventurous than that, though. Here’s a complete Smalltix/Vixen FFI :

    sub callVixen($receiver, $verb, *@args) {
        my $proc = run %*ENV ~ "/call:", $receiver, $verb, |@args, :out;
        my $answer = $proc.out.slurp: :close;
        $proc.sink;
        $answer.trim;
    }

    Vixen is merely a calling convention for processes; we can send a message to an object by doing some string formatting and running a subprocess. The response to a message, called an answer , is given by stdout . Non-zero return codes indicate failure and stderr will contain useful information for the user. The rest of the calling convention is handled by passing envp and calling the V/call: entrypoint.

    In addition to passing V in the environment, we will assume that there are Allocator and NixStore objects. Allocator allocates new objects and NixStore interacts with the Nix package manager; we will allocate a new object and store it in the Nix store. The relevant methods are V/clone: anAllocator , which allocates a shallow copy of V and serves as a blank object template, and NixStore/intern: anObject , which recursively copies an object from a temporary directory into the Nix store.

    The reader doesn’t need to know much about Nix. The only relevant part is that the Nix store is a system-wide immutable directory that might not be enumerable; it’s a place to store packages, but it’s hard to alter packages or find a package that the user hasn’t been told about.

    Name analysis

    We will need to know whether a name is used by a nested block. When we create an object representing that block, we will provide that object with each name that it uses. This is called name-use analysis or just use analysis and it is a type of name analysis . The two effects worth noting are when an expression uses a name and when a statement assigns to a name. We track the used names with a Set[Str] . For example, a keyword message uses a name if any of its arguments use a name:

    class VMessage {
        has VKeyword @.keywords;
        method uses(--> Set[Str]) { [(|)@.keywords.map({ $_.uses }) }
    }

    A sequence of expressions has its usage computed backwards; every time an expression is assigned to a name, we let that assignment shadow any further uses by removing it from the set of used names. This can be written with reduce but it’s important to preserve readability since this sort of logic can be subtly buggy and often must be revisted during debugging.

    class VExprs {
        has EmitStatement @.statements;
        has EmitLiteral $.rv;
        method uses(--> Set[Str]) {
            my $names = $.rv.uses;
            for @.statements.reverse {
                $names = ($names (-) $_.writes) (|) $_.call.uses;
            };
            $names;
        }
    }

    The .writes method merely produces the set of assigned names:

    class VAssignName does EmitStatement {
        has Str $.target;
        method writes(--> Set[Str]) { Set[Str].new($.target) }
    }

    A second compiler

    We now are ready to compile nested blocks. The overall workflow is to compute a closure for the inner block whose names are all used names in the block, except for parameters and global names. We rename everything in the closure with fresh symbols to avoid clashes and allow names like “self” to be closed over. We produce two scripts. One script accepts the closure’s values and attaches them to a new object; one script loads the closure and performs the action in the nested block upon the new object. We call into Vixen to allocate the prototype for the block, populate it, and intern it into the Nix store. Everything else is support code.

            my $closureNames = $uses (-) ($params (|) %globals);
            my %closure = $closureNames.keys.map: { $_ => $compiler.gensym ~ "_" ~ $_ };
            my $valueVerb = @.params ?? "value:" x @.params.elems !! "value";
            my $closureVerb = %closure ?? %closure.keys.map(* ~ ":").join !! "make";
            my $valueBlock = produceValueBlock($compiler, %closure, @.params, $.exprs);
            my $closureBlock = cloneForClosure($compiler, %closure);
            my $obj = callVixen(%*ENV, "clone:", $allocator);
            $compiler.writeBlock: $obj, $valueVerb, $valueBlock;
            $compiler.writeBlock: $obj, $closureVerb, $closureBlock;
            my $interned = callVixen(%*ENV, "intern:", $obj);

    One hunk of support code is in the generation of the scripts with produceValueBlock and cloneForClosure . These are open-coded actions against the $compiler object:

    sub cloneForClosure($compiler, %closure) {
        my $name = $compiler.genblock;
        $compiler.pushBlock: $name, %closure.keys;
        my $obj = $compiler.gensym;
        my $selfName = $compiler.useName: "self";
        $compiler.callBind: $obj, $selfName, "clone:", ($allocator,);
        my $rv = $compiler.useName: $obj;
        for %closure.kv -> $k, $v {
            my $arg = $compiler.useName: $k;
            $compiler.foreground: "redirfd -w 1 $rv/$v echo " ~ $arg;
        }
        $compiler.finishBlock: $rv;
        $name;
    }
    sub produceValueBlock($compiler, %closure, @params, $exprs) {
        my $name = $compiler.genblock;
        $compiler.pushBlock: $name, @params;
        my $selfName = $compiler.useName: "self";
        for %closure.kv -> $k, $v { $compiler.callBind: $k, $selfName, $v, [] };
        my $rv = $exprs.compileExprs: $compiler;
        $compiler.finishBlock: $rv;
        $name;
    }

    The compiler was augmented with methods for managing scopes of names and reassigning names, so that the define helper is no longer used at all. There’s also a method .writeBlock which encapsulates the process of writing out a script to disk.

    class Compiler {
        has Hash[Str@.scopes;
        method assignName($from, $to) { @.scopes[*-1]{ $to } = $from }
        method useName($name) {
            for @.scopes.reverse {
                return "\$\{" ~ $_$name } ~ "\}" if $_$name }:exists;
            };
            die "Name $name not in scope!";
        }
        method writeBlock($obj, $verb, $blockName) {
            spurt "$obj/$verb", %.lines$blockName }.trim-leading;
            chmod 0o755, "$obj/$verb";
        }
    }

    Closing thoughts

    This compiler is less jank than the typical compiler. There’s a few hunks of duplicated code, but otherwise the logic is fairly clean and direct. Raku supports a clean compiler mostly by requiring a grammar and an action class ; I had started out by writing imperative spaghetti actions, and it was up to me to decide to organize further. To optimize, it might be worth virtualizing assignments so that there is only one convention for calls; this requires further bookkeeping to not only track renames but also name usage. Indeed, at that point, the reader is invited to consider what SSA might look like. Another possible optimization is to skip allocating empty closures for blocks which don’t close over anything.

    It was remarkably easy to call into Vixen from Raku. I could imagine using the FFI as scaffolding to incrementally migrate this compiler to a self-hosting expression-language representation of itself. I could also imagine extending the compiler with FFI plugins that decorate or cross-cut compiler actions.

    This blogpost is currently over 400 lines. The full compiler is under 400 lines. We put Raku into a jar with Unix, Smalltalk, and Nix; shook it up, and let it sit for a few days. The result is a humble compiler for a simple expression language with a respectable amount of spice. Thanks to the Raku community for letting me share my thoughts. To all readers, whether you’re Pastafarian or not, whether you prefer red sauce or white sauce or even pesto, I hope that you have a lovely and peaceful Holiday.

    Fix HDMI-CEC weirdness with a Raspberry Pi and a $7 cable

    Hacker News
    johnlian.net
    2025-12-15 21:37:09
    Comments...
    Original Article

    For years I treated HDMI-CEC like a house spirit: sometimes helpful, mostly temperamental, never fully understood. My living-room stack is straightforward: Samsung TV on ARC (NOT eARC - story for another day), Denon AVR-X1700H hidden in a closet, Apple TV plus a bunch of consoles connected to the receiver, and a Raspberry Pi 4 already doing Homebridge duty. When it comes to CEC, the Apple TV handles it like a dream, but every console behaves like it missed the last week of CEC school. They wake the TV, switch the input, then leave the Denon asleep so I’m back to toggling audio outputs manually.

    My media closet where all the consoles are

    I documented the media closet build-out separately, so if you want the full wiring tour (and the before/after photos), start there.

    With the media closet, rewiring everything to the TV wasn’t an option and disabling CEC wasn’t viable (Apple TV works and it gets the most use). My first instinct was to lean on traditional automation stacks: HomeKit scenes to chain “TV on” into “receiver on” or wattage triggers via an Eve Energy plug. This kind of worked, but every extra layer added 30 seconds of lag or more. The last stop on that journey was a homebridge-cec-tv-control plugin , but while reading the README I realized I was about to pipe CEC messages through Node, Homebridge, and HomeKit before they hit the receiver. The Pi is wired into the rack already, so skipping those layers and going through /dev/cec0 directly was clearly the faster path.

    After an evening of struggling, the Pi now sits quietly on the HDMI bus, watching for consoles to announce themselves and issuing the single command Samsung + Denon should have exchanged on their own.

    This post follows the structure of my notes: build a small mental model of CEC, monitor the bus, copy whatever Apple TV does right, wrap it in Python, then ship it as a systemd unit.

    Small HDMI-CEC primer

    High-Definition Multimedia Interface Consumer Electronics Control , much better known as HDMI-CEC , is a low-bandwidth side channel that rides alongside HDMI video/audio. Everyone on the bus speaks in logical addresses ( 0x0 for TV, 0x5 for audio systems, 0x4/0x8/0xB 1 for playback devices, etc.) and tiny opcodes 2 such as 0x82 ( Active Source ) or 0x72 ( Set System Audio Mode ). Physical addresses are “lat/long” references inside the topology, so 3.0.0.0 can mean “AVR input source HDMI 3”.

    CEC is supposed to help consumers control their electronics, so in a healthy system the flow goes like this: console wakes and declares itself active, the TV notices there’s an ARC partner, somebody sends “please be the audio system”, the receiver wakes up, and audio comes out of the big speakers. For me, that path only occurred when Apple TV was involved. Sadly, when I woke a console, the TV switched inputs but audio stayed on the tinny TV speakers.

    To debug that mess I first wrote down how every device identified itself on the bus. Here are the specific CEC roles in my home theater:

    • TV – logical address 0x0
    • Audio system (Denon AVR-X1700H) – logical address 0x5
    • Playback devices – logical addresses 0x4 , 0x8 , 0xB (Apple TV, PS5, Switch 2 and Xbox all competing for the three playback slots 3 )
    • Broadcast – logical address 0xF (messages to everyone)

    And the key opcodes we ended up caring about:

    • 0x82 Active Source (“I am now the active input”)
    • 0x84 Report Physical Address (“this is where I live in the HDMI tree”)
    • 0x70 System Audio Mode Request
    • 0x72 Set System Audio Mode (Denon’s “I am now the audio system” broadcast)

    Monitoring the CEC bus with cec-client

    The Raspberry Pi 4 I have exposes /dev/cec0 interface on its micro-HDMI, and with a $7 micro-HDMI to HDMI cable plugged into HDMI input port on the receiver, it’s possible to monitor CEC traffic from everything connected to the receiver .

    Close-up photo of the Pi plugged into the TV’s ARC HDMI input, HDMI adapters visible

    I was initially hesitant because of some Hue Play Sync Box 4 trauma: every HDMI splitter or inline gadget I’ve tried in front of the TV caused weird EDID breakage, troubles with HDR negotiation, or outright signal loss. But once I understood the Pi never sits in the middle of the HDMI handshake my concerns went away. By plugging it into an unused HDMI input on the AVR, it behaves like just another participant on the shared CEC bus. No signal regeneration, no spoofed EDIDs, nothing for the rest of the chain to notice.

    So the topology looks like this:

    ---
    config:
      flowchart:
        htmlLabels: false
    ---
    flowchart LR
      classDef pi fill:#ffd399,stroke:#f97316,stroke-width:3px,color:#111,font-weight:bold;
      classDef misc fill:#1f2933,stroke:#4b5563,color:#f8fafc;
    
      subgraph Media Closet
        SW["`**Nintendo Switch 2**
        Playback 1 @ 0x8
        3.1.0.0`"]-- HDMI 1 ---AVR
        ATV["`**Apple TV**
        Playback 2 @ 0x4
        3.2.0.0`"]-- HDMI 2 ---AVR
        PI["`**Raspberry Pi**
        Recorder 1 @ 0x1
        3.3.0.0`"]-- micro HDMI to HDMI 3 ---AVR
        PC["`**PC**
        no CEC`"]-- HDMI 4 ---AVR
        XBOX["`**Xbox Series X**
        Playback 1 @ 0x8
        3.5.0.0`"]-- HDMI 5 ---AVR
        PS5["`**PS5**
        Playback 3 @ 0xB
        3.6.0.0`"]-- HDMI 6 ---AVR
      end
    
      subgraph Living Room
        TV["`**Samsung S95B TV**
        TV @ 0x0
        0.0.0.0`"]
      end
    
      AVR["`**Denon AVR-X1700H**
      Audio @ 0x5
      3.0.0.0`"]-- HDMI Out to HDMI 3 (ARC) ---TV
    
      class AVR,TV,SW,ATV,PC,XBOX,PS5 misc;
      class PI pi;
    

    Now, you can get cec-client from libcec . Install it with

    sudo apt update
    sudo apt install cec-utils
    

    Then do a quick scan to see which devices respond:

    echo "scan" | cec-client -s
    

    Example scan output from my setup below. As you can see, the Xbox and Switch both 3 claim logical address 0x8 1 :

    CEC bus information
    ===================
    device #0: TV
    address:       0.0.0.0
    active source: no
    vendor:        Samsung
    osd string:    TV
    CEC version:   1.4
    power status:  on
    language:      eng
    
    
    device #1: Recorder 1
    address:       3.3.0.0
    active source: no
    vendor:        Pulse Eight
    osd string:    CECTester
    CEC version:   1.4
    power status:  on
    language:      eng
    
    
    device #4: Playback 1
    address:       3.1.0.0
    active source: yes
    vendor:        Unknown
    osd string:    Switch 2
    CEC version:   1.3a
    power status:  on
    language:      ???
    
    
    device #5: Audio
    address:       3.0.0.0
    active source: no
    vendor:        Denon
    osd string:    AVR-X1700H
    CEC version:   1.4
    power status:  on
    language:      ???
    
    
    device #8: Playback 2
    address:       3.2.0.0
    active source: no
    vendor:        Apple
    osd string:    Apple TV
    CEC version:   2.0
    power status:  standby
    language:      ???
    
    
    device #B: Playback 3
    address:       3.6.0.0
    active source: no
    vendor:        Sony
    osd string:    PlayStation 5
    CEC version:   1.3a
    power status:  standby
    language:      ???
    

    If the expected devices show up, use monitor mode with the correct level 5 of logging:

    This command keeps the Pi quiet (monitor mode) yet gives you every bus transaction.

    A line such as TRAFFIC: [...] >> bf:82:36:00 means: logical 0xB (PS5) broadcast Active Source ( 0x82 ) with physical address 3.6.0.0 . That’s the packet you expect any console to send when it wakes up.

    Figuring out the magic handshake

    So I put the system in standby, start logging, then wake Apple TV. I got the expected Active Source burst, followed immediately by the Denon broadcasting that it has taken over audio:

    >> 8f:82:32:00       # Apple TV (logical 8) -> Broadcast: Active Source
    ...
    >> 8f:a6:06:10:56:10 # Apple TV (logical 8) -> Broadcast: ???
    >> 5f:72:01          # Denon (logical 5) -> Broadcast: Set System Audio Mode (on)
    

    Translated:

    1. Apple TV announces itself as the active source.
    2. Apple TV broadcasts some magic bits?
    3. Very soon after, the Denon tells everyone “System Audio Mode is on,” and the TV happily keeps output set to Receiver instead of flipping back to TV speakers.

    I did the exact same experiment with PS5, Xbox, Switch 2 and the result was different:

    >> bf:82:36:00       # PS5: Active Source
    # ...a bunch of reports, but no 5f:72:01
    

    So what was the 8f:a6:06:10:56:10 frame when Apple TV was involved? With debug logs, cec-client showed UNKNOWN (A6) . I suspect libCEC labels it unknown because it’s in the vendor-specific range. The following bytes ( 06:10:56:10 ) could be Apple’s proprietary payload, like some capability or extended control. It’s possible Samsung and Apple have a private handshake here that ultimately results in the Denon doing the right thing. It’s neat, but I couldn’t rely on it since it’s undocumented and sending it manually from the Raspberry Pi’s logical address had no effect. Impersonating Apple TV over CEC is not realistically viable and likely brittle.

    However, with cec-o-matic.com , it was easy to craft a CEC frame for the Raspberry Pi to send a normal system audio mode request:

    15:70:00:00 # TV (1) -> Audio (5): System Audio Mode Request
    

    Breaking it down:

    • 15 = source 0x1 (Recorder 1 = Pi) sends to destination 0x5 (Audio System = Denon)
    • 70 = opcode System Audio Mode Request
    • 00:00 = operands (TV’s physical address 0.0.0.0, plus “system audio status” = off/0, which Denon interprets as “please negotiate system audio mode and turn on ARC”)

    The second I typed this into cec-client ’s interactive shell with tx 15:70:00:00 , the Denon turned on and ARC anchored to the receiver even with only a console and TV powered on. I confirmed by checking the TV’s audio output:

    Photo of TV UI confirming receiver output

    So the solution started to emerge: whenever a console wakes up and claims Active Source, the Pi should step in and send 15:70:00:00 to the Denon to kickstart audio negotiation.

    Don’t spam the bus!

    Now that we know the basis of the automation, the most obvious thing to do is to write a bash script that loops cec-client every few seconds and blast on 5 . That sort of works, but it’s not ideal:

    • Using a loop means the automation is delayed instead of reacting to actual CEC events.
    • Every iteration spins up a new cec-client , binds to /dev/cec0 , sends one command, and tears down.
    • CEC is a shared bus, not a write-only GPIO.

    A better pattern is:

    1. Start a single long-lived cec-client process. 6
    2. Let it print every TRAFFIC line for you to parse.
    3. Feed it tx ... commands on stdin only when you need to intervene.

    The only catch: monitor mode ( -m ) can’t transmit. So for the automation we switch to:

    No -m here. cec-client still prints all the traffic, but now it also accepts commands. Our Python script treats it like a bridge between HDMI land and our own logic: stdout is an event stream, stdin is a control channel.

    The Python script

    It took some trial and error, but it wasn’t too difficult to write a small Python program that watches for consoles waking up and sends the magic 15:70:00:00 command when needed. I put it all on GitHub:

    jlian/cec_auto_audio

    The script logic goes:

    • Starts cec-client -d 8 as a subprocess.
    • Parses TRAFFIC lines.
    • Watches for Active Source ( 0x82 ) from any Playback logical address ( 0x4/0x8/0xB ).
    • Tracks when the Denon last broadcast Set System Audio Mode ( 5f:72:01 ) so we don’t fight Apple TV or the TV’s own logic.
    • Sends tx 15:70:00:00 at most once per console wake if nobody else has done it.

    A few notes:

    • The script doesn’t hard-code any device names, vendors, or physical addresses.
    • It treats any Playback logical address ( 0x4/0x8/0xB ) turning into Active Source as a “console wake” event.
    • It stays passive when Apple TV / Samsung / Denon manage to do the right thing themselves (because we observe a real 5f:72:01 ).
    • It runs as a single long-lived process tied to a single cec-client instance.

    To make sure it starts on boot and keeps running, I wrapped it in a simple systemd service. The unit file I used can be found in the GitHub README . I’ve been running it for a few days and feels rock solid.

    Generalizing this approach

    I hope this post gives you enough of a mental model to adapt this approach to your own CEC pain points. My solution is specific to the Denon + Samsung + consoles scenario, but the same pattern should work for other CEC quirks.

    Maybe your issue isn’t consoles not engaging the AVR. Maybe DTS never negotiates, or your TV keeps snapping back to its tiny speakers. The workflow is the same:

    1. Get the Pi onto the an HDMI port . Plug the Pi into the TV or Receiver’s HDMI input using a micro-HDMI–>HDMI cable (or adapter). Put it somewhere it can sit forever.

    2. Baseline the bus . Run:

      echo "scan" | cec-client -s -d 1
      

      to make sure your Pi can see all the expected devices, what logical/physical addresses they have, and what roles they use.

    3. Record a “good” scenario and a “bad” one . Use:

      to log traffic while you:

      • Trigger a good path (e.g., Apple TV gets 5.1 sound correctly).
      • Trigger a bad path (e.g., DTS falls back to stereo, or ARC drops to TV speakers).
    4. Diff the traces . Look for opcodes that show up in the good trace but are missing in the bad. In my case, the interesting delta was the presence of 5f:72:01 after Apple TV woke, and the absence of anything like it when a console woke alone.

    5. Inject the missing opcode manually . Go to cec-o-matic.com to build the missing frame 7 , then run:

      to use cec-client in interactive mode, then type tx ... for your suspected magic packet, and see if anything changes. If not, try again with a different frame.

      You likely would want to start with a frame like 1f:... (Pi logical address as Recording 1 0x1 to Broadcast 0xF ), or 15... (Pi to Audio System 0x5 ), depending on what you’re trying to achieve.

    6. Wrap it in code . Once you know the magic packet, wrap it in a tiny program like the one above and let the Pi quietly participate on the bus.

    You can picture the good vs bad paths like this:

    sequenceDiagram
      participant Src as Source (console/player)
      participant TV as TV (0)
      participant AVR as AVR / Soundbar (5)
    
      rect rgb(230,255,230)
        note over Src,AVR: Good path
        Src->>TV: Active Source (0x82)
        TV->>AVR: System Audio Mode Request (0x70)
        AVR->>TV: Set System Audio Mode On (0x72)
      end
    
      rect rgb(255,230,230)
        note over Src,AVR: Bad path
        Src->>TV: Active Source (0x82)
            note over TV,AVR: No audio-mode negotiation
      end
    

    Your job is to spot the missing step and teach the Pi to do it.

    Where this leaves my setup

    Apple TV keeps doing its thing. PS5, Xbox, or Switch now wake the TV, the Pi nudges the Denon within half a second, and audio stays glued to the receiver. Latency is low enough that it feels native. The Pi sits in the closet pretending to be a slightly overqualified remote.

    Picture of my TV and cat being comfortable

    There are still a couple of rough edges I haven’t tackled yet:

    • When a console goes to sleep, the TV sometimes “helpfully” switches to its antenna input. I don’t even have an antenna plugged in, so the net effect is a confusing “no signal” screen instead of falling back to Apple TV or a known-good input. That’s technically “correct” from the TV’s point of view (its own tuner is always a valid source), but wrong for how this setup is actually used.

    • My sunset TV automation can land on a dead input. I have a HomeKit automation that turns the TV on around sunset. Most of the time that means Apple TV wakes up with a nice aerial screensaver. But if the last input before power-off was a console, the TV wakes to that HDMI port and just shows “no signal”, which confuses other people in the house.

    These problems are similar, but require slightly different solutions:

    1. Console standby → TV becomes Active Source. When a console goes to sleep it tends to release the bus and the TV politely promotes its tuner. The helper could watch for that very specific frame pair (console Standby, TV Active Source) and, after a short grace period, switch the input to Apple TV.
    2. Sunset automation → no Active Source. In this case the TV powers on but nobody (not even the TV) claims Active Source, so it sits on the last HDMI port showing “no signal.” The helper needs to detect “TV on, Denon asleep, no Active Source within N ms,” then wake both Apple TV and the receiver and switch inputs.

    Or maybe we could unify both by having a state machine that tracks “who was Active Source most recently” and automatically falls back to Apple TV whenever the bus goes quiet or the TV promotes itself. Either way, the Pi’s job is to make sure there’s always a sane outcome.

    That would turn the Pi into a more general “HDMI shepherd”: not just keeping ARC pinned to the receiver when something is playing, but also steering the system back to a sane default when nothing is.

    There’s probably a small cottage industry of “two-page CEC scripts” waiting to be written. If you adapt this trick for some other HDMI-CEC horror story, send me the packet traces —I’m collecting folklore.

    PornHub extorted after hackers steal Premium member activity data

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-15 21:27:07
    Adult video platform PornHub is being extorted by the ShinyHunters extortion gang after the search and watch history of its Premium members was reportedly stolen in a recent Mixpanel data breach. [...]...
    Original Article

    PornHub

    Adult video platform PornHub is being extorted by the ShinyHunters extortion gang after the search and watch history of its Premium members was reportedly stolen in a recent Mixpanel data breach.

    Last week, PornHub disclosed that it was impacted by a recent breach at analytics vendor Mixpanel . Mixpanel suffered a breach on November 8th, 2025, after an SMS phishing (smishing) attack enabled threat actors to compromise its systems.

    "A recent cybersecurity incident involving Mixpanel, a third-party data analytics provider, has impacted some Pornhub Premium users," reads a PornHub security notice posted on Friday.

    "Specifically, this situation affects only select Premium users. It is important to note this was not a breach of Pornhub Premium's systems. Passwords, payment details, and financial information remain secure and were not exposed."

    PornHub says it has not worked with Mixpanel since 2021, indicating the stolen records are historical analytics data from 2021 or earlier.

    Mixpanel says the breach affected a "limited number" of customers, with OpenAI and CoinTracker previously disclosing they were affected.

    This is the first time it has been publicly confirmed that ShinyHunters was behind the Mixpanel breach.

    When contacting PornHub, the company did not provide additional comment to BleepingComputer beyond the security notice.

    PornHub search and watch history exposed

    Today, BleepingComputer learned that ShinyHunters began extorting Mixpanel customers last week, sending emails that began with "We are ShinyHunters" and warned that their stolen data would be published if a ransom was not paid.

    In an extortion demand sent to PornHub, ShinyHunters claims it stole 94GB of data containing over 200 million records of personal information in the Mixpanel breach.

    ShinyHunters later confirmed to BleepingComputer that they were behind the extortion emails, claiming the data consists of 201,211,943 records of historical search, watch, and download activity for the platform's Premium members.

    A small sample of data shared with BleepingComputer shows that the analytic events sent to Mixpanel contain a large amount of sensitive information that a member would not likely want publicly disclosed.

    This data includes a PornHub Premium member's email address, activity type, location, video URL, video name, keywords associated with the video, and the time the event occurred.

    Activity types seen by BleepingComputer include whether the PornHub subscriber watched or downloaded a video or viewed a channel. However, ShinyHunters also said the events include search histories.

    The ShinyHunters extortion group has been behind a string of data breaches this year by compromising various Salesforce integration companies to gain access to Salesforce instances and steal company data.

    The threat group is linked to the exploitation of the Oracle E-Business Suite zero-day (CVE-2025-61884), as well as to Salesforce/Drift attacks that impacted a large number of organizations earlier this year.

    More recently ShinyHunters conducted a breach at GainSight that allowed the threat actors to steal further Salesforce data from organizations.

    With it now confirmed that ShinyHunters is also behind the Mixpanel breach, the threat actors are responsible for some of the most significant data breaches in 2025, impacting hundreds of companies.

    ShinyHunters is also creating a new ransomware-as-a-service called ShinySpid3r, which will serve as a platform for them and threat actors associated with Scattered Spider to conduct ransomware attacks.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    PornHub extorted after hackers steal Premium member activity data

    Bleeping Computer
    www.bleepingcomputer.com
    2025-12-15 21:27:07
    Adult video platform PornHub is being extorted by the ShinyHunters extortion gang after the search and watch history of its Premium members was reportedly stolen in a recent Mixpanel data breach. [...]...
    Original Article

    PornHub

    Adult video platform PornHub is being extorted by the ShinyHunters extortion gang after the search and watch history of its Premium members was reportedly stolen in a recent Mixpanel data breach.

    Last week, PornHub disclosed that it was impacted by a recent breach at analytics vendor Mixpanel . Mixpanel suffered a breach on November 8th, 2025, after an SMS phishing (smishing) attack enabled threat actors to compromise its systems.

    "A recent cybersecurity incident involving Mixpanel, a third-party data analytics provider, has impacted some Pornhub Premium users," reads a PornHub security notice posted on Friday.

    "Specifically, this situation affects only select Premium users. It is important to note this was not a breach of Pornhub Premium's systems. Passwords, payment details, and financial information remain secure and were not exposed."

    PornHub says it has not worked with Mixpanel since 2021, indicating the stolen records are historical analytics data from 2021 or earlier.

    Mixpanel says the breach affected a "limited number" of customers, with OpenAI and CoinTracker previously disclosing they were affected.

    This is the first time it has been publicly confirmed that ShinyHunters was behind the Mixpanel breach.

    When contacting PornHub, the company did not provide additional comment to BleepingComputer beyond the security notice.

    After publishing our story, Mixpanel told BleepingComputer that it does not believe this data originated from the recent November breach.

    "Mixpanel is aware of reports that Pornhub has been extorted with data that that was allegedly stolen from us," Mixpanel told BleepingComputer.

    "We can find no indication that this data was stolen from Mixpanel during our November 2025 security Incident or otherwise."

    "The data was last accessed by a legitimate employee account at Pornhub’s parent company in 2023. If this data is in the hands of an unauthorized party, we do not believe that is the result of a security incident at Mixpanel."

    PornHub search and watch history exposed

    Today, BleepingComputer learned that ShinyHunters began extorting Mixpanel customers last week, sending emails that began with "We are ShinyHunters" and warned that their stolen data would be published if a ransom was not paid.

    In an extortion demand sent to PornHub, ShinyHunters claims it stole 94GB of data containing over 200 million records of personal information in the Mixpanel breach.

    ShinyHunters later confirmed to BleepingComputer that they were behind the extortion emails, claiming the data consists of 201,211,943 records of historical search, watch, and download activity for the platform's Premium members.

    A small sample of data shared with BleepingComputer shows that the analytic events sent to Mixpanel contain a large amount of sensitive information that a member would not likely want publicly disclosed.

    This data includes a PornHub Premium member's email address, activity type, location, video URL, video name, keywords associated with the video, and the time the event occurred.

    Activity types seen by BleepingComputer include whether the PornHub subscriber watched or downloaded a video or viewed a channel. However, ShinyHunters also said the events include search histories.

    The ShinyHunters extortion group has been behind a string of data breaches this year by compromising various Salesforce integration companies to gain access to Salesforce instances and steal company data.

    The threat group is linked to the exploitation of the Oracle E-Business Suite zero-day (CVE-2025-61884), as well as to Salesforce/Drift attacks that impacted a large number of organizations earlier this year.

    More recently ShinyHunters conducted a breach at GainSight that allowed the threat actors to steal further Salesforce data from organizations.

    With it now confirmed that ShinyHunters is also behind the Mixpanel breach, the threat actors are responsible for some of the most significant data breaches in 2025, impacting hundreds of companies.

    ShinyHunters is also creating a new ransomware-as-a-service called ShinySpid3r, which will serve as a platform for them and threat actors associated with Scattered Spider to conduct ransomware attacks.

    tines

    Break down IAM silos like Bitpanda, KnowBe4, and PathAI

    Broken IAM isn't just an IT problem - the impact ripples across your whole business.

    This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

    Secret Documents Show Pepsi and Walmart Colluded to Raise Food Prices

    Hacker News
    www.thebignewsletter.com
    2025-12-15 21:24:06
    Comments...
    Original Article

    Last month, the Atlanta Fed came out with a report showing a clear relationship between consolidation in grocery stores and the rate of food inflation. Unsurprisingly, where monopolies prevail, food inflation is 0.46 percentage points higher than where there is more competition. The study showed that from 2006-2020, the cumulative difference amounted to a 9% hike in food prices, and presumably since 2020, that number has gone much higher.

    Affordability, in other words, is a market power problem.

    And yesterday, we got specifics on just how market power in grocery stores works. The reason is because a nonprofit just forced the government to unseal a complaint lodged by Lina Khan’s FTC against Pepsi for colluding with Walmart to raise food prices across the economy. A Trump official tasked with dealing with affordability tried to hide this complaint, and failed. And now there’s a political and legal storm as a result.

    Let’s dive in.

    Everyone knows the players involved. Pepsi is a monster in terms of size, a $90 billion soft drink and consumer packaged goods company with multiple iconic beverage and food brands each worth over $1 billion, including Pepsi-Cola, Frito Lay, Mountain Dew, Starbucks (under license), Gatorade, and Aquafina. Walmart is a key partner, with between 20-25% of the grocery market.

    Pepsi was also a key player in the post-Covid ‘greedflation’ episode. “I actually think we’re capable of taking whatever pricing we need,” said CFO Hugh Johnston in 2022. And the company did just that, raising prices by double digit percentages for seven straight quarters in 2022-2023.

    The allegation is price discrimination, which is a violation of the Robinson-Patman Act, a law passed in 1936 to prevent big manufacturers and chain stores from acquiring too much market power. The specifics in the complaint are that Pepsi keeps wholesale prices on its products high for every outlet but Walmart, and Walmart in return offers prominent placement in stores for Pepsi products. This approach internally is called a “price gap” strategy. It’s a partnership between two giants to exclude rivals by ensuring that Walmart has an advantage over smaller rivals in terms of what it charges consumers, and so that Pepsi maintains its dominance on store shelves.

    This partnership comes in a number of forms. Pepsi offers allowances for Walmart, such as “Rollback” pricing, where specially priced soft drinks go into bins in highly visible parts of the store. The soft drink company gives Walmart “Save Even More” deals, online coupons and advertisements, and other merchandizing opportunities. Other outlets don’t get these same allowances, meaning they are charged higher prices.

    While Pepsi is a “must-have” product for grocery stores, Walmart is also massively powerful. In its investment documents, Pepsi notes that Walmart is its largest customer, the the loss of which “would have a material adverse effect” on its business. Walmart is so dominant that the internal communication of the two companies would show a comparison of prices at Walmart versus “ROM,” or “rest of market,” meaning grocery, mass, club, drug, and dollar channels. It’s everyone in the world versus Walmart.

    And Pepsi does a lot of alleged price discrimination to maintain the approval of Walmart. It goes far beyond special allowances and concessions to Walmart; Pepsi even polices prices at rival stores and prepares reports for Walmart showing them their pricing advantages on Pepsi products.

    When the “price gap” would narrow too much, Pepsi executives panicked with fear they might offend Walmart. They tracked “leakage,” meaning when consumers would buy Pepsi products outside of Walmart, which happened most often at stores where prices were more competitive. Pepsi kept logs on stores who would “self-fund” discounts, nicknaming them “offenders” of the price gap. It would note that where competition was fierce, such as in the Richmond-Raleigh-CLT corridor, it was harder to maintain a price gap for Walmart. This relationship went both ways; Walmart executives would complain to Pepsi if the “price gap” got too thin.

    To ensure that prices would go up at rival stores, Pepsi would adjust allowances, such as “adjusting rollback levers.” It would punish stores that refused to cooperate by raising wholesale prices. Retailers who were trying to discount Pepsi products to better compete with Walmart would find it increasingly difficult to do so; not only would Pepsi take away their promotional allowances, but they might find that discounting six-packs of soda would lead to Pepsi charging them higher wholesale prices for the soda.

    The FTC offered the example of Food Lion, a 1000-store chain in 10 states that cut prices on Pepsi products on its own to match or beat Walmart prices.

    In 2022, Pepsi believed that Food Lion had “heavily indexe[d]” its retail prices “against retails at [Walmart] and Kroger” and “set[] retails relative to these competitors.” Pepsi characterized Food Lion as the “worst offender” on the price gap for “beating [Walmart] in price.”

    As a result of Food Lion threatening Walmart’s price gap, Pepsi created a plan to nudge Food Lion’s retail prices on Pepsi products upward by reducing promotional payments and allowances to Food Lion and raising other costs for Food Lion. The plan advised that Pepsi “must commit to raising rate [on Food Lion] faster than market by minimum annually.”…

    Nonetheless, even with these price increases, Pepsi leadership continued to push its Food Lion sales team to “begin to CLOSE the gap” because “[w]e absolutely have to demonstrate progress [to Walmart] in the immediate term.”

    This arrangement benefits each side by extracting from consumers and rivals. Walmart gets to have a price advantage in Pepsi soft drink products against rival grocery stores and convenience stores, and Pepsi is able to exclude competitor access to better shelf space at the most important retailer. Consumers end up paying more for soda, new companies find it harder to get distribution access for new soft drink products to compete with Pepsi, and all non-Walmart retail stores are put at a disadvantage to Walmart. ILSR’s Stacy Mitchell laid out the terms of the deal as “Keep us the king of our domain and we’ll make you the king of yours.”

    This dynamic is why independent grocery stores are dying. “We can be almost certain that this is the same monopolistic deal Walmart has cut with other major grocery suppliers,” noted Mitchell. “It’s led to less competition, fewer local grocery stores, and higher prices.” To the end consumer, it creates an optimal illusion. Walmart appears to be a low-cost retailer, but that’s because it induces its suppliers to push prices up at rivals. The net effect is less competition at every level. There are more areas without grocery competition, which increases food inflation. And suppliers like Pepsi gain pricing power, such as that they exploited during the post-Covid moment.

    This kind of presumptively illegal price discrimination isn’t unique to the Pepsi-Walmart relationship. Pepsi is also being sued in a class action complaint for giving better deals for snack foods to big chains than it does to smaller stores, and Post is being sued by Snoop Dogg for working with Walmart to exclude sugar cereals produced by Snoop Dogg from its store shelves. You can find price discrimination everywhere in the economy, from shipping to ad buying to pharmaceutical distribution to liquor sales. And the resulting consolidation and high prices is also pervasive.

    So why are we only learning about this situation now? Well, the original allegation was filed in January, in the last days of the Khan FTC. We knew the general outline of the argument, but we didn’t know specifics, because the complaint was highly redacted . Was it a real conspiracy? Was it just that Pepsi considered Walmart a “superstore” and had different prices for different channels? Was there coercion? None of these questions could be answered; there were so many blacked out words we couldn’t even say for sure that the large power buyer referenced in the document was Walmart.

    Economists and fancy legal thinkers mocked the case endlessly. The FTC hates discounts! Price discrimination is good, it ends up lowering prices for consumers. The Robinson-Patman Act is stupid and pushes up prices. Suppliers always can only charge what “the market will bear” and if they could charge higher prices they’d already be doing it. And they’d never offer lower prices to any distributor; no lower than they had to. Yet these claims relied on the complaint never seeing the light of day.

    The reason for the secrecy was a choice by FTC Chair Ferguson. Normally, when the government files an antitrust case, the complaint is redacted to protect confidential business information, as this one against Pepsi was. Then the corporate defendant and the government haggle over what is genuinely confidential business information. Within a few weeks, complaints are unsealed with a few minor blacked out phrases, and the case goes on.

    In this case, however, Trump Federal Trade Commission Chair Andrew Ferguson abruptly dropped the case in February after Pepsi hired well-connected lobbyists. Small business groups were angry, but what was most interesting was the timing. Ferguson ended it the day before the government was supposed to go before the judge to manage the unsealing process. And that kept the complaint redacted. With the complaint kept secret, Ferguson, and his colleague Mark Meador, then publicly went on the attack. Ferguson’s statement was a bitter and personal invective against Khan; he implied she was lawless and partisan, that there was “no evidence” to support key contentions, and that he had to “clean up the Biden-Harris FTC’s mess,” which fellow commissioner Mark Meador later echoed.

    And that was where it was supposed to stay, secret, with mean-spirited name-calling and invective camouflaging the real secret Ferguson was trying to conceal. That secret is something we all know, but this complaint helped prove - the center of the affordability crisis in food is market power. If that got out, then Ferguson would have to litigate this case or risk deep embarrassment. So the strategy was to handwave about that mean Lina Khan to lobbyists, while keeping the evidence secret.

    However, the anti-monopoly movement and the court system actually worked. The Institute for Local Self-Reliance, an anti-monopoly group filed to make the full complaint public. Judge Jesse Matthew Furman agreed to hear ILSR’s case, with the U.S. Chamber of Commerce and Pepsi bitterly opposed. Last week, Furman directed the FTC unseal the complaint. So we finally got to see what Ferguson and Meador were trying to hide.

    The political reaction is just starting. Ferguson has pretended that he’s taking a leading role in the ‘affordability’ strategy of the Trump administration, it wouldn’t surprise me if there’s internal anger at him among Republicans for flubbing such an obvious way to lower consumer prices and then lying about it. The grocery industry, especially rural grocers victimized by this price discrimination, leans to the right.

    On the Democratic side, already we’re seeing states introducing price discrimination bills. There’s likely going to be bipartisan pressure on the FTC, which can and should reopen the case. There are already private Robinson-Patman Act cases, this complaint is likely to be picked up and used by plaintiffs who are excluded by the alleged scheme revealed in it. As a result of the publication of this complaint, Sabina Matos, the lieutenant governor of Rhode Island, just said that her state should ban this kind of behavior.

    But there’s also something deeper happening. Earlier this week, More Perfect Union came out with an important investigative report on a company called Instacart, which is helping retailers charge individual personalized prices for goods based on a shopper’s data profile. The story went viral and caused immense outrage because it said something we already know. Pricing is increasingly unfair and unequal, a mechanism to extract instead of a means of sending information signals to the public and producers to coordinate legitimate commercial activity. And there’s a historical analogy to the increasing popular frustration.

    The idea of the single price store, where a price is transparent and is the same for everyone, was created by department store magnate John Wanamaker in the post-Civil War era. Before founding his department store, Wanamaker was the first leader of the YMCA. He also created a Philadelphia mega-church. His single price strategy was part of an evangelical movement to morally purify America, the “Golden rule” applied to business. The price tag was political, an explicitly democratic attempt to treat everyone equally by eliminating the haggling and extractive approach of merchants.

    At the same time as Wanamaker operated his store, the Granger movement of farmers in the midwest and later Populists fought their own war on unfair pricing of railroads, with the slogan “public prices and no secret kickbacks.” In the 1899 conference on trusts in Chicago, widely considered the most important intellectual and political forum for the later treatment of the Sherman Act, there were bitter debates, but everyone agreed that price discrimination by railroads were fostering consolidation in a dangerous and inefficient roll-up of power. These movements took place at a moment of great technological change, when Americans were moving to cities and leaving the traditional dry goods store behind.

    Similarly, there was a big anti-chain store movement in the 1920s and 1930s to protect local producers and retailers, which ended up resulting in the Robinson-Patman Act, among other changes to law. That was a result of the Walmart or Amazon of its day, A&P, which would engage in price discrimination, opening outlets it called “killing stores” just to harm rivals. Over the past five years, we’ve seen a similar upsurge in anger over prices that drove the grangers, John Wanamaker, and the anti-chain store movement. Prices are becoming political again.

    This revival is being driven by two things. First, technology is enabling all sorts of new ways to price, which is to say, to organize commercial and political power. And we all feel the coercion. Second, we’re beginning to relearn our traditions. Our historical memory was erased in the 1970s by economists, who argued that price discrimination is affirmatively a good thing. But fortunately, they are losing the debate.

    As a result, today we’re seeing something similar to the anti-chain store movement of the 1920s and 1930s, with attempts to reinvigorate Robinson-Patman, and write and apply antitrust laws to algorithmic pricing choices. The Instacart scheme is a new way to extract, the alleged Walmart-Pepsi scheme is a classic way to extract. But increasingly, the public is realizing that pricing is political. And they don’t want to be cheated anymore.

    Thanks for reading! Your tips make this newsletter what it is, so please send me tips on weird monopolies, stories I’ve missed, or other thoughts. And if you liked this issue of BIG, you can sign up here for more issues, a newsletter on how to restore fair commerce, innovation, and democracy. Consider becoming a paying subscriber to support this work, or if you are a paying subscriber, giving a gift subscription to a friend, colleague, or family member. If you really liked it, read my book, Goliath: The 100-Year War Between Monopoly Power and Democracy .

    cheers,

    Matt Stoller.

    Discussion about this post

    Ready for more?

    The House Always Wins: Three Casinos Get Final Green Light to Operate in NYC

    hellgate
    hellgatenyc.com
    2025-12-15 21:17:09
    But each casino must have an "independent monitor" to ensure they keep all the promises they made while bidding for the licenses....
    Original Article

    On Monday, three massive casinos got their long-awaited licenses to open up in New York City—but state regulators vowed that they will only be able to operate if the gambling companies keep all of the promises they made along the way.

    In a chilly auditorium inside Riverbank State Park , the New York State Gaming Commission unanimously approved the casino license applications for Bally's in the Bronx, Hard Rock Metropolitan Park next to Citi Field, and Resorts World near JFK airport, on the condition that all three casinos have "outside independent monitors" for five years.

    After the commission voted to approve Metropolitan Park, which is backed by Mets owner Steve Cohen, a handful of protesters erupted in anger.

    "You chose a billionaire over New Yorkers! Shame on you!" one of them shouted. "Hochul must go! Shame on you!"

    Give us your email to read the full story

    Sign up now for our free newsletters.

    Sign up

    Canada's Carney called out for 'utilizing' British spelling

    Hacker News
    www.bbc.com
    2025-12-15 21:15:53
    Comments...
    Original Article

    Nadine Yousif Senior Canada reporter

    Canadian language experts are calling on Prime Minister Mark Carney to ditch British spelling in official documents, and 'utilize' Canadian spelling instead.

    Canadian English has been the standard in government communications for decades. But eagle-eyed linguists and editors have spotted British spellings — like "globalisation" and "catalyse" — in documents from the Carney government, including the budget.

    In an open letter, they asked Carney to stick to Canadian English, writing that it is "a matter of our national history, identity and pride".

    They note that Canadian English is unique because it borrows influence from both the US and the UK due to geography and history.

    It also includes "Canadianisms" that are unique to the country's lexicon, like the use of the word "toque" to describe a winter hat, or "washroom" instead of the American bathroom or the British loo.

    A big distinction between Canadian and British spelling is the use of the letter 'z' versus 's' in words like analyse. But Canadian English takes from British English in other ways, like using 'ou' in colour, rather than the American 'color'.

    Other British terms, however, are never used, like tyre for 'tire'.

    In the letter, dated 11 December and shared with BBC News, the linguists wrote that Canadian English is recognised and widely used in Canada, arguing that "if governments start to use other systems for spelling, this could lead to confusion about which spelling is Canadian."

    They add that using Canadian English is "the simplest way to take an 'elbows up' stance", referencing an ice hockey term that Carney has used to describe Canada's defiance in the face of US tariffs and 51st state jabs from President Donald Trump.

    The letter was sent by Editors Canada and signed by four professors of linguistics at various Canadian universities, along with the editor-in-chief of the Canadian English Dictionary.

    The BBC has reached out to Carney's office for comment.

    One of the signatories, Professor Stefan Dollinger at the University of British Columbia, said he and others feel strongly about the issue "because language expresses identity".

    "It seems kind of counter-productive that the Prime Minister's Office would now walk the clock back by half-a-century or more," Prof Dollinger told the BBC, noting how Canada's language has evolved from its past as a British colony.

    There were at least two notable uses of British English by Carney's office, said Kaitlin Littlechild, president of Editors Canada.

    The first was the Carney government's budget, released in November. The second is an October news release from the prime minister's office after a working visit to Washington, DC, where Carney met Trump.

    Ms Littlechild said it is difficult to decipher whether it is a "misunderstanding" or a "targeted directive".

    JK Chambers, a prominent Canadian linguist at the University of Toronto and another signatory, noted that Carney spent many years of his adult life in the UK, including seven years as governor of the Bank of England.

    "He obviously picked up some pretensions while he was there," Prof Chambers said via email, but added: "So far, bless him, he has not resorted to 'gaol' for 'jail.'"

    Disney, Immediately After Partnering With OpenAI for Sora, Sends Google a Cease-and-Desist Letter Accusing Them of Copyright Infringement on ‘Massive Scale’

    Daring Fireball
    variety.com
    2025-12-15 21:13:40
    Todd Spangler, reporting last week for Variety: As Disney has gone into business with OpenAI, the Mouse House is accusing Google of copyright infringement on a “massive scale” using AI models and services to “commercially exploit and distribute” infringing images and videos. On Wednesday evening...
    Original Article

    As Disney has gone into business with OpenAI , the Mouse House is accusing Google of copyright infringement on a “massive scale” using AI models and services to “commercially exploit and distribute” infringing images and videos.

    On Wednesday evening, attorneys for Disney sent a cease-and-desist letter to Google, demanding that Google stop the alleged infringement in its AI systems.

    “Google is infringing Disney’s copyrights on a massive scale, by copying a large corpus of Disney’s copyrighted works without authorization to train and develop generative artificial intelligence (‘AI’) models and services, and by using AI models and services to commercially exploit and distribute copies of its protected works to consumers in violation of Disney’s copyrights,” reads the letter to Google’s general counsel from law firm Jenner & Block on behalf of Disney.

    Popular on Variety

    The letter continued, “Google operates as a virtual vending machine, capable of reproducing, rendering, and distributing copies of Disney’s valuable library of copyrighted characters and other works on a mass scale. And compounding Google’s blatant infringement, many of the infringing images generated by Google’s AI Services are branded with Google’s Gemini logo, falsely implying that Google’s exploitation of Disney’s intellectual property is authorized and endorsed by Disney.”

    According to the letter, which Variety has reviewed, Disney alleges that Google’s AI systems and services infringe Disney characters including those from “Frozen,” “The Lion King,” “Moana,” “The Little Mermaid,” “Deadpool,” “Guardians of the Galaxy,” “Toy Story,” “Brave,” “Ratatouille,” “Monsters Inc.,” “Lilo & Stich,” “Inside Out” and franchises such as Star Wars, the Simpsons, and Marvel’s Avengers and Spider-Man. In its letter, Disney included examples of images it claims were generated by text prompts in Google’s AI apps, including of Darth Vader ( pictured above ).

    The allegations against Google follows cease-and-desist letters that Disney sent earlier to Meta and Character.AI , as well as litigation Disney filed together with NBCUniversal and Warner Bros. Discovery against AI companies Midjourney and Minimax alleging copyright infringement .

    Asked for comment, a Google spokesperson said, “We have a longstanding and mutually beneficial relationship with Disney, and will continue to engage with them. More generally, we use public data from the open web to build our AI and have built additional innovative copyright controls like Google-extended and Content ID for YouTube, which give sites and copyright holders control over their content.”

    According to Disney, the company has been raising its concerns with Google for months — but says Google hasn’t done anything in response, and that i anything, Google’s infringement has only increased during that time.

    Bob Iger, Disney’s CEO, in an interview with CNBC Thursday, said, “Well, we’ve been aggressive at protecting our IP, and we’ve gone after other companies that have not honored our IP, not respected our IP, not valued it. And this is another example of us doing just that.”

    Iger said Disney had been in discussions with Google “basically expressing our concerns” about its AI systems’ alleged infringement. “And ultimately, because we didn’t really make any progress, the conversations didn’t bear fruit, we felt we had no choice but to send them a cease-and-desist [letter].”

    Disney’s letter to Google demands that Google “immediately cease further copying, publicly displaying, publicly performing, distributing, and creating derivative works of Disney’s copyrighted characters” in “outputs of Google’s AI Services, including through YouTube’s mobile app, YouTube Shorts and YouTube.”

    In addition, Disney said Google must “immediately implement effective technological measures within Google’s AI Services and, as necessary, Google’s suite of products and services in which Google’s AI Services are integrated, to ensure that no future outputs infringe Disney works.

    “Disney will not tolerate the unauthorized commercial exploitation of its copyrighted characters and works by so-called AI services,” Disney’s cease-and-desist letter to Google says. “Here, Google’s conduct is particularly harmful because Google is leveraging its market dominance across multiple channels to distribute its AI Services and using the draw and popularity of infringed copyrighted works to help maintain that dominance.”

    Disney alleged that Google promoted a recent viral trend involving the creation of images of “figurines,” citing this post on X by Alphabet and Google CEO Sundar Pichai. According to Disney, Google has even supplied its own Gemini AI prompt to encourage users to take part in this trend, which “can be used to quickly and easily generate images of figurines of Disney’s copyrighted characters,” the letter says.

    The Disney lawyers included images of the alleged infringing figurine images in its letter:

    A Kernel Bug Froze My Machine: Debugging an Async-Profiler Deadlock

    Hacker News
    questdb.com
    2025-12-15 20:52:35
    Comments...
    Original Article

    QuestDB is the open-source time-series database for demanding workloads—from trading floors to mission control. It delivers ultra-low latency, high ingestion throughput, and a multi-tier storage engine. Native support for Parquet and SQL keeps your data portable, AI-ready—no vendor lock-in.


    I've been a Linux user since the late 90s, starting with Slackware on an underpowered AMD K6. Over the years I've hit plenty of bugs, but the last decade has been remarkably stable - until a kernel bug started freezing my machine whenever I used async-profiler .

    I'm not a kernel developer, but I found myself poking around kernel source code to understand the problem better and figure out what was going on under the hood.

    The problem

    I was about to start an investigation of latency spikes in QuestDB reported by a user. To do that, I wanted to use the async-profiler to capture CPU heatmaps .

    Screenshot of async-profiler heatmap

    Async-profiler heatmap example

    However, when I tried to attach the profiler, my machine froze completely. It did not respond to any keys, it was impossible to switch to a terminal, it did not respond to SSH. The only way to recover was to hard reboot it. I tried to start QuestDB with the profiler already configured to start at launch - the same result, a frozen machine almost immediately after the launch.

    I thought that was weird, this had not happened to me in years. It was already late in the evening, I felt tired anyway so I decided to call it a day. There was a tiny chance I was hallucinating and the problem would go away by itself overnight. A drowning man will clutch at a straw after all.

    The next day, I tried to attach the profiler again - same result, frozen machine. Async-profiler integration in QuestDB is a relatively new feature, so I thought there might be a bug in the integration code, perhaps a regression in the recent QuestDB release. So I built an older QuestDB version: The same result, frozen machine. This was puzzling - I positively knew this worked before. How do I know? Because I worked on the integration code not too long ago, and I tested the hell out of it.

    This was a strong hint that the problem was not in QuestDB, but rather in the environment. I've gotten lazy since my Slackware days and I have been using Ubuntu for years now and I realized that I had recently updated Ubuntu to the latest version: 25.10. Could it be that the problem is in the new Ubuntu version?

    At this point I started Googling around and I found a report created by a fellow performance aficionado, Francesco Nigro , describing exactly the same problem: machine freeze when using async-profiler. This was the final confirmation I was not hallucinating! Except Francesco is using Fedora, not Ubuntu. However, his Fedora uses the same kernel version as my Ubuntu: 6.17. I booted a machine with an older Ubuntu, started QuestDB and attached the profiler and it worked like a charm. This was yet another indication that the problem was in the system, possibly even in the kernel. This allowed me to narrow down my Google keywords and find this kernel patch which talks about the very same problem!

    I found it quite interesting: A kernel bug triggered by async-profiler causing machine freezes on recent mainstream distributions. After some poking I found a workaround: Start the profiler with -e ctimer option to avoid using the problematic kernel feature. I tried the workaround and indeed, with this option, the profiler worked fine and my machine did not freeze.

    Normally I'd move on, but I was curious. What exactly is going on under the hood? Why is it freezing? What is this ctimer thing? What exactly is the bug and how does the patch work? So I decided to dig deeper.

    Async-profiler is a sampling profiler. It periodically interrupts threads in the profiled application and collects their stack traces. The collected stack traces are then aggregated and visualized in various ways (flame graphs are one of the most popular visualizations). It has multiple ways to interrupt the profiled application, the most common one is using perf_events kernel feature. This is how it works by default on Linux assuming kernel paranoia settings allow it.

    perf_events Under the Hood

    The perf_events subsystem is a powerful Linux kernel feature for performance monitoring. For CPU profiling, async-profiler uses a software event called cpu-clock , which is driven by high-resolution timers ( hrtimers ) in the kernel.

    Here's the sequence of events during profiling:

    1. Setup : For each thread in the profiled application, async-profiler opens a perf_event file descriptor configured to generate a signal after a specified interval of CPU time (e.g., 10ms).
    2. Arming the event : The profiler calls ioctl(fd, PERF_EVENT_IOC_REFRESH, 1) to arm the event for exactly one sample. This is a one-shot mechanism, combined with the RESET at the end of the handler. The goal is to measure application CPU time only and exclude the signal's handler own overhead.
    3. Timer fires : When the configured CPU time elapses, the kernel's hrtimer fires and delivers a signal to the target thread.
    4. Signal handler : Async-profiler's signal handler captures the stack trace and records the sample . At the end of the handler, it resets the counter and re-arms the event for the next sample:

    ioctl(fd, PERF_EVENT_IOC_RESET, 0); // Clear the counter

    ioctl(fd, PERF_EVENT_IOC_REFRESH, 1); // Arm for exactly 1 more sample

    This cycle repeats for the duration of the profiling session, creating a stream of stack trace samples that are later aggregated into flame graphs or heatmaps.

    The Kernel Bug

    The kernel bug that caused my machine to freeze was introduced by commit 18dbcbfabfff ("perf: Fix the POLL_HUP delivery breakage") . Ironically, this commit was fixing a different bug, but it introduced a deadlock in the cpu-clock event handling.

    Here's what happens in the buggy kernel when the PERF_EVENT_IOC_REFRESH(1) counter reaches zero:

    1. hrtimer fires for cpu-clock event - perf_swevent_hrtimer() is called (inside hrtimer interrupt context)
    2. perf_swevent_hrtimer() calls __perf_event_overflow() - this processes the counter overflow
    3. __perf_event_overflow() decides to stop the event (counter reached 0 after PERF_EVENT_IOC_REFRESH(1) ) - calls cpu_clock_event_stop()
    4. cpu_clock_event_stop() calls perf_swevent_cancel_hrtimer() - this calls hrtimer_cancel() to cancel the timer
    5. DEADLOCK : hrtimer_cancel() waits for the hrtimer callback to complete - but we ARE inside the hrtimer callback! The system hangs forever waiting for itself

    The function hrtimer_cancel() is a blocking call - it spins waiting for any active callback to finish.

    int hrtimer_cancel(struct hrtimer *timer)

    {

    int ret;

    do {

    ret = hrtimer_try_to_cancel(timer);

    if (ret < 0)

    hrtimer_cancel_wait_running(timer);

    } while (ret < 0);

    return ret;

    }

    When called from inside that same callback, it waits forever. Since this happens in interrupt context with interrupts disabled on the CPU, that CPU becomes completely unresponsive. When this happens on multiple CPUs (which it does, since each thread has its own perf_event ), the entire system freezes.

    Click to see the deadlock visualized

    The Fix

    The kernel patch fixes this deadlock with two changes:

    1. Replace hrtimer_cancel() with hrtimer_try_to_cancel()

    - hrtimer_cancel(&hwc->hrtimer);

    + hrtimer_try_to_cancel(&hwc->hrtimer);

    hrtimer_try_to_cancel() is non-blocking - it returns immediately with:

    • 0 if the timer was not active
    • 1 if the timer was successfully cancelled
    • -1 if the timer callback is currently running

    Unlike hrtimer_cancel() , it doesn't spin waiting for the callback to finish. So when called from within the callback itself, it simply returns -1 and continues.

    1. Use PERF_HES_STOPPED flag as a deferred stop signal

    The stop function now sets a flag:

    static void cpu_clock_event_stop(struct perf_event *event, int flags)

    {

    + event->hw.state = PERF_HES_STOPPED;

    perf_swevent_cancel_hrtimer(event);

    ...

    }

    And the hrtimer callback checks this flag:

    static enum hrtimer_restart perf_swevent_hrtimer(struct hrtimer *hrtimer)

    {

    - if (event->state != PERF_EVENT_STATE_ACTIVE)

    + if (event->state != PERF_EVENT_STATE_ACTIVE ||

    + event->hw.state & PERF_HES_STOPPED)

    return HRTIMER_NORESTART;

    How It Works Together

    When cpu_clock_event_stop() is called from within the hrtimer callback:

    1. PERF_HES_STOPPED flag is set
    2. hrtimer_try_to_cancel() returns -1 (callback running) - but doesn't block
    3. Execution returns up the call stack back to perf_swevent_hrtimer()
    4. perf_swevent_hrtimer() completes and returns HRTIMER_NORESTART (because __perf_event_overflow() returned 1 , indicating the event should stop)
    5. The hrtimer subsystem sees HRTIMER_NORESTART and doesn't reschedule the timer

    When cpu_clock_event_stop() is called from outside the callback (normal case):

    1. PERF_HES_STOPPED flag is set
    2. hrtimer_try_to_cancel() returns 0 or 1 - timer is cancelled immediately
    3. If by chance the callback fires before cancellation completes, it sees PERF_HES_STOPPED and returns HRTIMER_NORESTART

    The PERF_HES_STOPPED flag acts as a safety net to make sure the timer stops regardless of the race between setting the flag and the timer firing.

    Debugging a kernel

    The explanation above is my understanding of the kernel bug and the fix based on reading the kernel source code. I am a hacker, I like to tinker. A theoretical understanding is one thing, but I wanted to see it in action. But how do you even debug a kernel? I'm not a kernel developer, but I decided to try. Here is how I did it.

    My intuition was to use QEMU since it allows one to emulate or virtualize a full machine. QEMU also has a built-in GDB server that allows you to connect GDB to the emulated machine .

    Setting up QEMU with Ubuntu

    I downloaded an Ubuntu 25.10 ISO image and created a new empty VM disk image:

    $ qemu-img create -f qcow2 ubuntu-25.10.qcow2 20G

    Then I launched QEMU to install Ubuntu:

    $ qemu-system-x86_64 \

    -enable-kvm \

    -m 4096 \

    -smp 4 \

    -drive file=ubuntu-25.10.qcow2,if=virtio \

    -cdrom ubuntu-25.10-desktop-amd64.iso \

    -boot d \

    -vga qxl

    The second command boots the VM from the ISO image and allows me to install Ubuntu on the VM disk image. I went through the installation process as usual. I probably could have used a server edition or a prebuilt image, but at this point I was already in unknown territory, so I wanted to make other things as simple as possible.

    QEMU screen showing Ubuntu installation

    Ubuntu installation in QEMU

    Once the installation was complete, I rebooted the VM:

    $ qemu-system-x86_64 \

    -enable-kvm \

    -m 4096 \

    -smp 4 \

    -drive file=ubuntu-25.10.qcow2,if=virtio \

    -netdev user,id=net0,hostfwd=tcp::9000-:9000 \

    -device virtio-net-pci,netdev=net0 \

    -monitor tcp:127.0.0.1:55555,server,nowait \

    -s

    and downloaded, unpacked and started QuestDB:

    $ curl -L https://github.com/questdb/questdb/releases/download/9.2.2/questdb-9.2.2-rt-linux-x86-64.tar.gz -o questdb.tar.gz

    $ tar -xzvf questdb.tar.gz

    $ cd questdb-9.2.2-rt-linux-x86-64

    $ ./bin/questdb start

    This was meant to validate that QuestDB works in the VM at all. Firefox was already installed in the Ubuntu desktop edition, so I just opened http://localhost:9000 in Firefox and verified QuestDB web console was up and running.

    QuestDB web console running in Ubuntu in QEMU

    QuestDB web console in QEMU

    The next step was to stop QuestDB and start it with a profiler attached:

    $ ./bin/questdb stop

    $ ./bin/questdb start -p

    At this point, I expected the virtual machine to freeze. However, it didn't. It was responsive as if nothing bad had happened. That was a bummer. I wanted to see the deadlock in action! I thought that perhaps QEMU is in a way shielding the virtual machine from the bug. But then I realized that the default Ubuntu uses paranoia settings that prevent perf_events from working properly and async-profiler falls back to using ctimer when perf_events are restricted. The kernel bug specifically lives in the perf_events hrtimer code path, so we must force async-profiler to use that path to trigger the bug.

    To fix this, I changed the paranoia settings:

    $ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid

    After this, I restarted QuestDB with the profiler again:

    $ ./bin/questdb stop

    $ ./bin/questdb start -p

    And this time, the virtual machine froze as expected! Success! I was able to reproduce the problem in QEMU!

    Attaching GDB to QEMU

    Now that I was able to reproduce the problem in QEMU, I wanted to attach GDB to the emulated machine to see the deadlock in action.

    Let's start GDB on the host machine and connect it to QEMU's built-in GDB server:

    $ gdb

    GNU gdb (Ubuntu 16.3-1ubuntu2) 16.3

    [...]

    (gdb) target remote :1234

    Remote debugging using :1234

    warning: No executable has been specified and target does not support

    determining executable automatically. Try using the "file" command.

    0xffffffff82739398 in ?? ()

    (gdb) info threads

    Id Target Id Frame

    * 1 Thread 1.1 (CPU#0 [running]) 0xffffffff82739398 in ?? ()

    2 Thread 1.2 (CPU#1 [running]) 0xffffffff82739398 in ?? ()

    3 Thread 1.3 (CPU#2 [running]) 0xffffffff827614d3 in ?? ()

    4 Thread 1.4 (CPU#3 [running]) 0xffffffff82739398 in ?? ()

    (gdb) thread apply all bt

    Side note: We just casually attached a debugger to a live kernel! How cool is that?

    We can see 4 threads corresponding to the 4 CPUs in the VM. The bt command shows the stack traces of all threads, but there is not much useful information since we don't have the kernel symbols loaded in GDB. Let's fix this. I am lazy again and take advantage of running exactly the same kernel version as the host machine so I can use the host's kernel image and symbol files.

    On the host machine, we need to add repositories with debug symbols and install the debug symbols for the running kernel:

    echo "deb http://ddebs.ubuntu.com questing main restricted universe multiverse" | sudo tee /etc/apt/sources.list.d/ddebs.list

    echo "deb http://ddebs.ubuntu.com questing-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ddebs.list

    echo "deb http://ddebs.ubuntu.com questing-proposed main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ddebs.list

    sudo apt install ubuntu-dbgsym-keyring

    sudo apt update

    sudo apt install linux-image-$(uname -r)-dbgsym

    With the debug symbols installed, I started GDB again and loaded the kernel image and symbols:

    $ gdb /usr/lib/debug/boot/vmlinux-$(uname -r)

    GNU gdb (Ubuntu 16.3-1ubuntu2) 16.3

    [...]

    gdb) target remote :1234

    Remote debugging using :1234

    0xffffffff9e9614d3 in ?? ()

    [...]

    (gdb) info threads

    Id Target Id Frame

    * 1 Thread 1.1 (CPU#0 [running]) 0xffffffff9e9614d3 in ?? ()

    2 Thread 1.2 (CPU#1 [running]) 0xffffffff9e939398 in ?? ()

    3 Thread 1.3 (CPU#2 [running]) 0xffffffff9e9614d3 in ?? ()

    4 Thread 1.4 (CPU#3 [running]) 0xffffffff9e9614d3 in ?? ()

    (gdb) quit

    and symbols were still NOT resolved! I had to capitulate and ask a LLM for help. After a bit of brainstorming, we realized that the kernel is compiled with KASLR enabled, so the kernel is loaded at a random address at each boot. The simplest way to fix this is to disable KASLR, I could not care less about security in my test VM. To disable KASLR, I edited the GRUB configuration, added the nokaslr parameter, updated GRUB and rebooted the VM:

    $ vim /etc/default/grub

    # Add nokaslr to the GRUB_CMDLINE_LINUX_DEFAULT line

    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nokaslr"

    $ sudo update-grub

    $ sudo reboot

    Then I set the paranoia settings again, started QuestDB with the profiler and attached GDB again. This time, the symbols were resolved correctly!

    $ gdb /usr/lib/debug/boot/vmlinux-$(uname -r)

    GNU gdb (Ubuntu 16.3-1ubuntu2) 16.3

    [...]

    (gdb) target remote :1234

    [...]

    (gdb) info threads

    Id Target Id Frame

    * 1 Thread 1.1 (CPU#0 [running]) csd_lock_wait (csd=0xffff88813bd3a460) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    2 Thread 1.2 (CPU#1 [running]) csd_lock_wait (csd=0xffff88813bd3b520) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    3 Thread 1.3 (CPU#2 [running]) hrtimer_try_to_cancel (timer=0xffff88802343d028) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    4 Thread 1.4 (CPU#3 [running]) hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    This looks much better! We can see that the first 2 threads are stuck in csd_lock_wait() function, presumably waiting for locks held by the other CPUs and threads 3 and 4 are in hrtimer_try_to_cancel() .

    The threads 3 and 4 are the interesting ones since they execute a function related to the kernel bug we are investigating. Let's switch to thread 4 and see its stack trace:

    (gdb) thread 4

    [Switching to thread 4 (Thread 1.4)]

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    1359 in /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c

    (gdb) bt

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    #1 hrtimer_cancel (timer=timer@entry=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1488

    #2 0xffffffff81700605 in perf_swevent_cancel_hrtimer (event=<optimized out>) at /build/linux-8YMEfB/linux-6.17.0/kernel/events/core.c:11818

    #3 perf_swevent_cancel_hrtimer (event=0xffff888023439f80) at /build/linux-8YMEfB/linux-6.17.0/kernel/events/core.c:11805

    #4 cpu_clock_event_stop (event=0xffff888023439f80, flags=0) at /build/linux-8YMEfB/linux-6.17.0/kernel/events/core.c:11868

    #5 0xffffffff81715488 in __perf_event_overflow (event=event@entry=0xffff888023439f80, throttle=throttle@entry=1, data=data@entry=0xffffc90002cd7cc0, regs=0xffffc90002cd7f48) at /build/linux-8YMEfB/linux-6.17.0/kernel/events/core.c:10338

    #6 0xffffffff81716eaf in perf_swevent_hrtimer (hrtimer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/events/core.c:11774

    #7 0xffffffff81538a03 in __run_hrtimer (cpu_base=<optimized out>, base=<optimized out>, timer=0xffff88802343a0e8, now=0xffffc90002cd7e58, flags=<optimized out>) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1761

    #8 __hrtimer_run_queues (cpu_base=cpu_base@entry=0xffff88813bda1400, now=now@entry=48514890563, flags=flags@entry=2, active_mask=active_mask@entry=15) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1825

    #9 0xffffffff8153995d in hrtimer_interrupt (dev=<optimized out>) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1887

    #10 0xffffffff813c4ac8 in local_apic_timer_interrupt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/apic/apic.c:1039

    #11 __sysvec_apic_timer_interrupt (regs=regs@entry=0xffffc90002cd7f48) at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/apic/apic.c:1056

    #12 0xffffffff82621724 in instr_sysvec_apic_timer_interrupt (regs=0xffffc90002cd7f48) at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/apic/apic.c:1050

    #13 sysvec_apic_timer_interrupt (regs=0xffffc90002cd7f48) at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/apic/apic.c:1050

    #14 0xffffffff81000f0b in asm_sysvec_apic_timer_interrupt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/include/asm/idtentry.h:574

    #15 0x00007b478171db80 in ?? ()

    #16 0x0000000000000001 in ?? ()

    #17 0x0000000000000000 in ?? ()

    We can see the exact sequence of function calls leading to the deadlock: hrtimer_try_to_cancel() called from cpu_clock_event_stop() , called from __perf_event_overflow() , called from perf_swevent_hrtimer() . This matches our understanding of the bug perfectly! This is the infinite loop in hrtimer_cancel() that causes the deadlock.

    Forensics and Playing God

    Okay, I have to admit that seeing a kernel stack trace is already somewhat satisfying, but we have a live (well, half-dead) kernel under a debugger. Let's have some fun. I want to touch the deadlock and understand why it took down the whole machine, and see if we can perform a miracle and bring it back to life.

    Confirming the suspect

    We know hrtimer_cancel is waiting for a callback to finish. But which callback? The stack trace says perf_swevent_cancel_hrtimer , but let's verify the hrtimer struct in memory actually points to the function we blame.

    I switched to the stuck thread (Thread 4 in my case) and looked at frame #0:

    (gdb) thread 4

    [Switching to thread 4 (Thread 1.4)]

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    1359 in /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c

    (gdb) frame 0

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    1359 in /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c

    (gdb) print *timer

    $1 = {node = {node = {__rb_parent_color = 18446612682661667048, rb_right = 0x0, rb_left = 0x0}, expires = 48514879474}, _softexpires = 48514879474, function = 0xffffffff81716dd0 <perf_swevent_hrtimer>, base = 0xffff88813bda1440, state = 0 '\000', is_rel = 0 '\000', is_soft = 0 '\000', is_hard = 1 '\001'}

    Let me explain these GDB commands: frame 0 selects the innermost stack frame - the function currently executing. In a backtrace, frame 0 is the current function, frame 1 is its caller, frame 2 is the caller's caller, and so on. By selecting frame 0, I can inspect local variables and parameters in hrtimer_try_to_cancel() .

    The print *timer command dereferences the timer pointer and displays the contents of the struct hrtimer :

    struct hrtimer {

    struct timerqueue_node node;

    ktime_t _softexpires;

    enum hrtimer_restart (*function)(struct hrtimer *);

    struct hrtimer_clock_base *base;

    u8 state;

    u8 is_rel;

    u8 is_soft;

    u8 is_hard;

    };

    The key field here is function - a pointer to a callback function that takes a struct hrtimer * and returns enum hrtimer_restart . This callback is invoked when the timer fires. GDB shows it points to 0xffffffff81716dd0 and helpfully resolves this address to perf_swevent_hrtimer . Since we're currently inside perf_swevent_hrtimer (look at frame #6 in our backtrace above), this confirms the self-deadlock: the timer is trying to cancel itself while its own callback is still running!

    The Mystery of the "Other" CPUs

    One question remained: If CPUs 3 and 4 are deadlocked in a loop, why did the entire machine freeze? Why couldn't I just SSH in and kill the process? The answer lies in those other threads we saw earlier, stuck in csd_lock_wait :

    (gdb) thread 1

    [Switching to thread 1 (Thread 1.1)]

    #0 csd_lock_wait (csd=0xffff88813bd3a460) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    warning: 351 /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c: No such file or directory

    CSD stands for Call Function Single Data. In Linux, when one CPU wants another CPU to do something (like flush a TLB or stop a perf_event), it sends an IPI (Inter-Processor Interrupt). If the target CPU is busy with interrupts disabled (which is exactly the case for our deadlocked CPUs 3 and 4), it never responds.

    The sender (CPU 0) sits there spinning , waiting for the other CPU to say "Done!" . Eventually, all CPUs end up waiting for the stuck CPUs and the entire system grinds to a halt.

    Performing a Kernel Resurrection

    This is the part where the real black magic starts. We know the kernel is stuck in this loop in hrtimer_cancel :

    do {

    ret = hrtimer_try_to_cancel(timer);

    } while (ret < 0);

    As long as hrtimer_try_to_cancel returns -1 (which it does, because the callback is running), the loop continues forever.

    But we have GDB. We can change reality.

    If we force the function to return 0 (meaning "timer not active"), the loop should break, cpu_clock_event_stop should finish, and the kernel should unfreeze. It might crash 1 millisecond later because we left the timer in an inconsistent state, but perhaps it's worth trying.

    First, let's double-check we are in the innermost frame, inside hrtimer_try_to_cancel :

    (gdb) thread 4

    [Switching to thread 4 (Thread 1.4)]

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    warning: 1359 /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c: No such file or directory

    (gdb) frame 0

    #0 hrtimer_try_to_cancel (timer=0xffff88802343a0e8) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    1359 in /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c

    Use the GDB finish command to let the function run to completion and pause right when it returns to the caller:

    We are now sitting at line 1490, right at the check if (ret < 0) .

    int hrtimer_cancel(struct hrtimer *timer)

    {

    int ret;

    do {

    ret = hrtimer_try_to_cancel(timer);

    if (ret < 0) // <-- we are here

    hrtimer_cancel_wait_running(timer);

    } while (ret < 0);

    return ret;

    }

    On x86_64, integer return values are passed in the %rax register. Since hrtimer_try_to_cancel returns an int (32-bit), we can use $eax (the lower 32 bits of %rax ):

    Exactly as expected. -1 means the timer callback is running, so the loop will continue. But since the CPU is paused, we can overwrite this value. We can lie to the kernel and tell it the timer was successfully cancelled (return code 1) or inactive (return code 0). I chose 0.

    (gdb) set $eax = 0

    (gdb) print $eax

    $3 = 0

    I crossed my fingers and unpaused the VM:

    (gdb) continue

    Continuing.

    And it did nothing. The VM was still frozen. Let's see what is going on:

    (gdb) info threads

    Id Target Id Frame

    1 Thread 1.1 (CPU#0 [running]) csd_lock_wait (csd=0xffff88813bd3a460) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    2 Thread 1.2 (CPU#1 [running]) csd_lock_wait (csd=0xffff88813bd3b520) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    3 Thread 1.3 (CPU#2 [running]) hrtimer_try_to_cancel (timer=0xffff88802343d028) at /build/linux-8YMEfB/linux-6.17.0/kernel/time/hrtimer.c:1359

    * 4 Thread 1.4 (CPU#3 [running]) csd_lock_wait (csd=0xffff88813bd3b560) at /build/linux-8YMEfB/linux-6.17.0/kernel/smp.c:351

    Now, thread 4 is also stuck in csd_lock_wait , just like threads 1 and 2. We managed to escape from the infinite loop in thread 4, but thread 3 is still stuck in hrtimer_try_to_cancel .

    We could try the same trick on thread 3, but would this be enough to unfreeze the entire system? For starters, we tricked the kernel into thinking the timer was inactive, but in reality it is still active. This is very thin ice to skate on - we might have just created more problems for ourselves. And more importantly, even if the kernel could escape the deadlock, the profiler would immediately try to re-arm the timer again, leading us back into the same deadlock.

    So I decided to give up on the resurrection attempt. The kernel was stuck, but at least I understood the problem now and I was pretty happy with my newly acquired kernel debugging skills.

    Conclusion

    While I couldn't perform a miracle and resurrect the frozen kernel, I walked away with a much deeper understanding of the machinery behind Linux perf_events and hrtimers. I learned how to set up QEMU for kernel debugging, how to attach GDB to a live kernel, and how to inspect kernel data structures in memory.

    For QuestDB users, the takeaway is simple: if you are on a kernel version 6.17, use the -e ctimer flag when profiling. It bypasses the buggy perf_events hrtimer path entirely. Or just wait for either the kernel fix to land in your distro or the next QuestDB release, which will include an async-profiler version that works around this issue .

    As for me, I’m going back to my code. The next time my machine freezes, I might just reboot it like a normal person. But where is the fun in that?

    Addendum: The Second Resurrection Attempt

    After writing this post, I kept thinking about that failed resurrection attempt. We got so close: We broke one CPU out of the deadlock, but the other was still stuck. I should have tried harder! So I started QEMU again, reproduced the deadlock, and this time came with a plan: use GDB to force kernel to kill the QuestDB Java process, so the profiler can't re-arm the timer.

    First, I needed to find the Java process. The perf_event structure has an owner field pointing to the task that created it:

    (gdb) print event->owner

    $1 = (struct task_struct *) 0xffff88810b2ed100

    (gdb) print ((struct task_struct *)0xffff88810b2ed100)->comm

    $2 = "java"

    (gdb) print ((struct task_struct *)0xffff88810b2ed100)->pid

    $3 = 4488

    Great, we found the Java process with PID 4488. Now, how do you kill a process when the kernel is deadlocked and can't process signals? You store the signal directly in memory. SIGKILL is signal 9, which means bit 8 in the signal bitmask:

    (gdb) set ((struct task_struct *)0xffff88810b2ed100)->signal->shared_pending.signal.sig[0] = 0x100

    With the pending kill signal in place, I broke the deadlock loop as before. The system hit another deadlock - a different CPU was now stuck waiting for a spinlock. The lock value showed it was held:

    (gdb) print *((unsigned int *)0xffff88813bda1400)

    $4 = 1

    I forcibly released the lock (what could possibly go wrong?) :

    (gdb) set *((unsigned int *)0xffff88813bda1400) = 0

    Then broke another hrtimer loop on a different CPU. It was like playing whack-a-mole with deadlocks - each Java thread had its own perf_event , and they were all hitting the same bug.

    After a few rounds of this, I ran continue and checked the threads:

    (gdb) continue

    Continuing.

    ^C

    Thread 4 received signal SIGINT, Interrupt.

    0xffffffff82621d8b in pv_native_safe_halt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/paravirt.c:82

    (gdb) info threads

    Id Target Id Frame

    1 Thread 1.1 (CPU#0 [halted ]) 0xffffffff82621d8b in pv_native_safe_halt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/paravirt.c:82

    2 Thread 1.2 (CPU#1 [halted ]) 0xffffffff82621d8b in pv_native_safe_halt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/paravirt.c:82

    3 Thread 1.3 (CPU#2 [halted ]) 0xffffffff82621d8b in pv_native_safe_halt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/paravirt.c:82

    * 4 Thread 1.4 (CPU#3 [halted ]) 0xffffffff82621d8b in pv_native_safe_halt () at /build/linux-8YMEfB/linux-6.17.0/arch/x86/kernel/paravirt.c:82

    (gdb) continue

    terminal screen showing gdb output

    GDB output showing all CPUs resting peacefully in halt state

    I looked at the QEMU window. The desktop was responsive. The mouse moved. Java was gone - killed by the SIGKILL we planted before breaking the deadlock. We actually did it. We resurrected a kernel-deadlocked machine by lying to it about return values, forcibly releasing locks, and planting signals in process memory. Would I recommend this in production (or anywhere outside a lab)? Absolutely not. But was it fun? Totally!

    QEMU screen showing Ubuntu desktop

    Lazarus rising from the dead