* Use spare CPU cores to remove in-game hitches on low-power devices
Per-frame game logic and drawing are cheap; the remaining stalls on
low-power handhelds are one-off main-thread hitches from chip SFX/cry
synthesis, RED++ atlas recolouring, image decodes and the require shim.
Move those onto background threads, each with a synchronous fallback so
behaviour is identical when threads are unavailable (POKEPORT_NO_THREAD=1,
no love.thread, worker failure).
Also adds an F3 frame-time overlay and POKEPORT_GAME_PROF headless
profiling to measure these costs.
* Address code review: shared WorkerLane, F3 gating, decode/epoch fixes
- Extract src/core/WorkerLane.lua (worker lifecycle, prioritised lanes,
per-lane epochs, bounded LRU) and rebuild the SFX and atlas/decode
prefetchers on it
- Keep decoded sprite sheets in every colour mode (the OBJ palette bake
re-reads them), skip oversized decodes, and let palette changes drop
only atlas bakes
- Only bump the SFX render epoch when the channel mix actually changes
- F3 overlay toggle no longer steals the key from rebinding screens or
player bindings; profilers stop only when a quit really proceeds
- Require fast path: test hook instead of a hot-path counter
- Shared tests/drivers/perf_util.lua; drivers restore their patches
* WorkerLane: no budget leak on duplicate sends, keep priority on requeue
- Lane:pump only counts a job against the in-flight budget once per key
- requeueInflight puts entries back at the head of their own queue, so
background hints no longer overtake front requests after a bump
- shutdown() keeps a fatally failed worker off
- Hiding the F3 overlay logs one summary line instead of a full block
* F3 overlay: last-120-frame stats, font-sized box, scale on large screens
* Prefetch: skip useless hints, no main-thread fallback, mix check vs worker config
- Don't queue decodes for sprite sheets whose Image and OBJ bake are cached
- Atlas hint is dropped (sync bake at build time) when the worker can't
decode the tileset itself, instead of decoding on the main thread
- SFX epoch bump compares the live mix with what the worker was last
configured with, replacing the load-time snapshot
* Sprite decode skip: raw-path cache check, never under RED++
* Gen 2 battles prefetch both parties' cries; sprite skip keyed by raw path
* Prefetch: skip cached tilesets outside RED++, log hint failures once
* Profiler: bench survives F3 hide, frame total excludes vsync; safer SFX pump, ordered requeue, tileset skip respects pending variants
* Profiler counts GPU stalls in present; water void-fill keeps tileset hint; README + test coverage
* Profiler vsync cap: measured cadence via RefreshRate, no per-frame allocation
* Use upstream's chip-worker prewarm for SFX/cries; drop our separate SFX worker
Our extra coverage (Gen 1 common SFX + party cries, both parties' battle
cries, lead move sounds, hit and battle SFX, Gen 2 bench cries) now goes
through ChipAudio.prewarmSfx/prewarmCry and a new Sound.prewarmMove.
* Skip decode hints for composed assets; share move cache key; drop dead demoteAll
* Drop dev-only profiling extras: POKEPORT_JITP hook and perf drivers
* README: name the worker threads POKEPORT_JIT leaves alone
* Profiler: count the pre-present vblank wait as present, log on NX quit
- time PresentSync.waitBeforePresent as "present" (the X11 GLX vblank wait
lands there), so it is excluded like the wait inside present
- NX quit now emits the PROF summary / overlay line too
- love.quit's scripted check reads FrameProfiler.benchRequested, so
POKEPORT_GAME_PROF=0 no longer counts as a bench run
- prefetchAtlas skips composed tileset sheets (Assets.isComposed)
- hit sounds defined once in EffectRegistry.HIT_SOUNDS, shared with the
battle prewarm (each hit still gets its own copy)
- drop redundant obpPaths, dead FrameProfiler.visible and the unused
Lane:bump requeue mode; restore applyOptions' doc comment
- On Android, allow game audio to mix with external audio by default instead of demanding exclusive audio focus and freezing when focus changes.
- Add an 'AUDIO MODE' option across Gen 1, Gen 2, and Gen 3 options menus and Launcher Settings:
- BOTH: Game BGM and SFX mix with external media.
- EXT ONLY: In-game BGM is muted while SFX remains active, mixing with external media.
- GAME ONLY: Requests exclusive audio focus (AUDIOFOCUS_GAIN), pausing background media.
- Guard Gen 3 waitse/waitfanfare audio script waits with timeouts and Audio._suspended checks so script execution never blocks indefinitely when audio is paused or interrupted.
- Expose setAudioMixWithSystem / love.audio.setMixWithSystem via JNI / C++ to dynamically toggle Android audio focus behavior based on user setting.
- Add unit test suite in tests/engine/audio_focus_mode_test.lua.
- Add normalizePad() and aliases in GamepadMap for triggers and sticks
- Automatically load custom mappings from SDL_GAMECONTROLLERCONFIG and gamecontrollerdb.txt
- Add support for left and right analog sticks, triggers (L2/R2), stick clicks (LS/RS), and extra SDL buttons (Guide, Touchpad, Paddles)
- Route analog stick axis thresholds into discrete directional press/release events for seamless in-game and menu navigation
- Support rebinding buttons, thumbstick directions, triggers, and stick clicks in the CONTROLS menu (BindingsMenu) with clear abbreviated labels (LS-UP, RS-DN, L2, R2, etc.)
- Integrate stick axis event dispatch across Gen1, Gen2, and Gen3 game loops
- Add comprehensive test suite in tests/engine/sdl_controller_support_test.lua covering all axes, buttons, holds, rebinding, and multi-generation routing
Ruby and Sapphire 1.0/1.1/1.2 import, play, save, convert and link. Gen 3 shaders, braille, weather and reflection parity, link room players, RS custom carts and launcher support, Gen 3 link fixes for Emerald and FireRed/LeafGreen.
- Align CacheContract firered overrides with pure ROM extraction
- Fix door animation extraction tile sizes, palettes, and strides
- Update storage chrome, pokedex, summary, and battle chrome extractors
- Ensure 100% self-contained ROM extraction without external dependencies
- Introduced the `POKEPORT_IDLE_AFTER` and `POKEPORT_IDLE_FPS` environment variables to manage presentation rates on static screens, allowing game logic and audio to maintain full speed.
- Updated the vsync handling to keep it enabled across all platforms, including KMSDRM handhelds, to leverage PresentSync for improved cadence and pacing.
- Enhanced FrameCap logic to ensure proper handling of performance caps in handheld environments, preventing unnecessary software pacing when hardware capabilities are sufficient.
- Adjusted documentation to reflect these changes and their impact on power efficiency and performance.
On weak single-core handhelds (RK3326/RG351MP) the main thread sits at
100% CPU, so every millisecond of jitter drops a frame: constant
stuttering while walking even at PERFORMANCE LOW with all mods off,
while average CPU usage stays unchanged. Three sources of per-frame
garbage and pause on the hot path:
- Game:step / Game:logicSpeed passed inline closures to ModRuntime.call,
allocating a fresh function 60 times per second. Hoist both to
module-level locals; behavior is identical.
- Game:update advanced the incremental collector every rendered frame.
Space it to every 4th frame: the explicit frees the comment refers
to still do the heavy lifting, collection stays incremental, and the
stepping itself stops competing with the 16.6ms frame budget.
- checkEmergencyQuit called love.joystick.getJoysticks() (a fresh
table) twice per frame to guard a 5-second hold combo. Cache the
joystick list and refresh it once per second; a 1s hotplug delay is
irrelevant against a 5s hold.
Tested on an RG351MP (RK3326, dArkOS, v0.2.41): eliminates the
constant walking stutter with all mods off, and with 11 mods
re-enabled. Average CPU is unchanged (the cost was variance, not
load), which matches the diagnosis.
- Updated PresentSync to accurately measure present() block time, ensuring that the probe does not misclassify sync status due to FrameCap sleep artifacts.
- Enhanced FixedStep to implement a hard ceiling on catch-up debt, preventing input starvation during high-speed scenarios.
- Adjusted logic in Game and Game2 to utilize the new catchupLimit function for setting maxAccum, ensuring consistent behavior across speed multipliers.
- Improved test coverage for PresentProbe and PresentSync to validate the new logic and edge cases.
the display sync stuff i added was probing whether vsync was actually working, but it was measuring the wrong thing. during the probe we software-cap at 60 for safety, and the probe was looking at the gap between frames... which includes the sleep 😅 ..... so it always looked like sync was fine even when the driver was ignoring it. on something like the ally x on windows thats a real problem. vsync says on, probe says gated, we lift the cap and snap logic to the panel hz, then youre basically uncapped. at 2–4x that turns into hitching, dropped frames, dropped input, that weird half second freeze. speed swapping wasnt desyncing the driver, it was just making the bad path hurt more.
the solution is just we time present() itself now, not the gap after it, this way the warmup sleep cant fake a pass. if sync is unclear or broken we just stay on a capped 60 and dont snap logic. Also fixed fixedstep so it snaps wall clock dt before applying speed, so in general the 2-4x speed swaps dont screw the pacing math anymore
- Introduced PresentSync module to manage display synchronization.
- Updated love.run() to handle display changes and resizing events with PresentSync.
- Enhanced FrameCap logic to accommodate PresentSync requirements.
- Modified VSync to report effective states and handle driver quirks.
- Updated options menus to reflect PresentSync availability and restrictions.
- Added tests to validate PresentSync functionality and its interaction with VSync settings.
should address issue #1910 and issue #1958
Stop joining ChipAudio on every EXIT GAME, always rebuild the Gen1 Game
module on Play-again, and treat iOS like Android for return-to-launcher so
release/mobile LOVE restarts no longer wipe Game.load or crash on quit.
Stop fanning out arbitrary field:release() during Game/Game2 reset — shared
modules use :release as a handle API, and that teardown left the Gen1
singleton unbootable (Game:load nil) on Play-again. Harden bootGame and
endGameSession to rebuild the module if load is missing.