From 80de59e71864375cb7a52dcfdaf55e8c032990e4 Mon Sep 17 00:00:00 2001 From: web3dev1337 <160291380+web3dev1337@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:38:20 +1100 Subject: [PATCH 1/3] fix: cap mobile device pixel ratio --- client/src/core/Renderer.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/core/Renderer.ts b/client/src/core/Renderer.ts index bc742730..ca932c0f 100644 --- a/client/src/core/Renderer.ts +++ b/client/src/core/Renderer.ts @@ -32,6 +32,7 @@ import Chunk from '../chunks/Chunk'; import Assets from '../network/Assets'; import EventRouter from '../events/EventRouter'; import Game from '../Game'; +import MobileManager from '../mobile/MobileManager'; import { modalAlert } from '../ui/Modal'; import { NetworkManagerEventType } from '../network/NetworkManager'; import { getTransparentSortKey, lerpColor } from '../three/utils'; @@ -492,7 +493,10 @@ export default class Renderer { private _onClientSettingsUpdate = (_payload: ClientSettingsEventPayload.IUpdate): void => { const { resolution } = this._game.settingsManager.qualityPerfTradeoff; - this._renderer.setPixelRatio(window.devicePixelRatio * resolution.multiplier); + const devicePixelRatio = MobileManager.isMobile + ? Math.min(window.devicePixelRatio, 2.0) + : window.devicePixelRatio; + this._renderer.setPixelRatio(devicePixelRatio * resolution.multiplier); this._clampTargetFogNearAndFar(); this._setupFog(); @@ -590,7 +594,11 @@ export default class Renderer { private _setupRenderer(): void { this._renderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight); - this._renderer.setPixelRatio(window.devicePixelRatio * this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier); + const resolutionMultiplier = this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier; + const devicePixelRatio = MobileManager.isMobile + ? Math.min(window.devicePixelRatio, 2.0) + : window.devicePixelRatio; + this._renderer.setPixelRatio(devicePixelRatio * resolutionMultiplier); this._renderer.info.autoReset = false; this._renderer.localClippingEnabled = false; // Be explicit about output space; this is cheap and avoids surprises across Three.js versions. From f7788d4c5f80ae7ae2be6e6ab94345e21ecd4d83 Mon Sep 17 00:00:00 2001 From: web3dev1337 <160291380+web3dev1337@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:10:34 +1100 Subject: [PATCH 2/3] fix: cap mobile devicePixelRatio --- client/src/core/Renderer.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/core/Renderer.ts b/client/src/core/Renderer.ts index ca932c0f..bd9ded46 100644 --- a/client/src/core/Renderer.ts +++ b/client/src/core/Renderer.ts @@ -42,6 +42,18 @@ import type { NetworkManagerEventPayload } from '../network/NetworkManager'; import { type ClientSettingsEventPayload, ClientSettingsEventType } from '../settings/SettingsManager'; const MISSING_SKYBOX_TEXTURE_PATH = '/textures/missing-skybox'; +const IOS_MAX_RENDER_PIXEL_RATIO = 1.5; +const OTHER_MOBILE_MAX_RENDER_PIXEL_RATIO = 2.0; + +const isIOSOrIPadOS = (): boolean => { + if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) { + return true; + } + + return /iPad|iPhone|iPod/.test(navigator.userAgent); +}; + +const MAX_MOBILE_DPR = isIOSOrIPadOS() ? IOS_MAX_RENDER_PIXEL_RATIO : OTHER_MOBILE_MAX_RENDER_PIXEL_RATIO; // Working variables const color = new Color(); @@ -494,7 +506,7 @@ export default class Renderer { const { resolution } = this._game.settingsManager.qualityPerfTradeoff; const devicePixelRatio = MobileManager.isMobile - ? Math.min(window.devicePixelRatio, 2.0) + ? Math.min(window.devicePixelRatio, MAX_MOBILE_DPR) : window.devicePixelRatio; this._renderer.setPixelRatio(devicePixelRatio * resolution.multiplier); @@ -596,7 +608,7 @@ export default class Renderer { this._renderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight); const resolutionMultiplier = this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier; const devicePixelRatio = MobileManager.isMobile - ? Math.min(window.devicePixelRatio, 2.0) + ? Math.min(window.devicePixelRatio, MAX_MOBILE_DPR) : window.devicePixelRatio; this._renderer.setPixelRatio(devicePixelRatio * resolutionMultiplier); this._renderer.info.autoReset = false; From 5cc5efa97e9ba9d1e334d1d8a0c3b223f88a0b5a Mon Sep 17 00:00:00 2001 From: web3dev1337 <160291380+web3dev1337@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:29:48 +1100 Subject: [PATCH 3/3] refactor: self-documenting names for mobile DPR safety cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SAFE_PIXEL_RATIO_IOS / SAFE_PIXEL_RATIO_MOBILE — names say why the cap exists - safeDevicePixelRatio() — single function replaces duplicated inline ternaries - gpuSafeMax — makes the GPU memory motivation readable without comments - isAppleMobileDevice() — clearer than isIOSOrIPadOS Co-Authored-By: Claude Opus 4.6 --- client/src/core/Renderer.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/client/src/core/Renderer.ts b/client/src/core/Renderer.ts index bd9ded46..54e6f04b 100644 --- a/client/src/core/Renderer.ts +++ b/client/src/core/Renderer.ts @@ -42,18 +42,22 @@ import type { NetworkManagerEventPayload } from '../network/NetworkManager'; import { type ClientSettingsEventPayload, ClientSettingsEventType } from '../settings/SettingsManager'; const MISSING_SKYBOX_TEXTURE_PATH = '/textures/missing-skybox'; -const IOS_MAX_RENDER_PIXEL_RATIO = 1.5; -const OTHER_MOBILE_MAX_RENDER_PIXEL_RATIO = 2.0; +const SAFE_PIXEL_RATIO_IOS = 1.5; +const SAFE_PIXEL_RATIO_MOBILE = 2.0; -const isIOSOrIPadOS = (): boolean => { - if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) { - return true; - } +const isAppleMobileDevice = (): boolean => { + if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) return true; return /iPad|iPhone|iPod/.test(navigator.userAgent); }; -const MAX_MOBILE_DPR = isIOSOrIPadOS() ? IOS_MAX_RENDER_PIXEL_RATIO : OTHER_MOBILE_MAX_RENDER_PIXEL_RATIO; +const safeDevicePixelRatio = (nativeRatio: number): number => { + if (!MobileManager.isMobile) return nativeRatio; + + const gpuSafeMax = isAppleMobileDevice() ? SAFE_PIXEL_RATIO_IOS : SAFE_PIXEL_RATIO_MOBILE; + + return Math.min(nativeRatio, gpuSafeMax); +}; // Working variables const color = new Color(); @@ -505,10 +509,7 @@ export default class Renderer { private _onClientSettingsUpdate = (_payload: ClientSettingsEventPayload.IUpdate): void => { const { resolution } = this._game.settingsManager.qualityPerfTradeoff; - const devicePixelRatio = MobileManager.isMobile - ? Math.min(window.devicePixelRatio, MAX_MOBILE_DPR) - : window.devicePixelRatio; - this._renderer.setPixelRatio(devicePixelRatio * resolution.multiplier); + this._renderer.setPixelRatio(safeDevicePixelRatio(window.devicePixelRatio) * resolution.multiplier); this._clampTargetFogNearAndFar(); this._setupFog(); @@ -607,10 +608,7 @@ export default class Renderer { private _setupRenderer(): void { this._renderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight); const resolutionMultiplier = this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier; - const devicePixelRatio = MobileManager.isMobile - ? Math.min(window.devicePixelRatio, MAX_MOBILE_DPR) - : window.devicePixelRatio; - this._renderer.setPixelRatio(devicePixelRatio * resolutionMultiplier); + this._renderer.setPixelRatio(safeDevicePixelRatio(window.devicePixelRatio) * resolutionMultiplier); this._renderer.info.autoReset = false; this._renderer.localClippingEnabled = false; // Be explicit about output space; this is cheap and avoids surprises across Three.js versions.