From fc9dc2d9734603babecaadacc81bd13081054336 Mon Sep 17 00:00:00 2001 From: Roxas2712 Date: Tue, 27 Oct 2026 10:48:49 +0100 Subject: [PATCH 1/2] Render verified native FRLG level-up streaks with queue-owned timing --- LICENSE-ROXAS2712.md | 88 ++++++++++++++++++++++ src/battle/BattleState.lua | 15 ++++ src/battle/Gen3Battle.lua | 5 ++ src/battle/Gen3LevelUpStreaks.lua | 78 +++++++++++++++++++ src/core/game3/battle/level_up_streaks.lua | 34 +++++++++ src/import/Gen3LevelUpArt.lua | 49 ++++++++++++ src/import/RomExtractorGen3.lua | 18 +++++ src/import/RomImporter.lua | 4 + src/render/NativeGbaAlpha.lua | 68 +++++++++++++++++ tests/engine/native_level_up_facade.lua | 41 ++++++++++ tests/engine/native_level_up_streaks.lua | 66 ++++++++++++++++ tools/native-level-up-blend/conf.lua | 6 ++ tools/native-level-up-blend/main.lua | 75 ++++++++++++++++++ tools/native-level-up/conf.lua | 6 ++ tools/native-level-up/main.lua | 35 +++++++++ 15 files changed, 588 insertions(+) create mode 100644 LICENSE-ROXAS2712.md create mode 100644 src/battle/Gen3LevelUpStreaks.lua create mode 100644 src/core/game3/battle/level_up_streaks.lua create mode 100644 src/import/Gen3LevelUpArt.lua create mode 100644 src/render/NativeGbaAlpha.lua create mode 100644 tests/engine/native_level_up_facade.lua create mode 100644 tests/engine/native_level_up_streaks.lua create mode 100644 tools/native-level-up-blend/conf.lua create mode 100644 tools/native-level-up-blend/main.lua create mode 100644 tools/native-level-up/conf.lua create mode 100644 tools/native-level-up/main.lua diff --git a/LICENSE-ROXAS2712.md b/LICENSE-ROXAS2712.md new file mode 100644 index 000000000..fbcffd47c --- /dev/null +++ b/LICENSE-ROXAS2712.md @@ -0,0 +1,88 @@ +# Roxas2712 — Fork Contributions + +Version 1.0 · Effective 25 October 2026 + +Copyright © 2026 Roxas2712, to the extent of rights held. +All rights reserved except as expressly granted below or already granted. + +## 1. Scope and ownership + +This notice applies only to original contributions made by Roxas2712 +(GitHub: Roxas2712) to `Roxas2712/Gen2Recomped`, including original additions +within modified files, insofar as he owns the relevant rights. Git history, +pull-request diffs and source provenance identify contributions; a commit +author or a whole-file change alone does not establish ownership of every +part of that file. + +It does not claim ownership of Cedric's work, Gen1Recomp / BOIS CLUB GAMES +work, other contributors' work, third-party dependencies, or ROM-derived +content. Their applicable licences and notices remain in force. It does not +grant permission to distribute the complete combined fork where another +rights holder's permission is required. + +## 2. General permission and reserved rights + +For contributions covered by this notice and not already licensed more +broadly, you may read and study the published source and make private, +non-commercial modifications and builds for your own use. This grants no +general permission to redistribute, sublicense, sell, or offer those +contributions as a hosted service. Other uses require Roxas2712's +written permission, except for the specific permission below and rights +available independently under applicable law. + +Keep the relevant copyright, licence and attribution notices. Do not +represent Roxas2712's contributions as exclusively authored by someone +else. Public source availability alone does not put them in the public +domain or grant an open-source licence. + +## 3. Specific permission for Cedric / UNDERdecoded + +Roxas2712 expressly grants Cedric (Ced; GitHub: UNDERdecoded / +UNDERdecodedHD) a non-exclusive, worldwide, perpetual, royalty-free +permission to use, reproduce, integrate, modify and distribute the covered +contributions that Roxas2712 submits or expressly offers through pull +requests to `UNDERdecoded/Gen2Recomped`, for Cedric's Gen2Recomped / +RetroRecompiled project. This includes its renamed continuation maintained +by Cedric, its official builds, and its commercial operation. + +Cedric may license the incorporated contributions to recipients as part of +that project, subject to preserving this permission's applicable credit and +notice requirements. This permission is not a copyright assignment, is not +exclusive, and does not cover unpublished work, separate repositories, +ModCompass service operation, credentials, or content not offered in those +pull requests. Roxas2712 retains his own rights to his contributions. + +For uses relying on this permission: + +- retain the relevant source copyright and licence notices; +- give visible credit in the project's README or equivalent public project + page, and in the application's existing About / Credits view, if present; +- use at least the following credit, or a substantially equivalent one: + + > Contributions by Roxas2712 — + > https://github.com/Roxas2712 + +A maintained consolidated contributor credit is sufficient; a separate +credit on every screen or for every change is not required. Adapted +contributions must not be attributed exclusively to another author. Other +contributors' credits must also remain intact. + +## 4. Earlier licences and permissions remain effective + +This notice does not revoke, narrow, or retrospectively add conditions to +any permission already validly granted, including MIT / GPL grants, +separate agreements, or permissions arising from previously submitted +pull requests. Where an earlier grant permits a use independently of this +notice, its existing terms govern that use. For such earlier uses, the +visible credit above is requested to the extent it is not already required. + +For subsequent contributions, this is the default statement of Roxas2712's own reserved rights and the specific PR permission, subject to any +applicable existing licence obligations or separately agreed terms. It +does not change another rights holder's licence or guarantee compatibility +of a particular combination of code. + +## 5. No warranty + +To the extent permitted by applicable law, these contributions are provided +as is, without warranty. No support obligation, endorsement, or rights to +third-party code, game assets, names, or trademarks are granted. diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 8f8e1409c..8795b119e 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -2605,6 +2605,7 @@ function BattleState:beginMsgLine() end function BattleState:updateQueue() + if self._levelUpStreaks and require('src.battle.Gen3LevelUpStreaks').update(self)then return true end if self.waitingUI then if self.game.stack:top() ~= self then return true end self.waitingUI = nil @@ -2728,6 +2729,10 @@ function BattleState:updateQueue() end if not self.current then local item = table.remove(self.queue, 1) + while item and item.gbaLevelUpStreaks do + if require('src.battle.Gen3LevelUpStreaks').start(self,item)then return true end + item=table.remove(self.queue,1) + end if not item then return false end if item.fn then self.nextInsert = 0 -- sayNext inserts right after this item @@ -3895,6 +3900,7 @@ end -- any pop (finish, script teardown) must silence the alarm loop -- (end_of_battle.asm clears wLowHealthAlarm when a battle ends) function BattleState:exit() + require('src.battle.Gen3LevelUpStreaks').release(self) -- Teachy TV temporarily replaces the player's party exactly like -- SavePlayerParty/LoadPlayerParty in teachy_tv.c. finish() normally -- restores it before the pop, but exit is the safety net for a forced state @@ -8954,6 +8960,15 @@ function BattleState:awardExp(fallen) require("src.pokemon.Evolution").changeHappiness(mon, "LEVELUP") end self:sayNext(Strings("%s grew\nto level %d!", name, lv)) + if GameVersion.isGen3() and self.data.constants.gen3LevelUpStreaks then + for position=0,3 do + local actor=self:battlerAt(position) + if actor and actor.mon==mon then + require('src.battle.Gen3LevelUpStreaks').queue(self,actor) + break + end + end + end self:uiNext(function() require("src.core.Sound").play(game.data, "Level_Up") return StatBox.new(game, mon) diff --git a/src/battle/Gen3Battle.lua b/src/battle/Gen3Battle.lua index dcaa62f71..fa6ddd971 100644 --- a/src/battle/Gen3Battle.lua +++ b/src/battle/Gen3Battle.lua @@ -3104,6 +3104,11 @@ function Gen3Battle.draw(battle) -- over them. The methods dispatch back here (BattleState:drawHUDs and -- friends), so the implementation is still this file's; what changed is -- that there is now a door in front of it. + shaken(function() + local layer=package.loaded['src.core.game3.battle.level_up_streaks'] + local draw=function()return require('src.battle.Gen3LevelUpStreaks').draw(battle)end + if layer then layer._nativeDraw(battle,draw)else draw()end + end) shaken(function() battle:drawHUDs(slide) end) -- the ball rides with the pics rather than with the windows: it is thrown -- across the field, and a screen shake moves the field diff --git a/src/battle/Gen3LevelUpStreaks.lua b/src/battle/Gen3LevelUpStreaks.lua new file mode 100644 index 000000000..68f940b97 --- /dev/null +++ b/src/battle/Gen3LevelUpStreaks.lua @@ -0,0 +1,78 @@ +-- Copyright (c) 2026 Roxas2712. See LICENSE-ROXAS2712.md. +-- Pure samples of the native 18-sprite sequence, driven by BattleState.frame. +-- No RNG, timer, GPU object, battle or save is retained here. +-- Interface/timing facts: pinned pret/pokefirered pokemon_special_anim_scene.c +-- and random.h (ce6406d8). Integer low bits avoid floating LCG overflow. +local M={} +local finish=0 +for index=1,18 do + local speed=(45*index+51)%64+32 + finish=math.max(finish,2*(index-1)+math.floor(1024/speed)+1) +end +function M.busy(frame)return frame