mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
fix: FAITHFUL RATIO works on Android and iOS
apply() returned false on its first line for mobile, so the option did nothing there. A phone has no window to resize, so the lock caps the render scale instead: the largest whole multiple of 160x144 the display holds, centred, black around it. Two parts beyond that. The scale is read off the display rather than from the desktop's 1X-4X ladder, which named a different fraction of every device and left the useful levels off the list; mobile shows ON or OFF. And the world pass, which expands to cover the whole display so letterbox becomes more map, is now sized against the locked viewport, so the lock reaches the overworld instead of showing more of it. Pixel perfect throughout, whole multiples only. Desktop and OFF are unchanged. Renames the row to FAITHFUL RATIO on both platforms; the saved key stays faithfulRes so existing settings carry over.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
-- FAITHFUL RES: lock the window to an exact 160x144 multiple so the surface
|
||||
-- FAITHFUL RATIO: lock the window to an exact 160x144 multiple so the surface
|
||||
-- is the Game Boy screen with no letterbox at all.
|
||||
--
|
||||
-- The interesting parts are the two things a naive setMode gets wrong: the
|
||||
@@ -121,11 +121,22 @@ T.eq(calls[1].flags.minwidth, FaithfulRes.MIN_W, "with conf.lua's floor restored
|
||||
T.eq(calls[1].flags.minheight, FaithfulRes.MIN_H, "on both axes")
|
||||
T.eq(FaithfulRes.locked, false, "and the module no longer claims the window")
|
||||
|
||||
-- mobile has no resizable window to lock
|
||||
-- Mobile has no resizable window to lock, so it locks the RENDER scale
|
||||
-- instead and never calls setMode. It used to report unlocked and do
|
||||
-- nothing at all, which is why the OPTIONS row was inert on Android and iOS;
|
||||
-- tests/engine/faithful_res_mobile.lua covers the scale side.
|
||||
calls = stubWindow(1)
|
||||
love.system = { getOS = function() return "Android" end }
|
||||
T.eq(FaithfulRes.apply(4), false, "mobile reports unlocked")
|
||||
T.eq(#calls, 0, "and never touches the window")
|
||||
T.eq(FaithfulRes.apply(4), true, "mobile locks, by capping the render scale")
|
||||
-- the level asked for is irrelevant on mobile: ON is ON, and the scale is
|
||||
-- read off the display so the picture is as big as exact pixels allow
|
||||
T.eq(FaithfulRes.scaleCap(), FaithfulRes.deviceScale(),
|
||||
"and the scale comes from the display, not from the level")
|
||||
T.eq(#calls, 0, "still without ever touching the window")
|
||||
T.eq(FaithfulRes.apply(0), false, "OFF releases it")
|
||||
T.eq(FaithfulRes.scaleCap(), nil, "and the cap goes away with it")
|
||||
T.eq(#calls, 0, "the window is left alone either way")
|
||||
FaithfulRes.mobileScale = 0
|
||||
|
||||
love.window, love.system = savedWindow, savedSystem
|
||||
if love.graphics then
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
-- FAITHFUL RATIO on Android / iOS.
|
||||
--
|
||||
-- On mobile the setting is ON or OFF, and ON means one thing: lock the
|
||||
-- viewport to the Game Boy's 10:9 at the largest WHOLE multiple this screen
|
||||
-- can hold, centred, black around it -- the way an emulator opens a Game Boy
|
||||
-- game on a phone.
|
||||
--
|
||||
-- Three things had to be true and none of them were:
|
||||
--
|
||||
-- * it had to apply at all. FaithfulRes.apply returned false on its first
|
||||
-- line for mobile, so the OPTIONS row did nothing on Android and iOS.
|
||||
-- A phone has no window to resize, so the lock caps the RENDER scale.
|
||||
--
|
||||
-- * it had to be sized for the device. The first cut kept the desktop's
|
||||
-- absolute 1X-4X ladder, which names a window size you can see on a
|
||||
-- desktop and means a different fraction of every phone: 4X was a quarter
|
||||
-- of a 1080p display and 5X/6X were not on the list at all. The scale is
|
||||
-- read off the display now, not chosen by the player.
|
||||
--
|
||||
-- * it had to work in the OVERWORLD. The world pass deliberately expands
|
||||
-- to cover the whole display so letterbox voids become more map. So the
|
||||
-- lock shrank the UI blit while the map kept filling the screen -- and
|
||||
-- showed MORE map, since a smaller scale fits more world pixels in.
|
||||
--
|
||||
-- Pixel perfect throughout: whole multiples only. The leftover is bars, and
|
||||
-- on a 9:20 phone there is a lot of it vertically. That is what a 10:9
|
||||
-- screen looks like on a tall display; stretching to reach the edges would
|
||||
-- resample every pixel, which is the one thing this setting exists to refuse.
|
||||
-- luajit tests/engine/faithful_res_mobile.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local FaithfulRes = require("src.core.FaithfulRes")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Zoom = require("src.render.Zoom")
|
||||
|
||||
local g = love.graphics
|
||||
local realDims, realPixelDims = g.getDimensions, g.getPixelDimensions
|
||||
local realOS = love.system and love.system.getOS
|
||||
local savedOffset = Zoom.offset
|
||||
love.system = love.system or {}
|
||||
|
||||
local function pose(w, h, osName)
|
||||
love.system.getOS = function() return osName or "Android" end
|
||||
g.getDimensions = function() return w, h end
|
||||
g.getPixelDimensions = function() return w, h end
|
||||
end
|
||||
|
||||
Renderer.uiWidth, Renderer.uiHeight = Renderer.WIDTH, Renderer.HEIGHT
|
||||
Zoom.offset = 0
|
||||
|
||||
-- ------------------------------------------------------- it applies at all
|
||||
|
||||
pose(1080, 2400) -- a Pixel 7, portrait
|
||||
T.eq(FaithfulRes.isMobile(), true, "the fixture reads as a phone")
|
||||
T.eq(FaithfulRes.apply(1), true, "FAITHFUL RATIO applies on mobile at all now")
|
||||
T.eq(FaithfulRes.locked, true, "and reports itself locked")
|
||||
|
||||
-- ------------------------------------------------- one ON, sized by the device
|
||||
|
||||
T.eq(FaithfulRes.maxLevel(), 1, "mobile offers ON, not a ladder of multiples")
|
||||
T.eq(#FaithfulRes.levels(), 2, "so the row is exactly OFF and ON")
|
||||
T.eq(FaithfulRes.label(1), "ON", "and ON is spelled ON, not 1X")
|
||||
T.eq(FaithfulRes.label(0), "OFF", "with OFF unchanged")
|
||||
|
||||
-- 1080/160 = 6.75 and 2400/144 = 16.6, so the largest WHOLE multiple is 6
|
||||
T.eq(FaithfulRes.deviceScale(), 6, "the scale comes off the display: 6x here")
|
||||
T.eq(FaithfulRes.scaleCap(), 6, "and that is what the renderer is told to lock")
|
||||
T.eq(Renderer:fitScale(), 6, "so a GB pixel is exactly 6 screen pixels")
|
||||
|
||||
-- the player never picks a smaller one, which is how the first cut managed to
|
||||
-- draw a postage stamp on a 1080p phone
|
||||
T.eq(FaithfulRes.normalize(3), 1, "any ON value is just ON")
|
||||
FaithfulRes.apply(1)
|
||||
T.eq(Renderer:fitScale(), 6, "and always lands on the device maximum")
|
||||
|
||||
-- --------------------------------------------------------- the overworld
|
||||
|
||||
FaithfulRes.apply(0)
|
||||
local unlockedW = Renderer:worldViewSize()
|
||||
T.check(unlockedW > 160, "unlocked, the overworld view covers the whole display")
|
||||
|
||||
FaithfulRes.apply(1)
|
||||
local lockedW, lockedH = Renderer:worldViewSize()
|
||||
T.eq(lockedW, 160, "locked, the overworld shows exactly a GB screen wide")
|
||||
T.eq(lockedH, 144, "and exactly a GB screen tall")
|
||||
T.check(lockedW < unlockedW,
|
||||
"so the lock SHRINKS the map area instead of growing it")
|
||||
|
||||
-- ------------------------------------------------------ pixel perfect
|
||||
|
||||
-- 6 x 160 = 960 of 1080 wide. Reaching the edges would need 6.75, which
|
||||
-- resamples every pixel; the bars are the honest answer.
|
||||
local cap = FaithfulRes.scaleCap()
|
||||
T.eq(cap, math.floor(cap), "the locked scale is a whole number, never fractional")
|
||||
T.eq(160 * cap, 960, "which puts the GB screen at 960 of 1080 pixels wide")
|
||||
|
||||
-- ------------------------------------------------------- rotation is free
|
||||
|
||||
-- fitScale reads the live drawable size every frame, so a rotate re-derives
|
||||
-- the scale with nothing to re-apply
|
||||
pose(1080, 2400); FaithfulRes.apply(1)
|
||||
T.eq(Renderer:fitScale(), 6, "portrait locks at 6x")
|
||||
pose(2400, 1080); FaithfulRes.apply(1)
|
||||
T.eq(Renderer:fitScale(), 7, "landscape re-derives to 7x (1080/144), still whole")
|
||||
T.eq(Renderer:worldViewSize(), 160, "and the overworld stays locked through it")
|
||||
|
||||
-- ---------------------------------------------------------- OFF is OFF
|
||||
|
||||
-- Nothing about OFF changed: it is the behaviour the game already had.
|
||||
pose(1080, 2400)
|
||||
FaithfulRes.apply(0)
|
||||
T.eq(FaithfulRes.locked, false, "OFF releases the lock")
|
||||
T.eq(FaithfulRes.scaleCap(), nil, "with no cap on the renderer")
|
||||
T.eq(Renderer:fitScale(), 6, "the UI fits exactly as it did before")
|
||||
T.check(Renderer:worldViewSize() > 160, "and the overworld fills the screen again")
|
||||
|
||||
-- ------------------------------------------------- desktop is untouched
|
||||
|
||||
pose(1280, 800, "Windows")
|
||||
T.eq(FaithfulRes.isMobile(), false, "the fixture reads as desktop")
|
||||
T.eq(FaithfulRes.maxLevel(), 4, "desktop keeps its 1X-4X ladder")
|
||||
T.eq(FaithfulRes.label(2), "2X", "and its labels")
|
||||
T.eq(FaithfulRes.scaleCap(), nil, "no cap: the window itself is the lock there")
|
||||
T.eq(Renderer:fitScale(), 5, "so fitScale is untouched (800/144 = 5)")
|
||||
|
||||
g.getDimensions, g.getPixelDimensions = realDims, realPixelDims
|
||||
if realOS then love.system.getOS = realOS end
|
||||
Zoom.offset = savedOffset
|
||||
FaithfulRes.locked = false
|
||||
FaithfulRes.mobileScale = 0
|
||||
|
||||
T.finish("faithful ratio mobile")
|
||||
Reference in New Issue
Block a user