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/core/Game.lua b/src/core/Game.lua index fc5db6c50..09f294f69 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -247,15 +247,26 @@ function Game:makeTitleState() self:adoptSave(self.save) ModRuntime.emit("save.created", { save = self.save }) self:applyOptions(self.save.options) - self.stack:push(OverworldState, self.save.player.map, - self.save.player.x, self.save.player.y, - self.save.player.facing) + local newSave, worldStarted = self.save, false + local function startWorld() + if worldStarted or self.save ~= newSave then return end + worldStarted = true + self.stack:push(OverworldState, newSave.player.map, + newSave.player.x, newSave.player.y, + newSave.player.facing) + end local newGameScreen = bootScreens(self).newGame - if newGameScreen == nil and not require("src.core.GameVersion").isGen2() then + local gen2 = require("src.core.GameVersion").isGen2() + if newGameScreen == nil and not gen2 then newGameScreen = "OakSpeech" end + -- GSC creates its world after clock/name selection. Publishing the + -- home map earlier can make a preparation cover wait for that map's + -- first draw while it still owns input over the introduction. + local deferWorld = gen2 and newGameScreen + if not deferWorld then startWorld() end if newGameScreen then - Screens.push(self, newGameScreen, function() end) + Screens.push(self, newGameScreen, deferWorld and startWorld or function() end) else -- PRISM HAS NO OAK SPEECH, and that is why its character customisation -- never appeared. Data.lua sets boot.screens.newGame = false for any diff --git a/tests/engine/gen2_new_game_lifecycle.lua b/tests/engine/gen2_new_game_lifecycle.lua new file mode 100644 index 000000000..316a8822b --- /dev/null +++ b/tests/engine/gen2_new_game_lifecycle.lua @@ -0,0 +1,49 @@ +-- Copyright (c) 2026 Roxas2712. See LICENSE-ROXAS2712.md. +package.path='./?.lua;./?/init.lua;'..package.path +local T=require('tests.modkit') +local Game=require('src.core.Game') +local Version=require('src.core.GameVersion') +local Screens=require('src.ui.Screens') +local World=require('src.world.OverworldController') +local Save=require('src.core.SaveData') +local oldVersion,oldPush,oldNew,oldGet=Version.get(),Screens.push,Save.newGame,Screens.get +Screens.get=function()return{new=function(_,callbacks)return callbacks end}end +local function newGame(g)return Game.makeTitleState(g).onNewGame()end +local function session(screen) + local g={save={player={name='OLD'}},data={},draws=0} + g.bootConfig=function()return {screens={newGame=screen}}end + g.data.field={boot={screens={newGame=screen}}} + g.adoptSave=function(self,s)T.eq(self.save,s,'save adopted before intro')end + g.applyOptions=function()end + g.stack={top=function()end,clearWorld=function()g.world=nil end, + push=function(_,state,map,x,y,facing) + T.eq(state,World,'native world owner created') + g.draws=g.draws+1;g.world={map={id=map},x=x,y=y,facing=facing} + end} + return g +end +Save.newGame=function()return {player={map='HOME',x=3,y=3,facing='down',gender='boy'},options={}}end +local intro +Screens.push=function(g,id,done)intro={game=g,id=id,done=done};return intro end +for _,edition in ipairs({'gold','silver','crystal'})do + Version.set(edition) + local g=session('OakSpeech');newGame(g) + T.eq(g.world,nil,edition..' has no map while clock/name screen owns input') + T.eq(g.draws,0,edition..' destination preparation cannot see a premature world') + T.eq(intro.id,'OakSpeech',edition..' keeps configured real introduction') + -- Answers belong to the adopted save; world creation reads their final values. + g.save.player.gender='girl';g.save.player.x=4 + intro.done();T.eq(g.draws,1,edition..' completion starts actual world once') + T.eq(g.world.x,4,edition..' final introduction position reaches world') + intro.done();T.eq(g.draws,1,edition..' duplicate completion is harmless') + g=session('OakSpeech');newGame(g);local stale=intro.done + newGame(g);stale();T.eq(g.world,nil,edition..' retired save cannot start a world') + intro.done();T.eq(g.draws,1,edition..' replacement introduction owns destination') + g=session(false);newGame(g) + T.eq(g.draws,1,edition..' no-intro datasets retain immediate world creation') +end +Version.set('red');local g=session('OakSpeech');newGame(g) +T.eq(g.draws,1,'Gen1 retains world beneath its introduction') +intro.done();T.eq(g.draws,1,'Gen1 callback does not create another world') +Screens.push,Save.newGame,Screens.get=oldPush,oldNew,oldGet;Version.set(oldVersion) +T.finish('native Gen2 new-game lifecycle')