From 175effd6fa2decf59f840fd2c6512e6ab6f4f909 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Thu, 16 Jul 2026 03:23:46 -0400 Subject: [PATCH] fix(iso): zoom direction + wall cutaway for readable openings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zoom: the wheel handler based its step on a hardcoded 1.7, but iso defaults to 1.0 — so the first scroll (either direction) jumped up to ~1.7 and read as "always zoom in". Base the step on the CURRENT effective zoom via SP._defaultZoom() instead. Same mode-aware default now used by the camera. Iso occlusion: tall walls (184px) hide the floor/openings behind them (inherent to iso painter order). Add a cutaway — walls between the camera and the player (higher x+y, within ~3 cells) draw at 0.28 alpha so you can see yourself and the openings right in front of you. --- html/js/render/isoRenderer.js | 11 ++++++++++- html/js/render/spGameRenderer.js | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/html/js/render/isoRenderer.js b/html/js/render/isoRenderer.js index a87371b..763fd0b 100644 --- a/html/js/render/isoRenderer.js +++ b/html/js/render/isoRenderer.js @@ -313,7 +313,7 @@ p = this._project(e.x, e.y, originX, originY); items.push({ type: 'entity', e: e, sx: p.x, sy: p.y, depth: e.x + e.y + 0.55 }); // Sticky camera target (keep last if the player is momentarily absent). - if (e.you || e.kind === 'player') this.focusPoint = { x: p.x, y: p.y + this.tileH }; + if (e.you || e.kind === 'player') { this.focusPoint = { x: p.x, y: p.y + this.tileH }; this._plx = e.x; this._ply = e.y; } } items.sort(function (a, b) { return a.depth === b.depth ? (a.y || 0) - (b.y || 0) : a.depth - b.depth; }); @@ -325,8 +325,17 @@ if (WALLISH[tileKind]) tileKind = this._wallVariant(scene, it.x, it.y, tileKind); else tileKind = this._floorVariant(tileKind, it.x, it.y); var rec = this._load(this._tileUrl(tileKind)); + // Cutaway: fade the walls sitting between the camera and the player (higher x+y, toward + // the SE camera, within a few cells) so they don't hide the player or the openings right + // in front of them. Iso occlusion otherwise makes near hallways impossible to read. + var cut = WALLISH[it.kind] && this._plx != null && + it.x >= this._plx && it.y >= this._ply && + (it.x + it.y) > (this._plx + this._ply) && + ((it.x - this._plx) + (it.y - this._ply)) <= 3; + if (cut) ctx.globalAlpha = 0.28; if (rec && rec.ready) this._drawImage(rec.img, it.sx, it.sy + this.tileH, this.imageW, this.imageH); else if (it.kind !== 'dark') this._diamond(it.sx, it.sy + this.tileH * 0.5, (legend[it.kind] && legend[it.kind].color) || '#3a4048'); + ctx.globalAlpha = 1; } else if (it.type === 'prop') { this._contactShadow(it.sx, it.sy + this.tileH * 1.4, 22, 9); rec = this._load(this._tileUrl(it.kind)); diff --git a/html/js/render/spGameRenderer.js b/html/js/render/spGameRenderer.js index 76d94d6..2fbce27 100644 --- a/html/js/render/spGameRenderer.js +++ b/html/js/render/spGameRenderer.js @@ -112,13 +112,23 @@ host._rkZoomBound = true; host.addEventListener('wheel', function (ev) { ev.preventDefault(); - SP._zoom = Math.max(0.6, Math.min(4, (SP._zoom || 1.7) * Math.exp(-ev.deltaY * 0.0015))); + // Base the step on the CURRENT effective zoom (mode-aware default if not yet set), + // NOT a hardcoded 1.7 — otherwise the first scroll on iso (default 1.0) jumped up to + // 1.7 and BOTH directions read as "zoom in". + var base = (SP._zoom != null) ? SP._zoom : SP._defaultZoom(); + SP._zoom = Math.max(0.4, Math.min(4, base * Math.exp(-ev.deltaY * 0.0015))); SP._applyCamera(); }, { passive: false }); } return SP._renderer; }; + // Mode-aware default zoom: iso tiles are large (84px) so they want less magnification than the + // small tiled/ascii cells (~24px). + SP._defaultZoom = function () { + return (SP._renderer && SP._renderer.name === 'iso') ? 1.0 : 1.7; + }; + // Centre the (whole-scene) canvas on the player via a CSS transform, clipped to the host — // renderer-agnostic (works for tiled / iso / 3d, each of which reports its own focusPoint). SP._applyCamera = function () { @@ -133,9 +143,7 @@ r.canvas.style.top = '0'; return; } - // Default zoom is mode-aware: iso tiles are large (84px), so a lower default frames a sensible - // number of them; tiled/ascii cells are small (~24px) so they want more magnification. - var scale = SP._zoom || (r.name === 'iso' ? 1.0 : 1.7); + var scale = (SP._zoom != null) ? SP._zoom : SP._defaultZoom(); 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