A consumer is handed {canvas, gl, ctxId, makeCurrent, destroy} and almost
always keeps only `gl`. wasmcart does exactly that: createWebGLImports
takes the WebGL2RenderingContext as `ctx`, and its teardown calls
`ctx.makeCurrent?.()` before deleting the cart's GL objects, so the
deletes are well-defined against its own context.
That optional-call was a silent no-op, because makeCurrent existed only
on the wrapper. And it matters more than it looks: native-gles dispatches
every GL call against ONE process-global current context, while object
names are plain integers carrying no context identity. Two contexts each
allocate texture name 1, 2, 3...
So a teardown that fails to switch contexts deletes ITS OWN name 3 out of
WHOEVER'S context is current. Measured 2026-08-21 with raw-id tracing:
[RAWDEL] ctx=C1 tbl2=>gl3 one cart owns GL name 3
[RAWDEL] ctx=C2 tbl2=>gl3 another cart deletes GL name 3
The victim was a live playtest window with a human playing in it. Its
scene texture was destroyed underneath it, so setCanvas came back
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (0x8cd7, attachment
GL_NONE, GL_INVALID_OPERATION) and the window went black at a steady
60fps -- while a CPU readback of that same cart still showed a perfect
picture, because that reads the cart's own FBO rather than what the
window presents. Verified fixed over seven consecutive teardowns across
both shared and private contexts: zero 0x8cd7, window held throughout.
Both properties are defined non-enumerably so nothing that walks the
context sees them, and makeCurrent is only installed when the context
does not already have one.
README documents the context-object surface and the name-collision
hazard, which is the part that makes an unnoticed no-op expensive.