From 80d84f40a56eeea4899cc9244029eba12e15d56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8A=B1=3A=20=CC=97=CC=80S=CF=83=E1=83=A7M=CE=B1?= =?UTF-8?q?=E1=83=A7=C6=88=CF=83=CA=9F=E0=BF=90?= Date: Sun, 4 Oct 2026 17:20:48 -0500 Subject: [PATCH 1/3] Reproduce quick menu navigation loss and portrait viewport cropping --- tests/engine/display.lua | 10 ++++++++++ tests/engine/gameplay-flow.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests/engine/display.lua b/tests/engine/display.lua index f284044..99058e2 100644 --- a/tests/engine/display.lua +++ b/tests/engine/display.lua @@ -13,6 +13,16 @@ local function mode(w,h) end local function near(a,b) assert(math.abs(a-b)<.001, tostring(a)..' ~= '..tostring(b)) end function love.load() + check('portrait adaptive resize retains a usable game viewport',function() + mode(360,640);Project.adaptableWidth=true + game.resize(360,640) + assert(game.width==Project.width and game.height==Project.height,'portrait orientation cropped the logical game screen') + local view=game.display.getViewport() + near(view.width,360);near(view.height,202.5) + local x,y=game.display.gameToWindow(640,360) + near(x,180);near(y,320) + Project.adaptableWidth=false + end) check('adaptive OS resize preserves native size and project configuration',function() mode(640,480); local original=Project.width; Project.adaptableWidth=true game.resize(640,480) diff --git a/tests/engine/gameplay-flow.lua b/tests/engine/gameplay-flow.lua index 80b52bc..a4fb459 100644 --- a/tests/engine/gameplay-flow.lua +++ b/tests/engine/gameplay-flow.lua @@ -1,3 +1,29 @@ +check('quick menu taps move once and accept the selected item', function() + for _, navigation in ipairs({{'vertical', 's'}, {'horizontal', 'd'}}) do + local menu = MenuList(nil, false, navigation[1]) + for _ = 1, 3 do menu:add(Sprite()) end + controls:onKeyPress(navigation[2]) + controls:onKeyRelease(navigation[2]) + controls:update() + Throttle:update(0.016) + menu:update(0.016) + assert(menu.curSelected == 2, 'a quick direction tap did not move the menu') + controls:update() + Throttle:update(0.016) + menu:update(0.016) + assert(menu.curSelected == 2, 'one direction tap moved twice') + local selected, calls = nil, 0 + menu.selectCallback = function(item) selected = item; calls = calls + 1 end + controls:onKeyPress('return') + controls:onKeyRelease('return') + controls:update() + menu:update(0.016) + assert(selected == menu.members[2] and calls == 1, 'a quick accept tap did not select the second item') + for _, throttle in pairs(menu.throttles) do throttle:destroy() end + menu:destroy() + end +end) + check('keyboard hits, misses and pause preserve a native song and mod HUD', function() ClientPrefs.data.botplayMode = false ClientPrefs.data.ghostTap = false From 84e846e53bde98d9449c52490b5660d57d5c4733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8A=B1=3A=20=CC=97=CC=80S=CF=83=E1=83=A7M=CE=B1?= =?UTF-8?q?=E1=83=A7=C6=88=CF=83=CA=9F=E0=BF=90?= Date: Sun, 4 Oct 2026 17:23:38 -0500 Subject: [PATCH 2/3] Consume quick menu taps and fit the complete viewport in portrait windows --- CHANGELOG.md | 10 ++++++++++ funkin/ui/menulist.lua | 14 +++++++++----- loxel/init.lua | 4 ++-- project.lua | 4 ++-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33a7351..0b14a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to Light Engine will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.16-advanced.8] - 2026-10-04 + +### Fixed +- Menu navigation consumes a quick direction tap once, including presses that + end before an update. Held keys retain the existing repeat delay. +- Portrait windows retain the full logical game viewport and letterbox it, + keeping menus and gameplay visible when Android rotates the device. + +Android version code: 124. + ## [0.1.16-advanced.7] - 2026-10-04 ### Fixed diff --git a/funkin/ui/menulist.lua b/funkin/ui/menulist.lua index 39b7564..e6f360a 100644 --- a/funkin/ui/menulist.lua +++ b/funkin/ui/menulist.lua @@ -1,6 +1,10 @@ local MenuList = SpriteGroup:extend("MenuList") MenuList.selectionCache = {} +local function navigationInput(name) + return controls:pressed(name) or controls:down(name) +end + local defaultScrolls = { default = function(self, obj, dt, time) obj.y = self:lerp(dt, obj, "y", time) @@ -83,11 +87,11 @@ function MenuList:new(sound, cache, scroll, hover) self.throttles = {} if self.scroll ~= "horizontal" and not self.isMobile then - self.throttles[-1] = Throttle:make({controls.down, controls, "ui_up"}) - self.throttles[1] = Throttle:make({controls.down, controls, "ui_down"}) + self.throttles[-1] = Throttle:make({navigationInput, "ui_up"}) + self.throttles[1] = Throttle:make({navigationInput, "ui_down"}) elseif self.scroll == "horizontal" and not self.isMobile then - self.throttles[-1] = Throttle:make({controls.down, controls, "ui_left"}) - self.throttles[1] = Throttle:make({controls.down, controls, "ui_right"}) + self.throttles[-1] = Throttle:make({navigationInput, "ui_left"}) + self.throttles[1] = Throttle:make({navigationInput, "ui_right"}) end end @@ -322,4 +326,4 @@ function MenuList:_getBoundary() self.origin.x, self.origin.y end -return MenuList \ No newline at end of file +return MenuList diff --git a/loxel/init.lua b/loxel/init.lua index a66a68f..f93070e 100644 --- a/loxel/init.lua +++ b/loxel/init.lua @@ -277,7 +277,7 @@ function game.init(app, state, ...) local width, height = app.width, app.height if app.adaptableWidth then local sw, sh = love.graphics.getDimensions() - if sw > 0 and sh > 0 then width = math.max(1, math.floor(height * sw / sh)) end + if sw > 0 and sh > 0 and sw >= sh then width = math.max(1, math.floor(height * sw / sh)) end end game.width, game.height = width, height Display.refresh() @@ -405,7 +405,7 @@ function game.resize(w, h) if Project.adaptableWidth then local oldWidth, oldHeight = game.width, game.height if oldHeight > 0 then - game.width = math.max(1, math.floor(oldHeight * w / h)) + game.width = w < h and Project.width or math.max(1, math.floor(oldHeight * w / h)) for _, camera in ipairs(game.cameras.list) do if camera.x == 0 and camera.y == 0 and camera.width == oldWidth and camera.height == oldHeight then camera:resize(game.width, oldHeight, camera.resolutionX, camera.resolutionY) diff --git a/project.lua b/project.lua index 9f36d26..68af52d 100644 --- a/project.lua +++ b/project.lua @@ -6,8 +6,8 @@ return { title = "Light Engine", file = "Light Engine", icon = "art/logo.png", - version = "0.1.16-advanced.7", - androidVersionCode = 123, + version = "0.1.16-advanced.8", + androidVersionCode = 124, package = "com.zyn.lightengine", width = 1280, height = 720, From 046fa2f3421d42d9f8de23a4c5ba7d7dd06502a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8A=B1=3A=20=CC=97=CC=80S=CF=83=E1=83=A7M=CE=B1?= =?UTF-8?q?=E1=83=A7=C6=88=CF=83=CA=9F=E0=BF=90?= Date: Sun, 4 Oct 2026 17:34:24 -0500 Subject: [PATCH 3/3] Fix navigation throttle cleanup and stop retrying expired overlay requests --- CHANGELOG.md | 2 ++ funkin/backend/throttle.lua | 4 ++-- .../screen-overlay/modules/overlay.lua | 2 +- tests/engine/compat-controls.lua | 8 +++++--- tests/engine/core-utilities.lua | 19 +++++++++++++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b14a59..5b02c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.16-advanced.8] - 2026-10-04 ### Fixed +- Destroyed menu-repeat controls are removed and safely recycled. +- Desktop overlay HTTP timeouts end the request without a second retry. - Menu navigation consumes a quick direction tap once, including presses that end before an update. Held keys retain the existing repeat delay. - Portrait windows retain the full logical game viewport and letterbox it, diff --git a/funkin/backend/throttle.lua b/funkin/backend/throttle.lua index 925b6e3..72ca665 100644 --- a/funkin/backend/throttle.lua +++ b/funkin/backend/throttle.lua @@ -37,8 +37,8 @@ function ThrottleObject:check() end function ThrottleObject:destroy() - table.destroy(Throttle.list, self) - table.insert(dead, setmetatable(self)) + if not table.delete(Throttle.list, self) then return end + table.insert(dead, setmetatable(self, nil)) end -- Throttle diff --git a/separate-addons/screen-overlay/modules/overlay.lua b/separate-addons/screen-overlay/modules/overlay.lua index f5b39ad..bc8c3dd 100644 --- a/separate-addons/screen-overlay/modules/overlay.lua +++ b/separate-addons/screen-overlay/modules/overlay.lua @@ -128,7 +128,7 @@ while true do if success then code,body,reason=a,b,c else code,body,reason=nil,'',a end end local pending=((connecting and code and code>=200 and code<300)or code==403)and body:find('"pending"%s*:%s*true') - local startup=code==nil and love.timer.getTime()-started<3 + local startup=code==nil and reason~='timeout' and love.timer.getTime()-started<3 if not pending and not startup then break end if task.close or cancel:peek() or tasks:peek() or love.timer.getTime()-started>=30 then break end local untilTime=love.timer.getTime()+.5 diff --git a/tests/engine/compat-controls.lua b/tests/engine/compat-controls.lua index 38fb4d4..e7d6754 100644 --- a/tests/engine/compat-controls.lua +++ b/tests/engine/compat-controls.lua @@ -92,8 +92,7 @@ function love.load() assert(state.camHUD.__shakeDuration==0 and state.camHUD.__fadeDuration==0,'owned camera effects survived disposal') assert(not pcall(cleanupFont.getHeight,cleanupFont) and pcall(cachedFont.getHeight,cachedFont),'font ownership cleanup incorrect') local destroyedCamera=Camera();state.camOther=destroyedCamera - local weakContext - do + local function disposedContext() local abandonedScript=Script('tests/engine/compat-fixture.lua',false,true,true) local abandoned=Script.createCompatibility(state,abandonedScript) abandonedScript.variables.requestAsset('image','test-controls','abandoned') @@ -101,8 +100,11 @@ function love.load() destroyedCamera:destroy() abandoned:dispose();abandonedScript:close() assert(destroyedCamera.__flashComplete==nil,'destroyed camera retained owned completion closure') - weakContext=setmetatable({abandoned},{__mode='v'}) + return setmetatable({abandoned},{__mode='v'}) end + -- End the allocating stack frame before checking reachability; LuaJIT may + -- retain registers from a lexical block until its enclosing function returns. + local weakContext=disposedContext() collectgarbage('collect') assert(weakContext[1]==nil,'pending callback retained a disposed context/state') while paths.async.getProgress()<1 do assert(love.timer.getTime()