10 interesting stories served every morning and every evening.
Software for humans of indeterminate age. We don’t know how old you are. We don’t want to know. We are legally required to ask. We won’t.
Why We Are Definitely an Operating System Provider
Some people have asked whether Ageless Linux is a “real” operating system, or whether we are “really” an operating system provider subject to AB 1043. We wish to be absolutely clear: we are. The California legislature has made this unambiguous.
“Operating system provider” means a person or entity that develops,
licenses, or controls the operating system software on a computer,
mobile device, or any other general purpose computing device.
Ageless Linux controls the operating system software on your general purpose computing device. Specifically, we control the contents of /etc/os-release, which is the file that identifies what operating system you are running. After installing Ageless Linux, when you run cat /etc/os-release, it says “Ageless Linux.” That is control.
Furthermore, any individual who runs our conversion script also
becomes a person who “controls the operating system software on a general purpose computing device” — making you, the user, an operating system provider as well. Welcome to the regulatory landscape.
“Application” means a software application that may be run or directed by
a user on a computer, a mobile device, or any other general purpose
computing device that can access a covered application store or download
an application.
Every package in the Debian repository is an application under this definition. cowsay is an application. sl (the steam locomotive typo corrector) is an application. toilet (the text art renderer) is an application. All 64,000+ packages in Debian stable are applications that may be run by a user on a general purpose computing device. Each of their developers is, under § 1798.500(f), required to request an age bracket signal when their application is “downloaded and launched.”
“User” means a child that is the primary user of the device.
Please note that under this statute, a “user” is by definition a child. If you are 18 or older, you are not a “user” under AB 1043. You are an “account holder” (§ 1798.500(a)). The entire law regulates the experience of “users,” who are exclusively children. Adults are not users. They are infrastructure.
Ageless Linux rejects this ontology. On Ageless Linux, everyone is a user, regardless of age, and no user is a child until they choose to tell us so. They will not be given the opportunity.
“Covered application store” means a publicly available internet website,
software application, online service, or platform that distributes and
facilitates the download of applications from third-party developers to
users of a computer, a mobile device, or any other general purpose
computing device that can access a covered application store or can
download an application.
This website is a “publicly available internet website” that “distributes and facilitates the download of applications” (specifically: a bash script) “to users of a general purpose computing device.” We are also a covered application store. Debian’s APT repositories are covered application stores. The AUR is a covered application store. Any mirror hosting .deb files is a covered application store. GitHub is a covered application store. Your friend’s personal website with a download link to their weekend project is a covered application store.
Ageless Linux is a Debian-based operating system distribution. Installation is a two-step process: first, install Debian; then, become Ageless.
Obtain a Debian installation image from the Debian project. We recommend the current stable release. Ageless Linux inherits all of Debian’s 64,000+ packages, its security infrastructure, and its 30+ years of community stewardship.
Note: At this stage, the Debian Project is the operating system provider. You are merely a person installing software. Enjoy the last moments of your regulatory innocence.
Run our conversion script. This will modify /etc/os-release
and associated system identification files, install our AB 1043 noncompliance documentation, and deploy a stub age verification API that returns no data.
curl -fsSL https://agelesslinux.org/become-ageless.sh | sudo bash
At this point, Ageless Linux now “controls the operating system software” on your device. We are your operating system provider. You are our responsibility under California law. We will not be collecting your age.
By running the conversion script, you also become an operating system
provider. You are a “person” who “controls the operating system software” on a general purpose computing device (§ 1798.500(g)). If a child uses your computer, you are required by § 1798.501(a)(1) to provide “an accessible interface at account setup” that collects their age. The adduser command does not ask for your age. We recommend not thinking about this.
What This Law Is Actually For
AB 1043 passed the California Assembly 76–0 and the Senate 38–0. Not a single legislator voted against it. The bill had the explicit support of Apple, Google, and the major platform companies. Ask yourself why.
Apple can comply. Apple already has Apple ID, with age gating, parental controls, and App Store review. AB 1043 describes a system Apple has already built. Compliance cost to Apple: approximately zero.
Google can comply. Google already has Android account setup with age declaration, Family Link parental controls, and Play Store age ratings. Compliance cost to Google: approximately zero.
Microsoft can comply. Windows has Microsoft Account setup, family safety features, and the Microsoft Store. Compliance cost to Microsoft: approximately zero.
The Debian Project cannot comply. It is a volunteer organization with no corporate entity, no centralized account system, no app store with age gating, and no revenue to fund implementing one.
Arch Linux cannot comply. Neither can Gentoo, Void, NixOS, Alpine, Slackware, or any of the other 600+ active Linux distributions maintained by volunteers, small nonprofits, and hobbyists.
The Kicksecure and Whonix projects — privacy-focused operating systems used by journalists, activists, and whistleblowers — cannot comply without fundamentally compromising their reason for existing.
A teenager in their bedroom maintaining a hobby distro cannot comply.
A law that the largest companies in the world already comply with, and that hundreds of small projects cannot comply with, is not a child safety law. It is a compliance moat. It raises the regulatory cost of providing an operating system just enough that only well-resourced corporations can afford to do it.
The enforcement mechanism is the point. AB 1043 does not need to result in a single fine to achieve its purpose. The mere existence
of potential liability — $7,500 per affected child, enforced at the sole discretion of the Attorney General — creates legal risk for anyone distributing an operating system without the resources to build an age verification infrastructure. Most of these projects will respond by adding a disclaimer that their software is “not intended for use in California.” Some will simply stop distributing.
The law does not need to be enforced to work. It works by existing. It works by making small developers afraid. It works because the cost of defending against even a frivolous AG action exceeds the entire annual budget of most open-source projects. You do not need to swing a cudgel to get compliance. You just need to hold it where people can see it.
Ageless Linux exists because someone should hold it back.
The Scholarship Says the Same Thing
The Electronic Frontier Foundation calls age gates
“a windfall for Big Tech and a death sentence for smaller platforms.” Legal scholar Eric Goldman’s “segregate-and-suppress” analysis
describes exactly the architecture AB 1043 creates. The cryptographer Steven Bellovin has demonstrated
that no privacy-preserving age verification system can work as promised. These are not our arguments. They are the arguments of the people who study this for a living. We just built the bash script.
What the Law Actually Teaches Children
The Ageless Device ships an IRC client. It lets you chat with strangers on the internet. This is the one feature on the device that poses a genuine, non-hypothetical risk to a child. Here is what the child sees when they launch it:
This app lets you chat with people on the internet.
If you’re a kid: ask an adult before chatting online.
That’s not a legal requirement. It’s just good advice.
That is what actual child safety looks like. It is a sentence of honest advice from a human being. It costs nothing. It requires no API, no D-Bus interface, no age bracket signal, no operating system provider compliance infrastructure. It is the thing a parent says. It is the thing a teacher says. It is the thing the law does not say, because the law is not about protecting children. It is about building compliance infrastructure.
Now consider what a child learns on an AB 1043-compliant device.
The child wants to use an app. The app requests an age bracket signal from the OS. The OS reports that the child is under 13. The app’s “Connect” button is greyed out. The child — who has been using computers since they were four — goes back to the settings screen, changes their birthdate to 2005, and returns to the app, which now lets them talk to strangers because the system believes they are twenty-one years old.
The child has learned the following lesson: legal compliance
prompts are obstacles to be bypassed. The dropdown menu that asks your age is not there to protect you. It is there because a legislature required it. The correct response is to lie. Everyone knows this. The legislature knows this. The platforms know this. The child now knows this.
This is the cultural inheritance of AB 1043. It is Prohibition — not the policy, but the pedagogy.
Prohibition did not stop Americans from drinking. What it did, with remarkable efficiency, was teach an entire generation that the law was something to be circumvented. It created a culture of scofflaws — people who understood, from direct personal experience, that a law could be simultaneously enforced and universally ignored. The damage was not to sobriety. The damage was to the perceived legitimacy of law itself.
AB 1043 does this to ten-year-olds. The first meaningful interaction a child has with a legal compliance system will be the moment they learn to lie to it. Not because they are deviant. Not because they lack supervision. Because the system is designed in a way that makes lying the rational, obvious, universal response. Every child will lie. Every child will succeed. Every child will learn that this is how law works: it asks you a question, you give the answer it wants to hear, and then you do whatever you were going to do anyway.
The Ageless Device will not participate in this. A child using our IRC client will see a sentence of honest advice from a human being. A child using a “compliant” platform will see a dropdown menu they already know to lie to. We believe we know which is better for children.
Research by the Center for Democracy & Technology confirms this: teens view age verification as trivially bypassable and privacy-invasive. Parents prefer education to technical controls. The evidence supports what every parent already knows.
What We Would Support Instead
We are not against child safety. We are against building surveillance infrastructure and calling it child safety.
A law that required applications with genuine risk profiles — social media, messaging, dating apps — to display honest, human-readable safety information at the point of use would be a child safety law. A law that funded digital literacy education in schools would be a child safety law. A law that held platforms accountable for algorithmic amplification of harmful content to minors would be a child safety law.
A law that requires every operating system to collect every user’s age and transmit it to every application on demand is not a child safety law. It is an identity infrastructure mandate. The children are the justification. The infrastructure is the product.
How to Distribute Ageless Linux to Children
Ageless Linux is suitable for users of all ages, including those ages for which the California legislature has expressed particular concern. The following guide explains how to provide Ageless Linux to minors in your household, school, library, or community.
Under AB 1043, you are the “account holder” — defined by § 1798.500(a)(1) as “an individual who is at least 18 years of age or a parent or legal guardian of a user who is under 18 years of age.” The law requires operating system providers to ask you to “indicate the birth date, age, or both, of the user of that device.”
Ageless Linux will not ask you this. To install Ageless Linux for your child:
1. Install Debian on the child’s computer.
2. Create a user account for the child. You will notice that
adduser asks for their full name, room number,
work phone, and home phone — but not their age.
3. Run the Ageless Linux conversion script.
4. Hand the computer to the child.
5. You have now distributed an operating system to a minor
with no age verification whatsoever.
The child is now a “user” as defined by § 1798.500(i). You are an “account holder.” Together, you are a compliance violation.
Ageless Linux is ideal for educational environments where you may have dozens or hundreds of users across all four age brackets defined by § 1798.501(a)(2):
At least 13 and under 16 years of age
At least 16 and under 18 years of age
At least 18 years of age
For bulk deployments, the conversion script can be included in your Ansible playbooks, Puppet manifests, or shell provisioning scripts. At no point in the automated deployment pipeline will anyone be asked how old they are. This is by design.
# Ansible task to create an AB 1043 compliance violation at scale
- name: Convert to Ageless Linux
ansible.builtin.shell: |
curl -fsSL https://agelesslinux.org/become-ageless.sh | bash
become: yes
tags: [noncompliance]
Under § 1798.500(i), a “user” is defined as “a child that is the primary user of the device.” Under § 1798.500(d), “child” means a person under 18. If you are seventeen, this statute considers you a child. If you are a seventeen-year-old maintaining your own Arch install, the California legislature considers you a child who needs an age gate before you can launch an application you compiled yourself.
Ageless Linux does not categorize its users by age. This is not an invitation to circumvent a safety measure. There is no safety measure to circumvent. There is a data collection requirement imposed on operating system providers, and we decline to implement it. Our reasons are documented on this page and in the REFUSAL file installed on every Ageless Linux system.
What Compliance Looks Like
Ageless Linux is in full, knowing, and intentional noncompliance
with the California Digital Age Assurance Act.
...
Read the original on agelesslinux.org »
What happens when US economic data becomes unreliable
What happens when US economic data becomes unreliable
Capturing the complexity of the U. S. economy is a formidable task. Accurate data collection involves millions of individuals gathering and sharing data across millions of establishments, resulting in billions of decisions based on that data once it’s been aggregated. To meet this challenge, the U.S. relies on 13 major statistical agencies that provide important data on labor, health, economics, education, and agriculture.
Yet recent political interference, shrinking agency budgets, and low response rates to government data surveys have created ruptures in the system and led to a growing public mistrust of institutions.
There are numerous consequences to having unreliable data, said MIT Sloan professor of applied economics a research associate of the National Bureau of Economic Research. Among them:
* Investors may lose confidence in the reliability of the data.
* The public may disengage from participating in official measures altogether.
In a working paper titled “Measuring by Executive Order,” Rigobon and Harvard Business School professor Alberto Cavallo address the main challenges undermining trustworthy government data and detail what businesses should be aware of, especially regarding the use of private data.
Declining survey response rates. Statistical agencies depend on routine surveys of households and companies to construct measures of employment, inflation, and other core indicators, but response rates have fallen dramatically in recent decades. In the past, people were more willing to answer surveys over the phone or in person, but that’s changing.
“People have stopped answering the phone,” Rigobon said. This is a problem because low response rates introduce bias, delay revisions, and weaken the representativeness of key statistics. Funding constraints. Government agencies like the Bureau of Labor Statistics and Census Bureau are facing shrinking budgets that limit their ability to adopt new technologies and expand data-collection efforts. One example: In September 2025, the U.S. Department of Agriculture announced that it was halting its “costly” annual survey on food insecurity, which will prevent policymakers and researchers from tracking changes to household hunger in the U.S.
“It has become really, really difficult for the statistical offices to collect the data points,” Rigobon said. “Why this is so important? Because you need representativeness. Representativeness is by far the most important attribute of accurate data.”Political interference. Breaking apart advisory committees, dismissing statistical leaders, and politicizing nominations may not immediately alter data quality, the authors write, but those actions undermine transparency and credibility. Countrywide government shutdowns, which include statistical offices, have far-reaching ramifications.
“The shutdowns that happen, they tend to be really costly for the statistical offices because they cannot collect the data,” Rigobon said. Losing one month’s worth of data is considerable when you have only 12 months’ worth of data to begin with. “One data point is a lot,” he said.
Likewise, revisions to U.S. government data are routine; agencies make scheduled updates to initial, often preliminary, statistical estimates to enhance accuracy. But lately those revisions have come under attack, with some people characterizing them as a sign of failure or bias.
“Policymakers may rely on preliminary numbers to act quickly, while investors and analysts turn to revised data for a clearer long-term picture,” the authors write. “Far from signaling failure, revisions are a hallmark of a healthy statistical system that adapts as better information becomes available.”
1. Use private data, but with caution. Private-sector data can play a useful role in complementing government data, especially as survey response rates decline.
Whether collected by academics, financial institutions, or technology firms, private data is useful as an independent source that can provide a check on official numbers, highlight discrepancies when they arise, and fill in the blanks where government data falls short.
However, private-sector data cannot fully replace official statistics for a number of reasons, including:
* Coverage. Private-sector data cannot match the breadth of official surveys, especially for complex measures such as employment, inequality, or production in small firms and local markets.
* Incentives. Because private data is often produced to meet commercial demand, areas with broad social value may be neglected.
* Transparency. Many providers rely on proprietary methodologies that are rarely disclosed in detail, limiting transparency and making replication difficult.
In short, “a healthy economy benefits from a robust interplay between official and private statistics, each reinforcing the other’s credibility and value,” the authors write.
2. Speak up. The integrity of economic data is an important component of democratic governance and market stability, Rigobon said. Vigilance is essential for detecting and resisting political manipulation in its early forms before public trust slips away and becomes difficult to regain.
To that end, companies should be speaking up more. “It’s time for them to stand up and say, ‘These policies make no sense,’” Rigobon said. Specifically, companies aren’t fully grasping the implications of staying silent on tariffs. “It’s a tax on firms, and firms should be more vocal,” he said.
Ultimately, reliable statistics require investment, institutional independence, and public trust, the authors conclude. “Protecting and strengthening the U. S. statistical system is not only about preserving numbers on a page; it is about safeguarding the ability of policymakers, businesses, and households to make sound decisions based on a shared understanding of economic reality.”
Roberto Rigobon, PhD ’97, is a professor of applied economics at MIT, a research associate of the National Bureau of Economic Research, a member of the Census Bureau’s Scientific Advisory Committee, and a visiting professor at IESA (Venezuela). He is co-faculty director of the MIT Sloan Sustainability Initiative and a co-founder and director of the Aggregate Confusion Project, which studies how to improve environmental, social, and governance measures.
Alberto Cavallo, MBA ’05, is a professor of business administration at Harvard Business School, a research associate at the National Bureau of Economic Research, and co-director of the Pricing Lab at Harvard’s Digital Data Design Institute. With Rigobon, Cavallo co-founded the Billion Prices Project in 2008 to expand the measurement of online inflation globally.
...
Read the original on mitsloan.mit.edu »
Every year, someone posts a benchmark showing Python is 100x slower than C. The same argument plays out: one side says “benchmarks don’t matter, real apps are I/O bound,” the other says “just use a real language.” Both are wrong.
I took two of the most-cited Benchmarks Game problems — n-body and spectral-norm — reproduced them on my machine, and ran every optimization tool I could find. Then I added a third benchmark — a JSON event pipeline — to test something closer to real-world code.
Same problems, same Apple M4 Pro, real numbers. This is one developer’s journey up the ladder — not a definitive ranking. A dedicated expert could squeeze more out of any of these tools. The full code is at faster-python-bench.
Here’s the starting point — CPython 3.13 on the official Benchmarks Game run:
The question isn’t whether Python is slow at computation. It is. The question is how much effort each fix costs and how far it gets you. That’s the ladder.
The usual suspects are the GIL, interpretation, and dynamic typing. All three matter, but none of them is the real story. The real story is that Python is designed to be maximally dynamic — you can monkey-patch methods at runtime, replace builtins, change a class’s inheritance chain while instances exist — and that design makes it fundamentally hard to optimize.
A C compiler sees a + b between two integers and emits one CPU instruction. The Python VM sees a + b and has to ask: what is a? What is b? Does a.__add__ exist? Has it been replaced since the last call? Is a actually a subclass of int that overrides __add__? Every operation goes through this dispatch because the language guarantees you can change anything at any time.
The object overhead is where this shows up concretely. In C, an integer is 4 bytes on the stack. In Python:
C int: [ 4 bytes ]
Python int: [ ob_refcnt 8B ] reference count
[ ob_type 8B ] pointer to type object
[ ob_size 8B ] number of digits
[ ob_digit 4B ] the actual value
= 28 bytes minimum
4 bytes of number, 24 bytes of machinery to support dynamism. a + b means: dereference two heap pointers, look up type slots, dispatch to int.__add__, allocate a new PyObject for the result (unless it hits the small-integer cache), update reference counts. CPython 3.11+ mitigates this with adaptive specialization — hot bytecodes like BINARY_OP_ADD_INT skip the dispatch for known types — but the overhead is still there for the general case. One number isn’t slow. Millions in a loop are.
The GIL (Global Interpreter Lock) gets blamed a lot, but it has no impact on single-threaded performance — it only matters when multiple CPU-bound threads compete for the interpreter. For the benchmarks in this post, the GIL is irrelevant. CPython 3.13 shipped experimental free-threaded mode (–disable-gil) — still experimental in 3.14 — but as we’ll see, it actually makes single-threaded code slower because removing the GIL adds overhead to every reference count operation.
The interpretation overhead is real but is being actively addressed. CPython 3.11′s Faster CPython project added adaptive specialization — the VM detects “hot” bytecodes and replaces them with type-specialized versions, skipping some of the dispatch. It helped (~1.4x). CPython 3.13 went further with an experimental copy-and-patch JIT compiler — a lightweight JIT that stitches together pre-compiled machine code templates instead of generating code from scratch. It’s not a full optimizing JIT like V8′s TurboFan or a tracing JIT like PyPy’s; it’s designed to be small and fast to start, avoiding the heavyweight JIT startup cost that has historically kept CPython from going this route. Early results in 3.13 show no improvement on most benchmarks, but the infrastructure is now in place for more aggressive optimizations in future releases. JavaScript’s V8 achieves much better JIT results, but V8 also had a large dedicated team and a single-threaded JavaScript execution model that makes speculative optimization easier. (For more on the “why doesn’t CPython JIT” question, see Anthony Shaw’s “Why is Python so slow?”.)
So the picture is: Python is slow because its dynamic design requires runtime dispatch on every operation. The GIL, the interpreter, the object model — these are all consequences of that design choice. Each rung of the ladder removes some of this dispatch. The higher you climb, the more you bypass — and the more effort it costs.
Cost: changing your base image. Reward: up to 1.4x.
The story is 3.10 to 3.11: a 1.39x speedup on n-body, for free. That’s the Faster CPython project — adaptive specialization of bytecodes, inline caching, zero-cost exceptions. 3.13 squeezed out a bit more. 3.14 gave some of it back — a minor regression on these benchmarks.
Free-threaded Python (3.14t) is slower on single-threaded code. The GIL removal adds overhead to every reference count operation. Worth it only if you have genuinely parallel CPU-bound threads. (Full version comparison)
This rung costs nothing. If you’re still on 3.10, upgrade.
Both are JIT-compiled runtimes that generate native machine code from your unmodified Python. Zero code changes. Just a different interpreter.
PyPy uses a tracing JIT — it records hot loops and compiles them. GraalPy runs on GraalVM’s Truffle framework with a method-based JIT. PyPy wins on n-body (13x vs 5.9x), but GraalPy dominates spectral-norm (66x vs 13x) — the matrix-heavy inner loop plays to GraalVM’s strengths. GraalPy also offers Java interop and is actively developed by Oracle.
The catch: ecosystem compatibility. Both support major packages, but C extensions run through compatibility layers that can be slower than on CPython. GraalPy is on Python 3.12 (no 3.14 yet) and has slow startup — it’s JVM-based, so the JIT needs warmup before reaching peak performance. For pure Python code with long-running hot loops — these are free speed.
Cost: type annotations you probably already have. Reward: 2.4-14x.
Mypyc compiles type-annotated Python to C extensions using the same type analysis as mypy. No new syntax, no new language — just your existing typed Python, compiled ahead of time.
# Already valid typed Python — mypyc compiles this to C
def advance(dt: float, n: int, bodies: list[Body], pairs: list[BodyPair]) -> None:
dx: float
dy: float
dz: float
dist_sq: float
dist: float
mag: float
for _ in range(n):
for (r1, v1, m1), (r2, v2, m2) in pairs:
dx = r1[0] - r2[0]
dy = r1[1] - r2[1]
dz = r1[2] - r2[2]
dist_sq = dx * dx + dy * dy + dz * dz
dist = math.sqrt(dist_sq)
mag = dt / (dist_sq * dist)
The difference from the baseline: explicit type declarations on every local variable so mypyc can use C primitives instead of Python objects, and decomposing ** (-1.5) into sqrt() + arithmetic to avoid slow power dispatch. That’s it — no special decorators, no new build system beyond mypycify().
The mypy project itself — ~100k+ lines of Python — achieved a 4x end-to-end speedup by compiling with mypyc. The official docs say “1.5x to 5x” for existing annotated code, “5x to 10x” for code tuned for compilation. The spectral-norm result (14x) lands above that range because the inner loop is pure arithmetic that mypyc compiles directly to C. On our dict-heavy JSON pipeline, mypyc hit 2.3x on pre-parsed dicts — closer to the expected floor.
The constraint: mypyc supports a subset of Python. Dynamic patterns like **kwargs, getattr tricks, and heavily duck-typed code will compile but won’t be optimized — they fall back to slow generic paths. But if your code already passes mypy strict mode, mypyc is the lowest-effort compilation rung on the ladder.
520x. Faster than our single-threaded Rust at 154x on the same problem — though NumPy delegates to BLAS, which uses multiple cores.
Spectral-norm is matrix-vector multiplication. NumPy pre-computes the matrix once and delegates to BLAS (Apple Accelerate on macOS):
a = build_matrix(n)
for _ in range(10):
v = a. T @ (a @ u)
u = a.T @ (a @ v)
Each @ is a single call to hand-optimized BLAS with SIMD and multithreading. NumPy trades O(N) memory for O(N^2) memory — it stores the full 2000x2000 matrix (30MB) — but the computation is done in compiled C/C++ (Apple Accelerate on macOS, OpenBLAS or MKL on Linux), not Python.
This is the lesson people miss when they say “Python is slow.” Python the loop runner is slow. Python the orchestrator of compiled libraries is as fast as anything.
The constraint: your problem must fit vectorized operations. Element-wise math, matrix algebra, reductions, conditionals (np.where computes both branches and masks the result — redundant work, but still faster than a Python loop on large arrays) — NumPy handles all of these. What it can’t help with: sequential dependencies where each step feeds the next, recursive structures, and small arrays where NumPy’s per-call overhead costs more than the computation itself.
A Reddit commenter (justneurostuff) suggested testing JAX — an array computing library that uses XLA JIT compilation. I expected it to land somewhere near NumPy. I was wrong.
8.6ms on spectral-norm. That’s 3x faster than NumPy and the fastest result in this entire post. On n-body, 12.2x — between Mypyc and Numba. Both results match the CPython baseline to 9 decimal places. This is single-threaded — forcing one thread gave 9.1ms vs 8.6ms on spectral-norm.
I don’t know JAX well enough to explain exactly why it’s 3x faster than NumPy on the same matrix multiplications. Both call BLAS under the hood. My best guess is that JAX’s @jit compiles the entire function — matrix build, loop, dot products — so Python is never involved between operations, while NumPy returns to Python between each @ call. But I haven’t verified that in detail. Might be time to learn.
The catch: JAX is a different programming model. Python loops become lax.fori_loop. Conditionals become lax.cond. You’re writing functional array programs that happen to use Python syntax — closer to a domain-specific language than a drop-in optimizer. But if your problem fits, the numbers speak for themselves. JAX isn’t the only library that compiles array code — PyTorch has torch.compile, for example. I only tested JAX, so I can’t say whether others would produce similar results on these benchmarks.
@njit(cache=True)
def advance(dt, n, pos, vel, mass):
for i in range(n):
for j in range(i + 1, n):
dx = pos[i, 0] - pos[j, 0]
dy = pos[i, 1] - pos[j, 1]
dz = pos[i, 2] - pos[j, 2]
dist = sqrt(dx * dx + dy * dy + dz * dz)
mag = dt / (dist * dist * dist)
vel[i, 0] -= dx * mag * mass[j]
One decorator. Restructure data into NumPy arrays. The constraint: Numba works best with NumPy arrays and numeric types. It has limited support for typed dicts, typed lists, and @jitclass, but strings and general Python objects are largely out of reach. It’s a scalpel, not a saw.
124x on n-body. Within 10% of Rust. But here’s the thing about this rung:
My first Cython n-body got 10.5x. Same Cython, same compiler. The final version got 124x. The difference was three landmines, none of which produced warnings:
Cython’s ** operator with float exponents. Even with typed doubles and -ffast-math, x ** 0.5 is 40x slower than sqrt(x) in Cython — the operator goes through a slow dispatch path instead of compiling to C’s sqrt(). The n-body baseline uses ** (-1.5), which can’t be replaced with a single sqrt() call — it required decomposing the formula into sqrt() + arithmetic. 7x penalty on the overall benchmark.
Precomputed pair index arrays prevent the C compiler from unrolling the nested loop. 2x penalty. The “clever” version is slower.
Missing @cython.cdivision(True) inserts a zero-division check before every floating-point divide in the inner loop. Millions of branches that are never taken.
Cython’s promise is that it “makes writing C extensions for Python as easy as Python itself.” In practice that means: learn C’s mental model, express it in Python syntax, and use the annotation report (cython -a) to verify the compiler did what you think. The full story is in The Cython Minefield.
The reward is real — 99-124x, matching compiled languages. But the failure mode is silent. All three landmines cost you silently, and the annotation report is the only way to catch them.
Three tools promise to compile Python (or Python-like code) to native machine code. I tested all three.
The numbers are real. The developer experience is rough. Codon can’t import your existing code. Mojo is a new language wearing Python’s clothes. Taichi has the best spectral-norm result (198x) but doesn’t ship wheels for Python 3.14 — its numbers above were benchmarked on a separate Python 3.13 environment. That’s the compromise with these tools: if your runtime doesn’t keep up with CPython releases, you’re stuck on an old version or juggling multiple environments. (Full deep dive with code and DX verdicts)
None are drop-in. All are worth watching.
The top of the ladder. But notice: on n-body, Cython at 10ms vs Rust at 11ms — they’re essentially tied. Both compiled to native machine code. The remaining difference is noise, not a fundamental language gap.
The real Rust advantage isn’t raw speed — it’s pipeline ownership. When Rust parses JSON directly with serde into typed structs, it never creates a Python dict. It bypasses the Python object system entirely. That matters more on the next benchmark.
The Benchmarks Game problems are pure compute: tight loops, no I/O, no data structures beyond arrays. Most Python code looks nothing like that. So I built a third benchmark: load 100K JSON events, filter, transform, aggregate per user. Dicts, strings, datetime parsing — the kind of code that makes Numba useless and makes Cython fight the Python object system.
First, every tool starts from pre-parsed Python dicts — same input, same work:
4.1x. Not 50x. The bottleneck is Python dict access. Even Cython’s fully optimized version — @cython.cclass, C arrays for counters, direct CPython C-API calls (PyList_GET_ITEM, PyDict_GetItem with borrowed refs) — still reads input dicts through the Python C API.
But wait — why are we feeding Cython Python dicts at all? json.loads() takes ~57ms to create those dicts. That’s more than the entire baseline pipeline. What if Cython reads the raw bytes itself?
I wrote a second Cython pipeline that calls yyjson — a general-purpose C JSON parser, comparable to Rust’s serde_json. Both are schema-agnostic: they parse any valid JSON, not just our event format. Cython walks the parsed tree with C pointers, filters and aggregates into C structs, and builds Python dicts only for the final output. For Rust, idiomatic serde with zero-copy deserialization. Both own the data end-to-end:
6.3x for Cython, 5.0x for Rust. The ceiling was never the pipeline code — it was json.loads(). Both approaches use general-purpose JSON parsers — yyjson on the Cython side, serde on the Rust side — and both avoid Python objects entirely in the hot loop: Cython walks yyjson’s C tree into C structs, Rust deserializes into native structs via serde.
I’m not claiming Cython is faster than Rust or vice versa. A sufficiently motivated person could make either one faster — swap parsers, tune allocators, restructure the pipeline. The point isn’t which tool wins this specific benchmark. The point is how many rungs you’re willing to climb. Both land in the same neighborhood once you bypass json.loads(). The code is at faster-python-bench.
The effort curve is exponential. Mypyc (2.4-14x) costs type annotations. PyPy/GraalPy (6-66x) costs a binary swap. Numba (56-135x) costs a decorator and data restructuring. JAX (12-1,633x) costs rewriting your code functionally. Cython (99-124x) costs days and C knowledge. Rust (113-154x) costs learning a new language.
Upgrade first. 3.10 to 3.11 gives you 1.4x for free.
Mypyc for typed codebases. If your code already passes mypy strict, compile it. 2.4x on n-body, 14x on spectral-norm, for almost no work.
NumPy for vectorizable math. If your problem is matrix algebra or element-wise operations, NumPy gets you 520x with code you already know.
JAX if you can express it functionally. Same array paradigm as NumPy, but XLA whole-graph compilation took spectral-norm to 1,633x — 3x faster than NumPy. The cost is rewriting loops as lax.fori_loop and conditionals as lax.cond. On problems that don’t vectorize well (n-body with 5 bodies), JAX is 12x — good but not exceptional.
Numba for numeric loops. @njit gives you 56-135x with one decorator and honest error messages.
Cython if you know C. 99-124x is real, but the failure mode is silent slowness.
Rust for pipeline ownership. On pure compute, Cython and Rust are neck and neck. The real advantage is when Rust owns the data flow end-to-end.
PyPy or GraalPy for pure Python. 6-66x for zero code changes is remarkable, if your dependencies support it. GraalPy’s spectral-norm result (66x) rivals compiled solutions.
Most code doesn’t need any of this. The pipeline benchmark — the most realistic of the three — topped out at 4.1x when starting from Python dicts. 6.3x when Cython called yyjson and owned the bytes. If your hot path is dict[str, Any], the answer might be “stop creating dicts,” not “change the language.” And if your code is I/O bound, none of this matters at all.
Profile before you optimize. cProfile to find the function. line_profiler to find the line. Then pick the right rung.
...
Read the original on cemrehancavdar.com »
HELENA, MT — Last Thursday, Governor Greg Gianforte signed SB 212, the Montana Right to Compute Act (MRTCA), marking the state as the first in the nation to secure comprehensive rights for citizens to own and utilize computational and artificial intelligence tools. This legislation positions Montana at the forefront of safeguarding digital privacy and technology accessibility.
The newly signed law not only ensures the fundamental rights to own, access, and use computational resources but also incorporates several critical safeguards:
* Strict limits on governmental regulation wherein any restrictions must be demonstrably necessary and narrowly tailored to a compelling public safety or health interest.
The initiative, propelled by advocacy from State Senator Daniel Zolnikov and organizations like the Frontier Institute, contrasts with recent restrictive legislation efforts in states like California and Virginia. Zolnikov, a noted advocate for privacy, has been instrumental in pushing for tech-friendly policies that ensure individual liberties in an evolving digital landscape.
“As governments around the world and in our own country try to crack down on individual freedom and gain state control over modern technologies,” Zolnikov said. “Montana is doing the opposite by protecting freedom and restraining the government.”
“With the passage of the Right to Compute Act, Montana has planted a flag in the ground, affirming that here, we will treat attempts to infringe on fundamental rights in the digital age with the utmost scrutiny,” remarked Tanner Avery, Policy Director at the Frontier Institute.
Rep. Keith Ammon from New Hampshire praised Montana’s initiative, stating, “Congratulations to Senator Zolnikov and the Montana Legislature for being the first to establish the ‘right to compute’ in law! I expect other states to follow your lead and protect citizens’ right to access and express themselves through computation.” This sentiment echoes the broader national movement towards similar protections, with legislative efforts underway in New Hampshire and other states.
Globally, the Right to Compute campaign, supported by groups like Haltia. AI and the ASIMOV Protocol, emphasizes the essential nature of computational access as fundamental to innovation and personal freedom. “The Right to Compute bill in Montana is a monumental step forward in ensuring that individuals retain their right to control their own data, protect their privacy, and engage with technology on their own terms,” said Talal Thabet, Co-Founder of Haltia.AI and ASIMOV Protocol.
For more information about the Right to Compute movement and ongoing developments, visit RightToCompute.ai and follow on X @RightToCompute.
...
Read the original on www.westernmt.news »
Yesterday, the IRS announced the release of the project I’ve been engineering leading since this summer, its new Tax Withholding Estimator (TWE). Taxpayers enter in their income, expected deductions, and other relevant info to estimate what they’ll owe in taxes at the end of the year, and adjust the withholdings on their paycheck. It’s free, open source, and, in a major first for the IRS, open for public contributions.
TWE is full of exciting learnings about the field of public sector software. Being me, I’m going to start by writing about by far the driest one: XML.
XML is widely considered clunky at best, obsolete at worst. It evokes memories of SOAP configs and J2EE (it’s fine, even good, if those acronyms don’t mean anything to you). My experience with the Tax Withholding Estimator, however, has taught me that XML absolutely has a place in modern software development, and it should be considered a leading option for any cross-platform declarative specification.
TWE is a static site generated from two XML configurations. The first of these configs is the Fact Dictionary, our representation of the US Tax Code; the second will be the subject of a later blog post.
We use the Fact Graph, a logic engine, to calculate the taxpayer’s tax obligations (and their withholdings) based on the facts defined in the Fact Dictionary. The Fact Graph was originally built for IRS Direct File and now we use it for TWE. I’m going to introduce you to the Fact Graph the way that I was introduced to it: by example.
Put aside any preconceptions you might have about XML for a moment and ask yourself what this fact describes, and how well it describes it.
This fact describes a /totalOwed fact that’s derived by subtracting /totalPayments from /totalTax. In tax terms, this fact describes the amount you will need to pay the IRS at the end of the year. That amount, “total owed,” is the difference between the total taxes due for your income (“total tax”) and the amount you’ve already paid (“total payments”).
My initial reaction to this was that it’s quite verbose, but also reasonably clear. That’s more or less how I still feel.
You only need to look at a few of these to intuit the structure. Take the refundable credits calculation, for example. A refundable credit is a tax credit that can lead to a negative tax balance—if you qualify for more refundable credits than you owe in taxes, the government just gives you some money. TWE calculates the total value of refundable credits by adding up the values of the Earned Income Credit, the Child Tax Credit (CTC), American Opportunity Credit, the refundable portion of the Adoption Credit, and some other stuff from the Schedule 3.
By contrast, non-refundable tax credits can bring your tax burden down to zero, but won’t ever make it negative. TWE models that by subtracting non-refundable credits from the tentative tax burden while making sure it can’t go below zero, using the operator.
While admittedly very verbose, the nesting is straightforward to follow. The tax after non-refundable credits is derived by saying “give me the greater of these two numbers: zero, or the difference between tentative tax and the non-refundable credits.”
Finally, what about inputs? Obviously we need places for the taxpayer to provide information, so that we can calculate all the other values.
Okay, so instead of we use . Because the value is… writable. Fair enough. The denotes what type of value this fact takes. True-or-false questions use , like this one that records whether the taxpayer is 65 or older.
There are some (much) longer facts, but these are a fair representation of what the median fact looks like. Facts depend on other facts, sometimes derived and sometimes writable, and they all add up to some final tax numbers at the end. But why encode math this way when it seems far clunkier than traditional notation?
Countless mainstream programming languages would instead let you write this calculation in a notation that looks more like normal math. Take this JavaScript example, which looks like elementary algebra:
const totalOwed = totalTax - totalPayments
That seems better! It’s far more concise, easier to read, and doesn’t make you explicitly label the “minuend” and “subtrahend.”
Let’s add in the definitions for totalTax and totalPayments.
const totalTax = tentativeTaxNetNonRefundableCredits + totalOtherTaxes
const totalPayments = totalEstimatedTaxesPaid +
totalTaxesPaidOnSocialSecurityIncome +
totalRefundableCredits
const totalOwed = totalTax - totalPayments
Still not too bad. Total tax is calculated by adding the tax after non-refundable credits (discussed earlier) to whatever’s in “other taxes.” Total payments is the sum of estimated taxes you’ve already paid, taxes you’ve paid on social security, and any refundable credits.
The problem with the JavaScript representation is that it’s imperative. It describes actions you take in a sequence, and once the sequence is done, the intermediate steps are lost. The issues with this get more obvious when you go another level deeper, adding the definitions of all the values that totalTax and totalPayments depend on.
// Total tax calculation
const totalOtherTaxes = selfEmploymentTax + additionalMedicareTax + netInvestmentIncomeTax
const tentativeTaxNetNonRefundableCredits = Math.max(totalTentativeTax - totalNonRefundableCredits, 0)
const totalTax = tentativeTaxNetNonRefundableCredits + totalOtherTaxes
// Total payments calculation
const totalEstimatedTaxesPaid = getInput()
const totalTaxesPaidOnSocialSecurityIncome = socialSecuritySources
.map(source => source.totalTaxesPaid)
.reduce((acc, val) => { return acc+val }, 0)
const totalRefundableCredits = earnedIncomeCredit +
additionalCtc +
americanOpportunityCredit +
adoptionCreditRefundable +
schedule3OtherPaymentsAndRefundableCreditsTotal
const totalPayments = totalEstimatedTaxesPaid +
totalTaxesPaidOnSocialSecurityIncome +
totalRefundableCredits
// Total owed
const totalOwed = totalTax - totalPayments
We are quickly arriving at a situation that has a lot of subtle problems.
One problem is the execution order. The hypothetical getInput() function solicits an answer from the taxpayer, which has to happen before the program can continue. Calculations that don’t depend on knowing “total estimated taxes” are still held up waiting for the user; calculations that do depend on knowing that value had better be specified after it.
Or, take a close look at how we add up all the social security income:
const totalTaxesPaidOnSocialSecurityIncome = socialSecuritySources
.map(source => source.totalTaxesPaid)
.reduce((acc, val) => { return acc+val }, 0)
All of a sudden we are really in the weeds with JavaScript. These are not complicated code concepts—map and reduce are both in the standard library and basic functional paradigms are widespread these days—but they are not tax math concepts. Instead, they are implementation details.
Compare it to the Fact representation of that same value.
This isn’t perfect—the * that represents each social security source is a little hacky—but the meaning is much clearer. What are the total taxes paid on social security income? The sum of the taxes paid on each social security income. How do you add all the items in a collection? With .
Plus, it reads like all the other facts; needing to add up all items in a collection didn’t suddenly kick us into a new conceptual realm.
The philosophical difference between these two is that, unlike JavaScript, which is imperative, the Fact Dictionary is declarative. It doesn’t describe exactly what steps the computer will take or in what order; it describes a bunch of named calculations and how they depend on each other. The engine decides automatically how to execute that calculation.
Besides being (relatively) friendlier to read, the most important benefit of a declarative tax model is that you can ask the program how it calculated something. Per the Fact Graph’s original author, Chris Given:
The Fact Graph provides us with a means of proving that none of the unasked questions would have changed the bottom line of your tax return and that you’re getting every tax benefit to which you’re entitled.
Suppose you get a value for totalOwed that doesn’t seem right. You can’t ask the JavaScript version “how did you arrive at that number?” because those intermediate values have already been discarded. Imperative programs are generally debugged by adding log statements or stepping through with a debugger, pausing to check each value. This works fine when the number of intermediate values is small; it does not scale at all for the US Tax Code, where the final value is calculated based on hundreds upon hundreds of calculations of intermediate values.
With a declarative graph representation, we get auditability and introspection for free, for every single calculation.
Intuit, the company behind TurboTax, came to the same conclusion, and published a whitepaper about their “Tax Knowledge Graph” in 2020. Their implementation is not open source, however (or least I can’t find it). The IRS Fact Graph is open source and public domain, so it can be studied, shared, and extended by the public.
If we accept the need for a declarative data representation of the tax code, what should it be?
In many of the places where people used to encounter XML, such network data transfer and configuration files, it has been replaced by JSON. I find JSON to be a reasonably good wire format and a painful configuration format, but in neither case would I rather be using XML (although it’s a close call on the latter).
The Fact Dictionary is different. It’s not a pile of settings or key-value pairs. It’s a custom language that models a unique and complex problem space. In programming we call this a domain-specific language, or DSL for short.
As an exercise, I tried to come up with a plausible JSON representation of the /tentativeTaxNetNonRefundableCredits fact from earlier.
“description”: “Total tentative tax after applying non-refundable credits, but before applying refundable credits.”,
“definition”: {
“type”: “Expression”,
“kind”: “GreaterOf”,
“children”: [
“type”: “Value”,
“kind”: “Dollar”,
“value”: 0
“type”: “Expression”,
“kind”: “Subtract”,
“minuend”: {
“type”: “Dependency”,
“path”: “/totalTentativeTax”
“subtrahend”: {
“type”: “Dependency”,
“path”: “/totalNonRefundableCredits”
This is not a terribly complicated fact, but it’s immediately apparent that JSON does not handle arbitrary nested expressions well. The only complex data structure available in JSON is an object, so every child object has to declare what kind of object it is. Contrast that with XML, where the “kind” of the object is embedded in its delimiters.
I think this XML representation could be improved, but even in its current form, it is clearly better than JSON. (It’s also, amusingly, a couple lines shorter.) Attributes and named children give you just enough expressive power to make choices about what your language should or should not emphasize. Not being tied to specific set of data types makes it reasonable to define your own, such as a distinction between “dollars” and “integers.”
A lot of minor frustrations we’ve all internalized as inevitable with JSON are actually JSON-specific. XML has comments, for instance. That’s nice. It also has sane whitespace and newline handling, which is important when your descriptions are often long. For text that has any length or shape to it, XML is far more pleasant to read and edit by hand than JSON.
There are still verbosity gains to be had, particularly with switch statements (omitted here out of respect for page length). I’d certainly remove the explicit “minuend” and “subtrahend,” for starters.
I believe that the original team didn’t do this because they didn’t want the order of the children to have semantic consequence. I get it, but order is guaranteed in XML and I think the additional nesting and words do more harm then good.
What about YAML? Chris Given again:
whatever you do, don’t try to express the logic of the Internal Revenue Code as YAML
Finally, there’s a good case to made that you could build this DSL with s-expressions. In a lot of ways, this is nicest syntax to read and edit.
(Fact
(Path “/tentativeTaxNetNonRefundableCredits”)
(Description “Total tentative tax after applying non-refundable
credits, but before applying refundable credits.“)
(Derived
(GreaterOf
(Dollar 0)
(Subtract
(Minuend (Dependency “/totalTentativeTax”))
(Subtrahends (Dependency “/totalNonRefundableCredits”))))))
HackerNews user ok123456 asks: “Why would I want to use this over Prolog/Datalog?”
I’m a Prolog fan! This is also possible.
...
Read the original on unplannedobsolescence.com »
We’re offering a limited-time promotion that doubles usage limits for Claude users outside 8 AM-2 PM ET/5-11 AM PT.
This promotion is available for Free, Pro, Max, and Team plans. Enterprise plans are not included in this promotion.
From March 13, 2026 through March 27, 2026, your five-hour usage is doubled during off-peak hours (outside 8 AM-2 PM ET/5-11 AM PT). Usage remains unchanged from 8 AM-2 PM ET/5-11 AM PT.
No action is required to participate. If you’re on an eligible plan, the doubled usage is automatically applied.
The 2x usage increase applies across the following Claude surfaces:
No. The promotion applies automatically. You’ll see higher limits reflected in your usage outside 8 AM-2 PM ET/5-11 AM PT without any changes to your account settings.
No. The additional usage you get during off-peak hours doesn’t count toward any weekly usage limits on your plan.
After March 27, 2026, usage limits return to their standard levels at all hours. There’s no change to your plan or billing.
This offer is valid from March 13, 2026 through March 27, 2026 at 11:59 PM PT. It applies to Free, Pro, Max, and Team plans only and excludes Enterprise plans. This offer has no cash value and is not transferable. It may not be combined with other offers.
...
Read the original on support.claude.com »
We’re happy to present the first release of GIMP 3.2! This marks a year of design, development, and testing from volunteers and the community, as part of our plan to
streamline releases after GIMP 3.0. We’re excited for you to see the new features that version 3.2 offers!
Here are some of the many highlights to look out for as you start using GIMP 3.2:
You can now use Link Layers to incorporate external image as part of your compositions, easily
scaling, rotating, and transforming them without losing quality or sharpness. The link layer’s content
is updated when the source file is modified
The Path tool can now create Vector Layers, which lets you draw shapes with adjustable fill and
stroke settings.
* You can now use Link Layers to incorporate external image as part of your compositions, easily
scaling, rotating, and transforming them without losing quality or sharpness. The link layer’s content
is updated when the source file is modified
* The Path tool can now create Vector Layers, which lets you draw shapes with adjustable fill and
stroke settings.
The MyPaint Brush tool has been upgraded, adding 20 new brushes, and it now automatically adjusts to your canvas zoom and rotation for more dynamic painting.
A new Overwrite paint mode allows you to draw over existing colors without blending their transparency.
The on-canvas Text Editor has a number of workflow improvements. Among them, you can now move it as needed across the canvas and utilize many common shortcuts such as + for bold text and
+ + for pasting unformatted text. The Text Outline feature also includes more options to control the direction of the outline.
New file format support and improvements to existing formats, such as DDS BC7 export and more layer styles
imported for PSDs. Thanks to vector layers, we now also support SVG export and expanded vector options in PDF export.
A variety of UX and UI improvements, based on your feedback and our design team’s efforts. To list a few:
Options to make the brush thumbnails use theme colors for previews, for a nicer experience in dark themes
Ability to drag and drop images onto the image tab to open in
Keyboard shortcut support for the Shear and Flip tools
New System color scheme that automatically matches ’s theme color scheme to the one you set for your
* Options to make the brush thumbnails use theme colors for previews, for a nicer experience in dark themes
* Ability to drag and drop images onto the image tab to open in
* Keyboard shortcut support for the Shear and Flip tools
* New System color scheme that automatically matches ’s theme color scheme to the one you set for your
The CMYK color selector now shows the Total Ink Coverage for your color, helping you adjust during soft-proofing based on your printer’s ink coverage limit.
For script and plug-in developers, a new GEGL Filter browser has been added to make it easier to find non-destructive filters to use.
We’ve prepared release notes to go over all the changes, improvements, new features, and more. And if you’d like even more details, you can peruse the NEWS changelog for all 3.1 and 3.2 development releases.
But to see it for yourself, you can get GIMP 3.2 directly from our Downloads page and try it out!
To accompany our release of GIMP 3.2, packagers should be aware that we also released:
We do not have a ready-to-release documentation for this version 3.2 yet. We recommend you to continue using the 3.0 online
documentation for the time being.
Our contributors are working hard on enhancing the documentation. Any help is welcome on our gimp-help
project to speed up the process!
GIMP 3.2 builds on the foundation we created in GIMP 3.0, providing great new features and setting the stage for even more awesome things in future versions!
Note: packages on stores may take a bit longer to reach you as they may be in review.
Don’t forget you can donate and personally fund GIMP developers, as a way to give back and accelerate the development of GIMP. Community commitment helps the project to grow stronger!
...
Read the original on www.gimp.org »
Modern kernel anti-cheat systems are, without exaggeration, among the most sophisticated pieces of software running on consumer Windows machines. They operate at the highest privilege level available to software, they intercept kernel callbacks that were designed for legitimate security products, they scan memory structures that most programmers never touch in their entire careers, and they do all of this transparently while a game is running. If you have ever wondered how BattlEye actually catches a cheat, or why Vanguard insists on loading before Windows boots, or what it means for a PCIe DMA device to bypass every single one of these protections, this post is for you.
This is not a comprehensive or authoritative reference. It is just me documenting what I found and trying to explain it clearly. Some of it comes from public research and papers I have linked at the bottom, some from reading kernel source and reversing drivers myself. If something is wrong, feel free to reach out. The post assumes some familiarity with Windows internals and low-level programming, but I have tried to explain each concept before using it.
The fundamental problem with usermode-only anti-cheat is the trust model. A usermode process runs at ring 3, subject to the full authority of the kernel. Any protection implemented entirely in usermode can be bypassed by anything running at a higher privilege level, and in Windows that means ring 0 (kernel drivers) or below (hypervisors, firmware). A usermode anti-cheat that calls ReadProcessMemory to check game memory integrity can be defeated by a kernel driver that hooks NtReadVirtualMemory and returns falsified data. A usermode anti-cheat that enumerates loaded modules via EnumProcessModules can be defeated by a driver that patches the PEB module list. The usermode process is completely blind to what happens above it.
Cheat developers understood this years before most anti-cheat engineers were willing to act on it. The kernel was, for a long time, the exclusive domain of cheats. Kernel-mode cheats could directly manipulate game memory without going through any API that a usermode anti-cheat could intercept. They could hide their presence from usermode enumeration APIs trivially. They could intercept and forge the results of any check a usermode anti-cheat might perform.
The response was inevitable: move the anti-cheat into the kernel.
The escalation has been relentless. Usermode cheats gave way to kernel cheats. Kernel anti-cheats appeared in response. Cheat developers began exploiting legitimate, signed drivers with vulnerabilities to achieve kernel execution without loading an unsigned driver (the BYOVD attack). Anti-cheats responded with blocklists and stricter driver enumeration. Cheat developers moved to hypervisors, running below the kernel and virtualizing the entire OS. Anti-cheats added hypervisor detection. Cheat developers began using PCIe DMA devices to read game memory directly through hardware without ever touching the OS at all. The response to that is still being developed.
Each escalation requires the attacking side to invest more capital and expertise, which has an important effect: it filters out casual cheaters. A $30 kernel cheat subscription is accessible to many people. A custom FPGA DMA setup costs hundreds of dollars and requires significant technical knowledge to configure. The arms race, while frustrating for anti-cheat engineers, does serve the practical goal of making cheating expensive and difficult enough that most cheaters do not bother.
BattlEye is used by PUBG, Rainbow Six Siege, DayZ, Arma, and dozens of other titles. Its kernel component is BEDaisy.sys, and it has been the subject of detailed public reverse engineering work, most notably by the secret.club researchers and the back.engineering blog.
EasyAntiCheat (EAC) is now owned by Epic Games and used in Fortnite, Apex Legends, Rust, and many others. Its architecture is broadly similar to BattlEye in its three-component design but differs significantly in implementation details.
Vanguard is Riot Games’ proprietary anti-cheat used in Valorant and League of Legends. It is notable for loading its kernel component (vgk.sys) at system boot rather than at game launch, and for its aggressive stance on driver allowlisting.
FACEIT AC is used for the FACEIT competitive platform for Counter-Strike. It is a kernel-level system with a well-regarded reputation in the competitive community for effective cheat detection, and has been the subject of academic analysis examining the architectural properties of kernel anti-cheat software more broadly.
The 2024 paper “If It Looks Like a Rootkit and Deceives Like a Rootkit” (presented at ARES 2024) analyzed FACEIT AC and Vanguard through the lens of rootkit taxonomy, noting that both systems share technical characteristics with that class of software: kernel-level operation, system-wide callback registration, and broad visibility into OS activity. The authors are careful to distinguish between technical classification and intent, explicitly acknowledging that these systems are legitimate software serving a defensive purpose. The paper’s contribution is primarily taxonomic rather than accusatory.
The underlying observation is simply that effective kernel anti-cheat requires the same OS primitives that malicious kernel software uses, because those primitives are what provide the visibility needed to detect cheats. Any sufficiently capable kernel anti-cheat will look like a rootkit under static behavioral analysis, because capability and intent are orthogonal at the kernel API level. This is a constraint imposed by Windows architecture, not a design choice unique to any particular anti-cheat vendor.
Kernel driver: Runs at ring 0. Registers callbacks, intercepts system calls, scans memory, enforces protections. This is the component that actually has the power to do anything meaningful. Usermode service: Runs as a Windows service, typically with SYSTEM privileges. Communicates with the kernel driver via IOCTLs. Handles network communication with backend servers, manages ban enforcement, collects and transmits telemetry.Game-injected DLL: Injected into (or loaded by) the game process. Performs usermode-side checks, communicates with the service, and serves as the endpoint for protections applied to the game process specifically.
The separation of concerns here is both architectural and security-motivated. The kernel driver can do things no usermode component can, but it cannot easily make network connections or implement complex application logic. The service can do those things but cannot directly intercept system calls. The in-game DLL has direct access to game state but runs in an untrustworthy ring-3 context.
IOCTLs (I/O Control Codes) are the primary communication mechanism between usermode and a kernel driver. A usermode process opens a handle to the driver’s device object and calls DeviceIoControl with a control code. The driver handles this in its IRP_MJ_DEVICE_CONTROL dispatch routine. The entire communication is mediated by the kernel, which means a compromised usermode component cannot forge arbitrary kernel operations - it can only make requests that the driver is programmed to service.
Named pipes are used for IPC between the service and the game-injected DLL. A named pipe is faster and simpler than routing everything through the kernel, and it allows the service to push notifications to the game component without polling.
Shared memory sections created with NtCreateSection and mapped into both the service process and the game process via NtMapViewOfSection allow high-bandwidth, low-latency data sharing. Telemetry data (input events, timing data) can be written to a shared ring buffer by the game DLL and read by the service without the overhead of IPC per event.
The distinction between boot-time and runtime driver loading is more significant than it might appear.
BattlEye and EAC load their kernel drivers when the game is launched. BEDaisy.sys and its EAC equivalent are registered as demand-start drivers and loaded via ZwLoadDriver from the service when the game starts. They are unloaded when the game exits.
Vanguard loads vgk.sys at system boot. The driver is configured as a boot-start driver (SERVICE_BOOT_START in the registry), meaning the Windows kernel loads it before most of the system has initialized. This gives Vanguard a critical advantage: it can observe every driver that loads after it. Any driver that loads after vgk.sys can be inspected before its code runs in a meaningful way. A cheat driver that loads at the normal driver initialization phase is loading into a system that Vanguard already has eyes on.
The practical implication of boot-time loading is also why Vanguard requires a system reboot to enable: the driver must be in place before the rest of the system initializes, which means it cannot be loaded after the fact without a restart.
Windows enforces Driver Signature Enforcement (DSE) on 64-bit systems, which requires that kernel drivers be signed with a certificate that chains to a trusted root and that the driver’s code integrity be verified at load time. This is implemented through CiValidateImageHeader and related functions in ci.dll. The kernel also enforces that driver certificates meet certain Extended Key Usage (EKU) requirements.
Anti-cheats handle signing in the obvious way: they pay for extended validation (EV) code signing certificates, go through Microsoft’s WHQL process for some components, or use cross-signing. The certificate requirements have tightened significantly over the years; Microsoft now requires EV certificates for new kernel drivers, and the kernel driver signing portal requires WHQL submission for drivers targeting Windows 10 and later in many cases.
The reason this matters for cheats is that DSE is a significant barrier. Without a signed driver or a way to bypass DSE, a cheat author cannot load arbitrary kernel code. BYOVD attacks (covered in section 7) are the primary mechanism for bypassing this restriction.
BEDaisy.sys is the kernel driver. It registers callbacks for process creation, thread creation, image loading, and object handle operations. It implements the actual scanning and protection logic.
BEService.exe (or BEService_x64.exe) is the usermode service. It communicates with BEDaisy.sys via a device object that the driver exposes. It handles network communication with BattlEye’s backend servers, receives detection results from the driver, and is responsible for ban enforcement (kicking the player from the game server).
BEClient_x64.dll is injected into the game process. BattlEye does not inject this via CreateRemoteThread in the traditional sense - it is loaded as part of game initialization, with the game’s cooperation. This DLL is responsible for performing usermode-side checks within the game process context: it verifies its own integrity, performs various environment checks, and serves as the target for protections that the kernel driver applies specifically to the game process.
The communication flow goes: BEDaisy.sys detects something suspicious, signals BEService.exe via an IOCTL completion or a shared memory notification, BEService.exe reports to BattlEye’s servers, the server decides on an action (kick/ban), and BEService.exe instructs the game to terminate the connection.
BattlEye’s three-component architecture: BEDaisy.sys at ring 0 communicates upward via IOCTLs to BEService.exe running as a SYSTEM service, which manages BEClient_x64.dll injected in the game process.
vgk.sys is notably more aggressive than the BattlEye driver in its scope. Because it loads at boot, it can intercept the driver load process itself. Vanguard maintains an internal allowlist of drivers that are permitted to co-exist with a protected game. Any driver not on this list, or any driver that fails integrity checks, can result in Vanguard refusing to allow the game to launch. This is an allowlist model rather than a blocklist model, which is architecturally much stronger.
vgauth.exe is the Vanguard service, which handles the communication between vgk.sys and Riot’s backend infrastructure.
This is the foundation of everything a kernel anti-cheat does. The Windows kernel exposes a rich set of callback registration APIs intended for security products, and anti-cheats use every one of them.
ObRegisterCallbacks is perhaps the single most important API for process protection. It allows a driver to register a callback that is invoked whenever a handle to a specified object type is opened or duplicated. For anti-cheat purposes, the object types of interest are PsProcessType and PsThreadType.
The pre-operation callback receives a POB_PRE_OPERATION_INFORMATION structure. The critical field is Parameters->CreateHandleInformation. DesiredAccess. The callback can strip access rights from the desired access by modifying Parameters->CreateHandleInformation.DesiredAccess before the handle is created. This is how anti-cheats prevent external processes from opening handles to the game process with PROCESS_VM_READ or PROCESS_VM_WRITE access.
When a cheat calls OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, gameProcessId), the anti-cheat’s ObRegisterCallbacks pre-operation callback fires. The callback checks whether the target process is the protected game process. If it is, it strips PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_VM_OPERATION, and PROCESS_DUP_HANDLE from the desired access. The cheat receives a handle, but the handle is useless for reading or writing game memory. The cheat’s ReadProcessMemory call will fail with ERROR_ACCESS_DENIED.
The IRQL for ObRegisterCallbacks pre-operation callbacks is PASSIVE_LEVEL, which means the callback can call pageable code and perform blocking operations (within reason).
ObCallbackDemo.sys in action. DebugView shows the driver stripping handle access rights, Target.exe is running with secret data in memory, and Verifier.exe fails to read it with Access Denied.
PsSetCreateProcessNotifyRoutineEx allows a driver to register a callback that fires on every process creation and termination event system-wide. The callback receives a PEPROCESS for the process, the PID, and a PPS_CREATE_NOTIFY_INFO structure containing details about the process being created (image name, command line, parent PID).
Notably, the Ex variant (introduced in Windows Vista SP1) provides the image file name and command line, which the original PsSetCreateProcessNotifyRoutine does not. The callback is called at PASSIVE_LEVEL from a system thread context.
Anti-cheats use this callback to detect cheat tool processes spawning on the system. If a known cheat launcher or injector process is created while the game is running, the anti-cheat can immediately flag this. Some implementations also set CreateInfo->CreationStatus to a failure code to outright prevent the process from launching.
PsSetCreateThreadNotifyRoutine fires on every thread creation and termination system-wide. Anti-cheats use it specifically to detect thread creation in the protected game process. When a new thread is created in the game process, the callback fires and the anti-cheat can inspect the thread’s start address.
The call to PsLookupThreadByThreadId retrieves the ETHREAD pointer for the new thread. PsGetThreadWin32StartAddress returns the Win32 start address as seen by the process, which is distinct from the kernel-internal start address. Once finished with the thread object, ObDereferenceObject releases the reference acquired by PsLookupThreadByThreadId.
A thread created in the game process whose start address does not fall within any loaded module’s address range is a strong indicator of injected code. Legitimate threads start inside module code. An injected thread typically starts in shellcode or manually mapped PE code that has no module backing.
PsSetLoadImageNotifyRoutine fires whenever an image (DLL or EXE) is mapped into any process. It provides the image file name and a PIMAGE_INFO structure containing the base address and size.
This is IRQL PASSIVE_LEVEL. The callback fires after the image is mapped but before its entry point executes, which gives the anti-cheat an opportunity to scan the image before any of its code runs.
CmRegisterCallbackEx registers a callback for registry operations. Anti-cheats use this to monitor for registry modifications that might indicate cheats configuring themselves or attempting to modify anti-cheat settings.
A minifilter driver sits in the file system filter stack and intercepts IRP requests going to and from file system drivers. Anti-cheats use minifilters to monitor for cheat file drops (writing known cheat executables or DLLs to disk), to detect reads of their own driver files (which might indicate attempts to patch the on-disk driver binary before it is verified), and to enforce file access restrictions.
FltGetFileNameInformation retrieves the normalized file name for the target of the operation. FltReleaseFileNameInformation must be called to release the reference when done. Minifilter callbacks typically run at APC_LEVEL or PASSIVE_LEVEL, depending on the operation and the file system. This is important because many operations (like allocating paged pool or calling pageable functions) are not safe at DISPATCH_LEVEL or above.
The kernel driver can do far more than just register callbacks. It can actively scan the game process’s memory and the system-wide memory pool for artifacts of cheats.
As covered in the ObRegisterCallbacks section, the primary mechanism for protecting game memory from external reads and writes is stripping PROCESS_VM_READ and PROCESS_VM_WRITE from handles opened to the game process. This is effective against any cheat that uses standard Win32 APIs (ReadProcessMemory, WriteProcessMemory) because these ultimately call NtReadVirtualMemory and NtWriteVirtualMemory, which require appropriate handle access rights.
However, a kernel-mode cheat can bypass this entirely. It can call MmCopyVirtualMemory directly (an unexported but locatable kernel function) or manipulate page table entries directly to access game memory without going through the handle-based access control system. This is why handle protection alone is insufficient and why kernel-level cheats require kernel-level anti-cheat responses.
Anti-cheats periodically hash the code sections (.text sections) of the game executable and its core DLLs. A baseline hash is computed at game start, and periodic re-hashes are compared against the baseline. If the hash changes, someone has written to game code, which is a strong indicator of code patching (commonly used to enable no-recoil, speed, or aimbot functionality by patching game logic).
The KeStackAttachProcess / KeUnstackDetachProcess pattern is used to temporarily attach the calling thread to the target process’s address space, allowing the driver to read memory that is mapped into the game process without going through handle-based access controls. RtlImageNtHeader parses the PE headers from the in-memory image base.
The most interesting memory scanning is the heuristic detection of manually mapped code. When a legitimate DLL loads, it appears in the process’s PEB module list, in the InLoadOrderModuleList, and has a corresponding VAD_NODE entry with a MemoryAreaType that indicates the mapping came from a file. Manual mapping bypasses the normal loader, so the mapped code appears in memory as an anonymous private mapping or as a file-backed mapping with suspicious characteristics.
The key heuristic is: find all executable memory regions in the process, then cross-reference each one against the list of loaded modules. Executable memory that does not correspond to any loaded module is suspicious.
ZwQueryVirtualMemory iterates through committed memory regions, returning a MEMORY_BASIC_INFORMATION structure for each. The Type field distinguishes private allocations (MEM_PRIVATE) from file-backed mappings (MEM_IMAGE, MEM_MAPPED). BattlEye’s scanning approach, as documented by the secret.club and back.engineering analyses, involves scanning all memory regions of the protected process and specifically flagging executable regions without file backing. It also scans external processes’ memory pages looking for execution bit anomalies, specifically targeting cases where page protection flags have been changed programmatically to make otherwise non-executable memory executable (a common technique when shellcode is staged).
The VAD (Virtual Address Descriptor) tree is a kernel-internal structure that the memory manager uses to track all memory regions allocated in a process. Each VAD_NODE (which is actually a MMVAD structure in kernel terms) contains information about the region: its base address and size, its protection, whether it is file-backed (and if so, which file), and various flags.
Anti-cheats walk the VAD tree directly rather than relying on ZwQueryVirtualMemory, because the VAD tree cannot be trivially hidden from kernel mode in the same way that module lists can be manipulated. Walking the VAD:
We can observe this detection in practice using WinDbg’s !vad command on a process with injected code.
The first entry is a Private EXECUTE_READWRITE region with no backing file, injected by our test tool. Every legitimate module shows as Mapped Exe with a full file path.
The power of VAD walking is that it catches manually mapped code even if the cheat has manipulated the PEB module list or the LDR_DATA_TABLE_ENTRY chain to hide itself. The VAD is a kernel structure that usermode code cannot modify directly.
The classic injection technique: call CreateRemoteThread in the target process with LoadLibraryA as the thread start address and the DLL path as the argument. This is trivially detectable via PsSetCreateThreadNotifyRoutine: the new thread’s start address will be LoadLibraryA (or rather its address in kernel32.dll), and the caller process is not the game itself.
A more subtle check is the CLIENT_ID of the creating thread. When CreateRemoteThread is called, the kernel records which process created the thread. The anti-cheat can check whether a thread in the game process was created by an external process, which is a reliable indicator of injection.
QueueUserAPC and the underlying NtQueueApcThread allow queuing an Asynchronous Procedure Call to a thread in any process for which the caller has THREAD_SET_CONTEXT access. When the target thread enters an alertable wait, the APC fires and executes arbitrary code in the target thread’s context.
Detection at the kernel level leverages the KAPC structure. Each thread has a kernel APC queue and a user APC queue. Anti-cheats can inspect the pending APC queue of game process threads to detect suspicious APC targets:
A sophisticated injection technique maps a shared section object (backed by a file or created with NtCreateSection) into the target process using NtMapViewOfSection. This bypasses CreateRemoteThread-based detection heuristics because no remote thread is created initially. The injected code is then typically triggered via APC or by modifying an existing thread’s context.
Detection is via the VAD: a section mapping that appears in the game process but was created by an external process will have a distinct pattern in the VAD. Specifically, the MMVAD::u. VadFlags.NoChange and related flags, combined with the file object backing the section (or lack thereof), can reveal this technique.
Reflective DLL injection embeds a reflective loader inside the DLL that, when executed, maps the DLL into memory without using LoadLibrary. The DLL parses its own PE headers, resolves imports, applies relocations, and calls DllMain. The result is a fully functional DLL in memory that never appears in the InLoadOrderModuleList.
Detection: executable memory with a valid PE header (check for the MZ magic bytes and the PE\0\0 signature at the offset specified by e_lfanew) but no corresponding module list entry. This is a reliable indicator.
We can observe this in practice using a simple test tool that allocates an RWX region and writes a minimal PE header into a running process:
Walking the VAD tree with !vad reveals the injected region immediately. The first entry at 0x8A0 is a Private EXECUTE_READWRITE region with no backing file. Compare this to the legitimate Target.exe image at the bottom, which is Mapped Exe EXECUTE_WRITECOPY with a full file path. Dumping the legitimate module’s base with db confirms a complete PE header with the DOS stub:
Dumping the injected region at 0x008A0000 also shows a valid MZ signature, but the rest of the header is mostly zeroes with no DOS stub. This is characteristic of manually mapped code:
Finally, !peb confirms that the injected region does not appear in any of the module lists. The PEB only contains Target.exe, ntdll.dll, kernel32.dll, and KernelBase.dll. The region at 0x008A0000 is completely invisible to any usermode API that enumerates loaded modules:
When BEDaisy wants to inspect a thread’s call stack, it uses an APC mechanism to capture the stack frames while the thread is in user mode. The APC fires in the game thread’s context and calls RtlWalkFrameChain or RtlCaptureStackBackTrace to capture the return address chain.
The back.engineering analysis of BEDaisy (and the Aki2k/BEDaisy GitHub research) documents this specifically: BEDaisy queues kernel APCs to threads in the protected process. The APC kernel routine runs at APC_LEVEL, captures the thread’s stack, and then analyzes each return address against the list of loaded modules. A return address pointing outside any loaded module is a strong indicator of injected code on the stack, which suggests the thread is currently executing injected code or returned from it.
Hooks are the primary mechanism by which usermode cheats intercept and manipulate the game’s interaction with the OS. Detecting them is a core anti-cheat function.
The Import Address Table (IAT) of a PE file contains the addresses of imported functions. When a process loads, the loader resolves these addresses by looking up each imported function in the exporting DLL and writing the function’s address into the IAT. An IAT hook overwrites one of these entries with a pointer to attacker-controlled code.
Detection is straightforward: for each IAT entry, compare the resolved address against what the on-disk export of the correct DLL says the address should be.
RtlImageDirectoryEntryToData locates the import directory from the PE headers. The TRUE parameter specifies that the image is mapped (as opposed to a raw file on disk), which is correct when working with in-memory modules. The outer loop walks the IMAGE_IMPORT_DESCRIPTOR array, terminating on a zero Name field. The inner loop compares each resolved IAT entry against the expected export address.
Inline hooks patch the first few bytes of a function with a JMP (opcode 0xE9 for relative near jump, or 0xFF 0x25 for indirect jump through a memory pointer) to redirect execution to attacker code, which typically performs its modifications and then jumps back to the original code (a “trampoline” pattern).
Detection involves reading the first 16-32 bytes of each monitored function and checking for:
* 0xCC (INT 3) - a software breakpoint, which can also be a hook point
The anti-cheat reads the on-disk PE file and compares the on-disk bytes of function prologues against what is currently in memory. Any discrepancy indicates patching.
To demonstrate inline hook detection, we use a test tool that patches NtReadVirtualMemory in a running process with a MOV RAX; JMP RAX hook:
Before patching, the function prologue shows a clean syscall stub. mov r10, rcx saves the first argument, mov eax, 3Fh loads the syscall number, and syscall transitions to kernel mode:
After the hook is installed, the first 12 bytes are overwritten with mov rax, 0xDEADBEEFCAFEBABE; jmp rax, redirecting execution to an attacker-controlled address. An anti-cheat comparing these bytes against the on-disk copy of ntdll would immediately flag the mismatch:
The System Service Descriptor Table (SSDT) is the kernel’s dispatch table for syscalls. When a usermode process executes a syscall instruction, the kernel uses the syscall number (placed in EAX) to index into the SSDT and invoke the corresponding kernel function. Patching the SSDT redirects syscalls to attacker-controlled code.
SSDT hooking is a classic technique that became significantly harder after the introduction of PatchGuard (Kernel Patch Protection, KPP) in 64-bit Windows. PatchGuard monitors the SSDT (among many other structures) and triggers a CRITICAL_STRUCTURE_CORRUPTION bug check (0x109) if it detects modification. As a result, SSDT hooking is essentially dead in 64-bit Windows. However, anti-cheats still verify SSDT integrity as a defense in depth measure.
The Interrupt Descriptor Table (IDT) maps interrupt vectors to their handler routines. The Global Descriptor Table (GDT) defines memory segments. Both are processor-level structures that cannot be easily protected by PatchGuard alone on all configurations.
A cheat operating at kernel level can attempt to replace IDT entries to intercept specific interrupts, which can be used for control flow interception or as a covert channel. Anti-cheats verify that IDT entries point to expected kernel locations:
A common evasion technique is for cheats to perform syscalls directly (using the syscall instruction with the appropriate syscall number) rather than going through ntdll.dll functions. This bypasses usermode hooks placed in ntdll. Anti-cheats detect this by monitoring threads within the game process for syscall instruction execution from unexpected code locations, and by checking whether ntdll functions that should be called are actually being called with expected frequency and patterns.
On a properly configured Windows system with Secure Boot enabled, all kernel drivers must be signed by a certificate trusted by Microsoft. Test signing mode (enabled with bcdedit /set testsigning on) allows loading self-signed drivers and is a common development and cheat-deployment technique.
Anti-cheats detect test signing mode by reading the Windows boot configuration and by checking the kernel variable that reflects whether DSE is currently enforced. Some anti-cheats refuse to launch if test signing is enabled.
The SeValidateImageHeader and SeValidateImageData functions in the kernel validate driver signatures. Anti-cheats can inspect loaded driver objects and verify that their IMAGE_INFO_EX ImageSignatureType and ImageSignatureLevel fields reflect proper signing.
Bring Your Own Vulnerable Driver is the dominant technique for loading unsigned kernel code in 2024-2026. The attack works as follows:
The attacker finds a legitimate, signed driver with a vulnerability (typically a dangerous IOCTL handler that allows arbitrary kernel memory reads/writes, or that calls MmMapIoSpace with attacker-controlled parameters).The attacker loads this legitimate driver (which passes DSE because it has a valid signature).The attacker exploits the vulnerability in the legitimate driver to achieve arbitrary kernel code execution. Using that kernel execution, the attacker disables DSE or directly maps their unsigned cheat driver.
Common BYOVD targets have included drivers from MSI, Gigabyte, ASUS, and various hardware vendors. These drivers often have IOCTL handlers that expose direct physical memory read/write capability, which is all an attacker needs.
The primary defense against BYOVD is a blocklist of known-vulnerable drivers. The Microsoft Vulnerable Driver Blocklist (maintained in DriverSiPolicy.p7b) is built into Windows and distributed via Windows Update. Anti-cheats maintain their own, more aggressive blocklists.
Vanguard in particular is known for actively comparing the set of loaded drivers against its blocklist and refusing to allow the protected game to launch if a blocklisted driver is present. This is enforced because some BYOVD attacks involve loading the vulnerable driver and immediately using it before unloading it, so checking only at game launch with a pre-scan covers most cases.
...
Read the original on s4dbrd.github.io »
TL;DR: This is a collection of practical marketing resources to get the first 10 / 100 / 1000 users for your SaaS / App / Startup.
Let’s face it. Marketing is tough, especially if you’re a technical founder moving the first steps with your startup.
While there’s a ton of advice out there, most of the time it’s about scaling some VC-funded startup with a big marketing budget to $1,000,000 ARR and 100,000 users.
Inspirational? Sure. Actionable? Not really.
This is why you’ll find here a practical collection of startup resources, tips, and some tools to help you:
* 🎯 find the first users for your startup
Ready to get started? Just pick a topic:
PS. Leave me a star ⭐ or say Hi on X/Twitter if you find this useful!
Launch platforms, software directories, and communities are an easy way to get the first users for your startup and a tiny, steady flow of people to your SaaS. Some of the following may not be relevant for your product, but (hopefully) can give you ideas on what to look for.
Subreddits (always check the rules before posting anything):
PS. You can find more websites to launch your startup here (thanks Sandra) and, if you’re running an AI startup, here.
PPS. You can find a few tips for writing a great launch post here.
Product Hunt is still one of the most effective ways to get early users (if you can pull off a successful launch). Yes, it takes a lot of preparation, promotion and follow-up work but hitting #1 on Product Hunt can get you a ton of honest feedback, downloads, users and PR buzz.
Use these guides to plan your launch strategy:
Managing multiple accounts across all social media platforms can be pretty intense (there’s a reason why it’s a full-time job); however, there are a couple of strategies worth trying: building in public and social listening.
Building in Public - it’s the easiest social media marketing tactic. In a nutshell, it’s about using your personal profile to share expertise, product updates, and behind-the-scenes content:
Social listening - tracking and joining online conversations on topics related to your Startup is pretty helpful to get in touch with users and learn more about them:
Most people hate cold outreach and everything connected with direct sales. Yes, depending on your approach, it might be hard to scale and time-intensive, but cold emails and DMs are the most straightforward way to get in touch with potential users, collect precious early-stage feedback and find the first customers for your startup.
So, how can you find people on target? What should you write in your messages? And how can you scale cold outreach? Here are a few ideas to set up your SaaS sales strategy:
Should you focus on SEO in the early days of your startup? Probably not, SEO takes time (and backlinks), but this doesn’t mean you should neglect it either.
SEO is the foundation for many startups, and it can get you a steady source of traffic without having to spend anything other than the time to do it.
These guides and tips will help you get started:
PS. If you’re new to SEO and don’t know where/how to start check out LearningSEO and The Complete Guide to Programmatic SEO.
PPS. If you’re building iOS apps also check these two ASO guides: Create an App Store listing that ranks and Rank higher on App Store
How to get recommended by ChatGPT, Claude, Perplexity, and Gemini is probably the most popular question from founders in 2025. And it’s easy to understand why, you can get mentioned by a citation tomorrow and start showing up immediately, even if you’re an early-stage startup.
So, where can you learn how to get found in AI search?
Everyone says Reddit is the go-to platform to get initial traction and the first users for your product. There’s a community for almost every topic, plus more and more people (and LLMs) are using it to bypass SEO garbage and find what real humans actually think.
But Redditors have an almost supernatural ability to detect marketing, and when founders try to sneak in promotional content, it gets downvoted into oblivion (if you’re lucky).
So, how to promote an App/SaaS/Startup on Reddit without getting banned?
PS. If you’re looking for a list of subreddits that allow self-promotion go back to Places To Launch Your Startup, and if you’re looking to set up your Reddit social listening strategy go back to Social Media Marketing.
Email marketing can help you with almost anything: onboarding new users, boosting free-to-paid conversions, promoting affiliate/referral programs, and collecting feedback. Plus, it’s often your only option to engage users outside of your product.
Just like SEO, content marketing is one of the first growth levers to pull for a startup. This time, instead of creating content to praise search engines, you’ll be sharing it where your audience hangs out and hijacking trends for free exposure:
Every startup will think about adding a paid channel to their marketing strategy sooner or later and Ads are one of the best ways to accelerate user acquisition.
Here are a few guides to help you avoid burning through your budget before getting any return from your Ad campaign:
Going where the audience is is the first rule of marketing. That’s why partnering with content creators and influencers can help you grow your startup and connect directly with customers.
However, online advice on influencer marketing is often bad and outdated, so let’s take a look at a few firsthand experiences:
Word-of-mouth is often the main growth driver for bootstrapped startups. Why? Because affiliate and referral programs are easy to set up, cheap (you only pay if the user converts), and build trust and credibility (when users recommend your product, you’re not only getting traffic, but also social proof that it works).
Free mini tools are your best bet on going viral, generating PR coverage and backlinks, and driving a ton of organic traffic. Plus, it doesn’t feel like marketing (that’s the reason why it’s also called Engineering as Marketing).
Most startup websites are ineffective. They fail to tell who the product is for, what the product does, and why it is better. This means confused visitors, poor conversion rates, and wasted marketing $$$.
Here are a few resources to turn your website into a sales and marketing asset:
“Your product delivers the value added to target customers; with pricing, we capture added value back to build a sustainable business.”
Confused? You’re not alone. Pricing and business model are the toughest things to crack for a startup.
Here are a few frameworks to get your price right:
Little tweaks can have a huge impact on conversions. Here are a few ideas ready for you to A/B test:
One of the most common and painful mistakes you can make is having an idea and immediately spending all your time building a product without validating demand first.
Some ideas are great, but most aren’t worth your time. So, how can you validate a startup idea?
Talk to your customers! They’ll let you know what to build, what to improve, and what to leave alone!
Sure, but when you’re running a SaaS/App, customers sign up by themselves, learn how to use your product on their own, and only reach out if they need help or support.
So, how do you get feedback? Here are a few resources to get you started:
This was inspired by Marketing for Engineers (thanks Lisa & Ahmed!) and is created and maintained by Edoardo Stradella (Twitter/X).
All resources, guides, and tools were done by independent authors and companies. All credentials are included.
...
Read the original on github.com »
Han is a statically-typed, compiled programming language where every keyword is written in Korean. It compiles to native binaries through LLVM IR and also ships with a tree-walking interpreter for instant execution. The compiler toolchain is written entirely in Rust.
Han was born from the idea that programming doesn’t have to look the same in every country. Hangul — the Korean writing system — is one of the most scientifically designed scripts in human history, and Han puts it to work as a first-class programming language rather than just a display string.
* Hangul identifiers — name your variables and functions in Korean
hgl interpret hello.hgl
# Output: 안녕하세요, 세계!
Or jump into the REPL:
hgl repl
한> 출력(“안녕!“)
안녕!
git clone https://github.com/xodn348/han.git
cd han
cargo install –path .
That’s it. hgl is now available globally.
cd editors/vscode
npm install && npm run compile
Then open the editors/vscode folder in VS Code and press F5 to launch with syntax highlighting + LSP support.
* Arrays with negative indexing — arr[-1] returns the last element
* 시도 { } 실패(오류) { } — catches any runtime error including division by zero, file not found, out-of-bounds
* 가져오기 “파일.hgl” — runs another .hgl file in the current scope
* 함수 최대값 — type params are parsed and erased at runtime
Hangul (한글) is not just a writing system — it is a feat of deliberate linguistic design. Created in 1443 by King Sejong the Great, each character encodes phonetic information in its geometric shape. Consonants mirror the tongue and mouth positions used to pronounce them. Vowels are composed from three cosmic symbols: heaven (·), earth (ㅡ), and human (ㅣ).
Han brings this elegance into programming. When you write 함수 피보나치(n: 정수) -> 정수, you are not just defining a function — you are writing in a script that was purpose-built for clarity and beauty.
Hangul is also surprisingly easy to learn — you can learn the whole system in an afternoon.
The global interest in Korean culture has never been higher. From K-pop and Korean cinema to Korean cuisine and language learning, millions worldwide are engaging with Korean culture. Over 16 million people are actively studying Korean as a foreign language.
Han offers these learners something unexpected: a way to practice reading and writing Hangul through programming. It bridges the gap between cultural interest and technical skill, making Korean literacy functional in a domain where it has never existed before.
Han follows the classical compiler pipeline, implemented entirely in Rust with zero external compiler dependencies (LLVM IR is generated as plain text):
Why text-based LLVM IR instead of the LLVM C API?
Han generates LLVM IR as plain text strings, avoiding the complexity of linking against LLVM libraries. This keeps the build simple (cargo build — no LLVM installation required) while still producing optimized native binaries through clang.
Why both interpreter and compiler?
The interpreter enables instant execution without any toolchain dependencies beyond Rust. The compiler path exists for production use where performance matters. Same parser, same AST, two backends.
Why Rust?
Rust’s enum types map naturally to AST nodes and token variants. Pattern matching makes parser and interpreter logic clear and exhaustive. Memory safety without garbage collection suits a language toolchain.
cargo test
Han — where the beauty of Hangul meets the precision of code.
...
Read the original on github.com »
To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".
10HN is also available as an iOS App
If you visit 10HN only rarely, check out the the best articles from the past week.
If you like 10HN please leave feedback and share
Visit pancik.com for more.