Files
wownerogue/test/spSceneAdapter_ultra.test.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

58 lines
2.2 KiB
JavaScript

// The single-player render-kit migration hinges on sceneFromGameState consuming the CLIENT state
// shape — sparse {y:{x:v}} tile maps (vs the server's arrays). Guards that adapter + the RK alias.
const fs = require('fs');
const path = require('path');
const vm = require('vm');
function loadSceneKit() {
const ctx = { console };
ctx.window = ctx;
vm.createContext(ctx);
vm.runInContext(fs.readFileSync(path.join(__dirname, '../html/js/render/sceneModel.js'), 'utf8'), ctx);
return ctx.RK;
}
const F = "'1"; // primary-floor char
describe('SP client-state → Scene adapter', () => {
const RK = loadSceneKit();
test('RK.sceneFromGameState is exposed directly (not only under RK.scene)', () => {
expect(typeof RK.sceneFromGameState).toBe('function');
expect(RK.sceneFromGameState).toBe(RK.scene.sceneFromGameState);
});
test('sparse {y:{x:v}} maps yield correct dims, kinds, and entities', () => {
const state = {
visibleTiles: { 0: { 0: '#', 1: F, 2: '>' }, 1: { 0: F, 1: F, 2: '#' } },
exploredTiles: { 0: { 0: '#', 1: F, 2: '>' }, 1: { 0: F, 1: F, 2: '#' } },
player: { x: 1, y: 1, facing: 'down' }, monster: { x: 0, y: 0 },
exit: [2, 0], treasure: [1, 1], items: {}, lighting: {}
};
const scene = RK.sceneFromGameState(state, { cryptoType: 'WOW' });
expect(scene.cols).toBe(3);
expect(scene.rows).toBe(2);
expect(scene.isDungeon).toBe(true);
expect(scene.grid[0]).toEqual(['wall', 'floor', 'exit']);
expect(scene.grid[1]).toEqual(['floor', 'floor', 'wall']);
const kinds = scene.entities.map(e => e.kind);
expect(kinds).toContain('player');
expect(kinds).toContain('monster');
expect(kinds).toContain('feature'); // exit + treasure
const player = scene.entities.find(e => e.kind === 'player');
expect(player).toMatchObject({ x: 1, y: 1 });
});
test('unexplored cells render as dark, not floor', () => {
const state = {
visibleTiles: { 0: { 0: F } }, // only (0,0) seen
exploredTiles: { 0: { 0: F } },
player: { x: 0, y: 0 }, items: {}, lighting: {}
};
const scene = RK.sceneFromGameState(state, {});
expect(scene.grid[0][0]).toBe('floor');
});
});