|
2 | 2 | <img src="./assets/logo.svg" alt="Nothing" width="474"> |
3 | 3 | </p> |
4 | 4 |
|
5 | | -# n0 ("nothing") |
| 5 | +# n0 |
6 | 6 |
|
7 | | -Nothing but drawing. An engine for everything drawable. |
| 7 | +**Nothing but drawing.** A 2D graphics engine that speaks the Web — browser-grade |
| 8 | +rendering, without a browser. |
8 | 9 |
|
9 | | -`n0` (pronounced "nothing") is a 2D graphics engine. |
| 10 | +`n0` (pronounced "nothing") turns SVG and HTML/CSS into exact pixels: from a |
| 11 | +command line, from a library, and eventually onto an editable realtime canvas. |
| 12 | +No scripting, no navigation, no network, no user agent. |
10 | 13 |
|
11 | | -## Status |
| 14 | +## The claim |
12 | 15 |
|
13 | | -The graphics engine lives here. It migrated from the |
14 | | -[Grida repository](https://github.com/gridaco/grida) with its full history |
15 | | -(2025→) carried over; Grida remains the service/editor monorepo and consumes |
16 | | -the engine only through published artifacts. |
| 16 | +n0 is the renderer half of a browser — embeddable, deterministic, and |
| 17 | +eventually editable. There is no agent half: |
17 | 18 |
|
18 | | -Two engines live side by side while the topology converges: the migrated |
19 | | -production engine (`crates/grida`, shipping as `@grida/canvas-wasm`) and |
20 | | -the v2 `n0` engine family (`crates/n0`), promoted from the |
21 | | -`model-v2-anchor` research branch. The v2 model program is tracked in |
22 | | -[gridaco/nothing#9](https://github.com/gridaco/nothing/issues/9). |
| 19 | +| n0 refuses | which buys | |
| 20 | +| ---------------------- | ------------------------------------------------------------------ | |
| 21 | +| scripting | determinism — same input, same declared time, same bytes | |
| 22 | +| navigation & network | one binary, no sandbox, no headless flags, no 200MB download | |
| 23 | +| the user-agent surface | a small attack surface and a core you can embed | |
| 24 | +| an ambient clock | frame-exact animation — dropped frames are structurally impossible | |
| 25 | + |
| 26 | +## What it's for |
| 27 | + |
| 28 | +- **Deterministic Web rendering.** SVG and HTML/CSS to exact pixels — in CI, in |
| 29 | + a container, on a machine that will never have a browser. Same input, same |
| 30 | + declared time, same bytes. |
| 31 | +- **Animation as exact time.** A source compiles once and is sampled at a |
| 32 | + declared signed-nanosecond instant. An exported sequence has no frame to drop. |
| 33 | +- **Realtime on heavy documents.** 60+ fps where an entire page is one frame |
| 34 | + inside an infinite canvas — panning, zooming, editing. |
| 35 | +- **Layout we own.** Browser-grade layout is where general-purpose layout |
| 36 | + libraries stop. Taffy carries flex until it doesn't. |
| 37 | +- **One engine, everywhere.** Native and WebAssembly, single-threaded — no |
| 38 | + worker threads to hide behind. |
| 39 | + |
| 40 | +## The pipeline |
| 41 | + |
| 42 | +One document, one cascade, one compiler, one contract, one kernel. |
| 43 | + |
| 44 | +```text |
| 45 | +source bytes (.svg | .html) |
| 46 | + → one namespace-aware document csscascade |
| 47 | + → one Stylo cascade csscascade |
| 48 | + → effective values: Base | Sample t websem |
| 49 | + → rframe::Frame websem |
| 50 | + → resolve → drawlist → paint n0 |
| 51 | +``` |
| 52 | + |
| 53 | +Each stage owns one decision. `csscascade` resolves values and decides no |
| 54 | +meaning; `websem` decides what will and will not render, and never touches a |
| 55 | +canvas; `rframe` is a vocabulary whose value is what it *cannot* express; `n0` |
| 56 | +decides how to get pixels, never what they mean. |
| 57 | + |
| 58 | +Two pieces are borrowed on purpose. The cascade is **Stylo** — Firefox's style |
| 59 | +engine, pinned to an upstream revision, not a CSS subset written here. The |
| 60 | +rasterizer is **Skia** — the same one Chromium rasterizes with, which is much of |
| 61 | +why byte-exact comparison against a Chromium bake is a reasonable bar at all. |
| 62 | +n0 is the architecture between them: it owns resolution, the drawlist, damage, |
| 63 | +caching, and time. It owns no cascade and no rasterizer. |
| 64 | + |
| 65 | +## Declared holes, never guessed pixels |
| 66 | + |
| 67 | +The engine refuses loudly, or it names the hole. It never guesses. |
| 68 | + |
| 69 | +```console |
| 70 | +$ # best-effort (the default): render what is admitted, declare the rest by name |
| 71 | +$ cargo run -q -p n0_cli --bin n0 -- fixtures/test-svg/probe/polygon-fill-probe.svg out.png 64x64 |
| 72 | +degraded: skipped svg/polygon[1]: unsupported element <polygon> |
| 73 | +rendered fixtures/test-svg/probe/polygon-fill-probe.svg -> out.png (64x64, base-shared-frame, 1 degraded, 223 bytes) |
| 74 | + |
| 75 | +$ # --strict: refuse on the first construct outside the slice |
| 76 | +$ cargo run -q -p n0_cli --bin n0 -- fixtures/test-svg/probe/polygon-fill-probe.svg out.png 64x64 --strict |
| 77 | +error: render failed: unsupported element <polygon> |
| 78 | +``` |
| 79 | + |
| 80 | +Where nothing degrades, the two admissions are frame-identical, and a law |
| 81 | +checks that across the whole corpus. |
| 82 | + |
| 83 | +```sh |
| 84 | +# a whole composition — containers, curves, strokes, one animated rect — |
| 85 | +# rendered at its authored state, and at exactly one second |
| 86 | +cargo run -p n0_cli --bin n0 -- \ |
| 87 | + fixtures/web-first/animation/svg-scene-cub-animation.svg cub.png 96x96 |
| 88 | +cargo run -p n0_cli --bin n0 -- \ |
| 89 | + fixtures/web-first/animation/svg-scene-cub-animation.svg cub-1s.png 96x96 \ |
| 90 | + --time-ns 1000000000 |
| 91 | +``` |
| 92 | + |
| 93 | +## Where it is today |
| 94 | + |
| 95 | +The Web path renders a standalone SVG, or an HTML document's first inline SVG: |
| 96 | +`<rect>`, `<circle>`, `<ellipse>`, `<path>` and `<line>` — filled and stroked — |
| 97 | +nested in `<g>` with the whole `transform` grammar; root sizing per SVG2 §8.2 |
| 98 | +with the full `preserveAspectRatio` grammar; and one exact-time `<animate>`. |
| 99 | +[`crates/n0_cli/README.md`](./crates/n0_cli/README.md) is the statement of |
| 100 | +record for that slice and what it refuses. |
| 101 | + |
| 102 | +Its corpus is 77 Chromium-baked cells and 10 sampled frames, byte-exact except |
| 103 | +six curved cells carrying a declared tolerance confined to the weighted |
| 104 | +rational conic. That describes one enumerated corpus — **it is not a |
| 105 | +conformance claim**, and no conformance score exists. |
| 106 | + |
| 107 | +Not admitted yet: text, gradients, clips, masks, filters, and opacity. Not built |
| 108 | +yet: any layout engine, any editor host, any WebAssembly target. |
| 109 | + |
| 110 | +**Nothing here is published.** There are no releases, and the only shipped |
| 111 | +artifact in the tree is the frozen v1 wasm package. Run it from a clone — |
| 112 | +[setup](./AGENTS.md#setup). |
| 113 | + |
| 114 | +## One pipeline |
| 115 | + |
| 116 | +2D has no perceptual slack: a 40px icon is either right or visibly wrong, and no |
| 117 | +level-of-detail trick hides it. So there is no fast renderer beside an exact one. |
| 118 | +Static is the same pipeline with an empty temporal input set — no camera delta, |
| 119 | +no previous frame, no dirty set. Realtime is the same pipeline allowed to reuse. |
| 120 | + |
| 121 | +> Modes may differ in **when and at what quality** they paint — never in **what |
| 122 | +> things mean**. |
| 123 | +
|
| 124 | +The architecture that makes realtime possible ships from day one; the |
| 125 | +optimizations it needs ship only once measured. So the sockets ship empty: |
| 126 | +`DirtyClass` classifies every operation's invalidation in `n0-model`, and the |
| 127 | +engine references it exactly zero times. It full-resolves every frame until |
| 128 | +correctness has earned the right to reuse. |
| 129 | + |
| 130 | +## The laws |
| 131 | + |
| 132 | +- **Never a silent wrong pixel.** Refuse, or name the hole. |
| 133 | +- **A module's identity is what it refuses**, and the refusal has a guarding |
| 134 | + test. `rframe` cannot express a gradient. `animation-sampling` owns no clock. |
| 135 | + `n0_cli` is architecturally forbidden from calling the renderer it replaced. |
| 136 | +- **Reuse ≡ fresh.** Any frame produced with reuse is byte-identical to the same |
| 137 | + frame produced from scratch. |
| 138 | +- **The oracle is external.** Chromium, or declared consensus. Never this |
| 139 | + engine's other half. Stylo bounds what can be *supported*; a gap there is a |
| 140 | + declared hole, never a wrong pixel. |
| 141 | + |
| 142 | +## Why the Web first |
| 143 | + |
| 144 | +The destination is n0's own source language — SVG with layout, reimagined. It |
| 145 | +is deliberately not being built yet. |
| 146 | + |
| 147 | +The Web is the only 2D graphics specification that is frozen, adversarially |
| 148 | +tested, and ships with a free executable oracle. A custom format has none of |
| 149 | +that. On the Web path every correctness question has an answer before anyone has |
| 150 | +an opinion — and the cascade, layout, paint, text, and animation built to satisfy |
| 151 | +it are the same ones the format will stand on. |
| 152 | + |
| 153 | +The format is not deprioritized. It is waiting for a bar that can grade it. |
| 154 | + |
| 155 | +## Also in this tree |
| 156 | + |
| 157 | +**A second engine.** [`crates/grida`](./crates/grida/README.md) is the mature |
| 158 | +v1: 19 node types, gradients, image filters, shadows, masks, PDF and SVG export, |
| 159 | +and a realtime estate benchmarked to 135K-node documents. It ships |
| 160 | +as `@grida/canvas-wasm` and is frozen there. It is being succeeded, not |
| 161 | +extended — its know-how migrates into n0 through contracts, never by copying, |
| 162 | +and the measurements in |
| 163 | +[`docs/wg/feat-2d/optimization.md`](./docs/wg/feat-2d/optimization.md) are the |
| 164 | +best existing description of what a realtime 2D engine actually needs. |
| 165 | + |
| 166 | +**Two custom formats, parked.** `.grida` (the v1 FlatBuffers binary) is frozen |
| 167 | +and read-only. `.n0.xml` (the authored source language) builds and is tested, |
| 168 | +and is deliberately not being expanded. Both are waiting on the same thing: a |
| 169 | +foundation proven against an oracle that can grade it. |
23 | 170 |
|
24 | 171 | ## Workspace |
25 | 172 |
|
26 | | -- [`crates/grida`](./crates/grida) — the legacy engine compatibility consumer |
27 | | -- [`crates/cg`](./crates/cg) — the backend-neutral canvas-graphics vocabulary |
28 | | -- [`crates/htmlcss`](./crates/htmlcss) — the extracted mature static HTML/CSS/SVG renderer |
29 | | -- [`crates/grida_editor`](./crates/grida_editor) — the editor core (document, history, commands) |
30 | | -- [`crates/grida-canvas-wasm`](./crates/grida-canvas-wasm) — WASM bindings (`@grida/canvas-wasm`) |
31 | | -- [`crates/math2`](./crates/math2) · [`crates/csscascade`](./crates/csscascade) · [`crates/fonts`](./crates/fonts) — foundations |
32 | | -- [`crates/grida_dev`](./crates/grida_dev) · [`crates/grida_wpt`](./crates/grida_wpt) — dev tools, benchmarks, reftests |
33 | | -- [`crates/n0`](./crates/n0) — the `n0` engine (v2): resolve → drawlist → paint |
34 | | -- [`crates/n0-model`](./crates/n0-model) · [`crates/n0_dev`](./crates/n0_dev) — the skia-free `anchor` model · the v2 dev shell |
35 | | -- [`crates/websem`](./crates/websem) · [`crates/rframe`](./crates/rframe) · [`crates/animation-sampling`](./crates/animation-sampling) — the Web semantic compiler · the resolved render contract it emits · the time axis both read |
36 | | -- [`crates/n0_cli`](./crates/n0_cli) — the thin `n0` file-render command host |
37 | | -- [`archive/model-v2/`](./archive/model-v2) — the frozen v2 workbench archive (decision record) |
38 | | -- [`format/`](./format) — the FlatBuffers schema (source of truth) |
39 | | -- [`docs/wg/`](./docs/wg) — the engine's normative working-group specs |
40 | | - |
41 | | -The repository is a Rust-first Cargo workspace. |
| 173 | +The Web path — `csscascade` → `websem` → `rframe` → `n0`: |
| 174 | + |
| 175 | +| crate | | |
| 176 | +| ------------------------------------------------------------- | ------------------------------------------------------------------ | |
| 177 | +| [`csscascade`](./crates/csscascade/README.md) | the Stylo bridge — one namespace-aware document, one cascade | |
| 178 | +| [`websem`](./crates/websem/README.md) | the Web semantic compiler — what will and will not render | |
| 179 | +| [`rframe`](./crates/rframe/README.md) | the resolved render contract — backend-free, provisional | |
| 180 | +| [`animation-sampling`](./crates/animation-sampling/README.md) | the time axis — no ambient clock | |
| 181 | +| [`n0`](./crates/n0/README.md) · [`n0-model`](./crates/n0-model/README.md) | the engine, and its skia-free model | |
| 182 | +| [`n0_cli`](./crates/n0_cli/README.md) | the `n0` binary | |
| 183 | + |
| 184 | +The v1 engine and its satellites: `grida`, `htmlcss`, `cg`, `grida_editor`, |
| 185 | +`grida-canvas-wasm`, `grida_dev`, `grida_wpt`. Foundations: `math2`, `fonts`. |
| 186 | +The full map is in [AGENTS.md](./AGENTS.md). |
| 187 | + |
| 188 | +## Docs |
| 189 | + |
| 190 | +[`docs/wg/`](./docs/wg/index.md) is the engine's working group — normative |
| 191 | +specifications, domain studies, and research, including 41 documents on |
| 192 | +Chromium's rendering architecture. Start at |
| 193 | +[the consolidation program](./docs/wg/consolidation/index.md) for how the two |
| 194 | +engines become one. |
| 195 | + |
| 196 | +Rust-first Cargo workspace. Licensed under [MIT](./LICENSE-MIT) or |
| 197 | +[Apache-2.0](./LICENSE-APACHE), at your option. |
0 commit comments