Skip to content
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.
46 changes: 46 additions & 0 deletions docs/native-battle-transition-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Native transition observations and source phases

`src.core.game3.battle_transition` observes the actual `BattleTransition` on
the native StateStack. Entry, tick, draw and finish wrappers are dispatched at
that state's native phases. Calling the source tick outside the update scope
does not advance it. Duplicate base calls reuse a phase result; completion
retires one exact admission and invokes its native continuation at most once.
Covered, finished and retired-dataset admissions cannot pop another screen.
Aborting does not invoke the battle callback. Failed presentation-end notices
are reported without discarding the native continuation.

The source sequence identity, frame and phase are projections of that state.
There is no copied task scheduler, simulation, palette timer or audio loop.
Registered native style selection and foreground draws remain authoritative.
The enum/selection catalogue distinguishes FRLG, Emerald and RS, including
RS's shorter enum, class ordinals, class-based exceptions and level equality.
The inherited Emerald class profile is corrected during RS import. An RS-only
cache revision transparently rebuilds old imports. Runtime profile reads stay
authoritative, including deliberately authored Emerald-shaped profiles.
See https://github.com/pret/pokeruby/blob/master/include/constants/trainers.h.

For existing geometric native styles, an owned copy of the current world
override passes through `drawWorld` before the renderer blits it. Only the
transition's own wipe/veil packets are consumed. Borrowed world images are
never mutated or released. Explicit legacy orientation is normalized once;
native orientation stays upright. Scratch targets are reused until their
view changes, then released on exit/teardown. Foreground `def.draw` styles
keep their native UI pass and are not invoked a second time as world effects.

Verification uses the actual native stack and state, an independent original
selection oracle, and Metal/OpenGL pixel comparisons against the previous
native renderer. It includes explicit orientation negatives, callback replay,
covered UI, retired saves, native peer draws and error/teardown checks.

## Open contracts

This is not original task/visual parity or complete renderer/game acceptance.
FRLG spiral and the additional Emerald/Frontier effect tasks are not yet
implemented in the native renderer. Native mugshots still need their complete
portrait/task state. `defs()` reports owned native style descriptors, not the
original task-function tables; `_fx` task internals are not fabricated.
`blackSpans`/`screenCoverage` explicitly report unavailable native mask
observations. Full original palette/audio tasks, foreground/UI-owned world
effect composition, arbitrary source effect authoring, all custom renderer
paths, five-edition battle/gameplay and full mod-coexistence acceptance remain
required. Package loading past this module alone is not a gameplay result.
95 changes: 95 additions & 0 deletions src/compat/gen1/TransitionCatalog.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
-- Cartridge enum facts; visual execution remains on native registered styles.
local M={}
local base={'BLUR','SWIRL','SHUFFLE','BIG_POKEBALL','POKEBALLS_TRAIL','CLOCKWISE_WIPE','RIPPLE','WAVE',
'SLICE','WHITE_BARS_FADE','GRID_SQUARES','ANGLED_WIPES'}
local tails={frlg={'LORELEI','BRUNO','AGATHA','LANCE','BLUE','SPIRAL'},
rse={'SIDNEY','PHOEBE','GLACIA','DRAKE','CHAMPION','AQUA','MAGMA','REGICE','REGISTEEL','REGIROCK',
'KYOGRE','GROUDON','RAYQUAZA','SHRED_SPLIT','BLACKHOLE','BLACKHOLE_PULSATE','RECTANGULAR_SPIRAL',
'FRONTIER_LOGO_WIGGLE','FRONTIER_LOGO_WAVE','FRONTIER_SQUARES','FRONTIER_SQUARES_SCROLL',
'FRONTIER_SQUARES_SPIRAL','FRONTIER_CIRCLES_MEET','FRONTIER_CIRCLES_CROSS','FRONTIER_CIRCLES_ASYMMETRIC_SPIRAL',
'FRONTIER_CIRCLES_SYMMETRIC_SPIRAL','FRONTIER_CIRCLES_MEET_IN_SEQ','FRONTIER_CIRCLES_CROSS_IN_SEQ',
'FRONTIER_CIRCLES_ASYMMETRIC_SPIRAL_IN_SEQ','FRONTIER_CIRCLES_SYMMETRIC_SPIRAL_IN_SEQ'}}
local commonStyles={'g3_blur','g3_swirl','g3_shuffle','g3_bigPokeball','g3_pokeballsTrail','g3_clockwiseBlackfade',
'g3_ripple','g3_wave','g3_slice','g3_whitefade','g3_grid','g3_angled'}
function M.family(edition)
if edition=='firered'or edition=='leafgreen'then return 'frlg'end
if edition=='emerald'or edition=='ruby'or edition=='sapphire'then return 'rse'end
end
function M.new(edition)
local family=assert(M.family(edition),'no native GBA transition catalogue')
local rs=edition=='ruby'or edition=='sapphire'
local c={ID={},names={},styles={},MUGSHOT_BY_ID={},TERRAIN={NORMAL=0,CAVE=1,FLASH=2,WATER=3},family=family}
for i,name in ipairs(base)do c.ID[name]=i-1;c.names[i-1]=name;c.styles[i-1]=commonStyles[i]end
for i,name in ipairs(tails[family])do
if not rs or i<=5 then c.ID[name]=11+i;c.names[11+i]=name end
end
if family=='frlg'then
c.styles[1],c.styles[4],c.styles[5],c.styles[8],c.styles[9]='frlg_swirl','frlg_balltrail','frlg_clockwise','frlg_slice','frlg_whitebars'
end
local names=family=='frlg'and{'lorelei','bruno','agatha','lance','blue'}or{'sidney','phoebe','glacia','drake','champion'}
for i,name in ipairs(names)do c.MUGSHOT_BY_ID[11+i]=name;c.styles[11+i]='g3_mugshot'end
if family=='rse'and not rs then
for i,name in ipairs({'aqua','magma','regice','registeel','regirock','kyogre','groudon','rayquaza'})do c.styles[16+i]='g3_'..name end
end
if rs then
for alias,name in pairs({CLOCKWISE_BLACKFADE='CLOCKWISE_WIPE',WHITEFADE='WHITE_BARS_FADE',SHARDS='ANGLED_WIPES',SYDNEY='SIDNEY',STEVEN='CHAMPION'})do c.ID[alias]=c.ID[name]end
end
c.TUNE={introFades=family=='frlg'and 2 or 3,blurDelay=family=='frlg'and 2 or 4,
wipeStepX=family=='frlg'and 32 or 16,wipeStepY=family=='frlg'and 16 or 8,
rippleFadeAt=family=='frlg'and 41 or 81,rippleFadeDelay=family=='frlg'and -8 or -2}
return c
end
function M.terrain(edition,opts)
opts=opts or{}
if opts.flash or(tonumber(opts.flashLevel)or 0)>0 then return 2 end
if M.family(edition)=='frlg'then
if opts.surfing or opts.isWater or opts.mapKind=='water'or opts.mapType==4 or opts.mapType==5 then return 3 end
if opts.isCave or opts.mapKind=='cave'or opts.mapKind=='dungeon'or opts.mapType==3 then return 1 end
else
if opts._surfable then return 3 end
if tonumber(opts.mapType)==4 then return 1 end
if tonumber(opts.mapType)==5 then return 3 end
end
return 0
end
local wild={{8,9},{5,10},{0,10},{7,6}}
local trainer={{4,11},{2,3},{0,10},{1,6}}
function M.pick(edition,opts,isTrainer)
opts=opts or{}
local frlg=M.family(edition)=='frlg';local cls=tonumber(opts.trainerClass);local id=tonumber(opts.trainerId)
if isTrainer then
if frlg then
if opts.trainerTower or opts.eReader then cls=nil end
if cls==87 then
for i,name in ipairs({'Lorelei','Bruno','Agatha','Lance'})do if opts['is'..name]then return 11+i end end
return ({[410]=12,[411]=13,[412]=14,[413]=15,[735]=12,[736]=13,[737]=14,[738]=15})[id]or 16
end
if cls==90 or opts.isRival or opts.isChampion then return 16 end
for i,name in ipairs({'Lorelei','Bruno','Agatha','Lance','Blue'})do if opts['is'..name]then return 11+i end end
else
local rs=edition=='ruby'or edition=='sapphire'
if id==1024 or cls==(rs and 32 or 38)then return 16 end
if cls==(rs and 24 or 31)then return ({[261]=12,[262]=13,[263]=14,[264]=15})[id]or 16 end
if edition=='emerald'then
if cls==9 or cls==53 or cls==49 then return 18 end
if cls==3 or cls==13 or cls==11 then return 17 end
end
end
elseif opts.pyramid and not frlg then
return (tonumber(opts.enemyLevel)or 3)<(tonumber(opts.playerLevel)or 5)and 0 or 10
end
local terrain=opts.terrain or M.terrain(edition,opts)
local row=(isTrainer and trainer or wild)[terrain+1]or(isTrainer and trainer or wild)[1]
return row[(tonumber(opts.enemyLevel)or 3)<(tonumber(opts.playerLevel)or 5)and 1 or 2]
end
function M.special(edition,group,opts)
opts=opts or{}
if group=='trainer_hill'or group=='secret_base'or group=='e_reader'or group=='battle_tower'then
return(tonumber(opts.enemyLevel)or 3)<(tonumber(opts.playerLevel)or 5)and 4 or 3
end
assert(edition=='emerald','transition group is unavailable for this edition')
local list=group=='pyramid'and{31,32,33}or group=='dome'and{29,31,32,33}or{29,30,31,32,34,35,36,37,38,39,40,41}
local random=opts.random or math.random
return list[(random(#list*65536)%#list)+1]
end
return M
Loading