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/ui/OptionsMenu.lua b/src/ui/OptionsMenu.lua index d6d1f500d..7daa525de 100644 --- a/src/ui/OptionsMenu.lua +++ b/src/ui/OptionsMenu.lua @@ -311,7 +311,10 @@ local function buildRows(game) -- DEFAULT is index 0 and the list is 1..#SHINY_ODDS, so the wrap is -- over #SHINY_ODDS + 1 values and lands back on DEFAULT past the end local i = (shinyOddsIndex(g) + (dir or 1)) % (#SHINY_ODDS + 1) - o.shinyOdds = i == 0 and nil or SHINY_ODDS[i] + -- Keep DEFAULT explicit in options.lua. Removing this key from the + -- live table leaves the previous stored rate in GenOptions.fold, + -- so reloading could turn the extra shiny roll back on. + o.shinyOdds = i == 0 and 0 or SHINY_ODDS[i] return true end }, { id = "musicVol", label = Strings("MUSIC VOL"), diff --git a/tests/engine/option_default_persistence.lua b/tests/engine/option_default_persistence.lua new file mode 100644 index 000000000..7f8fff5b9 --- /dev/null +++ b/tests/engine/option_default_persistence.lua @@ -0,0 +1,64 @@ +-- Copyright (c) 2026 Roxas2712. See LICENSE-ROXAS2712.md. +-- Exercise the real row, input routing, generation fold and options file. +package.path='./?.lua;./?/init.lua;'..package.path +local T=require('tests.modkit') +local Version=require('src.core.GameVersion') +local Menu=require('src.ui.OptionsMenu') +local Stack=require('src.core.StateStack') +local Save=require('src.core.SaveData') +local Pokemon=require('src.pokemon.Pokemon') +local original=Version.get() +local function press(game,key) + game.input.pressed={[key]=true};game.stack:top():update(1/60);game.input.pressed={} +end +local function selectOdds(game,root) + for _=1,#root.rows+2 do + local row=root.rows[root.index] + if row and row.id=='shinyOdds'then return root end + press(game,'down') + end + error('native SHINY ODDS row is unreachable') +end +for _,edition in ipairs({'red','gold','silver','crystal','emerald'})do + Version.set(edition) + local generation=Version.generation() + for _,overridden in ipairs({false,true})do + local fs=T.sdk.memfs({}) + local stored=Save.defaultOptions() + stored.shinyOdds=overridden and 4096 or 1 + if overridden then stored.perGeneration={[generation]={shinyOdds=1}}end + T.check(type(Save.saveOptions(stored,fs))=='table','seed previous ALWAYS selection') + local g={data=T.fixtures.fresh(),save={options=Save.optionsFor(edition,fs)},input={pressed={}},writes=0} + g.input.wasPressed=function(self,key)return self.pressed[key]==true end + g.stack=setmetatable({}, {__index=Stack});g.stack:init() + g.writeOptions=function(self) + self.writes=self.writes+1 + T.check(type(Save.saveOptions(self.save.options,fs,generation))=='table','native options write succeeds') + end + local root=Menu.new(g);g.stack:push(root) + local page=selectOdds(g,root) + T.eq(page.rows[page.index].value(g),'ALWAYS','actual row starts on stored ALWAYS') + press(g,'right') + T.eq(page.rows[page.index].value(g),'DEFAULT','normal input selects cartridge default') + T.eq(Pokemon.shinyOddsOf(g.save),nil,'default disables extra shiny roll immediately') + local loaded={options=Save.optionsFor(edition,fs)} + T.eq(Pokemon.shinyOddsOf(loaded),nil,'default remains disabled after loading options file') + T.eq(page.rows[page.index].value({save=loaded}),'DEFAULT','fresh menu cannot resurrect ALWAYS') + if overridden then + local persisted=Save.loadOptions(fs) + T.eq(persisted.shinyOdds,4096,'generation reset preserves shared extra-roll setting') + local other=generation==1 and 'gold' or 'red' + T.eq(Pokemon.shinyOddsOf({options=Save.optionsFor(other,fs)}),4096,'other generation retains its selected rate') + end + -- Start another menu from reloaded bytes: Left reaches ALWAYS and A wraps + -- back to the default through the same native input owner. + g.save=loaded;g.stack:init();root=Menu.new(g);g.stack:push(root) + page=selectOdds(g,root);press(g,'left') + T.eq(Pokemon.shinyOddsOf({options=Save.optionsFor(edition,fs)}),1,'left from persisted DEFAULT reaches ALWAYS') + press(g,'a') + T.eq(Pokemon.shinyOddsOf({options=Save.optionsFor(edition,fs)}),nil,'A also persists the return to DEFAULT') + T.eq(g.writes,3,'each accepted selection writes once') + end +end +Version.set(original) +T.finish('native default option persistence through shared and generation settings')