Chuks has two execution modes: a bytecode VM you develop against, and a native binary you ship. v0.1.1 is about making them agree everywhere, down to the last digit of an IEEE-754 float. Chuks runs your code two ways. A bytecode VM you develop against, it starts instantly, so the edit-run loop feels like a scripting language. And a native binary you ship, the compiler lowers your program to native code ahead of time, so production is fast. Code like a script, ship like a binary. That design only earns its keep if both modes compute the same thing. If a program behaves one way while you develop and another way once you ship, the two-mode model is a liability, not a feature. v0.1.1 is the release that makes them agree, everywhere they are asked to, down to the last digit. Where v0.1.0 was about speed, this one is about correctness. The headline numbers: 220 commits since v0.1.0, 446 files changed (+33,070 / -2,098), all 407 golden tests green on both the VM and as a native binary, 1,404 differential cells across 17 suites, and a ten-stage release preflight clean. Here is what that meant in practice. Agreement to the last ULP The most instructive fix in this release is a floating-point one, because it shows exactly the standard Chuks holds itself to. The n-body benchmark computed two different results depending on how it was run. As a native binary it printed 0.013051029850722899; on the VM it printed 0.009171719055316834. We checked against three other IEEE-754 runtimes, all three agreed with the VM, to the last digit. The native binary was the outlier. The cause was a fused multiply-add. The native backend was contracting a * b + c into a single instruction, which keeps the intermediate product at full internal precision through the addition and rounds once instead of twice. That sounds more accurate, and in isolation it is, but IEEE-754 prescribes a specific sequence with a specific rounding at each step, and every conforming runtime follows it. A fused multiply-add lands one unit in the last place, one ULP from the value the standard specifies. One ULP is the last digit of a sixteen-digit number. Invisible in a short program. But the first divergence appeared in exactly the shape a compiler wants to fuse: dx * dx + dy * dy + dz * dz Run 500,000 times, each result feeding the next, that one-ULP gap compounded into a difference you could read off the screen. Every float multiply is now rounded explicitly before anything can fuse it. Chuks and every other IEEE-754 runtime now print the same number. The fix costs about 11% on the most multiply-heavy loop in the benchmark set; roughly half of that came back from computing a repeated array-element read once instead of six times. The rest is gone, and we kept the fix anyway. The lost speed was the speed of a wrong answer, and there is no version of this that keeps both. The lasting part is the guard. We added a differential suite of 17 shapes covering every adjacency of a multiply to an addition, crossed with every path an operand takes to reach it. The inputs are adversarial, a case only counts if fusing genuinely changes the answer, and each carries the exact value computed in rational arithmetic, not just the VM's output. That last detail is what makes it airtight: if both backends were wrong the same way, comparing them to each other would prove nothing. Checking against a hand-computed exact value means two backends that agree with each other but not with IEEE-754 still fail. All 17 shapes fail without the fix. How these bugs surface Chuks has been carrying real software for several releases, a package registry and its frontend, HTTP services, a growing package ecosystem, an editor backend. Those programs turned up most of what v0.0.7 through v0.1.0 fixed, and they keep doing that job. What this cycle added was a different kind of pressure. A mobile framework is a large program whose generics cross package boundaries constantly: a class in one package specialized on a type from another, a specialization that has to be emitted somewhere neither package owns, closures capturing module state, the same private name declared in three files. And it runs on two targets that must agree, which turns any disagreement into a visible bug rather than a latent one. That combination is what shook these fixes loose, not because earlier releases were only tested, but because this workload leans hardest on the seam this release is about. One example, because it is representative. A generic function's specialization carries a map of type arguments, and by that stage the map holds compiled types, not Chuks ones. Naming the specialized class converted them back to Chuks names first, and that inverse only handled primitives. A class argument fell through to the opaque case, so the compiler emitted a reference to a Cell_ptr_any that nothing ever generated. The program ran perfectly on the VM and failed to build as a binary. The fix removed the round trip. The lasting part, again, is the guard: a class type argument axis was added to the generics differentials, because only a class argument makes a type-map value a pointer. That axis immediately found two more bugs, one of them a cross-package import cycle. The rule that came out of it: A real consumer build is the compiler's best fuzzer, and every bug it finds is converted into a generated test axis rather than a single regression case. That is why the battery grew by three axes this cycle instead of by forty-eight one-off tests. An axis is a permanent net; a regression test is a single caught fish. Live imports The one behavioral change worth reading carefully. Previously the VM copied an imported module-level variable at import time, while the native compiler read the exporter's current value, so the same program could print different numbers in development and in a release build. In Chuks, an import is a live binding: a window onto the exporting module, not a snapshot of it. The VM is what changed to match. // counter.chuks export var total: int = 0 export function add(n: int): void { total = total + n } // main.chuks import { total, add } from "./counter.chuks"; println(string(total)) // 0 add(5) println(string(total)) // 5, in both execution modes Three consequences: A binding is live. Reading an imported name always sees the exporting module's current value. Assigning to an imported name is a compile error. It is a read-only view, export a setter instead. Aliasing doesn't create a writable copy: import { f as g } gives you g, not f. A module is one instance per run. It used to be re-initialized for every importer, so its side effects ran once per importer and any accumulated state was discarded. If you were relying on the old copy behavior, the compiler tells you at the assignment. Reading a map yields an optional The other breaking change, and worth reading. A key that is not present has no value, so reading a typed map gives you an optional: var stock: map[string]int = {"apples": 3, "pears": 0} stock["kiwis"] == null // true stock["pears"] == null // false - a stored zero is a value, not an absence stock["kiwis"] ?? 0 // 0 Previously this was null on the VM and the zero value in a native build, and the difference was not cosmetic: m[k] + 5 printed a number as a binary and aborted the VM with a type mismatch. The same source working when shipped and dying in development is the worst shape a bug can take. The migration is ?? default, and it is free, that form compiles to a single lookup that never builds an optional, measured at the same speed as the old unchecked index over two million reads. A null check narrows the type, so if (m[k] != null) { total = total + m[k] } works too. .get(k), .getOr(k, d), and .has(k) are unchanged. Generics across packages Generics that stay inside one module were already solid. This release fixes what happens when a specialization has to be placed somewhere. The specialization of a generic from package A on a type from package B is emitted in B, next to the type, because A cannot import its own consumer. Nested arguments pin to the innermost class, so Box<Box<Node>> lands where Node lives rather than where Box does. A function specialization emitted into a foreign package qualifies its reads and its writes of the defining module's variables, and declared types of module-level variables are now published across packages so a reader no longer treats a typed value as opaque. Mobile: a real development loop The mobile toolchain gained a proper edit-reload loop. chuks pack collects an app and its dependencies into a single source bundle, chuks serve serves it with hot reload, and the Chuks Mobile Runtime (CMR) is an on-device VM that runs it. The device needs no compiler toolchain and no framework source, and a save reaches the running app as a delta. chuks pack type-checks and fails closed, a bundle that doesn't compile is never served, and when something fails at runtime the host shows a red error card with the file and line, built from a structured payload with clean paths, a code frame, and a filtered stack. Underneath it, --c-archive lets the AOT compiler emit a C-ABI bridge, which is what allows a native host a UIKit or Android app, to embed a compiled Chuks program in the first place. Streaming HTTP, both ways Server-Sent Events on the server: res.sse() res.push("tick", "1") res.end() and a pull-based streaming client, so a response is consumed as it arrives rather than buffered whole: const stream = http.open("GET", url, null) Also in this cycle: generic type inference (a generic call's type arguments are inferred from its arguments into the result type, so useState("") is a Cell<string> with no annotation), and as now accepts an interface, a generic instantiation, and a union, previously only a bare class name or a primitive. Where Chuks stands Nine benchmarks, five languages, best of seven runs on an M4 Max. Outputs are compared before any timing is reported, a benchmark where two languages compute different things is not a benchmark, and two of these were doing exactly that until this release. Milliseconds, lower is better. Read honestly: startup floors the small numbers. Chuks starts in 2.3ms, Bun 5.1ms, Python 12.2ms, Java 20.8ms, so the whole Java column below about 25ms is JVM startup rather than work. Chuks takes seven of nine against Bun and eight of nine against Java, and leads Python by between four and two-hundred-and-fifty times. The two it loses are allocation-heavy and hash-heavy, and one has a known cause: values() and keys() sort their keys for a deterministic order, which is 14.75ms of a 17.6ms call on a map of 100,000 entries. That is the next piece of performance work, not a mystery. The n-body row carries the footnote from earlier. The correctness fix cost 0.9ms there. Every runtime in that table computes 0.009171719055316834, and so does Chuks. Buying that 0.9ms back would mean printing a different number than everybody else. Upgrading chuks upgrade Two changes can break existing code, and both report themselves at the line responsible: Assignment to an imported binding is a compile error. Export a setter instead. Reading a typed map yields V?. Add ?? default where a value is required, also the fastest form, or bind it and check for null where absence means something. Everything else in this release makes programs that already worked on the VM behave the same way as a native binary. That is what the Correctness Release is: not a headline feature, but a promise that the thing you develop against and the thing you ship compute the same answer, verified across 1,404 differential cells, and held even when the correct answer is the slower one. Try Chuks at chuks.org