Files
wownerogue/html/js/render/spGameRenderer.js
T
jwinterm 1325b49b42 feat(render): single-player dungeon renders through the render kit (packs in gameplay)
The migration that makes the pack ladder pay off IN the dungeon, not just the tavern.
The SP game now draws via RK (Tiled/ASCII/Iso/3D + unlocked packs) instead of the
legacy ROT display.

- Adapter: sceneFromGameState now consumes the CLIENT state shape (sparse {y:{x:v}}
  tile maps, not just server arrays) via maxDims(). Verified + unit-tested.
- Bug fix: RK.sceneFromGameState was never actually exposed (only RK.scene.*), so
  matchClient + tavern spectator were silently no-op-ing. Aliased onto RK.
- Bridge (spGameRenderer.js, RK.SPGame): mounts an RK renderer in a dedicated host
  inside #game-display, toggles the legacy ROT canvases, zoom-enabled. Game._draw
  GameScreen prefers it and FALLS BACK to RenderEngine on any failure, so the live
  game can never blank. ROT still draws the splash/win/lose text screens.
- Picker (spRenderPicker.js): homepage mode + pack selector, gated by entitlements
  (free = Tiled/Original + ASCII; ladder unlocks Iso/3D/packs), rebuilds on identity.
- index.html loads the render-kit renderers + the bridge/picker.

Bundled with the catalog ladder (migration 028): free Original bare pack, ladder
1/5/10/20/40/50. Deploying makes the free tavern bare (by design) AND the dungeon
render-kit-ed. v1 renders the whole explored level + zoom (player-centered camera is
the next polish). Suite 62/435 green; adapter smoke-verified.
2026-07-12 10:14:56 -04:00

129 lines
4.6 KiB
JavaScript

// SP game render bridge (RK.SPGame) — renders the single-player DUNGEON through the render kit
// (Tiled / ASCII / Iso / 3D + unlocked packs) instead of the legacy ROT display, so a pack you
// bought applies in actual gameplay, not just the tavern. It mounts a renderer in a dedicated host
// inside #game-display and hides the legacy ROT canvases while a game is live; the retro splash /
// win / lose text screens still use ROT. If anything here is unavailable it returns false and the
// caller falls back to the legacy RenderEngine, so the live game can never hard-break.
(function (root) {
'use strict';
var RK = root.RK = root.RK || {};
var SP = RK.SPGame = RK.SPGame || {};
SP._renderer = null;
SP._mode = null;
SP._live = false;
function doc() { return root.document; }
function gameDisplay() { return doc() && doc().getElementById('game-display'); }
// A dedicated child host so the RK canvas doesn't collide with the ROT canvas / overlays.
function rkHost() {
var gd = gameDisplay();
if (!gd) return null;
var host = doc().getElementById('rk-game-host');
if (!host) {
host = doc().createElement('div');
host.id = 'rk-game-host';
host.style.display = 'none';
gd.appendChild(host);
}
return host;
}
SP.available = function () {
return !!(RK.createRenderer && RK.sceneFromGameState && gameDisplay());
};
SP.mode = function () {
if (!SP._mode) {
var m = (RK.loadMode && RK.loadMode('tiles')) || 'tiles';
SP._mode = (RK.canUseMode && !RK.canUseMode(m)) ? 'tiles' : m;
}
return SP._mode;
};
SP.setMode = function (mode) {
if (RK.canUseMode && !RK.canUseMode(mode)) return false;
SP._mode = mode;
if (RK.saveMode) RK.saveMode(mode);
SP._destroyRenderer();
if (SP._live && SP._lastState) SP.render(SP._lastState, SP._lastOpts);
return true;
};
// Re-mount after a pack switch (same mode, new active pack/assets).
SP.refreshPack = function () {
SP._destroyRenderer();
if (SP._live && SP._lastState) SP.render(SP._lastState, SP._lastOpts);
};
SP._destroyRenderer = function () {
if (SP._renderer && SP._renderer.destroy) { try { SP._renderer.destroy(); } catch (_) {} }
SP._renderer = null;
};
SP._ensureRenderer = function () {
if (SP._renderer) return SP._renderer;
var host = rkHost();
if (!host || !RK.createRenderer) return null;
SP._renderer = RK.createRenderer(SP.mode(), host, { cell: 24 });
if (RK.attachZoom) { try { RK.attachZoom(host); } catch (_) {} }
return SP._renderer;
};
function toggleLegacy(hide) {
var gd = gameDisplay();
if (!gd) return;
var kids = gd.children;
for (var i = 0; i < kids.length; i++) {
if (kids[i].id === 'rk-game-host') continue;
kids[i].style.display = hide ? 'none' : '';
}
}
// Show the render-kit view (hide the ROT splash canvases). Call on game start.
SP.show = function () {
if (!SP.available()) return false;
var host = rkHost();
if (!host) return false;
host.style.display = 'block';
toggleLegacy(true);
SP._live = true;
return true;
};
// Back to the ROT view (splash / win / lose). Call on game end.
SP.hide = function () {
SP._live = false;
SP._destroyRenderer();
var host = doc() && doc().getElementById('rk-game-host');
if (host) host.style.display = 'none';
toggleLegacy(false);
};
// Build the Scene from the SP client render-state and draw it. Returns true if it rendered
// (caller then skips the legacy renderer). Non-destructive on failure.
SP.render = function (clientState, opts) {
if (!SP._live || !SP.available()) return false;
var r = SP._ensureRenderer();
if (!r) return false;
opts = opts || {};
SP._lastState = clientState;
SP._lastOpts = opts;
try {
var scene = RK.sceneFromGameState(clientState, {
cryptoType: opts.cryptoType,
playerAppearance: opts.playerAppearance,
isSpectating: opts.isSpectating
});
r.render(scene);
return true;
} catch (e) {
if (root.console) console.warn('SPGame render failed; falling back to legacy:', e && e.message);
return false;
}
};
if (typeof module !== 'undefined' && module.exports) module.exports = SP;
})(typeof window !== 'undefined' ? window : this);