UR-10: long sessions stop at the 2 GiB heap cap
Build 38797 has a fixed 2 GiB WebAssembly heap. We can prove the ceiling and the failed allocation, but cache retention, fragmentation, and live working set still require an ArenaNet heap profile.
Some content-heavy sessions end when the WebAssembly heap reaches exactly 2,147,483,648 bytes. That maximum is declared in both artifacts ArenaNet ships: the WASM memory section and its generated JavaScript glue. The next allocation that needs more capacity is refused and the client aborts.
What we are asking ArenaNet to do: profile settled live ownership across a short multi-zone route, correct the responsible lifecycle, cache budget, or allocation pattern, and raise the wasm32 maximum to 4 GiB minus one page as additional headroom. The larger maximum must be built with the matching unsigned JavaScript pointer support; changing only the WASM declaration is not safe.
What the client says when it stops
One captured failure showed:
WASM heap at crash: 2048 MiB of 2048 MiB
ASSERTION FAILED: Out of memory!
../../../Engine/Gr/Gles3/GlTex.cpp:397This proves that the final failed request was in ArenaNet's GLES3 texture path. It does not prove that textures retained the entire heap. At the limit, whichever allocation asks for the next block raises its own assertion.
The cap is compiled into both artifacts
Build 38797's Gw.jspi.wasm declares one memory:
- initial: 4,096 pages, or 256 MiB;
- maximum: 32,768 pages, or 2 GiB.
The matching Gw.jspi.js contains:
var getHeapMax = () =>
// Stay one Wasm page short of 4GB: ...
2147483648;The host does not impose a smaller ceiling. Growth succeeds until the declared maximum is reached. wasm32 cannot provide more than a 4 GiB address space, so a larger maximum buys headroom but cannot make unbounded growth safe.
Reports and measured sessions
Reported failures include open-world sessions lasting several hours and texture-dense instanced content reaching the same boundary in roughly half an hour. Players have also reported shorter failures in a Resplendent Makuun vanquish and Dunes of Despair Hard Mode.
Our longest recorded non-crashing staircase reached 1,619 MiB after 136 minutes:
| Elapsed | Heap after step | Elapsed | Heap after step |
|---|---|---|---|
| 0.1 m | 256 MiB | 84.8 m | 1,124 MiB |
| 1.4 m | 442 MiB | 94.5 m | 1,220 MiB |
| 13.1 m | 627 MiB | 97.7 m | 1,316 MiB |
| 46.6 m | 724 MiB | 109.5 m | 1,427 MiB |
| 67.5 m | 930 MiB | 127.2 m | 1,523 MiB |
| 82.5 m | 1,027 MiB | 128.7 m | 1,619 MiB |
That session did not converge before the player quit. Newer short sessions do sometimes converge: one multi-zone run reached 733 MiB and stayed there while the host processed substantial additional image traffic. Both results are real. The route, cache warmth, and allocator reuse affect whether a particular session reproduces the growth.
WASM memory capacity never shrinks within a process. A flat heap means current allocations fit into existing capacity; a rising heap means they did not. A rising capacity alone cannot distinguish retained live bytes from fragmentation.
What allocation attribution found
Our first allocator experiment was too expensive to keep: it added about 47% steady-state overhead. Its useful historical snapshots identified several large ArenaNet allocation owners rather than one texture-only explanation:
| Exact build-38797 function | Reverse-engineered owner | Observed retained large bytes |
|---|---|---|
2797 | GlTex | 117–148 MiB |
1586 | ImgMem | 46–96 MiB |
4491 | model loading | 29–129 MiB |
3421 | unresolved secondary owner | 24–49 MiB |
The replacement research probe wraps only exact lifecycle functions for
ImgMem, models, and GlTex. It exports aggregates rather than pointers or
per-allocation events.
Static analysis found a complete reference-safe model lifecycle already in the client:
- release function
4629moves zero-reference models to an unused list with a one-second expiry; - acquire function
4628removes a reused model from that list; - sweep function
4649destroys expired unused entries through the normal virtual destructor; - the normal model update calls that sweep approximately every ten seconds.
A corrected short capture observed 6,092 normal model evictions and only 1.9 MiB in the zero-reference aggregate, but its bootstrap completeness flag was false. That data cannot authorize an external trim. It does show that the normal model sweep is active.
We found no certified standalone unused check for the image or texture
destruction paths. Calling a destructor, raw free, or glDeleteTexture from
the host would risk stale lookup entries and use-after-free defects.
The short internal reproduction ArenaNet can run
ArenaNet does not need to wait hours for the public abort. Source symbols and a heap profiler turn this into a short ownership test:
Record a settled baseline
Use build 38797 with allocation/free stacks. Record live bytes, the largest free block, free-bin distribution, and image/model/texture ownership.
Travel through ten distinct zones
Pause after transitions 3, 6, and 9 so unload work and the model sweep can finish. Revisit three earlier zones, then remain idle for 30 seconds.
Compare capacity with live ownership
- Capacity rises while live bytes return near baseline: fragmentation.
- Settled live bytes rise across unloads: retention or legitimate working set.
- Zero-reference bytes remain high: cache budget or sweep issue.
- Referenced bytes remain high: follow the retaining reference; do not destroy the object externally.
- Both capacity and live bytes plateau: normal warm-up for that route.
Repeat with a warm cache
Use the same build, route, account, and settings. A controlled comparison must change only one variable.
The first source paths worth inspecting are ImgMem allocation 1586, model
load 4491, and GlTex allocation 2797, plus their existing cache budgets
and normal release paths.
Our interim mitigation
Safe reload warning
GWonMac's production warning monitors the effective heap capacity while there is still room to return to a town or outpost, then lets the player choose when to reload. Reloading resets the WASM heap. It is recovery, not a memory fix.
Exact-build 4 GB research profile
We have also built an off-by-default research profile for build 38797. It changes the WASM maximum to 65,535 pages—4 GiB minus one 64 KiB page—and transforms the exact generated JavaScript to treat high wasm32 pointers as unsigned addresses.
The pair is hash-gated and fails closed: an unknown JS or WASM artifact runs untouched. Raising only the WASM maximum is explicitly refused.
Qualification completed so far:
- ArenaNet's real allocator grew to 3,025,928,192 bytes offline;
- it returned and reused allocations above 2 GiB;
- the live Electron client grew to 2,625 MiB;
- live
mallocreturned raw signed pointer-2078907408, unsigned address2216059888; - the transformed generated string glue wrote and read
GW4Gthrough that high pointer without trapping.
This proves the core approach across the former boundary. It does not prove every rare WebGL or host callback path, and it does not bound the underlying memory demand. The current profile remains a developer experiment. All five variants the production chain can emit—including Enhancements disabled—are hash-pinned. It still needs packaged high-address gameplay coverage, system-memory testing, and an explicit user setting before an experimental player release.
If released before an ArenaNet update, it will be labelled Experimental 4 GB memory mode, disabled by default, restricted to exact certified builds, easy to turn off, and recommended only on Macs with at least 16 GB of RAM.
What we will not do
- Change only the WASM maximum.
- Exceed wasm32's 4 GiB address space.
- Call raw
free, direct destructors, orglDeleteTexture. - Guess a cache function or context pointer.
- Replace ArenaNet's allocator.
- Automatically reload a player during a mission.
- Present the 4 GB profile as a fix for unbounded retention.
Acceptance criteria
For ArenaNet's corrective build:
- settled live ownership and heap capacity plateau across repeated content transitions;
- zero-reference cache entries leave through normal reference-safe eviction;
- freed large blocks are reusable when fragmentation was the cause;
- a three-hour content-diverse session does not approach the effective maximum;
- if the maximum is raised, generated JS and WASM agree and ordinary game paths work above 2 GiB;
- the client retains a clear failure reason if it exhausts the new maximum.
For our optional mitigation, every accepted JS/WASM pair must be pinned to an exact ArenaNet build, unknown builds must remain untouched, the mode must be visible in diagnostics, and disabling it must restore ordinary client behavior after restart.
Current conclusion
The 2 GiB limit and final failed allocation are in ArenaNet's shipped client. The reason some sessions consume the available address space still needs an ArenaNet source-level heap profile. Until that is fixed, a safe reload remains the conservative mitigation and an exact-build 4 GB mode can provide optional headroom for informed testers.