From 1a694893054568e4df22529b02f425694f4e32fb Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:29:51 +0200 Subject: [PATCH 1/3] fix(ios): pin voxel render target scale --- lib/PixelCanvas.lua | 9 +++++++++ lib/ShadowMap.lua | 2 +- lib/TiltShift.lua | 6 ++++-- lib/Voxel3D.lua | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 lib/PixelCanvas.lua diff --git a/lib/PixelCanvas.lua b/lib/PixelCanvas.lua new file mode 100644 index 0000000..ca25769 --- /dev/null +++ b/lib/PixelCanvas.lua @@ -0,0 +1,9 @@ +local V = ... + +local PixelCanvas = {} + +function PixelCanvas.new(w, h) + return pcall(love.graphics.newCanvas, w, h, { dpiscale = 1 }) +end + +return PixelCanvas diff --git a/lib/ShadowMap.lua b/lib/ShadowMap.lua index 18ceffd..d5afc76 100644 --- a/lib/ShadowMap.lua +++ b/lib/ShadowMap.lua @@ -186,7 +186,7 @@ end local function getCanvas(res) if canvas == false then return nil end if canvas and canvasRes == res then return canvas end - local ok, c = pcall(love.graphics.newCanvas, res, res) + local ok, c = V.require("PixelCanvas").new(res, res) if not (ok and c) then canvas = false return nil diff --git a/lib/TiltShift.lua b/lib/TiltShift.lua index d95c10e..a94081b 100644 --- a/lib/TiltShift.lua +++ b/lib/TiltShift.lua @@ -25,6 +25,7 @@ -- failure -- headless, no shader support) apply() hands the canvas back -- untouched, so every other path is byte-for-byte what it always was. +local V = ... local TiltShift = {} TiltShift.level = 0 @@ -85,9 +86,10 @@ end local function getCanvases(w, h) if not ping or cw ~= w or ch ~= h then - local ok, a = pcall(love.graphics.newCanvas, w, h) + local PixelCanvas = V.require("PixelCanvas") + local ok, a = PixelCanvas.new(w, h) if not ok then return nil end - local okB, b = pcall(love.graphics.newCanvas, w, h) + local okB, b = PixelCanvas.new(w, h) if not okB then return nil end -- the gaussian's fractional tap offsets need linear filtering a:setFilter("linear", "linear") diff --git a/lib/Voxel3D.lua b/lib/Voxel3D.lua index 471a688..d16dc71 100644 --- a/lib/Voxel3D.lua +++ b/lib/Voxel3D.lua @@ -33,6 +33,7 @@ local WorldCurve = V.require("WorldCurve") local Sky = V.require("Sky") local DayNight = V.require("DayNight") local GlassMask = V.require("GlassMask") +local PixelCanvas = V.require("PixelCanvas") local Voxel3D = {} @@ -640,7 +641,7 @@ function Voxel3D.beginScene(w, h, cx, cy, vw, vh, sky, slot) local name = slot or "world" local slotHeld = slots[name] if not (slotHeld and slotHeld.w == w and slotHeld.h == h) then - local ok, c = pcall(love.graphics.newCanvas, w, h) + local ok, c = PixelCanvas.new(w, h) if not ok then return false end c:setFilter("nearest", "nearest") if slotHeld then releaseSlot(slotHeld) end From acb2eadeb41b7038ed42be28438d830b2f847b7a Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:37:22 +0200 Subject: [PATCH 2/3] fix(ios): use decal shadows on Metal --- lib/ShadowMap.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ShadowMap.lua b/lib/ShadowMap.lua index d5afc76..8997a49 100644 --- a/lib/ShadowMap.lua +++ b/lib/ShadowMap.lua @@ -221,6 +221,9 @@ end -- where the canvas cannot be made -- VoxelScene then keeps the flat decal -- shadows, which need nothing but a quad. function ShadowMap.available() + if love.system and love.system.getOS and love.system.getOS() == "iOS" then + return false + end if not (love.graphics and love.graphics.newCanvas and love.graphics.setDepthMode) then return false From 3cd3fe431d1fbdec08a070764e7b0eae57a69646 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:58:40 +0200 Subject: [PATCH 3/3] fix(ios): preserve battle hud colors --- lib/OverworldBattle.lua | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/OverworldBattle.lua b/lib/OverworldBattle.lua index dddf97d..b93c0fd 100644 --- a/lib/OverworldBattle.lua +++ b/lib/OverworldBattle.lua @@ -287,6 +287,10 @@ end -- not nest. local session = nil +local function isIOS() + return love.system and love.system.getOS and love.system.getOS() == "iOS" +end + local function game() return require("src.core.Game") end @@ -484,11 +488,15 @@ function OverworldBattle.update(dt) -- reason the scene is: it binds a canvas of its own. After the frost, so -- the glass is frosted from the world alone and never from the glyphs -- about to sit on it. - local okHud, up = pcall(OverworldBattle.snapHUDs, session.battle, shot) + local ios = isIOS() + local okHud, up = false, false + if not ios then + okHud, up = pcall(OverworldBattle.snapHUDs, session.battle, shot) + end session.snapped = (okHud and up) and true or false -- once per battle, not once per frame: a driver that cannot do this cannot -- do it sixty times a second either, and the fallback is silent and fine - if not okHud and not session.hudWarned then + if not ios and not okHud and not session.hudWarned then session.hudWarned = true V.mod.log:warn("overworld battle HUD snap failed: %s -- the HUDs draw " .. "in the battle frame this battle", tostring(up)) @@ -928,6 +936,7 @@ function OverworldBattle.install() local innerText = BattleState.drawTextArea function BattleState:drawTextArea() if not self.dramaticShapeShot then return innerText(self) end + if isIOS() then return innerText(self) end local battle = self if not self.dramaticShapeDark then return withoutBoxFill(battle, innerText) end BattleHud.flipGlyphs(BattleScene.GB_W, BattleScene.GB_H, function() @@ -1146,6 +1155,17 @@ function OverworldBattle.drawHudPanels(battle) local shot = battle.dramaticShapeShot battle.dramaticShapeDark = nil if not shot then return end + if isIOS() then + local slide = (battle.introSlide or 0) * 4 + local enemy, player = OverworldBattle.hudLive(battle, slide) + local rect = OverworldBattle.HUD_RECT + love.graphics.setColor(1, 1, 1, 0.84) + if enemy then love.graphics.rectangle("fill", rect.enemy[1], rect.enemy[2], rect.enemy[3], rect.enemy[4]) end + if player then love.graphics.rectangle("fill", rect.player[1], rect.player[2], rect.player[3], rect.player[4]) end + love.graphics.setColor(1, 1, 1, 1) + battle.dramaticShapeDark = nil + return + end if snapped() then battle.dramaticShapeDark = session and session.dark or nil return