diff --git a/html/js/render/spGameRenderer.js b/html/js/render/spGameRenderer.js
index 41118f9..03cbbcb 100644
--- a/html/js/render/spGameRenderer.js
+++ b/html/js/render/spGameRenderer.js
@@ -102,15 +102,12 @@
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.
+ // The 3D renderer fills the host itself (setSize) and its OWN THREE camera follows the
+ // player — so DON'T apply a CSS transform (it would double-transform the WebGL 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 + ')';
+ r.canvas.style.transform = 'none';
+ r.canvas.style.left = '0';
+ r.canvas.style.top = '0';
return;
}
var scale = SP._zoom || 1.7;
diff --git a/html/js/render/threeRenderer.js b/html/js/render/threeRenderer.js
index fe4bf49..9131343 100644
--- a/html/js/render/threeRenderer.js
+++ b/html/js/render/threeRenderer.js
@@ -50,11 +50,17 @@
this._raf = requestAnimationFrame(function tick() { self._animate(); self._raf = requestAnimationFrame(tick); });
};
- ThreeRenderer.prototype._resize = function (scene) {
- var w = Math.max(420, scene.cols * 32);
- var h = Math.max(360, scene.rows * 28 + 120);
- this.renderer.setSize(w, h, false);
- var aspect = w / h, span = Math.max(scene.cols, scene.rows) * 0.78 + 2.5;
+ // Fill the host and frame a fixed "screenful" around the player. The camera FOLLOWS the player
+ // (see _animate) instead of framing the whole level, so 3D behaves like the other modes. Only
+ // does work when the host size actually changed (cheap to call every frame).
+ ThreeRenderer.prototype._fitToHost = function () {
+ var host = this.host;
+ var w = (host && (host.clientWidth || host.offsetWidth)) || 640;
+ var h = (host && (host.clientHeight || host.offsetHeight)) || 400;
+ if (w === this._cw && h === this._ch) return;
+ this._cw = w; this._ch = h;
+ this.renderer.setSize(w, h, true); // updateStyle:true → the canvas CSS-fills the host
+ var aspect = w / h, span = 8; // fixed zoom = a screenful around the player
this.camera.left = -span * aspect;
this.camera.right = span * aspect;
this.camera.top = span;
@@ -219,7 +225,7 @@
ThreeRenderer.prototype.render = function (scene) {
if (!scene || !this.renderer) return;
- this._resize(scene);
+ this._fitToHost();
var key = scene.cols + 'x' + scene.rows + ':' + scene.grid.map(function (r) { return r.join(''); }).join('/');
if (key !== this._sizeKey) {
this._buildTiles(scene);
@@ -273,6 +279,19 @@
o.rotation.y += (r - o.rotation.y) * 0.18;
}
}
+
+ // Player-follow camera: keep the fixed iso offset (8,9,8) but re-target the player's world
+ // position each frame. The player mesh itself lerps, so the camera glides smoothly with it.
+ this._fitToHost(); // pick up any host resize
+ var pcam = null;
+ for (var pid in this.entities) {
+ var pen = this.entities[pid];
+ if (pen.e && (pen.e.you || pen.e.kind === 'player')) { pcam = pen.obj; break; }
+ }
+ if (pcam) {
+ this.camera.position.set(pcam.position.x + 8, 9, pcam.position.z + 8);
+ this.camera.lookAt(pcam.position.x, 0, pcam.position.z);
+ }
this.renderer.render(this.scene, this.camera);
};