Skip to content

upstream mergers - #10

Closed
Nobbie248 wants to merge 365 commits into
iChris4:OpenXRfrom
Odrannnn:port
Closed

Nobbie248 wants to merge 365 commits into
iChris4:OpenXRfrom
Odrannnn:port

Conversation

@Nobbie248

Copy link
Copy Markdown
Collaborator

No description provided.

Odran and others added 30 commits October 17, 2026 00:09
The port's own legend entries and the "Mark room" prompt are extra UI: a
first playthrough should show the disc's map screen, and both were added
by this port, not by the disc. They are now behind "Extra map UI"
(`map_extra_ui`), off by default, on the F1 Tracker page beside the other
map and pickup settings, in the settings search index, the console
(`mapui [0|1]`) and the docs.

The setting gates exactly the two things it names: the legend entries (the
text, the row-count fit and the injected legend textures, which are not
even asked for while it is off) and the prompt with the pointer click that
marks a room. Pickup dots and their kinds, the saved marks, the cyan
drawing of them and the physical X button are untouched.

The legend's cache key carries the setting, so switching it with the map
screen open rebuilds the text at once: off, the pane is back to the disc's
own dimensions, pivot and text (measured identical before and after a
switch), and the hint pane is back at the frame's own transform with no
prompt and no click target, so nothing invisible marks a room. The key is
in the settings table with two sample values, which the settings round-trip
self-test applies and reads back; a wiring test checks the default, the
gates, the cache bit and that nothing else reads the setting.
Select the nearest larger aligned mip when a thumbnail or capped DDS tail would otherwise have an invalid compressed base level. Preserve the original mip payload and uncompressed selection behavior, with memory/file regression coverage.
F1 > Video > Compatibility gains a Renderer list on Windows, saved as
graphics_backend (auto, d3d12, vulkan, d3d11; default auto) and read at
window creation, so a change applies on the next start. Auto asks Aurora for
nothing in particular, keeping its own order (Direct3D 12, then Vulkan, then
Direct3D 11); an explicit choice is tried first and falls back to that order
when the driver can't start it, without touching the setting.

MP_GRAPHICS_BACKEND=auto|d3d12|vulkan|d3d11 overrides the setting for one run
(per-run MP_* precedence, like MP_OPENGLES); an unknown value warns and starts
on the saved selection. platform/include/port_graphics_backend.h owns the
selection-to-Aurora mapping, that override and the status the page draws, so
main.cpp and the page cannot describe a request one way and start it another:
the page names the API actually in use, reports "X could not start; using Y"
when what this run asked for (override included) fell back, and keeps the
restart notice on the saved selection. While an override is set it names the
API it asks for and says a change to the setting starts applying once the
variable is unset. The startup log names the API asked for and the one in use.

Android's OpenGL ES choice (opengles / MP_OPENGLES) is unchanged, and the
setting is ignored off Windows. Refs #135: the OpenGL side of it is out of
scope.
…d label

Two corrections to the merged map layout, both from the phone screenshot:

* The legend's fit cache and the hint pane's placement cache were keyed on the
  pane pointer alone. A released pane can come back at the same address when the
  map screen is closed and reopened (the pause screen's pools hand out what was
  just freed), which would keep the disc's geometry from being re-taken, skip the
  fit on the reopened frame and leave the mark prompt unplaced. Both caches are
  now cleared when the frame is dropped and when it is loaded, before anything
  reads them (PortForgetMapPanes).

* The disc's legend pane slides out by a fixed 15 units and its own text ends
  20.1 units left of the screen in that state. "Marked room" was wide enough
  (285 text px against the disc's 198) that its last glyphs (`oom`) stayed on
  screen at the left edge. The row is now "Marked": 233 px, so the whole block
  ends at -20.19 against the disc's -20.14, i.e. at least as far off screen as
  the disc's own legend at every key state.

tests/port_map_extra_ui_wiring.py gains the lifecycle guard (both resets, one
call on load before the legend pane is read, one on the drop) and the label
width guard.
NCollisionRayCast::Capsule 0xbf6f10 as pure geometry, with the source's own two
callees: CMath::SolveQuadratic 0x699b0 (disc = b*b + (a * -4) * c, the linear arm
below 0x34000000, the two roots ordered by the sign of a) and
NCollisionRayCast::InfiniteCylinder 0xbf73c0, plus the inline frecpe + two frecps
reciprocal at 0xbf70c4-0xbf70e8 and the bare FPRecipEstimate that sequence starts
from. The kernel is axis-selective (per-axis crosses and span tests), so it runs in
Remastered (Y up) axes: the public entry converts its port-frame inputs once,
before any arithmetic, and converts nothing back because its outputs are scalars.

The output follows the source rather than a cleaned-up geometry: tOut is
output-only (a miss leaves it untouched, the perpendicular branch reports
max(tNear, 0) while ignoring the ray length, and the general branch's tiny-clipped
arm returns inside without storing), the capsule epsilon is 1e-5 rather than the
quadratic's FLT_EPSILON, the clipped cylinder parameter is stored into the running
best before the length test so that a cap hit must beat it, the cap-inside arm is
gated on a positive best (so length == 0 reports nothing from inside a cap), and
the span, tangency and cap tests are all strict.

CMath::SqrtF 0x65f70 moves from the file-local copy in
platform/port_collision_failsafe.cpp to PortRemasteredGroundContacts::Source::SqrtF
so the discriminant root and the sphere tests share one instruction-identical
implementation (`!(v > 0)` guard, fmax floor 0x0554ad2e, two frsqrts refinements);
the failsafe's outputs are unchanged.

Geometry only: no caller, no contact list, no traversal, no materials and no
gameplay wiring - the library is not linked into the game binary - so this changes
no gameplay and makes no parity claim. Its rows are pinned bit-for-bit against the
game's own AArch64 instruction bodies run under qemu-aarch64: a 421-row selection
of them plus an 87-row bare-`frecpe` table, both regenerated deterministically by
the private lane's implementation/regenerate_fixture.sh. NaN and overflow inputs
are outside the header's finite contract.

CMakeLists.txt
docs/REMASTERED_MOVEMENT.md
platform/include/port_remastered_capsule_ray.h
platform/include/port_remastered_ground_contacts.h
platform/port_collision_failsafe.cpp
platform/port_remastered_capsule_ray.cpp
platform/port_remastered_ground_contacts.cpp
tests/fixtures/port_remastered_capsule_ray_isa.h
tests/port_remastered_capsule_ray.cpp
…nventory

Issue #136 (a Missile Launcher that left missiles at 0) could not be traced
from the log: it said a pickup was found and nothing more. The log now
follows each item and check:

- The session queues what it did (Session::TakeLogLine, like the chat
  lines) and the client writes it: the handshake with the slot, the checks
  the server has and the seed options that change grants; each
  ReceivedItems with its start and the expected index; each item with what
  it grants, or that a replay only counts it, or that it is unknown; gaps
  and the resend they ask for; rewinds.
- Checks: queued to send, queued while offline, or recorded already and so
  not sent again (no item comes back for those).
- Each grant the game takes, with the inventory's amount/capacity before
  and after.
- Each reconcile of a loaded game (save, resume, save state): what it
  records, and whether it is in step, rewound, or adopted; checks from
  another seed being dropped.
- Inbound DeathLink.
- Randomizer seeds: each pickup the game grants itself, with the inventory
  after, in the main log (randomizer_checks.log is unchanged).

docs/ARCHIPELAGO.md lists the lines; port_apclient_tests checks the
session's for the #136 sequence (connect, rewind, resend, a gap, the
Missile Launcher, an unknown item).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
0.28.0 was bumped but never tagged; the release now ships the 93 commits
that landed after it, so its date and summary say so.
The checklist named Linux, Windows and Android only, so the 0.28.0 release
started without the Flatpak and macOS builds every release ships.
)

CPlayer applies the aim twice a tick: ProcessInput with the tick's travel,
then Update to revalidate it with none left. The second AimState::Update
(and PointerAim::MoveLagged) reset the turn the first had recorded to be
drawn after the tick, so frames between ticks drew no stick turn at all and
the view turned in tick-sized steps (60 Hz) while the rest of the scene was
drawn at the display rate. PortMouse::TickLag keeps what the tick's first
applied update recorded; the next tick starts fresh, so a released stick
still draws no turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
… (issue #141)

The retail grounded mover resets the flag at the top of every tick; the
Remastered ground mover (052d325) never touched it, so a value set by a
retail tick (the dynamic-contact fallback, a tick with the retained state
invalid) stayed set. While orbiting, ComputeDash only writes the velocity
when the flag is clear, so locking on kept the player moving at the
velocity they had until the lock was released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFkbc95d4u2mzXdwQLeqBM
UpdateMouseAim runs twice a tick (CPlayer::ProcessInput, then Update), and
both applied the pointer dead box's edge turn for a whole tick period, so the
view turned at twice pointer_aim_turn. The frames between ticks preview one
tick's edge turn, so the drawn view fell behind and jumped by the other half
at every tick. Only the tick's first applied update turns now (TickLag is
renamed TickAim and answers First()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
With point and click menus on, a one-finger drag on the map was turned
into a pan, so touch had no way to pitch the map and two fingers did
the same thing as one. A one-finger drag now rotates (yaw and pitch) as
a left mouse drag does, and two fingers pan; twist and pinch are
unchanged. The hold that stops the map drifting back to its area now
follows two-finger pans instead of any touch drag.

Fixes #142

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WUhWhzqwXbK889n1gJtk8B
Adds "Swap map finger gestures" (map_swap_fingers, MP_MAP_SWAP_FINGERS, off by
default). On, one finger pans the map and a two-finger drag rotates it; twist
and pinch are unchanged and the mouse is never affected.

Players used to one-finger pan, from the old behaviour or the Android overlay,
had no way to keep it. The setting is greyed out when Point and click menus is
off.
ApplyStorageClamp writes MP_STORAGE_CLAMP into the process environment
when the setting is On or Off, and the settings dump then listed it under
`port: env:` exactly like a variable the user set (issue #143's log read
as a user override). The line now ends in "(from storage_clamp)" when the
port set it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
…ings

Issue #143's self-test failed every case with black pixels, which can't
tell "draws don't land" from "the copy or readback is broken": every
case goes draw -> EFB copy -> readback, the background is black and the
logged alpha was a constant.

Self-test:
- `readback`: a texture written from the CPU, copied straight back (no GX).
- `efb-clear-copy`: the EFB cleared by a copy and copied again (no draw).
- The readback buffer starts filled with A5 bytes, so a block no copy
  reached says so instead of reading as black.
- Real alpha in FAIL lines, and a `diagnosis:` line naming the stage the
  failures point at (readback, EFB copy or draws).
- The caller re-sends the game's copy clear colour afterwards.

Driver workarounds (Video > Compatibility, collapsed, `driver_workarounds`):
checkboxes for d3d11_use_discard_view off, d3d11_disable_cpu_buffers,
d3d11_use_unmonitored_fence, and Dawn validation back on. They reach
Aurora through new AuroraConfig fields and apply at device creation;
MP_DAWN_ENABLE / MP_DAWN_DISABLE still apply after them, and either list
can now turn off one of Aurora's default toggles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
- A start with workarounds ticked leaves driver_workarounds_starting in
  the user folder until frame 60. A start that finds it unticks them all,
  logs it and says so in the section; the marker stays until a start
  reaches the game, so repeated crashes keep them off.
- MP_DRIVER_WORKAROUNDS=0 (or a list of names) replaces the boxes for one
  run and leaves the setting and the marker alone.
- Driver error checks last one start: unticked as they are applied, with
  a log line and a note in the section while they're on.
- The application's d3d11_* toggles only reach a D3D11 device; elsewhere
  the log lists them as ignored.
- GPU self-test: a clean pass logs a diagnosis saying it doesn't cover
  presenting the frame. Docs note the count is now x/12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
A bug-fix release: smooth right-stick turning (#140), one-finger map rotation on touch with a swap setting (#142), the Remastered lock-on slide (#141), and the Driver workarounds settings and GPU self-test stages for #143.
The keyboard and mouse drive player 1, so a controller left plugged in
shook for them (issue #138). CRumbleGenerator now stops the motors while
the keyboard or mouse is the last input used (PortPrompts::KeyboardActive),
and the voices keep running so going back to the pad resumes the effect.
The last-input watch is now installed even without prompt icons.

Controls > Options gains a Rumble checkbox bound to the game's own rumble
option, so it matches the pause menu and is saved with the game options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MiQ1bpvnDUcDjm3sXqignS
Hold rumble off on keyboard/mouse; add a Rumble toggle to F1 Controls
The game's Visor Opacity is one value for every visor, and it never reaches
the energy readout. Four new port settings (Video > Display in F1, console
`hudopacity`, persisted as hud_opacity_xray/thermal/scan/energy) take a
percent or "visor":

- X-Ray, Thermal and Scan: that visor's HUD uses its own opacity, and so do
  the visor and beam menus and the minimap while that visor is on. Default
  "visor", as the game does.
- Energy: the bar, tanks, digits and low-energy warning. Default 100, as the
  game draws them; "visor" makes them follow the HUD they are on.

Original experience ignores all four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRESLeS2QtkW2zgnvGfsZy
Match the independently audited inverse-exposure policy for the exact held-charge child and material. Preserve other effects and LDR behavior; effects stage 28 requires a re-import.
Leave the non-PC visor and beam menu code as decompiled, reject a
non-numeric hudopacity value instead of reading it as 0, and say in the
tooltip and docs that the ENERGY label and its frame follow the visor's HUD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRESLeS2QtkW2zgnvGfsZy
configure.py, its dtk-template helpers in tools/, config/, orig/,
sha1/, buildstrings/, scripts/ and docs/decomp/ only serve the original
decompilation's matching ninja build. CMake, CI, packaging and the port
tools never read them, so they cluttered the top level next to the
port's own files. They now live together under decomp/; src/ and
include/ stay where they are because the port compiles them.

The build still runs from the repository root as
`python3 decomp/configure.py`: configure.py points tools_dir and the
config paths at decomp/, the config.yml files name decomp/orig and
decomp/config, decompctx.py and rename_symbol.py find the root from
their new depth, and .gitignore/.gitattributes and the docs and
comments that named the old paths follow. The generated build.ninja
differs from before only in those paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175uBWzi5S5io3pmcnbvymd
X-Ray, Thermal and Scan HUDs can each have their own opacity instead of the game's single Visor Opacity, and the energy readout gets its own (default 100, as the game draws it). Original experience ignores all four.
tools/README.md lists every script with one line on what it does and
whether the build, CTest, CI or packaging runs it, and documents the
manual checks CTest cannot run (they need retail data, retrotool or a
running game) and how to build their helper tools.

anuv_slots.py answered a one-off question about the ANUV converter's
texcoord slots; it needs an external anuv.py and a material index that
nothing in the repository produces, so it goes.
card_inspect.py, disc_diff.py and upstream_screen.py stay as hand-run
debugging and merge aids.

test_game_options_persistence.py is a rig regression check, not a tool,
so it moves to tests/game_options_persistence_rig.py (its repo-root
lookup is unchanged at that depth). It needs a disc and Xvfb, so it is
not registered in CTest, nor are the three port_remastered_*_check.py
scripts, which need extracted Remastered data and retrotool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175uBWzi5S5io3pmcnbvymd
Odran added 29 commits October 19, 2026 13:22
Remastered rotates vectors with ROTV outside a component too (POFS, ADV1)
and normalises the particle velocity with PNCV. Both were dropped.

ROTV by REUL (axis order 0), EXTR or CMRT becomes the port-only PRTV: the
vector, the rotation kind, REUL's three angles. It evaluates the rotation
first, then the vector, as CVERotateVector 0x2a0328 does, in Remastered's
space (EXTR and CMRT are conjugated into it). PNCV becomes the port-only
PNCV, which runs the particle velocity through one FRSQRTE step refined by
FRSQRTS (CVENormalizedCompensatedVelocity 0x2a2800) and gives (0, 1, 0)
below 2^-46. Retail effects are unchanged; kEffects is not bumped.
… vars

Converted Remastered effects use a ninth real particle variable (ADV9, read
by PAP9) and four per-particle ints (IAV0..3, read by PAP0..3), neither of
which retail has. The converter now writes them, with each one's
constant-after-start flag, as a port-only PPVR table in SortVars order
(dependency, then stream order). Spawn evaluates every entry; later frames
skip the constant ones (a CNST, or an IRND of constants), so an IAV drawn
from IRND is drawn once per particle on the one RNG, as Remastered does.
The ints live as raw bits after ADV1..9 in CAdvancedValues. Generators with
only ADV1..8 keep the old PADO output and evaluation unchanged, and
ImportStage::kEffects is not bumped.
ELPS GetValue 0x2ca73c guards both normalizes with CVector3f::IsNormalizable
0x6b030: all lanes finite and (x*x+y*y)+z*z >= 1e-28 (0x10fd87b5), not the
1.42e-14 skMinNormalizableMagSqd the port assumed. Normalize/AsNormalized are
the same FRSQRTE-refined math NormalizeInPlace already has. Boundary test added.
# Conflicts:
#	CMakeLists.txt
Remastered's KEYF (CREKeyframeInput, ctor 0x296490, GetValue 0x29661c) reads its
keys at an input element's value, optionally wrapping from a loop start once
past the last key, and blends linearly between neighbours. The converter now
turns a version-2 KEYF into the port-only PKYF (loop byte, N, A, B, R, keys,
input); another version or a key count that differs from N stays dropped with
a reason. The element keeps the original's operation order (D = 1 / R once,
separate products and sums, saturating truncation). Retail effects unchanged.
Remastered's SLTW (GetLifetimeTweenElement 0x26a640 on time slot 0) blends two
reals by the system tween, clamp(system frame / system lifetime, 0, 1), which
the context keeps at ctx+0x60 (UpdateLifetimeTweenValues 0x24ef38). The port
had no system tween, so CParticleGlobals now carries one, set next to every
SetEmitterTime from the generator's frame and PSLT (lifetime 0 reads as 1, the
infinite 0x7FFFFF/0x1000000 gives 0). The converter turns SLTW(a, b) into the
port-only PSLW; the element is a + tween * (b - a) in the original's order.
Retail effects unchanged. The reciprocal is a plain 1 / lifetime, within an
ulp of the original's estimate.
A CRLN whose octave slot is set is Remastered's CVECurlNoise_PerlinOctave3d
(slice 0), which the importer dropped. It now becomes the port-only PCRO
(P, frequency, octave count, gain, A) inside the same PCRM/BSIS wrapper as
PCRN. CurlNoiseOctave3d sums gain^k * noise(p * freq * 2^k) in the original's
order, with the central-difference step added before the frequency multiplies
the coordinate. The VECF is released by the octave class, so it is not carried.
Slices 1-3 stay dropped: their layout is undecoded and no effect needs them.
ImportStage::kEffects is not bumped.
# Conflicts:
#	platform/port_remastered_effect.cpp
The system tween divided by the lifetime with a plain 1/x, which differs from
Remastered in the last bit for many lifetimes (13, 15, 19, 21, ...).
UpdateLifetimeTweenValues 0x24ef38 takes FRECPE of the lifetime, refines it
with two FRECPS/fmul steps, multiplies by the frame, and clamps with two fcsel
(0 - t >= 0 gives 0, t - 1 >= 0 gives 1). PortSetSystemTime now runs the same
steps through PortParticleMath::InverseScale, which is that sequence.
kEffects 61: the emitter, vector, var and misc element drops convert now.
CCEConstant holds four real elements, so each channel needs its own CNST; the
bare floats failed the retail reader and four effects kept the disc's PART.
The child-not-written log now says why the reader refused it.
A composite whose children are all CNST was given bit 0, so RAND, SPAF, TPVF,
MPXV, SINE, PULS, CHAN ... were latched at spawn. Only the decoded shapes
(CNST, IRND, KPIN, plain ADD_/SUB_/DIVD) are claimed constant; anything else is
re-evaluated every frame (Finish 0x24f9a4, IsConstantAfterStart 0x250b18).
The converter's validator now rejects a duplicate variable slot like the
factory does. On a bad slot or a duplicate the factory still reads the item's
element so the rest of the property stays in step, then drops the table, and it
logs the fall back to the retail ADV path.
A written ADV/IAV reading a dropped variable would see zero where Remastered
sees its value. The readers are dropped too, transitively, with a reason, and
a retail ADV1..8 property is taken back out of the part.
…ler on a variable count

Compile1d resolves a CRCV 0 count from MAXP only for the SEMR position child, and Init's shuffler needs a constant count.
…pace

PNCV and PRTV give Remastered's numbers; a mod vector under MPCB runs in retail's space, so BSIS wraps them there. PRTV's vector is always evaluated in Remastered's space. ROTV by INTR or RLCL is dropped with a reason.
A swoosh evaluates no per-particle variables, so a read of PAP0..3 or PAP9 and a
property of ADV9 or IAVn have nothing behind them; drop them with a reason.
A PKYF with a zero or implausible key count evaluates to 0 instead of indexing key 0,
and a PCRC whose children fail to read is refused like the cone elements.
A child generator that updates or renders inside a system left its own tween in
xPortSystemTween for the parent's next PSLW; Remastered keeps it per system.
Each test now runs every converted part through SplitRetailEffect, so a
layout the reader rejects fails the test. The factory's PKYF key-count cap
now matches the validator (1..0x100000); every other port-only id was
compared and reads the validator's layout.
…ime cap

UpdateLifetimeTweenValues (0x24ef38) compares the lifetime with exactly
0x1000000 (the no-PSLT value and the PSLT clamp); the port zeroed anything
from 0x7FFFFF up. The per-system context is built per CElementGen::Update
(0x2460e0) and InitialUpdate (0x245e44) from this+0x2c/+0x180, which the
port's SParticleSystem save/restore reproduces for children and render.
…R and TMTR

COLR is evaluated per particle in UpdateExistingParticles 0x24a26c with that
particle's own var block; TMTR is read from the same block by
FillInParticleTextureTransformMaterialData 0x2acfc0/0x2ad058. The port's
UV-site and COLR bindings already match; no behaviour change.
…pped effects convert

The shipped files give ELPS's last argument as a GetBool (CNST and a byte), but
the parser signature took a raw byte, so the parse fell back to a four-argument
guess whose euler swallowed the speed and the converter dropped the emitter
(E7638EFC, twice). Signature back to VVVRe, the converter unwraps the CNST, and
the test writes the flag the way the files do.
ELPS now reads the surface-only flag the way the files give it, so the
E7638EFC emitters convert; the stage must re-run to pick that up.
@Nobbie248 Nobbie248 closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants