HN Debrief

Ported my C game to WASM, here's every bug that I hit

  • Programming
  • Web
  • Games
  • Developer Tools
  • Security

The post is a bug diary from porting a C puzzle game to WebAssembly with Emscripten. The failures were mundane and revealing: code assumed 64-bit pointers, asset files embedded raw pointers, OpenGL behavior that desktop drivers tolerated failed under browser graphics rules, and missing Emscripten export flags produced confusing runtime breakage. That made the piece useful less as a WASM victory lap and more as a checklist of old native shortcuts that collapse the moment you target the web.

If you are moving C or C++ code to WebAssembly, treat it like a real cross-platform port, not a recompile. Audit pointer-size assumptions, replace direct struct dumps with versioned serializers, and plan on a thin JavaScript boundary for UI and platform I/O.

Discussion mood

Mostly positive and pragmatic. People liked the writeup and saw it as a realistic account of porting native code to the browser, but the comments were blunt that many of the bugs came from brittle C habits like serializing raw structs and assuming pointer sizes rather than from WebAssembly itself.

Key insights

  1. 01

    Raw struct files are the real bug

    Dumping C structs straight to disk was the deeper portability failure, not just a bad fit for WASM. Even if pointer size had matched, compiler-dependent padding, enum sizing, booleans, field reordering, and future schema changes would still make the format fragile. A real serializer buys you validation, versioning, and forward compatibility instead of hoping one compiler and one ABI remain frozen forever.

    If you persist native data, define a file format instead of treating memory as one. Add explicit field sizes, version tags, and bounds checks before you do the port.

      Attribution:
    • diath #1
    • flohofwoe #1
    • DanielHB #1
    • jcranmer #1
  2. 02

    Wasm32 survives because sandboxing is faster

    The 32-bit address space is not there because WebAssembly lacks 64-bit integers. It is there because browser engines can make wasm32 memory accesses fast by confining them to a reserved virtual memory cage and letting hardware do most bounds enforcement. Memory64 removes that convenience and needs extra checks, so it is mainly for cases that truly need more address space, not a default upgrade path.

    Do not opt into memory64 just because your native build is 64-bit. Stay on wasm32 unless your workload really needs over 4 gigabytes or pointer-sized integers that cannot be reworked.

      Attribution:
    • flohofwoe #1 #2
    • senfiaj #1
    • sestep #1
  3. 03

    The clean split is logic in WASM

    The most credible implementation advice was to keep the engine in C or C++ and let JavaScript or TypeScript handle browser-native UI, I/O, and reactivity. That avoids fighting the platform over DOM access, file APIs, and event wiring, while still preserving most of the original codebase. One detailed example used Embind plus a narrow callback surface so the simulation stayed native but rendering and interface logic stayed in the web stack where they belong.

    Design a thin host boundary early. Expose simulation state and commands to JS, then build UI and platform integration on the browser side instead of pushing everything through C APIs.

      Attribution:
    • flohofwoe #1
    • samiv #1
    • DonHopkins #1
  4. 04

    Renderers should avoid per-frame bridge copies

    For graphics-heavy ports, the expensive mistake is often not whether rendering happens in JS or WASM but how much data crosses the boundary every frame. A stronger pattern is to keep simulation memory in WASM and let the renderer read shared views into linear memory, rather than marshaling tile maps, sprites, or state through high-level bindings on every update. That keeps the browser-side renderer flexible without turning the JS-WASM bridge into the bottleneck.

    Profile the boundary, not just the renderer. If you render from JS, pass stable memory views or coarse commands instead of copying large frame-state objects every tick.

      Attribution:
    • deadbabe #1
    • DonHopkins #1
  5. 05

    Sandboxing does not erase C memory bugs

    Compiling C to WebAssembly contains damage better than running the same code unsandboxed, but it does not neutralize memory corruption inside the module's own linear memory. Once state is corrupted, an attacker may still be able to change program behavior, leak sensitive in-app data, or abuse any host capabilities the module was given. WebAssembly narrows blast radius. It does not turn unsafe code into safe code.

    Keep doing normal memory-safety work on WASM ports. Fuzz parsers, validate asset files, and minimize the host capabilities exposed to the module.

      Attribution:
    • jedisct1 #1
    • pjmlp #1
  6. 06

    WASM debugging is no longer the blocker

    Browser debugging for native code has improved enough that real breakpoints, memory inspection, and source-level stepping are now practical. The more useful setup mentioned here was the VSCode WASM DWARF extension driving Chrome, which gives a normal build-and-debug loop instead of relying on a browser-only extension and guesswork.

    Set up source-level debugging before you start porting. It will pay for itself the first time you chase a pointer-width or export mismatch bug.

      Attribution:
    • fyrn_ #1
    • flohofwoe #1

Against the grain

  1. 01

    WASM is not a native UI runtime

    The hopeful "web and native have finally merged" framing overstates what browser-hosted WASM can do. Inside the browser you still do not get native window creation or direct access to platform UI frameworks. You get a browser surface with browser rules, which is useful, but it is not the same thing as a Swift or Win32 app target.

    Do not promise teams that WASM will let them write one native desktop UI for the browser. Treat browser UI as its own platform with its own constraints.

      Attribution:
    • arcadialeak #1
    • frollogaston #1 #2
  2. 02

    Just another platform still means JS glue

    The claim that the web is now just another target rang hollow for people who wanted a self-sufficient native runtime. In practice important browser capabilities still come through JavaScript interop, and some newer WASM features remain gated by uneven browser support, with Safari called out repeatedly. That does not make WASM useless. It does mean the abstraction is leakier than the slogan suggests.

    Budget for browser-specific code and compatibility testing. If your team is allergic to JS entirely, WebAssembly will not remove that friction.

      Attribution:
    • gspr #1
    • trumpdong #1
    • whizzter #1

In plain english

ABI
Application Binary Interface, the low-level rules that let compiled code from different components work together.
DOM
Document Object Model, the browser's programming interface for HTML pages and UI elements.
DWARF
A debugging information format that lets tools map compiled code back to source files, variables, and line numbers.
Embind
An Emscripten library for exposing C++ classes and functions to JavaScript.
Emscripten
A toolchain that compiles C and C++ code to WebAssembly and provides browser integration code.
memory64
A WebAssembly feature that allows 64-bit memory addressing so programs can access more than 4 gigabytes of memory.
OpenGL
Open Graphics Library, a cross-platform graphics application programming interface for rendering 2D and 3D graphics.
TypeScript
A typed superset of JavaScript that compiles to standard JavaScript.
VSCode
Visual Studio Code, a popular code editor with debugging and extension support.
WASM
WebAssembly, a portable binary format used to run code in browsers and other sandboxed environments.
wasm32
The common WebAssembly mode where memory addresses and pointers are 32 bits wide.

Reference links

WASM memory model and performance

Serialization and data layout

UI and platform integration

  • wasi-gfx
    Cited as an example of a host API effort that could make non-browser graphics access more standardized for WASM.

Debugging tools

Security references

Project examples

  • Scorched Planets
    A commenter shared their own old-game-to-WASM port as a comparable real-world example.
  • MicropolisCore repository
    A large example of a C++ simulation engine ported to WebAssembly with a TypeScript and Svelte frontend.
  • Micropolis Web demo
    Live demo of the Micropolis simulator and tile renderer running on the web.
  • VitaMoo demo
    Shared as a related WebGPU character animation demo from the same project family.