Chapter 64 Web I: C++23 to WebAssembly
CNA does not have a separate JavaScript implementation. Its Web builds send the same C++23 framework through Emscripten and link it with SDL’s browser port. Configuration admits 24 renderer identities on Emscripten, but five are the web-focused evidence set examined here: WEBGL1, WEBGL2, CANVAS, HTML_DOM, and SVG_DOM. One configure still packages exactly one selected identity into a WebAssembly application. That compact description hides three independent questions: can the source be translated, can the program survive the browser main-loop hand-off, and has the selected renderer actually been exercised? This chapter answers only the first. Chapters 65 and 66 answer the other two.
64.1 The toolchain changes the execution contract
The root build selects WEBGL2 by default when EMSCRIPTEN is true. This is a public renderer identity backed by the EasyGL family, just as WEBGL1 selects another configuration of that family. HTML_DOM, SVG_DOM, and CANVAS select distinct implementations. The choice is still exclusive: one configure produces one renderer, not a universal Web binary containing all five.
Exception handling is the first project-wide adjustment. CNA uses C++ exceptions in ordinary control and error paths, so the Web build enables real WebAssembly exceptions with -fwasm-exceptions. This is not merely a compiler-acceptance flag. It affects what the generated cleanup machinery catches, and therefore participates directly in the main-loop lifetime defect studied in Chapter 65.
The build remains single-threaded. No Web target enables Emscripten pthread support, and the tree has no corresponding cross-origin isolation setup. One CnaTests configuration uses ASYNCIFY=1 for asynchronous WebSocket/ENet behavior; that transformation is not evidence of a worker-thread architecture. A port that needs concurrent game, networking, or asset work would have to define that architecture and its hosting headers explicitly.
64.2 Assets must cross the virtual-filesystem boundary
A native executable can discover files already present beside its working directory. A browser cannot. Emscripten exposes a virtual filesystem, and a file exists there only if the page creates it or the linker packages it.
At the audited revision, exactly two build sites use --preload-file: the 2D graphics demo and the sound demo. There is no project-wide asset manifest, no --embed-file policy, and no custom shell file that silently fills the gap. Consequently, successful translation of a target proves nothing about the availability of its content at run time. Each Web application must account for every filename that can reach TitleContainer or ContentManager.
The same distinction applies to writes. SDL’s preference path resolves inside Emscripten’s in-memory filesystem, under /libsdl. CNA does not mount IDBFS, call FS.syncfs, or bridge its storage abstractions to localStorage. Saved data therefore survives within the current page instance but not a reload. StorageDevice and GamerServices persistence on a native platform must be described as volatile on Web until a durable browser store is deliberately connected.
64.3 Networking is a different topology, not a transparent port
Emscripten’s socket shim does not report a usable ephemeral bound port, so CNA’s ENet host path requests fixed port 61191 instead of ENET_PORT_ANY. That host role is meaningful for a Node relay, not an ordinary browser tab: the browser-side client path replaces any listening host with an outbound-only ENet client before connecting. The entire UDP-broadcast discovery implementation is excluded under __EMSCRIPTEN__. A Web build can therefore preserve SystemLink message transport without preserving native hosting and LAN-discovery topology.
The CnaTests-only ASYNCIFY=1 setting lets synchronous-looking polling yield to the JS event loop during WebSocket/ENet tests. It does not add listening sockets, make discovery available, or propagate to ordinary demos. Build evidence for the network module must therefore name the role and host, not merely say that ENet linked.
64.4 What a successful build establishes
A completed Emscripten link is meaningful evidence. It establishes that the selected source closure is accepted by the Web toolchain, that its C++23 surface is compatible with the chosen SDK, and that the requested renderer’s symbols can be resolved. It does not establish any of the following:
-
•
that all runtime assets were packaged;
-
•
that save data survives a page reload;
-
•
that a frame was reached after Emscripten took control;
-
•
that generated WebGL shaders compiled in a browser;
-
•
that the page displayed the selected renderer rather than merely loading its module.
This boundary matters because a bare-Node launch is a poor substitute for a browser. DOM renderers need window and document; SDL-backed renderers need browser host services; WebGL needs a real graphics context. Errors such as window is not defined or an SDL initialization failure say that the wrong host was used, not that the renderer’s drawing semantics were tested.
64.5 A reproducible build record
Evidence for a Web build should record at least the CNA commit, emsdk version, renderer identity, configure options, named target, and produced .html, .js, and .wasm artifacts. It should also list every preload mapping. Without the mapping, two apparently identical builds may have different runtime content.
For testing, the record should say whether the target was merely linked, launched under Node, or served over HTTP to a named browser. These are ascending evidence levels, not synonyms. The current HTML-DOM workflow, for example, pins emsdk 6.0.3 and serves six pages to Playwright’s Chromium. That is a browser execution result; a successful build of the other identities is not equivalent to it.
64.6 The portability lesson
WebAssembly preserves much of the language and loses much of the process model. The hard porting work is therefore at the boundaries: ownership across a non-returning host call, assets crossing into a virtual filesystem, persistence crossing into a browser store, and a verdict crossing out of a process whose exit status the harness cannot observe. Treating those boundaries as explicit interfaces keeps “Web build” from becoming a claim broader than the evidence behind it.