Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions LICENSE-ROXAS2712.md
Original file line number Diff line number Diff line change
@@ -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, third-party engine 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.
56 changes: 56 additions & 0 deletions docs/native-player-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Native player phase compatibility

The compatibility player module operates on the live native GBA actor. It does
not create a second avatar or copy the source movement/event simulation.

Importing it alone leaves ordinary native input unchanged. A wrapper of its
`update(game,input)` entry point is dispatched from the real overworld player
phase. The base update accepts an input proxy, preserving the native field
handler's interaction, movement, collision, bike and movement-hook ownership.
An idle `tick` is deferred until a wrapper has had the opportunity to submit
its `forcedStep(direction,1)` arrival. There is at most one native player clock
advance in that phase; the shared native `finishPlayerStep` runs synchronously
before the wrapper resumes. Repeated ticks cannot duplicate arrivals. Scripted
NPC/player ordering, including a script queue draining on its landing frame,
continues through the original native path.

Position, movement and elevation fields address the current native actor;
fractional body coordinates are preserved. Bike state addresses the native
in-memory save. Running eligibility asks the existing native rules without
requiring the B button to be held. Position synchronization changes only the
in-memory native player record and never calls persistence. Input proxies are
restored even when a handler raises. Menu/script/transition ownership prevents
additional input, and actual loader teardown unwinds wrappers and retires phase
observations without bootstrapping compatibility modules on native-only boots.

## Verification

`luajit tools/player-runtime/check.lua` runs 261 ROM-free checks across all five
GBA editions and checks the three native GBC editions keep their original phase.
It uses the real native player clock, input handler and arrival bookkeeping on
small synthetic fields, with authored event handlers replaced by counting
callbacks. Coverage includes fractional writes, forced arrivals, repeat ticks,
running gates, surf bob, script queue drain ordering, input exceptions, world
replacement and real loader teardown. It is also registered in the engine tier.

The composed fork passes 330/330 engine suites, SDK 3/3 and Dramatic Shape B30.
A read-only SDK probe of the full unchanged 1.0.9 source package now reaches
the missing `src.core.game3.collision` interface on all five editions. A previous
reduced source fixture lacks vendor metadata after this point and is excluded
from that result. These are loader/CPU checks, not GPU or gameplay acceptance.

## Remaining contracts

This is the player phase foundation, not complete mod compatibility. The
distinct Game3 session facade, collision/field services, full scripted avatar
operations and pose/atlas resources still need integration. In particular, do
not claim all surf/dive/ledge/bike transitions or unchanged rendering have been
accepted from these synthetic checks. Real maps, actual events and visuals
must be verified after the remaining modules allow the package to initialize.

The standalone upstream branch runs 108 checks on its two registered GBA
editions; LeafGreen, Ruby and Sapphire are explicitly skipped there until
cartridge PRs #183, #186 and #187 are integrated. All five run in the composed
fork. On pristine upstream main `13af468b`, the engine tier has 10 existing
failures (64/74 suites). The player branch has the same 10 failures (65/75),
with no newly failing suite. The composed fork includes the existing fixes.
26 changes: 26 additions & 0 deletions docs/native-session-world-admission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Persistent native world admission

Native session observations resolve an explicit entry-world scope first,
then a world stored in the physical state list, then that stack's admitted
`worldState`. A persistent world is independent of the overlay list; an empty
list or a covering menu does not remove its map/player observations.

Input still belongs to the native stack's active state. An uncovered
persistent world accepts input; a covering menu keeps it, including when
that menu is hidden only for rendering. Dataset/map identity checks remain
required. A modern empty stack cannot revive an unadmitted `game.overworld`
pointer. Legacy tool hosts without a physical state list keep their explicit
world fallback.

The initial five-edition regression reproduced 50 failed assertions. The
expanded suite passes 150 checks. It uses the actual native StateStack and player
with synthetic map data, including native field-move entry/retirement over a
persistent world, covering UI, stale datasets and failed entry scopes. This
does not establish original mod initialization, full rendering or gameplay.

The manual `tools/session-runtime/persistent-world-check.lua` requires the
native persistent-world StateStack protocol, the session fixture and native
field-move presentation. The current upstream API-2 prerequisite composition
does not yet provide that StateStack protocol. Its ordinary physical-stack
path is qualified separately; the 150 persistent-world checks above refer
to the composed native fork, not that older host.
72 changes: 72 additions & 0 deletions src/compat/gen1/NativeSession.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
-- Resolve the live owner without bootstrapping Game or keeping a retired world.
local Version = require('src.core.GameVersion')
local M = {}
local entering=setmetatable({},{__mode='k'})
local unpack=table.unpack or unpack
local function pack(...)return {n=select('#',...),...}end
function M.withWorld(game,world,fn,...)
local owner,why=M.game(game);assert(owner,why)
assert(type(world)=='table'and world.isOverworld and world.map and world.player
and owner.data and owner.data.maps and owner.data.maps[world.map.id]==world.map.def,'foreign native world scope')
local previous=entering[owner];entering[owner]=world
local result=pack(pcall(fn,...));entering[owner]=previous
if not result[1]then error(result[2],0)end
return unpack(result,2,result.n)
end
function M.game(game)
local explicit = game ~= nil
local facade = package.loaded['src.core.Game3']
if type(facade) == 'table' and type(facade.native) == 'function' then
game = facade.native(game)
if explicit and game == nil then return nil, 'native Game3 facade retired' end
end
game = game or package.loaded['src.core.Game']
local info = Version.info(Version.get())
if type(game) ~= 'table' or not info or info.generation ~= 3 then
return nil, 'no native GBA session'
end
return game
end
function M.data(game)
local owner,why=M.game(game);if not owner then return nil,why end
local data=owner.data
if type(data)~='table'or type(data.tilesets)~='table'or type(data.map_tilesets)~='table'
or type(data.constants)~='table'or type(data.constants.gen3Layout)~='table'then
return nil,'native GBA tileset data unavailable'
end
return owner,data
end
function M.resolve(game)
local why;game,why=M.game(game);if not game then return nil,why end
local states = game.stack and game.stack.states
local world=entering[game]
if not world and type(states) == 'table' then
for i = #states, 1, -1 do
if states[i].isOverworld then world = states[i]; break end
end
-- The physical list can contain overlays only. Its persistent world is
-- still a real native admission, independently of the convenience pointer.
if not world then world=game.stack.worldState end
elseif not world then
world = game.overworld
end
if not (world and world.isOverworld and world.player and world.map) then
return nil, 'no active native player'
end
if not (game.data and game.data.maps and world.map.def
and game.data.maps[world.map.id] == world.map.def) then
return nil, 'native world belongs to a retired dataset'
end
return game, world, world.player
end
function M.acceptsInput(game, world, player)
local owner, current, actor = M.resolve(game)
if owner ~= game or current ~= world or actor ~= player then return false end
local states = game.stack and game.stack.states
if type(states)=='table' then
local active=type(game.stack.active)=='function' and game.stack:active() or states[#states]
if active~=world then return false end
end
return not world.battleActive and not (world.busy and world:busy())
end
return M
65 changes: 65 additions & 0 deletions src/compat/gen1/PlayerRuntime.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-- One compatibility call scope inside the native overworld player phase.
-- An idle tick is deferred: free-body wrappers may then submit their one
-- cell arrival without advancing animation, bobbing or events a second time.
local Session = require('src.compat.gen1.NativeSession')
local M = {}
function M.new()
local active
local records = setmetatable({},{__mode='k'})
local runtime = {}
function runtime:scope() return active end
function runtime:record(player)
local record=records[player]
if not record then record={stepSerial=0};records[player]=record end
return record
end
function runtime:tick(game)
local c = active
if not c or (game and game ~= c.game and game ~= c.facade) then
return nil, 'player ticks belong to the native fixed-step phase'
end
if c.advanced then return false end
local _, world, player = Session.resolve(c.game)
if world ~= c.world or player ~= c.player then return false end
if not c.finishing and not player.moving then return false end
c.advanced = true
local x,y=player.cellX,player.cellY
if not player.moving and player.compatWalkInPlace and not (player.bumpFrames and player.bumpFrames>0) then
player.animClock=(player.animClock or 0)+1
end
local stepped = player:update()
if stepped then
local record=self:record(player)
record.prevCellX,record.prevCellY=x,y;record.stepSerial=record.stepSerial+1
end
c.world:finishPlayerStep(stepped, false)
return stepped
end
function runtime:input(input)
local c = active
if not c or c.inputPolled or not Session.acceptsInput(c.game,c.world,c.player) then return end
c.inputPolled = true
local previous = c.game.input
c.game.input = input or previous
local ok, result = xpcall(function() return c.world:handleInput() end, debug.traceback)
c.game.input = previous
if not ok then error(result,0) end
return result
end
function runtime:run(game, world, facade, input, update)
assert(not active, 'nested native player phase')
local c = {game=game,world=world,player=world.player,facade=facade}
active = c
local ok, why = xpcall(function()
update(facade,input)
if active ~= c then return end -- teardown during a callback retires the scope
c.finishing = true
self:tick(game)
end, debug.traceback)
active = nil
if not ok then error(why,0) end
end
function runtime:release() active = nil;records=setmetatable({},{__mode='k'})end
return runtime
end
return M
Loading