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.
47 changes: 47 additions & 0 deletions docs/frame-pacing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# P1: VSync and the software deadline

The run loop records whether it actually presented a frame. After presentation,
`FrameCap.pace` skips the software wait when VSync is active and the FPS cap is
at least the reported display refresh rate. Without presentation, with VSync
off, with an unknown rate, or with a lower FPS cap, the existing carried
deadline, one-millisecond sleep chunks and stall re-anchor remain active.

The default-on rollback switch is independent of all other performance work:

```lua
require("src.core.FrameCap").vsyncPacing = false
```

Display flags are sampled at most once per second to avoid allocating their
table each frame. Provider/VSync changes invalidate that sample; other display
rate changes are observed on the next periodic sample. Existing option values,
simulation timing, mod interfaces and callback call sites are unchanged.

`frame pacing: sleep` in the existing F11 FrameProfile measures the helper,
including its software wait. `present (GPU wait / vsync)` retains its original
scope. Removing a redundant software wait can move wall time into present
without changing FPS.

The owned-window probe runs the real helper and FrameProfile with the switch
off/on, each after 120 warm-up frames and over 120 measured frames. Run through
the guarded launcher:

```sh
python3 tools/check_frame_pacing.py --love /path/to/love --report /path/to/owned/qa/report.json
```

An older public checkout can use `--qa-tools /path/to/fork/tools`. The launcher
pins inputs, uses a separate QA identity, retains raw profile logs and records
SHA-256 hashes of the actual measured source files alongside the Git parent.

Observed macOS/arm64, 60-Hz display, VSync 1, cap 60:

| Backend | Software wait off/on, ms/frame | Present off/on, ms/frame | CPU off/on, ms/frame |
| --- | ---: | ---: | ---: |
| LÖVE 11.5 OpenGL | 0.00 / 0.01 | 16.59 / 16.54 | 0.672 / 0.790 |
| LÖVE 12 Metal | 16.43 / 0.01 | 0.13 / 16.56 | 0.769 / 0.629 |

Both enabled runs made zero extra sleep calls and stayed near 60 FPS, with no
measured interval exceeding 1.5 display periods. These short blank-window
checks establish the pacing decision and its profiler scope. They do not
establish a universal CPU/FPS gain, loaded-world behavior, or handheld results.
34 changes: 34 additions & 0 deletions docs/frame-profile-sections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Frame-profile sections for world, presentation and DS audio

F11 enables the existing `FrameProfile` report. These additional sections use
its shared no-op closer when profiling is disabled; there is no new setting.
All labels begin with spaces, so nested timings do not get counted again as
top-level frame time. Multiple spans with the same label accumulate.

| Label | Work measured |
| --- | --- |
| `world: tiles` | Initial map/neighbour draws, declined-pipeline fallback and the flat upper layer |
| `world: entities` | Flat ghosts, main and above-layer entities, or tilted billboards |
| `world: FX` | Flat/tilted effect groups, pipeline `ctx.drawFx` and final map overlays |
| `tiles: ensureWindow` | Window validation/refill, including cached and missing-art returns |
| `renderer: zone blit` | Actual `blitCanvas` calls, including the unshaded fallback |
| `renderer: present pipeline` | Present processing and final native output |
| `voxel: sun pass` | Shadow preparation, including cache-hit/rate-limit decisions |
| `voxel: terrain` | Main and neighbouring terrain submission, per eye |
| `voxel: water` | Water draw-list construction, planar reflection and water submission, per eye |
| `voxel: cards` | Main character and staged-battle card submission, per eye |
| `audio: Nitro render` | One DS mixer block, including SoundData creation |

These are wall-clock submission timings, not GPU execution times. Reflection
copies of cards contribute to the water section; the cards section measures
the visible scene's card pass. The world labels describe selected drawing
phases rather than an exhaustive partition of `drawWorld`; setup, sorting and
other work remain visible in the enclosing section. Voxel timings run inside
the selected world pipeline. Hook names, arguments and dispatch order are
unchanged. An exception still takes its existing cleanup/error path; an
unfinished span is discarded by the existing profiler contract.

A1 adds observability and makes no performance claim. Desktop before/after
frame timings and ARM/device timings: **not measured**. Capture the same scene,
viewport, camera, mod configuration and profile window when measuring later
optimizations. Profiling itself is disabled by F11 / `FrameProfile.setEnabled(false)`.
51 changes: 51 additions & 0 deletions docs/gc-frame-budget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# P2: incremental GC in unused deadline time

`Performance.gcBudget` defaults to `true`. At boot the collector uses pause 120
and step multiplier 300. Each concrete `Performance.CAPS` row additionally has
`gcPause`, `gcStepMul` and `gcBudgetSeconds`; tiers may reduce the default
0.0005-second idle allowance. No options are rewritten.

The ordinary engine's fixed `step(1)` moves out of `Game:update`. The run loop
uses no-argument incremental steps before its remaining software wait. It starts
no further step after the earlier of the actual deadline and the half-millisecond
allowance, and stops on completion of a cycle. An expired deadline or a stopped
collector receives no work. VSync never gets an invented future budget; only
the actual remainder of its carried deadline is eligible, and no extra sleep
is added after VSync.

The budget is cooperative: a single indivisible collector step can finish past
the boundary. It is not a preemption guarantee for an arbitrary heap/finalizer.
The F11 child section `frame pacing: gc budget` is nested in the existing
`frame pacing: sleep` section, so the parent includes collection plus waiting.

The fork's pre-existing Gen3 `Game3.stepGC` delegation stays at its original
update boundary with the original arguments/behavior. A frame owned by that
public seam gets no second idle GC service. Its delegated/custom work remains
outside the engine idle allowance. This preserves consumers that replace it.

The independent rollback switch is:

```lua
require("src.core.Performance").gcBudget = false
```

The next frame restores the previous pause/multiplier and uses the old native
update step. The separate P1 switch and callback interfaces are unchanged.

`tools/check_frame_pacing.py --gc --love /path/to/love --report /owned/qa/gc.json`
runs the guarded native comparison. P1 stays enabled for both phases; only P2
changes. The workload allocates 512 short tables per frame, with 120 warm-up
and 120 measured frames per phase. Raw FrameProfile logs, original tuning,
heap samples and measured-source SHA-256 hashes are retained.

Observed composed fork, macOS/arm64, 60 Hz, VSync 1, cap 60:

| Backend | Original fixed step, ms/frame | New idle mean / max, ms | Heap peak before / after, KiB |
| --- | ---: | ---: | ---: |
| LÖVE 11.5 OpenGL | 0.00 (rounded) | 0.098 / 0.499 | 918 / 775 |
| LÖVE 12 Metal | 0.00 (rounded) | 0.120 / 0.278 | 1,179 / 692 |

Short-workload CPU means were 1.012 → 0.920 ms/frame and 0.717 → 0.651 ms/frame,
respectively. Both retained approximately 60 FPS and restored the original
200/200 tuning when disabled. These are bounded synthetic allocation checks,
not full-game heap, long-session, arbitrary-finalizer or handheld measurements.
51 changes: 51 additions & 0 deletions docs/hot-path-dispatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# P3: native update/draw dependency dispatch

`Performance.hotPathHoist` defaults to `true`. Set it to `false` to restore
per-call require/pcall dispatch, the per-frame blit closure and fresh native
presentation records independently of P1/P2.

`HotModules` lazily retains successful module values. Live fields and explicit
replacement or clearing of `package.loaded` remain observable; failed optional
loads are not cached. The Discord update uses its existing protected call with
the original `dt`, without allocating a wrapper closure after initialization.
No module path, hook argument or hook ordering changes.

The renderer's blit adapter carries DPI as explicit arguments and still calls
the public `blitCanvas` method. Its existing `uiPresentation` field becomes an
engine-owned live view. A table installed there by a consumer is never mutated
by the reuse path.

Only the unwrapped native renderer may borrow the private viewport scratch.
Public `endFrame` calls always return detached records, and replacing that
method bypasses the private shortcut. HUD hook arguments are detached at the
moment of dispatch, including a hook registered during compose/output. The
fork's window UI also retains its public record path. Scratch is never included
in a hook record.

Run `tools/check_frame_pacing.py --hot-path --love /path/to/love --report
/owned/qa/hot.json` through the QA storage guard. The owned synthetic state draws
a rectangle through real `Game:draw`, canvases and composition. Each switch
phase has 120 warm-up and 120 measured frames at 60 Hz, VSync 1, P1 enabled.
The log contains the actual F11 `draw: whole frame` section; raw JSON also records
total draw wall time, process CPU, source fingerprints and an untimed require
counter. This is not a complete overworld, battle or mod scene. Profiling is on
in both measured phases.

Measured on macOS arm64 (milliseconds/frame, switch off → on):

| Tree / backend | F11 draw (rounded) | Raw draw mean | Process CPU | require calls |
| --- | ---: | ---: | ---: | ---: |
| Fork / LÖVE 11.5 OpenGL | 0.16 → 0.21 | 0.166 → 0.218 | 0.787 → 0.919 | 15 → 6 |
| Fork / LÖVE 12 Metal | 16.36 → 16.38 | 16.373 → 16.383 | 1.134 → 1.026 | 15 → 6 |
| Public adaptation on ed5189c2 / LÖVE 11.5 OpenGL | 0.21 → 0.21 | 0.223 → 0.215 | 0.952 → 0.936 | 14 → 5 |
| Public adaptation on ed5189c2 / LÖVE 12 Metal | 16.44 → 16.44 | 16.455 → 16.449 | 0.844 → 0.831 | 14 → 5 |

Queue submission can absorb GPU/VSync waiting in Metal; these are wall-time
sections, not isolated GPU durations. The short runs show reduced dependency
dispatch and known allocations, with mixed CPU/timing results. They do not
establish a general frame-rate or CPU improvement. Dedicated ARM handheld,
Android, long-session and full-game measurements: not measured.

Regression checks cover cached dependency identity/replacement/retry, detached
public and late HUD records, method wrappers, DPI forwarding and private native
reuse. Existing output/hook tests and the compatibility acceptance are retained.
61 changes: 61 additions & 0 deletions docs/native-field-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Native GBA field presentation

`src.core.game3.field_view` connects a source field-draw replacement to the
admitted native overworld. `OverworldController:drawWorld` invokes its `draw`
callback with the native Game3 facade and actual world-view dimensions. The
ordinary renderer remains the direct path when there is no replacement or
presentation override. Requiring the module does not create a session.

The default `draw(game, width, height, options)` paints the native actors and
native field-effect bodies using owned source terrain atlases. It also works
for offscreen captures. `actorsOnly`, `skipActors`, billboard placement and the
`endUnderActors` upper-layer callback have explicit render scopes. A retained
upper-layer callback expires when that draw ends. Graphics pushes, canvas and
shader state are fenced, including errors. A declining source draw falls back
to the native renderer without consuming another tile-animation cadence.
Rendering never steps native actors, field effects, scripts or Flash tasks.

Terrain remains in 16px cells. The native room's true extent determines a
connection seam; the source layout's even storage padding cannot enlarge the
room. Camera placement uses the player's cell centre, the capture viewport
and explicit source panning. Connected pairs use their own atlases. Reused
batches rebind rotating texture uploads without rebuilding stationary cell
geometry. The typed map graph is observed once per preparation, rather than
rebuilding map/neighbor observations for each visible cell.

Palette edits affect owned source atlases and survive asset-cache invalidation;
the caller's palette words are detached. Borrowed native sheets are neither
patched nor released. Transparent reflection covers use the same current
source tile indices and palette words as the source terrain. Batches, cover
images and atlas leases retire with their rendering cohort. Save/dataset/mod
owner changes and a replaced native map cannot revive a retired presentation.
The fork additionally shares admitted-world lookup across map readers and
snapshot/warm-bound observers. Persistent-world hosts require that admission
follow-up; this public field change does not invent a replacement stack.

Flash radii and default levels come from the imported native records. Native
updates retain the one-pixel animation clock. Both field paths use integer
scanline masks, with compressed equal-span runs and a private geometry cache.
Mask visibility accounts for the actual player centre under camera pan.

Validation uses synthetic native maps, bank records and actors, not ROMs or
player saves. CPU coverage checks native admission, source atlas ownership,
options, camera coordinates, reloads, palette lifetime and native draw cadence.
The GPU host checks all five GBA editions on LÖVE 11.5 OpenGL and LÖVE 12
OpenGL/Metal, three capture sizes, true room/connection boundaries, upright
actor markers, palette/texture uploads, scanline masks and resource retirement.
The independent pinned scanline comparison covers 5,025 radius/view/centre
combinations. These checks do not establish unchanged-mod initialization,
original effect-task/art/timing parity, full gameplay or device acceptance.

Open work includes source-specific mosaic/order customization and void-fade
presentation, full effect-task interfaces, original-mod initialization and a
real gameplay route. iOS, Android and Switch still need their own runtime and
playability qualification. Native actor/effect behavior itself continues to be
owned by the engine. The public integration requires the native draw helpers
proposed in #291 and the separately published map/atlas/session prerequisites;
it must not replace missing host ownership with a simulated world.

The public patch also reuses the committed native GraphicsScope guard, which
is absent from the public base. It fences real graphics state and leaves
borrowed canvases and images with their native owners.
Loading