MRPCI/web/index.html
m3ultra caf4d5e558 web: savegames through the page, since there is no saves/ directory
mrpci_request_save / mrpci_save_out / mrpci_save_in carry a SaveData across
the wasm boundary, so the browser build can save and restore even though
Command::SaveGame's std::fs call goes nowhere there. The moment being stored
is exactly the one the native build writes to saves/<slot>.json — the same
GameState::save_data — so a slot means the same thing in both places.

F5 now asks the page instead of the filesystem; F7 points at the SAVES menu,
because restoring needs to know *which* slot and a function key can't say.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:17:10 +10:00

115 lines
4.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MRPCI — Monster Robot Party Creative Interpreter</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='14' font-size='14'>🎨</text></svg>" />
<link rel="stylesheet" href="arcade.css" />
<style>
html, body { margin: 0; height: 100%; background: #05070b; overflow: hidden; }
/* miniquad sizes its drawing buffer from the canvas's *CSS* box, so the
canvas needs real dimensions — `max-width` inside a centring grid
resolves to zero and the engine renders a 0x0 frame. Give it the
whole area below the arcade bar, like MRPGI does. */
canvas {
position: absolute; inset: 34px 0 24px 0;
width: 100%; height: calc(100% - 58px);
display: block; outline: none; image-rendering: pixelated; background: #0b0d11;
}
body.arcade-standalone-page canvas { inset: 0 0 24px 0; height: calc(100% - 24px); }
#boot {
position: absolute; inset: 34px 0 0 0; display: grid; place-items: center;
background: #05070b; color: #8296aa; z-index: 20;
font: 12px/1 ui-monospace, Menlo, monospace; letter-spacing: 0.16em;
text-transform: uppercase; transition: opacity 400ms ease; pointer-events: none;
}
#keys {
position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
padding: 4px 10px; background: rgba(6,10,16,0.86); color: #6f8296;
border-top: 1px solid rgba(120,190,255,0.18);
font: 11px/1.5 ui-monospace, Menlo, monospace;
}
#keys b { color: #9fb4c8; font-weight: 600; }
</style>
</head>
<body class="arcade-inset">
<canvas id="glcanvas" tabindex="1"></canvas>
<div id="boot">starting the interpreter…</div>
<div id="keys">
<b>click</b> verb at pixel · <b>right-click</b> cycle verb · <b>F1F4</b> walk/look/do/talk ·
<b>I</b> inventory · <b>Enter</b> type a command · <b>F5/F7</b> save/restore · <b>M</b> mute · <b>N</b> scanlines
</div>
<script src="mq_js_bundle.js"></script>
<script>
// miniquad resolves the engine's undefined imports at runtime; register
// before the wasm loads or the instantiation fails.
let onSaveOut = null
miniquad_add_plugin({
register_plugin: (imports) => {
imports.env.mrpci_save_out = (ptr, len) => {
const json = new TextDecoder().decode(new Uint8Array(wasm_memory.buffer, ptr, len))
if (onSaveOut) onSaveOut(json)
}
},
version: '1',
name: 'mrpci_bridge',
})
/** Copy a string into the engine and call an exported entry point. */
function handOver(fn, json) {
const bytes = new TextEncoder().encode(json)
const ptr = wasm_exports.mrpci_alloc(bytes.length)
new Uint8Array(wasm_memory.buffer, ptr, bytes.length).set(bytes)
fn(ptr, bytes.length)
}
/** Hand a WorldBundle JSON to the running engine. */
function sendBundleToEngine(json) {
handOver(wasm_exports.mrpci_world_in, json)
}
/**
* The engine emits its progress a frame after being asked, so a save
* is a round trip, not a return value — hence the promise. The timeout
* matters: if the engine is wedged, the menu should say so rather than
* hang on a button press forever.
*/
function captureSave() {
return new Promise((resolve) => {
if (!wasm_exports?.mrpci_request_save) return resolve(null)
const timer = setTimeout(() => { onSaveOut = null; resolve(null) }, 2000)
onSaveOut = (json) => {
clearTimeout(timer)
onSaveOut = null
resolve(json)
}
wasm_exports.mrpci_request_save()
})
}
load('mrpci.wasm')
const engineReady = () => typeof wasm_exports !== 'undefined' && !!wasm_exports
const t = setInterval(() => {
if (!engineReady()) return
document.getElementById('boot').style.opacity = '0'
clearInterval(t)
}, 200)
</script>
<script src="picker.js"></script>
<script>
MRPArcade.init({
engine: 'mrpci',
ready: () => typeof wasm_exports !== 'undefined' && !!wasm_exports,
load: (json) => sendBundleToEngine(json),
saves: {
capture: () => captureSave(),
restore: (json) => handOver(wasm_exports.mrpci_save_in, json),
},
})
</script>
</body>
</html>