Builds a browser-playable .html/.js/.wasm/.data via `./build-web.sh` (no
new CMake top-level target — Emscripten is detected at configure time and
takes the existing kobodl through a web-flavored pipeline).
Key pieces:
- CMakeLists.txt: route SDL3 through `--use-port=sdl3`; substitute
`--use-port=libpng` for the missing sdl3_image port (Emscripten 5.0.7
ships only sdl3 + sdl3_ttf). pthread (atomics+bulk-memory) lifted to
kobo_deps so every TU agrees with wasm-ld's --shared-memory. Link with
-sASYNCIFY (avoids rewriting kobo's blocking main loop around
emscripten_set_main_loop), -sALLOW_MEMORY_GROWTH/-sMAXIMUM_MEMORY=1gb,
-sPTHREAD_POOL_SIZE=4, -lidbfs.js, and --preload-file data@/data.
- graphics/img_compat.{h,c}: libpng-backed IMG_Load shim, used in
sprite.c / gfxengine.cpp / filters.c via #include "img_compat.h".
Native builds passthrough to <SDL3_image/SDL_image.h> unchanged.
- web/shell.html: HTML wrapper that mounts /home as IDBFS for persistent
scores/config. Startup gated on addRunDependency until syncfs(true)
finishes, then /home/scores is mkdir'd post-load (the game's addPlayer
doesn't auto-create the score dir; see TODO in score.cpp).
beforeunload + visibilitychange flushes /home back to IndexedDB.
- build-web.sh: emcmake wrapper + a static server that sets the
COOP/COEP headers SharedArrayBuffer/pthreads require.
Source fixes needed to build under Emscripten's libc/libcxx:
- Renamed enemy_kind `pipe2` -> `pipe2k` (collided with libc pipe2(2)
always declared in Emscripten's <unistd.h>).
- graphics/toolkit.cpp: added missing <stdio.h> for snprintf (Emscripten's
libcxx headers don't pull it in transitively).
- states.cpp: gated the "Quit Kobo Deluxe" main-menu button on
!__EMSCRIPTEN__; exit() on the web leaves a dead canvas.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
63 lines
2.3 KiB
HTML
63 lines
2.3 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Kobo Deluxe</title>
|
|
<style>
|
|
html, body { margin: 0; height: 100%; background: #000; color: #ccc;
|
|
font-family: monospace; }
|
|
#wrap { display: flex; flex-direction: column; align-items: center;
|
|
justify-content: center; height: 100%; }
|
|
canvas { background: #000; image-rendering: pixelated; outline: none;
|
|
max-width: 100%; max-height: 100vh; }
|
|
#status { padding: 8px; font-size: 13px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="wrap">
|
|
<canvas id="canvas" tabindex="-1" oncontextmenu="event.preventDefault()"></canvas>
|
|
<div id="status">Loading…</div>
|
|
</div>
|
|
<script>
|
|
var statusEl = document.getElementById('status');
|
|
var Module = {
|
|
canvas: (function () {
|
|
var c = document.getElementById('canvas');
|
|
c.addEventListener('webglcontextlost', function (e) {
|
|
alert('WebGL context lost. Reload to continue.'); e.preventDefault();
|
|
}, false);
|
|
return c;
|
|
})(),
|
|
print: function (t) { console.log(t); },
|
|
printErr: function (t) { console.warn(t); },
|
|
setStatus: function (t) { statusEl.textContent = t; },
|
|
preRun: [function () {
|
|
// Persist config + high scores across reloads via IndexedDB.
|
|
// syncfs(true) is async and replaces /home with whatever's in IDBFS,
|
|
// so we (1) gate startup on it via addRunDependency, and (2) re-mkdir
|
|
// /home/scores *after* the load (the game's addPlayer doesn't auto-
|
|
// create the score dir; see the TODO in score.cpp:504).
|
|
try { FS.mkdir('/home'); } catch (e) {}
|
|
FS.mount(IDBFS, {}, '/home');
|
|
Module.addRunDependency('idbfs');
|
|
FS.syncfs(true, function (err) {
|
|
if (err) console.warn('IDBFS load failed:', err);
|
|
try { FS.mkdir('/home/scores'); } catch (e) {}
|
|
Module.removeRunDependency('idbfs');
|
|
});
|
|
}],
|
|
postRun: [function () {
|
|
// Push the in-memory /home back to IndexedDB on tab close. visibility-
|
|
// change covers mobile, where beforeunload is unreliable.
|
|
var flush = function () { FS.syncfs(false, function () {}); };
|
|
window.addEventListener('beforeunload', flush);
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.visibilityState === 'hidden') flush();
|
|
});
|
|
}],
|
|
};
|
|
</script>
|
|
{{{ SCRIPT }}}
|
|
</body>
|
|
</html>
|