diff --git a/html/js/render/asciiRenderer.js b/html/js/render/asciiRenderer.js
index ee5258a..62e107d 100644
--- a/html/js/render/asciiRenderer.js
+++ b/html/js/render/asciiRenderer.js
@@ -84,6 +84,17 @@
ctx.fillText(e.label, cx, e.y * cell - 1);
}
}
+
+ // Camera focus — centre the SP camera on the player cell (same convention as the tiled
+ // renderer). Sticky: only update when the player is present, so switching TO ascii mid-game
+ // doesn't leave focusPoint null (which pinned the whole grid to the corner → black screen).
+ for (var pj = 0; pj < scene.entities.length; pj++) {
+ var pe = scene.entities[pj];
+ if (pe.kind === 'player' || pe.you) {
+ this.focusPoint = { x: pe.x * cell + cell / 2, y: pe.y * cell + cell / 2 };
+ break;
+ }
+ }
};
AsciiRenderer.prototype.destroy = function () {
diff --git a/html/js/render/spGameRenderer.js b/html/js/render/spGameRenderer.js
index 5563ff7..41118f9 100644
--- a/html/js/render/spGameRenderer.js
+++ b/html/js/render/spGameRenderer.js
@@ -102,13 +102,24 @@
var r = SP._renderer;
var host = doc() && doc().getElementById('rk-game-host');
if (!r || !r.canvas || !host) return;
+ // The 3D renderer drives its OWN THREE camera and sizes its canvas to the level, so a
+ // player-follow CSS transform would zoom/crop it wrongly. Instead scale-to-FIT the whole
+ // canvas into the host and centre it (no player follow) — a sane, non-broken view.
+ if (r.name === '3d') {
+ var cw = r.canvas.width || 1, ch = r.canvas.height || 1;
+ var hw3 = host.clientWidth || host.offsetWidth || 640, hh3 = host.clientHeight || host.offsetHeight || 400;
+ var fit = Math.min(hw3 / cw, hh3 / ch);
+ r.canvas.style.transformOrigin = '0 0';
+ r.canvas.style.transform = 'translate(' + ((hw3 - cw * fit) / 2) + 'px,' + ((hh3 - ch * fit) / 2) + 'px) scale(' + fit + ')';
+ return;
+ }
var scale = SP._zoom || 1.7;
var fp = r.focusPoint;
// ROBUSTNESS: if the renderer hasn't reported a focus this frame — a race at game start, or a
// camera tick reading a just-(re)created renderer instance before its first render — derive the
// focus straight from the last known player cell (tiled coords). Without this the camera falls
// back to a scale-only transform that pins the whole level to the top-left corner.
- if (!fp && r.name === 'tiles' && SP._lastState && SP._lastState.player &&
+ if (!fp && (r.name === 'tiles' || r.name === 'ascii') && SP._lastState && SP._lastState.player &&
typeof SP._lastState.player.x === 'number') {
var _c = r.cell || 24;
fp = { x: (SP._lastState.player.x + 0.5) * _c, y: (SP._lastState.player.y + 0.5) * _c };