Why We Wrote a Geometry Kernel from Scratch
Why Modelcore uses a TypeScript kernel instead of a WASM wrapper.
Several browser CAD tools run real geometry client-side. SketchUp for Web is a browser modeler with WebAssembly. CADmium documents a Rust geometry stack with JavaScript bindings compiled to WebAssembly for browser execution. OpenCascade.js exposes Open CASCADE as JavaScript bindings built as a WebAssembly module using Emscripten. The browser is no longer just a thin viewport for server-rendered geometry; it is a viable execution environment for solid modeling.
Modelcore uses a geometry kernel written from scratch in TypeScript. The key decision was not whether a kernel can run in the browser, but whether to wrap an existing kernel or build one that matches our architecture directly.
The WASM tradeoff
Compiling an established kernel to WebAssembly gets you decades of battle-tested algorithms. That's a real advantage. But it also gets you decades of API surface designed for desktop workflows, a language boundary between your application and your geometry, and debugging that requires you to reason across two runtime environments at once.
When a CSG boolean produces a bad result, I want to set a breakpoint in the kernel, inspect the half-edge graph, and step through face reconstruction. In a WASM-wrapped kernel, that means reading C++ stack traces through source maps, if they exist. In our kernel, it means opening browser devtools: same language, same debugger, same type system as the rest of the application.
This directly affects debugging speed. Most hard geometry bugs are subtle topology issues that require inspecting internal state in detail.
What the kernel actually is
The core data structure is a half-edge boundary representation. Every edge in a model is stored as a pair of directed half-edges. Each half-edge knows its twin, its next neighbor in the face loop, its origin vertex, and the face it bounds. This gives constant-time adjacency queries: given a face, walk its boundary; given an edge, find neighboring faces; given a vertex, enumerate everything that touches it.
We separate topology from geometry. The half-edge graph stores relationships. A separate vertex registry stores positions. A solid manager tracks which faces belong to which solid. These are independent authorities with no redundant state between them, which makes failures easier to localize.
This separation is enforced through package boundaries and interface contracts, not just convention. If a React component needs topological information, it queries the kernel through a defined interface. This helps prevent the bug class where the UI and kernel quietly disagree about model state.
Designed for collaboration, not retrofitted
Most geometry kernels were designed for a single user on a single machine. Collaboration is often added later through file locking or state synchronization. We built our kernel knowing from day one that multiple people would edit the same model simultaneously.
That shaped the architecture in ways that are expensive to retrofit. Every operation is a serializable command. Operations are fail-atomic, producing a valid solid or no change at all (described in more detail in our correctness contract). Command execution is designed to stay consistent across clients (covered in our post on deterministic IDs).
These were foundational decisions, not late additions.
I've written about how this enables real-time multiplayer and what our correctness guarantees look like in practice.
No inherited assumptions
Writing from scratch means we did not inherit patterns designed for a different era of software. We did not adopt a file-save-oriented persistence model. There's no assumption that the user is alone. There's no API shaped by COM interfaces or C linkage conventions.
The abstractions match what a browser-based collaborative modeler actually needs: commands that serialize to JSON, operations that are safe to replay, topology queries that can run in a worker thread, and validation gates that treat correctness as a production concern rather than a debug assertion. This is also aligned with the broader product direction in Modelcore: Sketch-Speed, Solid-First Modeling.
The honest tradeoff
We gave up decades of accumulated edge-case handling. OpenCascade and Parasolid have seen geometry we haven't. Our kernel is younger, and coverage is still growing compared to mature kernels.
In return, we have a kernel we understand deeply, can debug quickly, and can adapt to browser-first collaboration requirements without crossing a WASM boundary for core geometry logic.
Whether that tradeoff is worth it depends on what you're building. For the kind of tool Modelcore is - a sketch-speed direct modeler where correctness and collaboration matter from the start - it has been the right call for us so far.